@trypattern/sdk 0.1.29 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,9 @@ type CardProps = {
11
11
  to?: AppPath;
12
12
  href?: string;
13
13
  onClick?: () => void;
14
+ hover?: 'zoom' | 'outline';
15
+ isPressable?: boolean;
14
16
  style?: Placement;
15
17
  };
16
- export declare function Card({ children, surface, padding, paddingBlock, to, href, onClick, style }: CardProps): import("react").JSX.Element;
18
+ export declare function Card({ children, surface, padding, paddingBlock, to, href, onClick, hover, isPressable, style }: CardProps): import("react").JSX.Element;
17
19
  export {};
@@ -2,12 +2,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Button as BaseButton } from '@base-ui/react/button';
3
3
  import * as stylex from '@stylexjs/stylex';
4
4
  import { Link as RouterLink } from 'react-router';
5
- import { colors, radii, shadows, space } from "../theme/tokens.stylex.js";
5
+ import { borders, breakpoints, colors, motion, radii, shadows, space } from "../theme/tokens.stylex.js";
6
6
  import { focusRing } from "./controlLook.js";
7
7
  import { interact, textOf } from "./interaction.js";
8
- export function Card({ children, surface = 'subtle', padding = 'lg', paddingBlock, to, href, onClick, style }) {
8
+ export function Card({ children, surface = 'subtle', padding = 'lg', paddingBlock, to, href, onClick, hover, isPressable = false, style }) {
9
9
  const isInteractive = to !== undefined || href !== undefined || onClick !== undefined;
10
- const className = stylex.props(styles.card, surfaces[surface], paddings[padding], paddingBlock !== undefined && paddingBlocks[paddingBlock], isInteractive && styles.interactive, isInteractive && focusRing.visible, style).className;
10
+ const className = stylex.props(styles.card, surfaces[surface], paddings[padding], paddingBlock !== undefined && paddingBlocks[paddingBlock], isInteractive && styles.interactive, isInteractive && focusRing.visible, isInteractive && hover === 'zoom' && stylex.defaultMarker(), isInteractive && hover === 'outline' && styles.outline, isInteractive && isPressable && styles.pressable, style).className;
11
11
  const report = (event) => {
12
12
  interact({
13
13
  primitive: 'Card',
@@ -40,6 +40,30 @@ const styles = stylex.create({
40
40
  textAlign: 'start',
41
41
  textDecoration: 'none',
42
42
  outlineOffset: 0
43
+ },
44
+ outline: {
45
+ boxShadow: {
46
+ default: shadows.default,
47
+ [breakpoints.canHover]: {
48
+ default: shadows.default,
49
+ ':hover': `0 0 0 ${borders.thin} ${colors.borderStrong}`
50
+ }
51
+ },
52
+ transitionTimingFunction: motion.easeOut,
53
+ transitionDuration: motion.fast,
54
+ transitionProperty: 'box-shadow, transform'
55
+ },
56
+ pressable: {
57
+ transform: {
58
+ default: null,
59
+ ':active': {
60
+ default: 'scale(0.98)',
61
+ [breakpoints.reducedMotion]: 'none'
62
+ }
63
+ },
64
+ transitionTimingFunction: motion.easeOut,
65
+ transitionDuration: motion.fast,
66
+ transitionProperty: 'box-shadow, transform'
43
67
  }
44
68
  });
45
69
  const surfaces = stylex.create({
@@ -3,7 +3,7 @@ type DialogProps = {
3
3
  label: string;
4
4
  isOpen: boolean;
5
5
  onOpenChange: (isOpen: boolean) => void;
6
- placement?: 'center' | 'top';
6
+ placement?: 'center' | 'top' | 'sheet';
7
7
  size?: 'sm' | 'md' | 'lg' | 'xl';
8
8
  children: ReactNode;
9
9
  };
@@ -1,9 +1,51 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Dialog as BaseDialog } from '@base-ui/react/dialog';
3
3
  import * as stylex from '@stylexjs/stylex';
4
- import { colors, layers, motion, radii, shadows } from "../theme/tokens.stylex.js";
4
+ import { useRef, useState } from 'react';
5
+ import { breakpoints, colors, layers, motion, radii, shadows, space } from "../theme/tokens.stylex.js";
5
6
  import { interact } from "./interaction.js";
6
7
  export function Dialog({ label, isOpen, onOpenChange, placement = 'center', size = 'md', children }) {
8
+ const start = useRef(null);
9
+ const [drag, setDrag] = useState(0);
10
+ const isSheet = placement === 'sheet';
11
+ const close = () => {
12
+ interact({ primitive: 'Dialog', event: 'close', label });
13
+ onOpenChange(false);
14
+ };
15
+ const dragged = useRef(0);
16
+ const handle = (element) => {
17
+ if (element === null)
18
+ return undefined;
19
+ const grab = (event) => {
20
+ start.current = event.clientY;
21
+ element.setPointerCapture(event.pointerId);
22
+ };
23
+ const move = (event) => {
24
+ if (start.current === null)
25
+ return;
26
+ dragged.current = Math.max(0, event.clientY - start.current);
27
+ setDrag(dragged.current);
28
+ };
29
+ const release = () => {
30
+ if (start.current === null)
31
+ return;
32
+ start.current = null;
33
+ if (dragged.current > DRAG_TO_CLOSE)
34
+ close();
35
+ dragged.current = 0;
36
+ setDrag(0);
37
+ };
38
+ element.addEventListener('pointerdown', grab);
39
+ element.addEventListener('pointermove', move);
40
+ element.addEventListener('pointerup', release);
41
+ element.addEventListener('pointercancel', release);
42
+ return () => {
43
+ element.removeEventListener('pointerdown', grab);
44
+ element.removeEventListener('pointermove', move);
45
+ element.removeEventListener('pointerup', release);
46
+ element.removeEventListener('pointercancel', release);
47
+ };
48
+ };
7
49
  return (_jsx(BaseDialog.Root, { open: isOpen, onOpenChange: (next) => {
8
50
  interact({
9
51
  primitive: 'Dialog',
@@ -11,9 +53,9 @@ export function Dialog({ label, isOpen, onOpenChange, placement = 'center', size
11
53
  label
12
54
  });
13
55
  onOpenChange(next);
14
- }, children: _jsxs(BaseDialog.Portal, { children: [placement === 'center' ? (_jsx(BaseDialog.Backdrop, { className: (state) => stylex.props(styles.backdrop, (!state.open || state.transitionStatus === 'starting') &&
15
- styles.backdropHidden).className })) : null, _jsx(BaseDialog.Popup, { "aria-label": label, className: (state) => stylex.props(styles.popup, placements[placement], widths[size], (!state.open || state.transitionStatus === 'starting') &&
16
- hiddenPlacements[placement], !state.open && styles.exiting).className, children: _jsx("div", { ...stylex.props(styles.frame), children: children }) })] }) }));
56
+ }, children: _jsxs(BaseDialog.Portal, { children: [placement === 'center' || isSheet ? (_jsx(BaseDialog.Backdrop, { className: (state) => stylex.props(styles.backdrop, (!state.open || state.transitionStatus === 'starting') &&
57
+ styles.backdropHidden).className })) : null, _jsx(BaseDialog.Popup, { "aria-label": label, className: (state) => stylex.props(styles.popup, placements[placement], isSheet ? sheetWidths[size] : widths[size], (!state.open || state.transitionStatus === 'starting') &&
58
+ hiddenPlacements[placement], !state.open && styles.exiting, isSheet && drag > 0 && styles.dragged(drag)).className, children: _jsxs("div", { ...stylex.props(styles.frame, isSheet && styles.sheetFrame), children: [isSheet ? (_jsx("div", { "aria-hidden": "true", ref: handle, ...stylex.props(styles.handle), children: _jsx("span", { ...stylex.props(styles.grip) }) })) : null, children] }) })] }) }));
17
59
  }
18
60
  const styles = stylex.create({
19
61
  backdrop: {
@@ -42,6 +84,42 @@ const styles = stylex.create({
42
84
  transitionTimingFunction: motion.easeOut,
43
85
  transitionDuration: motion.exit
44
86
  },
87
+ dragged: (y) => ({
88
+ transform: `translateY(${y}px)`,
89
+ transitionDuration: '0s'
90
+ }),
91
+ sheetFrame: {
92
+ maxHeight: {
93
+ default: 'calc(100dvh - 2rem)',
94
+ [breakpoints.compact]: `calc(100dvh - ${space.xxxl} * 2)`
95
+ },
96
+ borderRadius: {
97
+ default: radii.xxxl,
98
+ [breakpoints.compact]: `${radii.xxxxl} ${radii.xxxxl} 0 0`
99
+ },
100
+ boxShadow: {
101
+ default: shadows.extraLarge,
102
+ [breakpoints.compact]: `0 -8px 32px ${colors.borderStrong}`
103
+ }
104
+ },
105
+ handle: {
106
+ display: {
107
+ default: 'none',
108
+ [breakpoints.compact]: 'flex'
109
+ },
110
+ flexShrink: 0,
111
+ justifyContent: 'center',
112
+ paddingTop: space.sm,
113
+ paddingBottom: space.xs,
114
+ touchAction: 'none',
115
+ cursor: 'grab'
116
+ },
117
+ grip: {
118
+ width: `calc(${space.xxl} + ${space.xs})`,
119
+ height: `calc(${space.xs} + 1px)`,
120
+ backgroundColor: colors.borderStrong,
121
+ borderRadius: radii.full
122
+ },
45
123
  frame: {
46
124
  display: 'flex',
47
125
  flexDirection: 'column',
@@ -54,6 +132,24 @@ const styles = stylex.create({
54
132
  }
55
133
  });
56
134
  const placements = stylex.create({
135
+ sheet: {
136
+ top: {
137
+ default: '50%',
138
+ [breakpoints.compact]: 'auto'
139
+ },
140
+ bottom: {
141
+ default: 'auto',
142
+ [breakpoints.compact]: 0
143
+ },
144
+ left: {
145
+ default: '50%',
146
+ [breakpoints.compact]: 0
147
+ },
148
+ transform: {
149
+ default: 'translate(-50%, -50%)',
150
+ [breakpoints.compact]: 'none'
151
+ }
152
+ },
57
153
  top: {
58
154
  top: '20vh',
59
155
  transform: 'translateX(-50%)'
@@ -64,6 +160,17 @@ const placements = stylex.create({
64
160
  }
65
161
  });
66
162
  const hiddenPlacements = stylex.create({
163
+ sheet: {
164
+ pointerEvents: 'none',
165
+ opacity: {
166
+ default: 0,
167
+ [breakpoints.compact]: 1
168
+ },
169
+ transform: {
170
+ default: 'translate(-50%, -50%) scale(0.95)',
171
+ [breakpoints.compact]: 'translateY(100%)'
172
+ }
173
+ },
67
174
  top: {
68
175
  pointerEvents: 'none',
69
176
  opacity: 0,
@@ -81,3 +188,30 @@ const widths = stylex.create({
81
188
  lg: { maxWidth: 'min(720px, calc(100% - 2rem))' },
82
189
  xl: { maxWidth: 'min(800px, calc(100% - 2rem))' }
83
190
  });
191
+ const sheetWidths = stylex.create({
192
+ sm: {
193
+ maxWidth: {
194
+ default: 'min(400px, calc(100% - 2rem))',
195
+ [breakpoints.compact]: '100%'
196
+ }
197
+ },
198
+ md: {
199
+ maxWidth: {
200
+ default: 'min(560px, calc(100% - 2rem))',
201
+ [breakpoints.compact]: '100%'
202
+ }
203
+ },
204
+ lg: {
205
+ maxWidth: {
206
+ default: 'min(720px, calc(100% - 2rem))',
207
+ [breakpoints.compact]: '100%'
208
+ }
209
+ },
210
+ xl: {
211
+ maxWidth: {
212
+ default: 'min(800px, calc(100% - 2rem))',
213
+ [breakpoints.compact]: '100%'
214
+ }
215
+ }
216
+ });
217
+ const DRAG_TO_CLOSE = 96;
@@ -0,0 +1,17 @@
1
+ import type { MediaImage } from '../media.ts';
2
+ import type { Placement } from './Placement.ts';
3
+ type GalleryPhoto = MediaImage & {
4
+ readonly id: string;
5
+ };
6
+ type GalleryProps = {
7
+ label: string;
8
+ photos: readonly GalleryPhoto[];
9
+ value: string | null;
10
+ onChange: (id: string) => void;
11
+ sizes: string;
12
+ aspect?: number;
13
+ priority?: boolean;
14
+ style?: Placement;
15
+ };
16
+ export declare function Gallery({ label, photos, value, onChange, sizes, aspect, priority, style }: GalleryProps): import("react").JSX.Element;
17
+ export {};
@@ -0,0 +1,198 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button as BaseButton } from '@base-ui/react/button';
3
+ import * as stylex from '@stylexjs/stylex';
4
+ import { useEffect, useRef } from 'react';
5
+ import { breakpoints, colors, galleryMarker, motion, radii, space } from "../theme/tokens.stylex.js";
6
+ import { focusRing } from "./controlLook.js";
7
+ import { Icon } from "./Icon.js";
8
+ import { Image } from "./Image.js";
9
+ import { interact } from "./interaction.js";
10
+ import { Text } from "./Text.js";
11
+ export function Gallery({ label, photos, value, onChange, sizes, aspect, priority = false, style }) {
12
+ const track = useRef(null);
13
+ const shown = useRef(null);
14
+ const latest = useRef(onChange);
15
+ latest.current = onChange;
16
+ const index = Math.max(0, photos.findIndex((photo) => photo.id === value));
17
+ const current = photos[index];
18
+ useEffect(() => {
19
+ const element = track.current;
20
+ if (element === null)
21
+ return undefined;
22
+ const observer = new IntersectionObserver((entries) => {
23
+ for (const entry of entries) {
24
+ const id = entry.target.getAttribute('data-photo');
25
+ if (entry.intersectionRatio < SHOWN || id === null)
26
+ continue;
27
+ if (shown.current === id)
28
+ continue;
29
+ shown.current = id;
30
+ latest.current(id);
31
+ }
32
+ }, { root: element, threshold: [SHOWN] });
33
+ for (const slide of element.children)
34
+ observer.observe(slide);
35
+ return () => observer.disconnect();
36
+ }, [photos]);
37
+ useEffect(() => {
38
+ const element = track.current;
39
+ if (element === null || current === undefined)
40
+ return;
41
+ if (shown.current === current.id)
42
+ return;
43
+ shown.current = current.id;
44
+ const slide = element.children[index];
45
+ if (!(slide instanceof HTMLElement))
46
+ return;
47
+ element.scrollTo({
48
+ left: slide.offsetLeft -
49
+ Number.parseFloat(getComputedStyle(element).paddingLeft),
50
+ behavior: prefersReducedMotion() ? 'auto' : 'smooth'
51
+ });
52
+ }, [current, index]);
53
+ const go = (next) => {
54
+ const photo = photos[(next + photos.length) % photos.length];
55
+ if (photo === undefined)
56
+ return;
57
+ interact({ primitive: 'Gallery', event: 'change', label });
58
+ onChange(photo.id);
59
+ };
60
+ const hasMany = photos.length > 1;
61
+ return (_jsxs("section", { "aria-roledescription": "carousel", "aria-label": label, ...stylex.props(styles.gallery, style), children: [_jsxs("div", { ...stylex.props(styles.stage, galleryMarker), children: [_jsx("div", { ref: track, tabIndex: hasMany ? 0 : undefined, ...stylex.props(styles.track, focusRing.visible, focusRing.inside), children: photos.map((photo, at) => (_jsx("div", { "data-photo": photo.id, role: "group", "aria-roledescription": "slide", "aria-label": `${at + 1} of ${photos.length}`, ...stylex.props(styles.slide, hasMany && styles.peeking), children: _jsx(Image, { image: photo, sizes: sizes, ...(aspect === undefined ? {} : { aspect }), priority: priority && at === 0 }) }, photo.id))) }), hasMany ? (_jsxs(_Fragment, { children: [_jsx(BaseButton, { "aria-label": "Previous photo", onClick: () => go(index - 1), className: stylex.props(styles.arrow, styles.previous, focusRing.visible)
62
+ .className, children: _jsx(Icon, { name: "chevronLeft" }) }), _jsx(BaseButton, { "aria-label": "Next photo", onClick: () => go(index + 1), className: stylex.props(styles.arrow, styles.next, focusRing.visible)
63
+ .className, children: _jsx(Icon, { name: "chevronRight" }) }), _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.count), children: _jsx(Text, { variant: "caption", weight: "medium", tone: "inherit", children: `${index + 1} of ${photos.length}` }) })] })) : null] }), hasMany ? (_jsx("div", { ...stylex.props(styles.dots), children: photos.map((photo, at) => (_jsx(BaseButton, { "aria-label": `Show photo ${at + 1} of ${photos.length}`, "aria-current": at === index ? 'true' : undefined, onClick: () => go(at), className: stylex.props(styles.dotTarget, focusRing.visible).className, children: _jsx("span", { ...stylex.props(styles.dot, at === index && styles.dotShown) }) }, photo.id))) })) : null] }));
64
+ }
65
+ const SHOWN = 0.6;
66
+ function prefersReducedMotion() {
67
+ return (typeof window !== 'undefined' &&
68
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches);
69
+ }
70
+ const styles = stylex.create({
71
+ gallery: {
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ gap: {
75
+ default: space.lg,
76
+ [breakpoints.compact]: space.sm
77
+ },
78
+ minWidth: 0
79
+ },
80
+ stage: {
81
+ position: 'relative'
82
+ },
83
+ track: {
84
+ position: 'relative',
85
+ display: 'flex',
86
+ gap: space.sm,
87
+ paddingInline: {
88
+ default: 0,
89
+ [breakpoints.compact]: space.lg
90
+ },
91
+ overflowX: 'auto',
92
+ overflowY: 'hidden',
93
+ overscrollBehaviorX: 'contain',
94
+ outlineOffset: 0,
95
+ scrollSnapType: 'x mandatory',
96
+ scrollPaddingInline: {
97
+ default: 0,
98
+ [breakpoints.compact]: space.lg
99
+ },
100
+ scrollbarWidth: 'none',
101
+ borderRadius: {
102
+ default: radii.lg,
103
+ [breakpoints.compact]: 0
104
+ }
105
+ },
106
+ slide: {
107
+ flexShrink: 0,
108
+ width: '100%',
109
+ overflow: 'hidden',
110
+ scrollSnapAlign: 'start',
111
+ scrollSnapStop: 'always',
112
+ borderRadius: radii.lg
113
+ },
114
+ peeking: {
115
+ width: {
116
+ default: '100%',
117
+ [breakpoints.compact]: `calc(100% - ${space.xxxl})`
118
+ }
119
+ },
120
+ arrow: {
121
+ position: 'absolute',
122
+ top: '50%',
123
+ display: {
124
+ default: 'none',
125
+ [breakpoints.canHover]: 'flex'
126
+ },
127
+ alignItems: 'center',
128
+ justifyContent: 'center',
129
+ width: `calc(${space.xxl} + ${space.sm})`,
130
+ height: `calc(${space.xxl} + ${space.sm})`,
131
+ padding: 0,
132
+ color: colors.primaryForeground,
133
+ backgroundColor: colors.container,
134
+ borderStyle: 'none',
135
+ borderRadius: radii.full,
136
+ boxShadow: `0 0 0 .5px ${colors.borderDefault}, 0 1px 2px ${colors.borderStrong}, 0 4px 12px -4px ${colors.borderStrong}`,
137
+ opacity: {
138
+ default: 0,
139
+ [stylex.when.ancestor(':hover', galleryMarker)]: 1,
140
+ ':focus-visible': 1
141
+ },
142
+ transform: 'translateY(-50%)',
143
+ transitionTimingFunction: motion.easeOut,
144
+ transitionDuration: motion.fast,
145
+ transitionProperty: 'opacity'
146
+ },
147
+ previous: {
148
+ left: space.lg
149
+ },
150
+ next: {
151
+ right: space.lg
152
+ },
153
+ count: {
154
+ position: 'absolute',
155
+ right: {
156
+ default: space.lg,
157
+ [breakpoints.compact]: `calc(${space.xxl} + ${space.md})`
158
+ },
159
+ bottom: {
160
+ default: space.lg,
161
+ [breakpoints.compact]: space.md
162
+ },
163
+ display: 'flex',
164
+ alignItems: 'center',
165
+ height: space.xl,
166
+ paddingInline: `calc(${space.sm} + ${space.xxs})`,
167
+ color: colors.whiteFill,
168
+ pointerEvents: 'none',
169
+ backgroundColor: colors.overlay,
170
+ borderRadius: radii.full
171
+ },
172
+ dots: {
173
+ display: 'flex',
174
+ alignItems: 'center',
175
+ justifyContent: 'center',
176
+ height: space.xl
177
+ },
178
+ dotTarget: {
179
+ display: 'flex',
180
+ alignItems: 'center',
181
+ justifyContent: 'center',
182
+ width: space.xl,
183
+ height: space.xl,
184
+ padding: 0,
185
+ backgroundColor: 'transparent',
186
+ borderStyle: 'none',
187
+ borderRadius: radii.full
188
+ },
189
+ dot: {
190
+ width: `calc(${space.xs} + ${space.xxs})`,
191
+ height: `calc(${space.xs} + ${space.xxs})`,
192
+ backgroundColor: colors.borderStrong,
193
+ borderRadius: radii.full
194
+ },
195
+ dotShown: {
196
+ backgroundColor: colors.primaryForeground
197
+ }
198
+ });
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ type HeaderProps = {
3
+ children: ReactNode;
4
+ };
5
+ export declare function Header({ children }: HeaderProps): import("react").JSX.Element;
6
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as stylex from '@stylexjs/stylex';
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { borders, colors, layers, motion } from "../theme/tokens.stylex.js";
5
+ export function Header({ children }) {
6
+ const top = useRef(null);
7
+ const [isScrolled, setScrolled] = useState(false);
8
+ useEffect(() => {
9
+ const element = top.current;
10
+ if (element === null)
11
+ return undefined;
12
+ const observer = new IntersectionObserver(([entry]) => setScrolled(entry !== undefined && !entry.isIntersecting));
13
+ observer.observe(element);
14
+ return () => observer.disconnect();
15
+ }, []);
16
+ return (_jsxs(_Fragment, { children: [_jsx("span", { ref: top, "aria-hidden": "true", ...stylex.props(styles.top) }), _jsx("header", { ...stylex.props(styles.header, isScrolled && styles.scrolled), children: children })] }));
17
+ }
18
+ const styles = stylex.create({
19
+ top: {
20
+ display: 'block',
21
+ height: borders.thin
22
+ },
23
+ header: {
24
+ position: 'sticky',
25
+ top: 0,
26
+ zIndex: layers.header,
27
+ flexShrink: 0,
28
+ marginTop: `calc(-1 * ${borders.thin})`,
29
+ backgroundColor: 'transparent',
30
+ borderBottomColor: 'transparent',
31
+ borderBottomStyle: 'solid',
32
+ borderBottomWidth: borders.thin,
33
+ transitionTimingFunction: motion.easeOut,
34
+ transitionDuration: motion.fast,
35
+ transitionProperty: 'background-color, border-color'
36
+ },
37
+ scrolled: {
38
+ backgroundImage: `linear-gradient(color-mix(in oklab, ${colors.page} 80%, transparent))`,
39
+ borderBottomColor: colors.borderDefault,
40
+ backdropFilter: 'blur(16px)'
41
+ }
42
+ });
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as stylex from '@stylexjs/stylex';
3
3
  import { useState } from 'react';
4
4
  import { imageSources, transformsOn } from "../media.js";
5
- import { colors, motion } from "../theme/tokens.stylex.js";
5
+ import { breakpoints, colors, motion } from "../theme/tokens.stylex.js";
6
6
  export function Image({ image, sizes, priority = false, aspect, style }) {
7
7
  const key = image?.key ?? null;
8
8
  const [seen, setSeen] = useState({
@@ -30,7 +30,7 @@ export function Image({ image, sizes, priority = false, aspect, style }) {
30
30
  }
31
31
  }, alt: image.alt, width: sources.width, height: sources.height, loading: priority ? 'eager' : 'lazy', decoding: priority ? 'auto' : 'async', fetchPriority: priority ? 'high' : 'auto', sizes: sizes, srcSet: sources.srcSet, src: sources.src, onLoad: () => setSeen((current) => current.key === key && current.status === 'shown'
32
32
  ? current
33
- : { key, status: 'fading' }), onError: () => setSeen({ key, status: 'failed' }), ...stylex.props(styles.image, status === 'fading' && styles.fading, status === 'shown' && styles.shown) })] }));
33
+ : { key, status: 'fading' }), onError: () => setSeen({ key, status: 'failed' }), ...stylex.props(styles.image, styles.zoomable, status === 'fading' && styles.fading, status === 'shown' && styles.shown) })] }));
34
34
  }
