@patternmode/aperto 0.1.4 → 0.2.1

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.
package/README.md CHANGED
@@ -35,6 +35,40 @@ export function MediaGroupExample() {
35
35
  }
36
36
  ```
37
37
 
38
+ ## Optimized images
39
+
40
+ Aperto's built-in image renderer uses a plain `img` so the package stays
41
+ framework agnostic. If your app uses Next.js, pass `renderImage` and return
42
+ your own `next/image` `Image` component:
43
+
44
+ ```tsx
45
+ import Image from "next/image";
46
+
47
+ const leadSrc = media[0]?.src;
48
+
49
+ <Aperto.Group
50
+ media={media}
51
+ renderImage={({ alt, item, src, variant }) => {
52
+ const isLeadImage = variant === "expanded" || item.src === leadSrc;
53
+
54
+ return (
55
+ <Image
56
+ alt={alt ?? ""}
57
+ fetchPriority={isLeadImage ? "high" : "auto"}
58
+ fill
59
+ loading={isLeadImage || variant === "thumbnail" ? "eager" : "lazy"}
60
+ sizes={variant === "thumbnail" ? "(max-width: 640px) 50vw, 320px" : "90vw"}
61
+ src={String(src)}
62
+ />
63
+ );
64
+ }}
65
+ >
66
+ {media.map((item, index) => (
67
+ <Aperto.Thumbnail key={item.id ?? item.src} index={index} />
68
+ ))}
69
+ </Aperto.Group>;
70
+ ```
71
+
38
72
  ## Install
39
73
 
40
74
  ```bash
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import * as react from 'react';
2
- import { ImgHTMLAttributes, ReactNode, VideoHTMLAttributes, ComponentPropsWithoutRef } from 'react';
2
+ import { ImgHTMLAttributes, ReactNode, VideoHTMLAttributes, ComponentPropsWithRef, Ref, CSSProperties, ComponentPropsWithoutRef } from 'react';
3
3
  import * as Dialog from '@radix-ui/react-dialog';
4
- import * as react_jsx_runtime from 'react/jsx-runtime';
5
4
  import { Transition } from 'motion/react';
6
5
 
7
6
  /**
8
- * @patternmode/aperto
7
+ * Package: @patternmode/aperto
9
8
  *
10
9
  * Shared element transitions with physics-based drag dismissal.
11
10
  * Built on Radix Dialog + Motion.
@@ -23,22 +22,42 @@ interface BaseMediaItem {
23
22
  title?: string;
24
23
  width?: number;
25
24
  }
25
+ /** Image media item rendered as a thumbnail and expanded image. */
26
26
  interface ApertoImageItem extends BaseMediaItem {
27
+ /** Required alt text for the image renderer. */
27
28
  alt: string;
28
29
  type: "image";
29
30
  }
31
+ /** Video media item rendered with a thumbnail and expanded player. */
30
32
  interface ApertoVideoItem extends BaseMediaItem {
31
33
  alt?: string;
34
+ /** Optional captions track URL for the expanded video. */
32
35
  captionsSrc?: string;
33
36
  poster?: string;
37
+ /** Required thumbnail image shown before expansion. */
34
38
  thumbnailSrc: string;
35
39
  type: "video";
36
40
  }
41
+ /** Media item supported by Aperto's grouped media API. */
37
42
  type ApertoMediaItem = ApertoImageItem | ApertoVideoItem;
43
+ /**
44
+ * Custom image renderer for image media.
45
+ *
46
+ * Aperto's default renderer is a plain `img` so the package stays framework
47
+ * agnostic. Next.js consumers should return their own `next/image` `Image`
48
+ * component here, using `variant` to choose thumbnail vs expanded sizing,
49
+ * loading, or fetch priority behavior.
50
+ */
38
51
  type RenderImage = (props: ImgHTMLAttributes<HTMLImageElement> & {
39
52
  item: ApertoImageItem;
40
53
  variant: "expanded" | "thumbnail";
41
54
  }) => ReactNode;
