react-morpheus 0.1.3 → 0.1.5
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 +131 -26
- package/dist/index.d.ts +8 -4
- package/dist/index.js +47 -33
- package/dist/style.css +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,46 +2,151 @@
|
|
|
2
2
|
|
|
3
3
|
A controlled React component for morphing one UI state into another.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
source
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
React Morpheus exists for interfaces where a control should become the next
|
|
6
|
+
piece of UI instead of opening a detached popover. The component keeps the
|
|
7
|
+
source surface, destination surface, and overlay explicit. Your app still owns
|
|
8
|
+
state and content; Morpheus owns the measured transition between those two
|
|
9
|
+
surfaces.
|
|
10
|
+
|
|
11
|
+
## Demo
|
|
12
|
+
|
|
13
|
+
<!-- react-morpheus-demo:start -->
|
|
14
|
+
https://github.com/user-attachments/assets/f90e7096-f587-4793-8e80-476cf844569d
|
|
15
|
+
<!-- react-morpheus-demo:end -->
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bun add react-morpheus
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm i react-morpheus
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
yarn add react-morpheus
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Import the component and stylesheet, keep the open state in your app, and pass
|
|
34
|
+
both surfaces to Morpheus.
|
|
11
35
|
|
|
12
36
|
```tsx
|
|
13
|
-
import {
|
|
37
|
+
import { useState } from "react";
|
|
38
|
+
import {
|
|
39
|
+
Morpheus,
|
|
40
|
+
MorphAnchor,
|
|
41
|
+
morphSpringPresets,
|
|
42
|
+
} from "react-morpheus";
|
|
14
43
|
import "react-morpheus/style.css";
|
|
15
44
|
|
|
16
45
|
function Example() {
|
|
17
46
|
const [expanded, setExpanded] = useState(false);
|
|
18
47
|
const source = (
|
|
19
48
|
<button type="button" onClick={() => setExpanded(true)}>
|
|
20
|
-
Open
|
|
49
|
+
Open menu
|
|
21
50
|
</button>
|
|
22
51
|
);
|
|
23
52
|
|
|
24
53
|
return (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
<Morpheus
|
|
55
|
+
direction="bottom"
|
|
56
|
+
anchor={MorphAnchor.TopMiddle}
|
|
57
|
+
expanded={expanded}
|
|
58
|
+
onClose={() => setExpanded(false)}
|
|
59
|
+
overlayColor="#0f172a"
|
|
60
|
+
overlayOpacity={0.18}
|
|
61
|
+
overlayBlur={3}
|
|
62
|
+
overlayZIndex={1200}
|
|
63
|
+
spring={morphSpringPresets.smooth}
|
|
64
|
+
collapsedContent={source}
|
|
65
|
+
expandedContent={
|
|
66
|
+
<section>
|
|
67
|
+
<h2>San Francisco</h2>
|
|
68
|
+
<p>Context, actions, and details live here.</p>
|
|
69
|
+
<button type="button" onClick={() => setExpanded(false)}>
|
|
70
|
+
Close
|
|
71
|
+
</button>
|
|
72
|
+
</section>
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
37
75
|
);
|
|
38
76
|
}
|
|
39
77
|
```
|
|
40
78
|
|
|
41
|
-
|
|
79
|
+
`Morpheus` does not manage its own open state. Opening is the responsibility of
|
|
80
|
+
the source component's own click handler. Overlay clicks call `onClose`; update
|
|
81
|
+
your controlled state there. Use `beforeClose` and `afterClose` for work that
|
|
82
|
+
should run around the closing animation.
|
|
83
|
+
|
|
84
|
+
## Props
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Required | Notes |
|
|
87
|
+
| --- | --- | --- | --- |
|
|
88
|
+
| `direction` | `"top" \| "right" \| "bottom" \| "left"` | Required | Where the panel opens from. Also chooses the default anchor when `anchor` is omitted. |
|
|
89
|
+
| `anchor` | `MorphAnchor` | Optional | The edge or point that stays visually anchored. |
|
|
90
|
+
| `expanded` | `boolean` | Required | Controlled open state owned by your app. |
|
|
91
|
+
| `onClose` | `() => void` | Optional | Called when the overlay requests closing. Use it to update your controlled open state. |
|
|
92
|
+
| `beforeClose` | `() => void` | Optional | Called when the closing animation starts, after `expanded` has changed to `false`. Useful for close-intent side effects. |
|
|
93
|
+
| `afterClose` | `() => void` | Optional | Called after the closing animation completes. |
|
|
94
|
+
| `collapsedContent` | `ReactNode` | Required | The source surface Morpheus measures, renders, and uses as the opener. |
|
|
95
|
+
| `expandedContent` | `ReactNode` | Required | The destination surface Morpheus measures and animates into. |
|
|
96
|
+
| `className` | `string` | Optional | Classes applied to the root wrapper. Useful for block layout and responsive width constraints. |
|
|
97
|
+
| `overlayColor` | `string` | Optional | Backdrop color shown while expanded. |
|
|
98
|
+
| `overlayOpacity` | `number` | Optional | Backdrop opacity from `0` to `1`. |
|
|
99
|
+
| `overlayBlur` | `number` | Optional | Backdrop blur in pixels. |
|
|
100
|
+
| `overlayZIndex` | `number` | Optional | Stacking level for the expanded surface. Defaults to `1000`; the overlay renders one layer below it. |
|
|
101
|
+
| `spring` | `Transition` | Optional | Framer Motion transition. Pass your own transition or use `morphSpringPresets`. |
|
|
102
|
+
|
|
103
|
+
## Pitfalls
|
|
104
|
+
|
|
105
|
+
### Responsiveness
|
|
106
|
+
|
|
107
|
+
Let the collapsed trigger keep its natural size. Morpheus reads that box before
|
|
108
|
+
animating to the expanded surface.
|
|
109
|
+
|
|
110
|
+
Put responsive width rules on `className`, such as viewport-bound max widths, so
|
|
111
|
+
the animated shell has stable constraints on narrow screens.
|
|
112
|
+
|
|
113
|
+
Pick an anchor that matches the source location. Top anchors feel right for
|
|
114
|
+
menus opening below a trigger; bottom anchors feel right for surfaces opening
|
|
115
|
+
upward; middle anchors feel better for centered inspectors or command surfaces.
|
|
116
|
+
|
|
117
|
+
### Parent Container Overflow
|
|
118
|
+
|
|
119
|
+
Do not place Morpheus inside a container that clips overflow, such as
|
|
120
|
+
`overflow: hidden`, `overflow: clip`, or scroll containers. The expanded surface
|
|
121
|
+
is positioned from the local source element and can extend outside its original
|
|
122
|
+
container while animating.
|
|
123
|
+
|
|
124
|
+
### Stacking
|
|
125
|
+
|
|
126
|
+
`overlayZIndex` controls the expanded morph surface. The overlay renders one
|
|
127
|
+
layer below it so apps can move the whole morphing stack above their own chrome.
|
|
128
|
+
Without this prop, Morpheus uses surface `1000` and overlay `999`.
|
|
129
|
+
|
|
130
|
+
## Development
|
|
131
|
+
|
|
132
|
+
`example.html` is a small local playground for trying Morpheus in a browser
|
|
133
|
+
without setting up an app shell. Use it to check the default interaction,
|
|
134
|
+
directions, anchors, and overlay behavior while changing the component.
|
|
135
|
+
|
|
136
|
+
### Scripts
|
|
137
|
+
|
|
138
|
+
- `bun run build` builds declarations, bundled ESM output, and CSS.
|
|
139
|
+
- `bun run check` runs TypeScript without emitting files.
|
|
140
|
+
- `bun run publish:npm` builds and publishes to npm.
|
|
141
|
+
- `bun run release:patch` bumps the patch version, builds, tags, and publishes.
|
|
142
|
+
- `bun run release:minor` bumps the minor version, builds, tags, and publishes.
|
|
143
|
+
- `bun run release:major` bumps the major version, builds, tags, and publishes.
|
|
144
|
+
|
|
145
|
+
## Credits
|
|
146
|
+
|
|
147
|
+
Inspired by Family Wallet and Benji Taylor's
|
|
148
|
+
[Family Values](https://benji.org/family-values) post.
|
|
149
|
+
|
|
150
|
+
## License
|
|
42
151
|
|
|
43
|
-
|
|
44
|
-
- `bun run tag` creates an annotated git tag for the current package version.
|
|
45
|
-
- `bun run tag:push` pushes tags to `origin`.
|
|
46
|
-
- `bun run deploy:npm` builds and publishes to npm.
|
|
47
|
-
- `bun run release:npm` builds, creates the version tag, and publishes to npm.
|
|
152
|
+
Released under the [MIT License](https://github.com/shivekkhurana/react-morpheus/blob/master/LICENSE).
|
package/dist/index.d.ts
CHANGED
|
@@ -77,7 +77,8 @@ type MorphProps = {
|
|
|
77
77
|
direction: MorphDirection;
|
|
78
78
|
anchor?: MorphAnchor | undefined;
|
|
79
79
|
expanded: boolean;
|
|
80
|
-
|
|
80
|
+
onClose?: (() => void) | undefined;
|
|
81
|
+
beforeClose?: (() => void) | undefined;
|
|
81
82
|
afterClose?: (() => void) | undefined;
|
|
82
83
|
collapsedContent: ReactNode;
|
|
83
84
|
expandedContent: ReactNode;
|
|
@@ -85,14 +86,16 @@ type MorphProps = {
|
|
|
85
86
|
overlayColor?: string;
|
|
86
87
|
overlayOpacity?: number;
|
|
87
88
|
overlayBlur?: number;
|
|
89
|
+
overlayZIndex?: number;
|
|
88
90
|
spring?: Transition;
|
|
89
91
|
};
|
|
90
92
|
type MorphOverlayProps = {
|
|
91
93
|
expanded: boolean;
|
|
92
|
-
|
|
94
|
+
onClose?: (() => void) | undefined;
|
|
93
95
|
color: string;
|
|
94
96
|
opacity: number;
|
|
95
97
|
blur: number;
|
|
98
|
+
zIndex: number;
|
|
96
99
|
};
|
|
97
100
|
type MorphMeasurementNodesProps = {
|
|
98
101
|
collapsedRef: RefObject<HTMLDivElement | null>;
|
|
@@ -106,6 +109,7 @@ type MorphShellProps = {
|
|
|
106
109
|
animatedSize: PanelSize;
|
|
107
110
|
visualStyle: PanelVisualStyle;
|
|
108
111
|
overflowVisible: boolean;
|
|
112
|
+
zIndex: number;
|
|
109
113
|
spring: Transition;
|
|
110
114
|
onMorphComplete: () => void;
|
|
111
115
|
onMorphStart: () => void;
|
|
@@ -113,6 +117,7 @@ type MorphShellProps = {
|
|
|
113
117
|
};
|
|
114
118
|
type MorphContentLayersProps = {
|
|
115
119
|
expanded: boolean;
|
|
120
|
+
settledState: "collapsed" | "expanded" | null;
|
|
116
121
|
collapsedContent: ReactNode;
|
|
117
122
|
expandedContent: ReactNode;
|
|
118
123
|
collapsedLayerSize: PanelSize;
|
|
@@ -128,11 +133,10 @@ type MorphContentLayersProps = {
|
|
|
128
133
|
targetOpacityTransition: Transition;
|
|
129
134
|
};
|
|
130
135
|
type MorphLayerInteractivityProps = {
|
|
131
|
-
"aria-hidden": boolean;
|
|
132
136
|
inert?: boolean;
|
|
133
137
|
};
|
|
134
138
|
|
|
135
|
-
declare function Morpheus({ direction, anchor, expanded,
|
|
139
|
+
declare function Morpheus({ direction, anchor, expanded, onClose, beforeClose, afterClose, collapsedContent, expandedContent, className, overlayColor, overlayOpacity, overlayBlur, overlayZIndex: zIndex, spring, }: MorphProps): react.JSX.Element;
|
|
136
140
|
|
|
137
141
|
declare const defaultAnchorByDirection: Record<MorphDirection, MorphAnchor>;
|
|
138
142
|
declare const morphSpringPresets: {
|
package/dist/index.js
CHANGED
|
@@ -226,7 +226,7 @@ function invertPosition(position) {
|
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
function getLayerInteractivityProps(interactive) {
|
|
229
|
-
return interactive ? {
|
|
229
|
+
return interactive ? {} : { inert: true };
|
|
230
230
|
}
|
|
231
231
|
function getMorphMotionState({
|
|
232
232
|
expanded,
|
|
@@ -291,17 +291,18 @@ function getOverlayBackgroundColor(color, opacity) {
|
|
|
291
291
|
}
|
|
292
292
|
function MorphOverlay({
|
|
293
293
|
expanded,
|
|
294
|
-
|
|
294
|
+
onClose,
|
|
295
295
|
color,
|
|
296
296
|
opacity,
|
|
297
|
-
blur
|
|
297
|
+
blur,
|
|
298
|
+
zIndex
|
|
298
299
|
}) {
|
|
299
|
-
|
|
300
|
-
|
|
300
|
+
const MotionOverlay = onClose ? motion.button : motion.div;
|
|
301
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: expanded ? /* @__PURE__ */ jsx(
|
|
302
|
+
MotionOverlay,
|
|
301
303
|
{
|
|
302
|
-
type: "button",
|
|
303
|
-
|
|
304
|
-
className: "fixed inset-0 z-40 cursor-default",
|
|
304
|
+
...onClose ? { type: "button", "aria-label": "Close morph" } : { "aria-hidden": true },
|
|
305
|
+
className: "rmorph:fixed rmorph:inset-0 rmorph:cursor-default",
|
|
305
306
|
initial: {
|
|
306
307
|
opacity: 0
|
|
307
308
|
},
|
|
@@ -315,16 +316,17 @@ function MorphOverlay({
|
|
|
315
316
|
style: {
|
|
316
317
|
backdropFilter: `blur(${blur}px)`,
|
|
317
318
|
backgroundColor: getOverlayBackgroundColor(color, opacity),
|
|
318
|
-
WebkitBackdropFilter: `blur(${blur}px)
|
|
319
|
+
WebkitBackdropFilter: `blur(${blur}px)`,
|
|
320
|
+
zIndex: zIndex - 1
|
|
319
321
|
},
|
|
320
|
-
onClick:
|
|
322
|
+
onClick: onClose
|
|
321
323
|
}
|
|
322
324
|
) : null });
|
|
323
325
|
}
|
|
324
326
|
|
|
325
327
|
// src/component.tsx
|
|
326
328
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
327
|
-
var liveSurfaceContentClassName = "absolute
|
|
329
|
+
var liveSurfaceContentClassName = "rmorph:absolute";
|
|
328
330
|
function MorphMeasurementNodes({
|
|
329
331
|
collapsedRef,
|
|
330
332
|
expandedRef,
|
|
@@ -337,7 +339,7 @@ function MorphMeasurementNodes({
|
|
|
337
339
|
{
|
|
338
340
|
ref: collapsedRef,
|
|
339
341
|
"aria-hidden": "true",
|
|
340
|
-
className: "invisible absolute inset-x-0 top-0 pointer-events-none",
|
|
342
|
+
className: "rmorph:invisible rmorph:absolute rmorph:inset-x-0 rmorph:top-0 rmorph:pointer-events-none",
|
|
341
343
|
children: collapsedContent
|
|
342
344
|
}
|
|
343
345
|
),
|
|
@@ -346,7 +348,7 @@ function MorphMeasurementNodes({
|
|
|
346
348
|
{
|
|
347
349
|
ref: expandedRef,
|
|
348
350
|
"aria-hidden": "true",
|
|
349
|
-
className: "invisible absolute inset-x-0 top-0 pointer-events-none",
|
|
351
|
+
className: "rmorph:invisible rmorph:absolute rmorph:inset-x-0 rmorph:top-0 rmorph:pointer-events-none",
|
|
350
352
|
children: expandedContent
|
|
351
353
|
}
|
|
352
354
|
)
|
|
@@ -358,6 +360,7 @@ function MorphShell({
|
|
|
358
360
|
animatedSize,
|
|
359
361
|
visualStyle,
|
|
360
362
|
overflowVisible,
|
|
363
|
+
zIndex,
|
|
361
364
|
spring,
|
|
362
365
|
onMorphComplete,
|
|
363
366
|
onMorphStart,
|
|
@@ -366,7 +369,7 @@ function MorphShell({
|
|
|
366
369
|
return /* @__PURE__ */ jsx2(
|
|
367
370
|
motion2.div,
|
|
368
371
|
{
|
|
369
|
-
className:
|
|
372
|
+
className: "rmorph:absolute rmorph:shadow-xs",
|
|
370
373
|
initial: false,
|
|
371
374
|
animate: {
|
|
372
375
|
width: animatedSize.width,
|
|
@@ -378,7 +381,8 @@ function MorphShell({
|
|
|
378
381
|
onAnimationComplete: onMorphComplete,
|
|
379
382
|
onAnimationStart: onMorphStart,
|
|
380
383
|
style: {
|
|
381
|
-
overflow: overflowVisible ? "visible" : "hidden"
|
|
384
|
+
overflow: overflowVisible ? "visible" : "hidden",
|
|
385
|
+
zIndex: expanded ? zIndex : 0
|
|
382
386
|
},
|
|
383
387
|
transition: spring,
|
|
384
388
|
children
|
|
@@ -387,6 +391,7 @@ function MorphShell({
|
|
|
387
391
|
}
|
|
388
392
|
function MorphContentLayers({
|
|
389
393
|
expanded,
|
|
394
|
+
settledState,
|
|
390
395
|
collapsedContent,
|
|
391
396
|
expandedContent,
|
|
392
397
|
collapsedLayerSize,
|
|
@@ -419,6 +424,7 @@ function MorphContentLayers({
|
|
|
419
424
|
opacity: sourceOpacityTransition
|
|
420
425
|
},
|
|
421
426
|
style: {
|
|
427
|
+
display: expanded && settledState === "expanded" ? "none" : void 0,
|
|
422
428
|
width: collapsedLayerSize.width,
|
|
423
429
|
height: collapsedLayerSize.height,
|
|
424
430
|
pointerEvents: expanded ? "none" : "auto",
|
|
@@ -444,6 +450,7 @@ function MorphContentLayers({
|
|
|
444
450
|
opacity: targetOpacityTransition
|
|
445
451
|
},
|
|
446
452
|
style: {
|
|
453
|
+
display: !expanded && settledState === "collapsed" ? "none" : void 0,
|
|
447
454
|
width: expandedLayerSize.width,
|
|
448
455
|
height: expandedLayerSize.height,
|
|
449
456
|
pointerEvents: expanded ? "auto" : "none",
|
|
@@ -458,20 +465,26 @@ function Morpheus({
|
|
|
458
465
|
direction,
|
|
459
466
|
anchor,
|
|
460
467
|
expanded,
|
|
461
|
-
|
|
468
|
+
onClose,
|
|
469
|
+
beforeClose,
|
|
462
470
|
afterClose,
|
|
463
471
|
collapsedContent,
|
|
464
472
|
expandedContent,
|
|
465
|
-
className = "relative inline-block align-top",
|
|
473
|
+
className = "rmorph:relative rmorph:inline-block rmorph:align-top",
|
|
466
474
|
overlayColor = "#05070a",
|
|
467
475
|
overlayOpacity = 0.54,
|
|
468
476
|
overlayBlur = 0,
|
|
477
|
+
overlayZIndex: zIndex = 1e3,
|
|
469
478
|
spring = defaultPanelSpring
|
|
470
479
|
}) {
|
|
471
480
|
const measurements = useMorphMeasurements();
|
|
472
481
|
const [animationEnabled, setAnimationEnabled] = useState2(false);
|
|
473
482
|
const [shellOverflowVisible, setShellOverflowVisible] = useState2(false);
|
|
483
|
+
const [settledState, setSettledState] = useState2(
|
|
484
|
+
expanded ? "expanded" : "collapsed"
|
|
485
|
+
);
|
|
474
486
|
const hasOpenedRef = useRef2(expanded);
|
|
487
|
+
const beforeCloseCalledRef = useRef2(false);
|
|
475
488
|
useEffect(() => {
|
|
476
489
|
if (!measurements.ready) {
|
|
477
490
|
return;
|
|
@@ -479,18 +492,6 @@ function Morpheus({
|
|
|
479
492
|
const frame = requestAnimationFrame(() => setAnimationEnabled(true));
|
|
480
493
|
return () => cancelAnimationFrame(frame);
|
|
481
494
|
}, [measurements.ready]);
|
|
482
|
-
useEffect(() => {
|
|
483
|
-
if (!expanded || !onCloseIntent) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
const handleKeyDown = (event) => {
|
|
487
|
-
if (event.key === "Escape" && !event.repeat) {
|
|
488
|
-
onCloseIntent();
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
492
|
-
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
493
|
-
}, [expanded, onCloseIntent]);
|
|
494
495
|
const motionState = getMorphMotionState({
|
|
495
496
|
expanded,
|
|
496
497
|
direction,
|
|
@@ -507,10 +508,11 @@ function Morpheus({
|
|
|
507
508
|
MorphOverlay,
|
|
508
509
|
{
|
|
509
510
|
expanded,
|
|
510
|
-
|
|
511
|
+
onClose,
|
|
511
512
|
color: overlayColor,
|
|
512
513
|
opacity: overlayOpacity,
|
|
513
|
-
blur: overlayBlur
|
|
514
|
+
blur: overlayBlur,
|
|
515
|
+
zIndex
|
|
514
516
|
}
|
|
515
517
|
),
|
|
516
518
|
/* @__PURE__ */ jsxs("div", { className, "aria-live": "polite", children: [
|
|
@@ -523,7 +525,7 @@ function Morpheus({
|
|
|
523
525
|
expandedContent
|
|
524
526
|
}
|
|
525
527
|
),
|
|
526
|
-
/* @__PURE__ */ jsx2("div", { className: "relative mx-auto block w-fit align-top", children: shouldRenderStaticSource ? /* @__PURE__ */ jsx2("div", { className: "inline-block align-top", children: collapsedContent }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
528
|
+
/* @__PURE__ */ jsx2("div", { className: "rmorph:relative rmorph:mx-auto rmorph:block rmorph:w-fit rmorph:align-top", children: shouldRenderStaticSource ? /* @__PURE__ */ jsx2("div", { className: "rmorph:inline-block rmorph:align-top", children: collapsedContent }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
527
529
|
/* @__PURE__ */ jsx2(
|
|
528
530
|
"div",
|
|
529
531
|
{
|
|
@@ -543,23 +545,35 @@ function Morpheus({
|
|
|
543
545
|
animatedSize: motionState.animatedSize,
|
|
544
546
|
visualStyle: motionState.visualStyle,
|
|
545
547
|
overflowVisible,
|
|
548
|
+
zIndex,
|
|
546
549
|
spring: motionState.activeSpring,
|
|
547
550
|
onMorphComplete: () => {
|
|
548
551
|
if (expanded) {
|
|
549
552
|
hasOpenedRef.current = true;
|
|
553
|
+
beforeCloseCalledRef.current = false;
|
|
554
|
+
setSettledState("expanded");
|
|
550
555
|
setShellOverflowVisible(true);
|
|
551
556
|
return;
|
|
552
557
|
}
|
|
558
|
+
setSettledState("collapsed");
|
|
553
559
|
if (hasOpenedRef.current) {
|
|
554
560
|
hasOpenedRef.current = false;
|
|
555
561
|
afterClose?.();
|
|
556
562
|
}
|
|
557
563
|
},
|
|
558
|
-
onMorphStart: () =>
|
|
564
|
+
onMorphStart: () => {
|
|
565
|
+
if (!expanded && hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
566
|
+
beforeCloseCalledRef.current = true;
|
|
567
|
+
beforeClose?.();
|
|
568
|
+
}
|
|
569
|
+
setShellOverflowVisible(false);
|
|
570
|
+
setSettledState(expanded ? "collapsed" : "expanded");
|
|
571
|
+
},
|
|
559
572
|
children: /* @__PURE__ */ jsx2(
|
|
560
573
|
MorphContentLayers,
|
|
561
574
|
{
|
|
562
575
|
expanded,
|
|
576
|
+
settledState,
|
|
563
577
|
collapsedContent,
|
|
564
578
|
expandedContent,
|
|
565
579
|
collapsedLayerSize: motionState.collapsedLayerSize,
|
package/dist/style.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}.rmorph\:pointer-events-none{pointer-events:none}.rmorph\:invisible{visibility:hidden}.rmorph\:absolute{position:absolute}.rmorph\:fixed{position:fixed}.rmorph\:relative{position:relative}.rmorph\:inset-0{inset:0}.rmorph\:inset-x-0{inset-inline:0}.rmorph\:top-0{top:0}.rmorph\:mx-auto{margin-inline:auto}.rmorph\:block{display:block}.rmorph\:inline-block{display:inline-block}.rmorph\:w-fit{width:fit-content}.rmorph\:cursor-default{cursor:default}.rmorph\:align-top{vertical-align:top}.rmorph\:shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-morpheus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A React component for morphing one surface into other.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
+
"prepare": "bun run build",
|
|
27
28
|
"build": "bun run build:js && bun run build:css",
|
|
28
29
|
"build:js": "tsup src/index.ts --format esm --dts --external react --external react-dom --external framer-motion --clean",
|
|
29
30
|
"build:css": "tailwindcss -i src/style.css -o dist/style.css --minify",
|