35
35
  const MISSING_ASPECT = 4 / 3;
36
36
  function servedWithTransforms() {
@@ -68,9 +68,24 @@ const styles = stylex.create({
68
68
  opacity: 1,
69
69
  transitionTimingFunction: motion.easeOut,
70
70
  transitionDuration: motion.base,
71
- transitionProperty: 'opacity'
71
+ transitionProperty: 'opacity, transform'
72
72
  },
73
73
  shown: {
74
74
  opacity: 1
75
+ },
76
+ zoomable: {
77
+ transform: {
78
+ default: null,
79
+ [breakpoints.canHover]: {
80
+ default: null,
81
+ [stylex.when.ancestor(':hover')]: {
82
+ default: 'scale(1.04)',
83
+ [breakpoints.reducedMotion]: 'none'
84
+ }
85
+ }
86
+ },
87
+ transitionTimingFunction: motion.easeOut,
88
+ transitionDuration: motion.slide,
89
+ transitionProperty: 'transform'
75
90
  }
76
91
  });
@@ -32,6 +32,7 @@ declare const builtInIcons: {
32
32
  checkCircle: string[];
33
33
  alertCircle: string[];
34
34
  chevronDown: string;
35
+ chevronLeft: string;
35
36
  chevronRight: string;
36
37
  previous: string;
37
38
  next: string;
@@ -61,6 +61,7 @@ const builtInIcons = {
61
61
  'M11.5 15.75a0.5 0.5 0 1 0 1 0a0.5 0.5 0 1 0 -1 0'
62
62
  ],
63
63
  chevronDown: 'M7 10l5 5 5-5',
64
+ chevronLeft: 'M14 7l-5 5 5 5',
64
65
  chevronRight: 'M10 7l5 5-5 5',
65
66
  previous: 'M15 18l-6-6 6-6',
66
67
  next: 'M9 18l6-6-6-6',
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  export type Interaction = {
3
- primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup' | 'Calendar';
3
+ primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup' | 'Calendar' | 'Gallery';
4
4
  event: 'click' | 'change' | 'select' | 'open' | 'close' | 'submit' | 'navigate';
5
5
  label: string;
6
6
  page: string | null;
@@ -176,6 +176,8 @@ export declare const domainSummary: z.ZodObject<{
176
176
  }>;
177
177
  error: z.ZodNullable<z.ZodString>;
178
178
  startedAt: z.ZodString;
179
+ registeredAt: z.ZodNullable<z.ZodString>;
180
+ expiresAt: z.ZodNullable<z.ZodString>;
179
181
  checkedAt: z.ZodNullable<z.ZodString>;
180
182
  finishedAt: z.ZodNullable<z.ZodString>;
181
183
  }, z.core.$strip>;
@@ -615,6 +617,8 @@ export declare const publishServices: {
615
617
  }>;
616
618
  error: z.ZodNullable<z.ZodString>;
617
619
  startedAt: z.ZodString;
620
+ registeredAt: z.ZodNullable<z.ZodString>;
621
+ expiresAt: z.ZodNullable<z.ZodString>;
618
622
  checkedAt: z.ZodNullable<z.ZodString>;
619
623
  finishedAt: z.ZodNullable<z.ZodString>;
620
624
  }, z.core.$strip>>>>;