55
+ /**
56
+ * Custom video renderer for video media.
57
+ *
58
+ * Use this to replace the default `video` element while preserving the supplied
59
+ * accessibility, poster, and control props.
60
+ */
42
61
  type RenderVideo = (props: VideoHTMLAttributes<HTMLVideoElement> & {
43
62
  item: ApertoVideoItem;
44
63
  variant: "expanded" | "thumbnail";
@@ -77,50 +96,98 @@ interface MotionPreset {
77
96
  }
78
97
  /** Dismissal behaviour config */
79
98
  interface DismissibleConfig {
80
- /** Distance in px to trigger dismissal (default: 100) */
99
+ /**
100
+ * Distance in px to trigger dismissal.
101
+ *
102
+ * Default `100`.
103
+ */
81
104
  threshold?: number;
82
- /** Velocity in px/s to trigger dismissal (default: 500) */
105
+ /**
106
+ * Velocity in px/s to trigger dismissal.
107
+ *
108
+ * Default `500`.
109
+ */
83
110
  velocity?: number;
84
111
  }
85
112
 
86
113
  interface ApertoProps {
87
114
  classNames?: ApertoClassNames;
88
- /** Whether dragging can dismiss the expanded media (default: true). */
115
+ /**
116
+ * Whether dragging can dismiss the expanded media.
117
+ *
118
+ * Default `true`.
119
+ */
89
120
  dismissible?: boolean | DismissibleConfig;
121
+ /** Single media item rendered by the standalone Aperto component. */
90
122
  media: ApertoMediaItem;
123
+ /**
124
+ * Overrides Aperto's plain `img` renderer for image media.
125
+ *
126
+ * Use this to render framework-specific image components such as Next.js
127
+ * `Image` while keeping Aperto itself framework agnostic.
128
+ */
91
129
  renderImage?: RenderImage;
92
130
  renderVideo?: RenderVideo;
93
131
  }
94
132
  interface ApertoGroupProps {
95
133
  children: ReactNode;
96
134
  classNames?: ApertoClassNames;
97
- /** Whether dragging can dismiss the expanded media (default: true). */
135
+ /**
136
+ * Whether dragging can dismiss the expanded media.
137
+ *
138
+ * Default `true`.
139
+ */
98
140
  dismissible?: boolean | DismissibleConfig;
141
+ /** Controlled active media index. Pair with `onIndexChange`. */
99
142
  index?: number;
143
+ /**
144
+ * Initial active media index for uncontrolled groups.
145
+ *
146
+ * Default `0`.
147
+ */
100
148
  initialIndex?: number;
101
149
  media: ApertoMediaItem[];
102
- /** Motion preset for open/close transitions */
150
+ /**
151
+ * Motion preset for open/close transitions, or per-part motion overrides.
152
+ *
153
+ * Default `"smooth"`.
154
+ */
103
155
  motion?: MotionPresetName | MotionVariants;
156
+ /**
157
+ * Motion preset for next/previous navigation inside expanded media.
158
+ *
159
+ * Default `"glide"`.
160
+ */
104
161
  navigationMotion?: NavigationMotionPresetName;
162
+ /** Called whenever grouped media navigation changes the active index. */
105
163
  onIndexChange?: (index: number) => void;
164
+ /**
165
+ * Overrides Aperto's plain `img` renderer for image media.
166
+ *
167
+ * Use this to render framework-specific image components such as Next.js
168
+ * `Image` while keeping Aperto itself framework agnostic.
169
+ */
106
170
  renderImage?: RenderImage;
107
171
  renderVideo?: RenderVideo;
108
172
  }
109
173
  interface ApertoThumbnailProps {
174
+ /** Optional custom thumbnail content; defaults to media thumbnail rendering. */
110
175
  children?: ReactNode;
111
176
  className?: string;
177
+ /** Index into the parent group's `media` array. */
112
178
  index: number;
113
179
  }
114
180
 
115
- declare function ApertoGroup({ children, classNames, dismissible, index: controlledIndex, initialIndex, media, motion: motionProp, navigationMotion, onIndexChange, renderImage, renderVideo, }: ApertoGroupProps): react_jsx_runtime.JSX.Element;
116
-
117
- declare function ApertoSingle({ classNames, dismissible, media, renderImage, renderVideo, }: ApertoProps): react_jsx_runtime.JSX.Element;
181
+ declare const ApertoGroup: ({ children, classNames, dismissible, index, initialIndex, media, motion: motionProp, navigationMotion, onIndexChange, renderImage, renderVideo, }: ApertoGroupProps) => react.JSX.Element;
118
182
 
119
- declare function ApertoThumbnail({ children, className, index, }: ApertoThumbnailProps): react_jsx_runtime.JSX.Element | null;
183
+ declare const ApertoThumbnail: ({ children, className, index }: ApertoThumbnailProps) => react.JSX.Element | null;
120
184
 
121
- declare const ApertoClose: react.ForwardRefExoticComponent<Omit<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>, "ref"> & react.RefAttributes<HTMLButtonElement>>;
185
+ declare const ApertoClose: {
186
+ ({ children, ref, ...props }: ComponentPropsWithRef<typeof Dialog.Close>): react.JSX.Element;
187
+ displayName: string;
188
+ };
122
189
 
123
- interface ApertoContentProps extends Omit<ComponentPropsWithoutRef<typeof Dialog.Content>, "asChild" | "forceMount"> {
190
+ interface ApertoContentProps extends Omit<ComponentPropsWithRef<typeof Dialog.Content>, "asChild" | "forceMount"> {
124
191
  /** Motion preset override for this content panel, independent of the root preset. */
125
192
  motion?: MotionPresetName;
126
193
  /** Built-in positioning strategy. Use "none" for custom primitive compositions. */
@@ -128,24 +195,34 @@ interface ApertoContentProps extends Omit<ComponentPropsWithoutRef<typeof Dialog
128
195
  /** Internal shared layout ID override for grouped media. */
129
196
  sharedLayoutId?: string | false;
130
197
  }
131
- declare const ApertoContent: react.ForwardRefExoticComponent<ApertoContentProps & react.RefAttributes<HTMLDivElement>>;
198
+ declare const ApertoContent: {
199
+ ({ children, className, motion: motionOverride, placement, ref, sharedLayoutId, style, ...props }: ApertoContentProps): react.JSX.Element;
200
+ displayName: string;
201
+ };
132
202
 
133
- declare const ApertoDescription: react.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>, "ref"> & react.RefAttributes<HTMLParagraphElement>>;
203
+ declare const ApertoDescription: {
204
+ ({ ref, ...props }: ComponentPropsWithRef<typeof Dialog.Description>): react.JSX.Element;
205
+ displayName: string;
206
+ };
134
207
 
135
208
  interface ApertoOverlayProps {
136
209
  className?: string;
137
210
  /** Internal flag used to fade the overlay during measured close transitions. */
138
211
  fadeOut?: boolean;
139
- style?: React.CSSProperties;
212
+ ref?: Ref<HTMLDivElement>;
213
+ style?: CSSProperties;
140
214
  }
141
- declare const ApertoOverlay: react.ForwardRefExoticComponent<ApertoOverlayProps & react.RefAttributes<HTMLDivElement>>;
215
+ declare const ApertoOverlay: {
216
+ ({ className, fadeOut, ref, style }: ApertoOverlayProps): react.JSX.Element;
217
+ displayName: string;
218
+ };
142
219
 
143
220
  interface ApertoPortalProps {
144
221
  children: ReactNode;
145
222
  /** Container element for the portal (default: document.body) */
146
223
  container?: HTMLElement;
147
224
  }
148
- declare function ApertoPortal({ children, container }: ApertoPortalProps): react_jsx_runtime.JSX.Element;
225
+ declare const ApertoPortal: ({ children, container }: ApertoPortalProps) => react.JSX.Element;
149
226
 
150
227
  interface ApertoRootProps extends Omit<ComponentPropsWithoutRef<typeof Dialog.Root>, "open" | "onOpenChange"> {
151
228
  children: ReactNode;
@@ -162,11 +239,14 @@ interface ApertoRootProps extends Omit<ComponentPropsWithoutRef<typeof Dialog.Ro
162
239
  /** Force reduced motion regardless of system preference */
163
240
  reduceMotion?: boolean;
164
241
  }
165
- declare function ApertoRoot({ children, dismissible, layoutId: layoutIdProp, motion: motionProp, open: controlledOpen, onOpenChange: controlledOnOpenChange, reduceMotion: reduceMotionProp, ...dialogProps }: ApertoRootProps): react_jsx_runtime.JSX.Element;
242
+ declare const ApertoRoot: ({ children, dismissible, layoutId: layoutIdProp, motion: motionProp, open: controlledOpen, onOpenChange: controlledOnOpenChange, reduceMotion: reduceMotionProp, ...dialogProps }: ApertoRootProps) => react.JSX.Element;
166
243
 
167
- declare const ApertoTitle: react.ForwardRefExoticComponent<Omit<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>, "ref"> & react.RefAttributes<HTMLHeadingElement>>;
244
+ declare const ApertoTitle: {
245
+ ({ ref, ...props }: ComponentPropsWithRef<typeof Dialog.Title>): react.JSX.Element;
246
+ displayName: string;
247
+ };
168
248
 
169
- interface ApertoTriggerProps extends ComponentPropsWithoutRef<typeof Dialog.Trigger> {
249
+ interface ApertoTriggerProps extends ComponentPropsWithRef<typeof Dialog.Trigger> {
170
250
  /** Internal flag used by grouped thumbnails to raise only the active trigger. */
171
251
  active?: boolean;
172
252
  /** Override motion preset for this trigger */
@@ -174,14 +254,17 @@ interface ApertoTriggerProps extends ComponentPropsWithoutRef<typeof Dialog.Trig
174
254
  /** Internal shared layout ID override for grouped thumbnails. */
175
255
  sharedLayoutId?: string | false;
176
256
  }
177
- declare const ApertoTrigger: react.ForwardRefExoticComponent<ApertoTriggerProps & react.RefAttributes<HTMLButtonElement>>;
257
+ declare const ApertoTrigger: {
258
+ ({ active, children, motion: motionOverride, ref, sharedLayoutId, ...props }: ApertoTriggerProps): react.JSX.Element;
259
+ displayName: string;
260
+ };
178
261
 
179
262
  /**
180
263
  * Motion presets — each bundles transition timing AND drag physics
181
264
  * so "snappy" feels snappy everywhere: open, close, and drag.
182
265
  *
183
- * Curves and springs are local copies of the Howells motion tokens so the
184
- * published package has no private workspace runtime dependencies.
266
+ * Curves and springs come from the shared @howells/motion tokens so motion
267
+ * feel stays consistent across the catalog.
185
268
  */
186
269
  declare const PRESETS: Record<MotionPresetName, MotionPreset>;
187
270
  type ComponentType = "trigger" | "content" | "backdrop";
@@ -193,33 +276,69 @@ type ComponentType = "trigger" | "content" | "backdrop";
193
276
  *
194
277
  * If `prefers-reduced-motion` is active, always returns "reduced".
195
278
  */
196
- declare function resolvePreset(componentType: ComponentType, componentMotion: MotionPresetName | undefined, globalPreset: MotionPresetName, variants: MotionVariants | undefined, reduceMotion: boolean): MotionPreset;
279
+ declare const resolvePreset: (componentType: ComponentType, componentMotion: MotionPresetName | undefined, globalPreset: MotionPresetName, variants: MotionVariants | undefined, reduceMotion: boolean) => MotionPreset;
197
280
 
198
281
  /** Lower-level compound component namespace for advanced composition. */
199
282
  declare const ApertoPrimitive: {
200
- Close: react.ForwardRefExoticComponent<Omit<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>, "ref"> & react.RefAttributes<HTMLButtonElement>>;
201
- Content: react.ForwardRefExoticComponent<ApertoContentProps & react.RefAttributes<HTMLDivElement>>;
202
- Description: react.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>, "ref"> & react.RefAttributes<HTMLParagraphElement>>;
203
- Overlay: react.ForwardRefExoticComponent<ApertoOverlayProps & react.RefAttributes<HTMLDivElement>>;
204
- Portal: typeof ApertoPortal;
205
- Root: typeof ApertoRoot;
206
- Title: react.ForwardRefExoticComponent<Omit<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>, "ref"> & react.RefAttributes<HTMLHeadingElement>>;
207
- Trigger: react.ForwardRefExoticComponent<ApertoTriggerProps & react.RefAttributes<HTMLButtonElement>>;
283
+ Close: {
284
+ ({ children, ref, ...props }: react.ComponentPropsWithRef<react.ForwardRefExoticComponent<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>>>): react.JSX.Element;
285
+ displayName: string;
286
+ };
287
+ Content: {
288
+ ({ children, className, motion: motionOverride, placement, ref, sharedLayoutId, style, ...props }: ApertoContentProps): react.JSX.Element;
289
+ displayName: string;
290
+ };
291
+ Description: {
292
+ ({ ref, ...props }: react.ComponentPropsWithRef<react.ForwardRefExoticComponent<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>>>): react.JSX.Element;
293
+ displayName: string;
294
+ };
295
+ Overlay: {
296
+ ({ className, fadeOut, ref, style }: ApertoOverlayProps): react.JSX.Element;
297
+ displayName: string;
298
+ };
299
+ Portal: ({ children, container }: ApertoPortalProps) => react.JSX.Element;
300
+ Root: ({ children, dismissible, layoutId: layoutIdProp, motion: motionProp, open: controlledOpen, onOpenChange: controlledOnOpenChange, reduceMotion: reduceMotionProp, ...dialogProps }: ApertoRootProps) => react.JSX.Element;
301
+ Title: {
302
+ ({ ref, ...props }: react.ComponentPropsWithRef<react.ForwardRefExoticComponent<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>>>): react.JSX.Element;
303
+ displayName: string;
304
+ };
305
+ Trigger: {
306
+ ({ active, children, motion: motionOverride, ref, sharedLayoutId, ...props }: ApertoTriggerProps): react.JSX.Element;
307
+ displayName: string;
308
+ };
208
309
  };
209
310
  /** Primary media-first component namespace. */
210
- declare const Aperto: typeof ApertoSingle & {
211
- Group: typeof ApertoGroup;
311
+ declare const Aperto: (({ classNames, dismissible, media, renderImage, renderVideo, }: ApertoProps) => react.JSX.Element) & {
312
+ Group: ({ children, classNames, dismissible, index, initialIndex, media, motion: motionProp, navigationMotion, onIndexChange, renderImage, renderVideo, }: ApertoGroupProps) => react.JSX.Element;
212
313
  Primitive: {
213
- Close: react.ForwardRefExoticComponent<Omit<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>, "ref"> & react.RefAttributes<HTMLButtonElement>>;
214
- Content: react.ForwardRefExoticComponent<ApertoContentProps & react.RefAttributes<HTMLDivElement>>;
215
- Description: react.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>, "ref"> & react.RefAttributes<HTMLParagraphElement>>;
216
- Overlay: react.ForwardRefExoticComponent<ApertoOverlayProps & react.RefAttributes<HTMLDivElement>>;
217
- Portal: typeof ApertoPortal;
218
- Root: typeof ApertoRoot;
219
- Title: react.ForwardRefExoticComponent<Omit<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>, "ref"> & react.RefAttributes<HTMLHeadingElement>>;
220
- Trigger: react.ForwardRefExoticComponent<ApertoTriggerProps & react.RefAttributes<HTMLButtonElement>>;
314
+ Close: {
315
+ ({ children, ref, ...props }: react.ComponentPropsWithRef<react.ForwardRefExoticComponent<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>>>): react.JSX.Element;
316
+ displayName: string;
317
+ };
318
+ Content: {
319
+ ({ children, className, motion: motionOverride, placement, ref, sharedLayoutId, style, ...props }: ApertoContentProps): react.JSX.Element;
320
+ displayName: string;
321
+ };
322
+ Description: {
323
+ ({ ref, ...props }: react.ComponentPropsWithRef<react.ForwardRefExoticComponent<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>>>): react.JSX.Element;
324
+ displayName: string;
325
+ };
326
+ Overlay: {
327
+ ({ className, fadeOut, ref, style }: ApertoOverlayProps): react.JSX.Element;
328
+ displayName: string;
329
+ };
330
+ Portal: ({ children, container }: ApertoPortalProps) => react.JSX.Element;
331
+ Root: ({ children, dismissible, layoutId: layoutIdProp, motion: motionProp, open: controlledOpen, onOpenChange: controlledOnOpenChange, reduceMotion: reduceMotionProp, ...dialogProps }: ApertoRootProps) => react.JSX.Element;
332
+ Title: {
333
+ ({ ref, ...props }: react.ComponentPropsWithRef<react.ForwardRefExoticComponent<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>>>): react.JSX.Element;
334
+ displayName: string;
335
+ };
336
+ Trigger: {
337
+ ({ active, children, motion: motionOverride, ref, sharedLayoutId, ...props }: ApertoTriggerProps): react.JSX.Element;
338
+ displayName: string;
339
+ };
221
340
  };
222
- Thumbnail: typeof ApertoThumbnail;
341
+ Thumbnail: ({ children, className, index }: ApertoThumbnailProps) => react.JSX.Element | null;
223
342
  };
224
343
 
225
344
  export { Aperto, type ApertoClassNames, ApertoClose, ApertoContent, type ApertoContentProps, ApertoDescription, ApertoGroup, type ApertoGroupProps, type ApertoImageItem, type ApertoMediaItem, ApertoOverlay, type ApertoOverlayProps, ApertoPortal, type ApertoPortalProps, ApertoPrimitive, type ApertoProps, ApertoRoot, type ApertoRootProps, ApertoThumbnail, type ApertoThumbnailProps, ApertoTitle, ApertoTrigger, type ApertoTriggerProps, type ApertoVideoItem, type DismissibleConfig, type DragSpringConfig, type MotionPreset, type MotionPresetName, type MotionProp, type MotionVariants, type NavigationMotionPresetName, PRESETS, type RenderImage, type RenderVideo, resolvePreset };