@patternmode/aperto 0.1.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/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/index.d.ts +224 -0
- package/dist/index.js +1113 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Howells
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @patternmode/aperto
|
|
2
|
+
|
|
3
|
+
Opinionated, styled thumbnail-to-expanded media transitions for React.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Aperto, type ApertoMediaItem } from "@patternmode/aperto";
|
|
7
|
+
import "@patternmode/aperto/styles.css";
|
|
8
|
+
|
|
9
|
+
const media: ApertoMediaItem[] = [
|
|
10
|
+
{
|
|
11
|
+
type: "image",
|
|
12
|
+
src: "/images/studio-large.jpg",
|
|
13
|
+
thumbnailSrc: "/images/studio-thumb.jpg",
|
|
14
|
+
alt: "Ceramic vessels on a linen-covered studio table",
|
|
15
|
+
title: "Studio table",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: "video",
|
|
19
|
+
src: "/videos/room-study.mp4",
|
|
20
|
+
thumbnailSrc: "/images/room-study-thumb.jpg",
|
|
21
|
+
poster: "/images/room-study-poster.jpg",
|
|
22
|
+
alt: "A slow interior pan across a quiet room",
|
|
23
|
+
title: "Room study",
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function MediaGroupExample() {
|
|
28
|
+
return (
|
|
29
|
+
<Aperto.Group media={media} dismissible={{ threshold: 120, velocity: 600 }}>
|
|
30
|
+
{media.map((item, index) => (
|
|
31
|
+
<Aperto.Thumbnail key={item.id ?? item.src} index={index} />
|
|
32
|
+
))}
|
|
33
|
+
</Aperto.Group>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add @patternmode/aperto
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
React and React DOM are peer dependencies.
|
|
45
|
+
|
|
46
|
+
## Drag dismissal
|
|
47
|
+
|
|
48
|
+
Expanded media can be dismissed by dragging past a distance or velocity
|
|
49
|
+
threshold. Pass `dismissible={false}` to disable drag dismissal, or pass
|
|
50
|
+
`{ threshold, velocity }` to tune the gesture.
|
|
51
|
+
|
|
52
|
+
## Primitive transitions
|
|
53
|
+
|
|
54
|
+
Use the Aperto Primitive API for custom shared-element dialogs that are not
|
|
55
|
+
Media Transitions. `Aperto.Primitive.Content` is centered by default; pass
|
|
56
|
+
`placement="none"` when the panel should own its own positioning.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ImgHTMLAttributes, ReactNode, VideoHTMLAttributes, ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import * as Dialog from '@radix-ui/react-dialog';
|
|
4
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
+
import { Transition } from 'motion/react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @patternmode/aperto
|
|
9
|
+
*
|
|
10
|
+
* Shared element transitions with physics-based drag dismissal.
|
|
11
|
+
* Built on Radix Dialog + Motion.
|
|
12
|
+
*
|
|
13
|
+
* Inspired by Cambio (https://github.com/raphaelsalaja/cambio)
|
|
14
|
+
* by Raphael Salaja. Reimplemented for Radix UI primitives.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface BaseMediaItem {
|
|
18
|
+
description?: string;
|
|
19
|
+
height?: number;
|
|
20
|
+
id?: string;
|
|
21
|
+
src: string;
|
|
22
|
+
thumbnailSrc?: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
width?: number;
|
|
25
|
+
}
|
|
26
|
+
interface ApertoImageItem extends BaseMediaItem {
|
|
27
|
+
alt: string;
|
|
28
|
+
type: "image";
|
|
29
|
+
}
|
|
30
|
+
interface ApertoVideoItem extends BaseMediaItem {
|
|
31
|
+
alt?: string;
|
|
32
|
+
poster?: string;
|
|
33
|
+
thumbnailSrc: string;
|
|
34
|
+
type: "video";
|
|
35
|
+
}
|
|
36
|
+
type ApertoMediaItem = ApertoImageItem | ApertoVideoItem;
|
|
37
|
+
type RenderImage = (props: ImgHTMLAttributes<HTMLImageElement> & {
|
|
38
|
+
item: ApertoImageItem;
|
|
39
|
+
variant: "expanded" | "thumbnail";
|
|
40
|
+
}) => ReactNode;
|
|
41
|
+
type RenderVideo = (props: VideoHTMLAttributes<HTMLVideoElement> & {
|
|
42
|
+
item: ApertoVideoItem;
|
|
43
|
+
variant: "expanded" | "thumbnail";
|
|
44
|
+
}) => ReactNode;
|
|
45
|
+
interface ApertoClassNames {
|
|
46
|
+
closeButton?: string;
|
|
47
|
+
content?: string;
|
|
48
|
+
counter?: string;
|
|
49
|
+
nextButton?: string;
|
|
50
|
+
overlay?: string;
|
|
51
|
+
previousButton?: string;
|
|
52
|
+
thumbnail?: string;
|
|
53
|
+
}
|
|
54
|
+
/** Named motion preset */
|
|
55
|
+
type MotionPresetName = "snappy" | "smooth" | "bouncy" | "reduced";
|
|
56
|
+
/** Expanded media navigation transition preset */
|
|
57
|
+
type NavigationMotionPresetName = "float" | "glide" | "snap";
|
|
58
|
+
/** Per-component motion variant overrides */
|
|
59
|
+
interface MotionVariants {
|
|
60
|
+
backdrop?: MotionPresetName;
|
|
61
|
+
content?: MotionPresetName;
|
|
62
|
+
trigger?: MotionPresetName;
|
|
63
|
+
}
|
|
64
|
+
/** Motion prop: a single preset name, per-component variants, or nothing */
|
|
65
|
+
type MotionProp = MotionPresetName | MotionVariants;
|
|
66
|
+
/** Spring config for drag physics */
|
|
67
|
+
interface DragSpringConfig {
|
|
68
|
+
damping: number;
|
|
69
|
+
restDelta: number;
|
|
70
|
+
stiffness: number;
|
|
71
|
+
}
|
|
72
|
+
/** Resolved preset with transition timing and drag physics */
|
|
73
|
+
interface MotionPreset {
|
|
74
|
+
drag: DragSpringConfig;
|
|
75
|
+
transition: Transition;
|
|
76
|
+
}
|
|
77
|
+
/** Dismissal behaviour config */
|
|
78
|
+
interface DismissibleConfig {
|
|
79
|
+
/** Distance in px to trigger dismissal (default: 100) */
|
|
80
|
+
threshold?: number;
|
|
81
|
+
/** Velocity in px/s to trigger dismissal (default: 500) */
|
|
82
|
+
velocity?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface ApertoProps {
|
|
86
|
+
classNames?: ApertoClassNames;
|
|
87
|
+
/** Whether dragging can dismiss the expanded media (default: true). */
|
|
88
|
+
dismissible?: boolean | DismissibleConfig;
|
|
89
|
+
media: ApertoMediaItem;
|
|
90
|
+
renderImage?: RenderImage;
|
|
91
|
+
renderVideo?: RenderVideo;
|
|
92
|
+
}
|
|
93
|
+
interface ApertoGroupProps {
|
|
94
|
+
children: ReactNode;
|
|
95
|
+
classNames?: ApertoClassNames;
|
|
96
|
+
/** Whether dragging can dismiss the expanded media (default: true). */
|
|
97
|
+
dismissible?: boolean | DismissibleConfig;
|
|
98
|
+
index?: number;
|
|
99
|
+
initialIndex?: number;
|
|
100
|
+
media: ApertoMediaItem[];
|
|
101
|
+
/** Motion preset for open/close transitions */
|
|
102
|
+
motion?: MotionPresetName | MotionVariants;
|
|
103
|
+
navigationMotion?: NavigationMotionPresetName;
|
|
104
|
+
onIndexChange?: (index: number) => void;
|
|
105
|
+
renderImage?: RenderImage;
|
|
106
|
+
renderVideo?: RenderVideo;
|
|
107
|
+
}
|
|
108
|
+
interface ApertoThumbnailProps {
|
|
109
|
+
children?: ReactNode;
|
|
110
|
+
className?: string;
|
|
111
|
+
index: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
declare function ApertoGroup({ children, classNames, dismissible, index: controlledIndex, initialIndex, media, motion: motionProp, navigationMotion, onIndexChange, renderImage, renderVideo, }: ApertoGroupProps): react_jsx_runtime.JSX.Element;
|
|
115
|
+
|
|
116
|
+
declare function ApertoSingle({ classNames, dismissible, media, renderImage, renderVideo, }: ApertoProps): react_jsx_runtime.JSX.Element;
|
|
117
|
+
|
|
118
|
+
declare function ApertoThumbnail({ children, className, index, }: ApertoThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
119
|
+
|
|
120
|
+
declare const ApertoClose: react.ForwardRefExoticComponent<Omit<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>, "ref"> & react.RefAttributes<HTMLButtonElement>>;
|
|
121
|
+
|
|
122
|
+
interface ApertoContentProps extends Omit<ComponentPropsWithoutRef<typeof Dialog.Content>, "asChild" | "forceMount"> {
|
|
123
|
+
/** Motion preset override for this content panel, independent of the root preset. */
|
|
124
|
+
motion?: MotionPresetName;
|
|
125
|
+
/** Built-in positioning strategy. Use "none" for custom primitive compositions. */
|
|
126
|
+
placement?: "center" | "none";
|
|
127
|
+
/** Internal shared layout ID override for grouped media. */
|
|
128
|
+
sharedLayoutId?: string | false;
|
|
129
|
+
}
|
|
130
|
+
declare const ApertoContent: react.ForwardRefExoticComponent<ApertoContentProps & react.RefAttributes<HTMLDivElement>>;
|
|
131
|
+
|
|
132
|
+
declare const ApertoDescription: react.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>, "ref"> & react.RefAttributes<HTMLParagraphElement>>;
|
|
133
|
+
|
|
134
|
+
interface ApertoOverlayProps {
|
|
135
|
+
className?: string;
|
|
136
|
+
/** Internal flag used to fade the overlay during measured close transitions. */
|
|
137
|
+
fadeOut?: boolean;
|
|
138
|
+
style?: React.CSSProperties;
|
|
139
|
+
}
|
|
140
|
+
declare const ApertoOverlay: react.ForwardRefExoticComponent<ApertoOverlayProps & react.RefAttributes<HTMLDivElement>>;
|
|
141
|
+
|
|
142
|
+
interface ApertoPortalProps {
|
|
143
|
+
children: ReactNode;
|
|
144
|
+
/** Container element for the portal (default: document.body) */
|
|
145
|
+
container?: HTMLElement;
|
|
146
|
+
}
|
|
147
|
+
declare function ApertoPortal({ children, container }: ApertoPortalProps): react_jsx_runtime.JSX.Element;
|
|
148
|
+
|
|
149
|
+
interface ApertoRootProps extends Omit<ComponentPropsWithoutRef<typeof Dialog.Root>, "open" | "onOpenChange"> {
|
|
150
|
+
children: ReactNode;
|
|
151
|
+
/** Whether dragging can dismiss the content (default: true) */
|
|
152
|
+
dismissible?: boolean | DismissibleConfig;
|
|
153
|
+
/** Layout ID for shared element transition (auto-generated if omitted) */
|
|
154
|
+
layoutId?: string;
|
|
155
|
+
/** Motion preset or per-component variants */
|
|
156
|
+
motion?: MotionPresetName | MotionVariants;
|
|
157
|
+
/** Controlled open state */
|
|
158
|
+
open?: boolean;
|
|
159
|
+
/** Callback when open state changes */
|
|
160
|
+
onOpenChange?: (open: boolean) => void;
|
|
161
|
+
/** Force reduced motion regardless of system preference */
|
|
162
|
+
reduceMotion?: boolean;
|
|
163
|
+
}
|
|
164
|
+
declare function ApertoRoot({ children, dismissible, layoutId: layoutIdProp, motion: motionProp, open: controlledOpen, onOpenChange: controlledOnOpenChange, reduceMotion: reduceMotionProp, ...dialogProps }: ApertoRootProps): react_jsx_runtime.JSX.Element;
|
|
165
|
+
|
|
166
|
+
declare const ApertoTitle: react.ForwardRefExoticComponent<Omit<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>, "ref"> & react.RefAttributes<HTMLHeadingElement>>;
|
|
167
|
+
|
|
168
|
+
interface ApertoTriggerProps extends ComponentPropsWithoutRef<typeof Dialog.Trigger> {
|
|
169
|
+
/** Internal flag used by grouped thumbnails to raise only the active trigger. */
|
|
170
|
+
active?: boolean;
|
|
171
|
+
/** Override motion preset for this trigger */
|
|
172
|
+
motion?: MotionPresetName;
|
|
173
|
+
/** Internal shared layout ID override for grouped thumbnails. */
|
|
174
|
+
sharedLayoutId?: string | false;
|
|
175
|
+
}
|
|
176
|
+
declare const ApertoTrigger: react.ForwardRefExoticComponent<ApertoTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Motion presets — each bundles transition timing AND drag physics
|
|
180
|
+
* so "snappy" feels snappy everywhere: open, close, and drag.
|
|
181
|
+
*
|
|
182
|
+
* Curves and springs are local copies of the Howells motion tokens so the
|
|
183
|
+
* published package has no private workspace runtime dependencies.
|
|
184
|
+
*/
|
|
185
|
+
declare const PRESETS: Record<MotionPresetName, MotionPreset>;
|
|
186
|
+
type ComponentType = "trigger" | "content" | "backdrop";
|
|
187
|
+
/**
|
|
188
|
+
* Three-level motion resolution:
|
|
189
|
+
* 1. Component-level override (highest)
|
|
190
|
+
* 2. Per-component variant from Root
|
|
191
|
+
* 3. Global preset from Root (lowest)
|
|
192
|
+
*
|
|
193
|
+
* If `prefers-reduced-motion` is active, always returns "reduced".
|
|
194
|
+
*/
|
|
195
|
+
declare function resolvePreset(componentType: ComponentType, componentMotion: MotionPresetName | undefined, globalPreset: MotionPresetName, variants: MotionVariants | undefined, reduceMotion: boolean): MotionPreset;
|
|
196
|
+
|
|
197
|
+
/** Lower-level compound component namespace for advanced composition. */
|
|
198
|
+
declare const ApertoPrimitive: {
|
|
199
|
+
Close: react.ForwardRefExoticComponent<Omit<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>, "ref"> & react.RefAttributes<HTMLButtonElement>>;
|
|
200
|
+
Content: react.ForwardRefExoticComponent<ApertoContentProps & react.RefAttributes<HTMLDivElement>>;
|
|
201
|
+
Description: react.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>, "ref"> & react.RefAttributes<HTMLParagraphElement>>;
|
|
202
|
+
Overlay: react.ForwardRefExoticComponent<ApertoOverlayProps & react.RefAttributes<HTMLDivElement>>;
|
|
203
|
+
Portal: typeof ApertoPortal;
|
|
204
|
+
Root: typeof ApertoRoot;
|
|
205
|
+
Title: react.ForwardRefExoticComponent<Omit<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>, "ref"> & react.RefAttributes<HTMLHeadingElement>>;
|
|
206
|
+
Trigger: react.ForwardRefExoticComponent<ApertoTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
207
|
+
};
|
|
208
|
+
/** Primary media-first component namespace. */
|
|
209
|
+
declare const Aperto: typeof ApertoSingle & {
|
|
210
|
+
Group: typeof ApertoGroup;
|
|
211
|
+
Primitive: {
|
|
212
|
+
Close: react.ForwardRefExoticComponent<Omit<Dialog.DialogCloseProps & react.RefAttributes<HTMLButtonElement>, "ref"> & react.RefAttributes<HTMLButtonElement>>;
|
|
213
|
+
Content: react.ForwardRefExoticComponent<ApertoContentProps & react.RefAttributes<HTMLDivElement>>;
|
|
214
|
+
Description: react.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>, "ref"> & react.RefAttributes<HTMLParagraphElement>>;
|
|
215
|
+
Overlay: react.ForwardRefExoticComponent<ApertoOverlayProps & react.RefAttributes<HTMLDivElement>>;
|
|
216
|
+
Portal: typeof ApertoPortal;
|
|
217
|
+
Root: typeof ApertoRoot;
|
|
218
|
+
Title: react.ForwardRefExoticComponent<Omit<Dialog.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>, "ref"> & react.RefAttributes<HTMLHeadingElement>>;
|
|
219
|
+
Trigger: react.ForwardRefExoticComponent<ApertoTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
220
|
+
};
|
|
221
|
+
Thumbnail: typeof ApertoThumbnail;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
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 };
|