@@ -637,6 +641,8 @@ export declare const publishServices: {
637
641
  }>;
638
642
  error: z.ZodNullable<z.ZodString>;
639
643
  startedAt: z.ZodString;
644
+ registeredAt: z.ZodNullable<z.ZodString>;
645
+ expiresAt: z.ZodNullable<z.ZodString>;
640
646
  checkedAt: z.ZodNullable<z.ZodString>;
641
647
  finishedAt: z.ZodNullable<z.ZodString>;
642
648
  }, z.core.$strip>>>;
@@ -656,6 +662,8 @@ export declare const publishServices: {
656
662
  }>;
657
663
  error: z.ZodNullable<z.ZodString>;
658
664
  startedAt: z.ZodString;
665
+ registeredAt: z.ZodNullable<z.ZodString>;
666
+ expiresAt: z.ZodNullable<z.ZodString>;
659
667
  checkedAt: z.ZodNullable<z.ZodString>;
660
668
  finishedAt: z.ZodNullable<z.ZodString>;
661
669
  }, z.core.$strip>>>;
@@ -217,6 +217,8 @@ export const domainSummary = z.object({
217
217
  step: z.enum(domainSteps),
218
218
  error: z.string().nullable(),
219
219
  startedAt: z.string(),
220
+ registeredAt: z.string().nullable(),
221
+ expiresAt: z.string().nullable(),
220
222
  checkedAt: z.string().nullable(),
221
223
  finishedAt: z.string().nullable()
222
224
  });
package/dist/react.d.ts CHANGED
@@ -24,6 +24,8 @@ export { EmptyState } from './components/EmptyState.tsx';
24
24
  export { Form } from './components/Form.tsx';
25
25
  export { Icon } from './components/Icon.tsx';
26
26
  export { IconButton } from './components/IconButton.tsx';
27
+ export { Gallery } from './components/Gallery.tsx';
28
+ export { Header } from './components/Header.tsx';
27
29
  export { Image } from './components/Image.tsx';
28
30
  export type { IconName } from './components/icons.ts';
29
31
  export { Kbd } from './components/Kbd.tsx';
package/dist/react.js CHANGED
@@ -22,6 +22,8 @@ export { EmptyState } from "./components/EmptyState.js";
22
22
  export { Form } from "./components/Form.js";
23
23
  export { Icon } from "./components/Icon.js";
24
24
  export { IconButton } from "./components/IconButton.js";
25
+ export { Gallery } from "./components/Gallery.js";
26
+ export { Header } from "./components/Header.js";
25
27
  export { Image } from "./components/Image.js";
26
28
  export { Kbd } from "./components/Kbd.js";
27
29
  export { Link } from "./components/Link.js";
@@ -3,6 +3,7 @@ const TOKENS_FILE = 'src/theme/tokens.stylex.ts';
3
3
  const APP_THEME_FILE = 'src/theme/theme.ts';
4
4
  const APP_COLORS_FILE = 'src/theme/colors.stylex.ts';
5
5
  const SCROLL_AREA_FILE = 'src/components/ScrollArea.tsx';
6
+ const GALLERY_FILE = 'src/components/Gallery.tsx';
6
7
  const SELECTABLE_FILE = 'src/components/Text.tsx';
7
8
  const PRIMITIVE_ELEMENTS = new Set([
8
9
  'button',
@@ -65,6 +66,8 @@ const PRIMITIVES = new Set([
65
66
  'CopyButton',
66
67
  'EmptyState',
67
68
  'Form',
69
+ 'Gallery',
70
+ 'Header',
68
71
  'Icon',
69
72
  'IconButton',
70
73
  'Image',
@@ -374,8 +377,9 @@ const hoverUsesHighlight = styleRule((declarations) => declarations
374
377
  message: HOVER_USES_HIGHLIGHT_MESSAGE
375
378
  })));
376
379
  const scrollViewOnly = styleRule((declarations, context) => {
377
- if (inFile(context, SCROLL_AREA_FILE))
380
+ if (inFile(context, SCROLL_AREA_FILE) || inFile(context, GALLERY_FILE)) {
378
381
  return [];
382
+ }
379
383
  return declarations
380
384
  .filter((d) => SCROLLING_OVERFLOW_PROPERTIES.has(d.property) &&
381
385
  ['auto', 'scroll'].includes(stringValue(d.value) ?? ''))
@@ -1,6 +1,10 @@
1
1
  import * as stylex from '@stylexjs/stylex';
2
+ export declare const galleryMarker: Readonly<{
3
+ readonly marker: stylex.StyleXClassNameFor<"marker", symbol>;
4
+ }>;
2
5
  export declare const breakpoints: {
3
6
  compact: string;
7
+ canHover: string;
4
8
  reducedMotion: string;
5
9
  coarsePointer: string;
6
10
  };
@@ -147,6 +151,7 @@ export declare const widths: stylex.VarGroup<Readonly<{
147
151
  toast: string;
148
152
  }>, symbol>;
149
153
  export declare const layers: {
154
+ header: string;
150
155
  scrim: string;
151
156
  sidebar: string;
152
157
  menu: string;
@@ -1,6 +1,8 @@
1
1
  import * as stylex from '@stylexjs/stylex';
2
+ export const galleryMarker = stylex.defineMarker();
2
3
  export const breakpoints = stylex.defineConsts({
3
4
  compact: '@media (max-width: 767px)',
5
+ canHover: '@media (hover: hover)',
4
6
  reducedMotion: '@media (prefers-reduced-motion: reduce)',
5
7
  coarsePointer: '@media (pointer: coarse)'
6
8
  });
@@ -147,6 +149,7 @@ export const widths = stylex.defineVars({
147
149
  toast: '356px'
148
150
  });
149
151
  export const layers = stylex.defineConsts({
152
+ header: '10',
150
153
  scrim: '15',
151
154
  sidebar: '20',
152
155
  menu: '20',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trypattern/sdk",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",