react-morpheus 0.1.3 → 0.1.4
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 +31 -21
- package/dist/index.d.ts +7 -4
- package/dist/index.js +37 -33
- package/dist/style.css +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
A controlled React component for morphing one UI state into another.
|
|
4
4
|
|
|
5
5
|
`Morpheus` does not manage its own open state. Keep the `expanded` boolean in
|
|
6
|
-
your app state
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
your app state and pass your trigger to `collapsedContent` so Morpheus can
|
|
7
|
+
render, measure, and morph from it. Opening is the responsibility of the source
|
|
8
|
+
component's own click handler.
|
|
9
|
+
Overlay clicks call `onClose`; update your controlled state there. Use
|
|
10
|
+
`beforeClose` and `afterClose` for work that should run around the closing
|
|
11
|
+
animation.
|
|
11
12
|
|
|
12
13
|
```tsx
|
|
13
14
|
import { Morpheus, MorphAnchor } from "react-morpheus";
|
|
@@ -22,26 +23,35 @@ function Example() {
|
|
|
22
23
|
);
|
|
23
24
|
|
|
24
25
|
return (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</>
|
|
26
|
+
<Morpheus
|
|
27
|
+
direction="bottom"
|
|
28
|
+
anchor={MorphAnchor.TopMiddle}
|
|
29
|
+
expanded={expanded}
|
|
30
|
+
onClose={() => setExpanded(false)}
|
|
31
|
+
beforeClose={() => console.log("closing")}
|
|
32
|
+
afterClose={() => console.log("closed")}
|
|
33
|
+
collapsedContent={source}
|
|
34
|
+
expandedContent={<div>Expanded content</div>}
|
|
35
|
+
overlayZIndex={1200}
|
|
36
|
+
/>
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
`overlayZIndex` controls the expanded morph surface. The overlay renders one
|
|
42
|
+
layer below it so apps can move the whole morphing stack above their own
|
|
43
|
+
chrome. Without this prop, Morpheus uses surface `1000` and overlay `999`.
|
|
44
|
+
|
|
45
|
+
Do not place Morpheus inside a container that clips overflow, such as
|
|
46
|
+
`overflow: hidden`, `overflow: clip`, or scroll containers. The expanded surface
|
|
47
|
+
is positioned from the local source element and can extend outside its original
|
|
48
|
+
container while animating.
|
|
49
|
+
|
|
41
50
|
## Scripts
|
|
42
51
|
|
|
43
52
|
- `bun run build` builds declarations and bundled ESM output.
|
|
44
|
-
- `bun run
|
|
45
|
-
- `bun run
|
|
46
|
-
- `bun run
|
|
47
|
-
- `bun run release:
|
|
53
|
+
- `bun run check` runs TypeScript without emitting files.
|
|
54
|
+
- `bun run publish:npm` builds and publishes to npm.
|
|
55
|
+
- `bun run release:patch` bumps the patch version, builds, tags, and publishes.
|
|
56
|
+
- `bun run release:minor` bumps the minor version, builds, tags, and publishes.
|
|
57
|
+
- `bun run release:major` bumps the major version, builds, tags, and publishes.
|
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;
|
|
@@ -128,11 +132,10 @@ type MorphContentLayersProps = {
|
|
|
128
132
|
targetOpacityTransition: Transition;
|
|
129
133
|
};
|
|
130
134
|
type MorphLayerInteractivityProps = {
|
|
131
|
-
"aria-hidden": boolean;
|
|
132
135
|
inert?: boolean;
|
|
133
136
|
};
|
|
134
137
|
|
|
135
|
-
declare function Morpheus({ direction, anchor, expanded,
|
|
138
|
+
declare function Morpheus({ direction, anchor, expanded, onClose, beforeClose, afterClose, collapsedContent, expandedContent, className, overlayColor, overlayOpacity, overlayBlur, overlayZIndex: zIndex, spring, }: MorphProps): react.JSX.Element;
|
|
136
139
|
|
|
137
140
|
declare const defaultAnchorByDirection: Record<MorphDirection, MorphAnchor>;
|
|
138
141
|
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
|
|
@@ -458,20 +462,23 @@ function Morpheus({
|
|
|
458
462
|
direction,
|
|
459
463
|
anchor,
|
|
460
464
|
expanded,
|
|
461
|
-
|
|
465
|
+
onClose,
|
|
466
|
+
beforeClose,
|
|
462
467
|
afterClose,
|
|
463
468
|
collapsedContent,
|
|
464
469
|
expandedContent,
|
|
465
|
-
className = "relative inline-block align-top",
|
|
470
|
+
className = "rmorph:relative rmorph:inline-block rmorph:align-top",
|
|
466
471
|
overlayColor = "#05070a",
|
|
467
472
|
overlayOpacity = 0.54,
|
|
468
473
|
overlayBlur = 0,
|
|
474
|
+
overlayZIndex: zIndex = 1e3,
|
|
469
475
|
spring = defaultPanelSpring
|
|
470
476
|
}) {
|
|
471
477
|
const measurements = useMorphMeasurements();
|
|
472
478
|
const [animationEnabled, setAnimationEnabled] = useState2(false);
|
|
473
479
|
const [shellOverflowVisible, setShellOverflowVisible] = useState2(false);
|
|
474
480
|
const hasOpenedRef = useRef2(expanded);
|
|
481
|
+
const beforeCloseCalledRef = useRef2(false);
|
|
475
482
|
useEffect(() => {
|
|
476
483
|
if (!measurements.ready) {
|
|
477
484
|
return;
|
|
@@ -479,18 +486,6 @@ function Morpheus({
|
|
|
479
486
|
const frame = requestAnimationFrame(() => setAnimationEnabled(true));
|
|
480
487
|
return () => cancelAnimationFrame(frame);
|
|
481
488
|
}, [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
489
|
const motionState = getMorphMotionState({
|
|
495
490
|
expanded,
|
|
496
491
|
direction,
|
|
@@ -507,10 +502,11 @@ function Morpheus({
|
|
|
507
502
|
MorphOverlay,
|
|
508
503
|
{
|
|
509
504
|
expanded,
|
|
510
|
-
|
|
505
|
+
onClose,
|
|
511
506
|
color: overlayColor,
|
|
512
507
|
opacity: overlayOpacity,
|
|
513
|
-
blur: overlayBlur
|
|
508
|
+
blur: overlayBlur,
|
|
509
|
+
zIndex
|
|
514
510
|
}
|
|
515
511
|
),
|
|
516
512
|
/* @__PURE__ */ jsxs("div", { className, "aria-live": "polite", children: [
|
|
@@ -523,7 +519,7 @@ function Morpheus({
|
|
|
523
519
|
expandedContent
|
|
524
520
|
}
|
|
525
521
|
),
|
|
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: [
|
|
522
|
+
/* @__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
523
|
/* @__PURE__ */ jsx2(
|
|
528
524
|
"div",
|
|
529
525
|
{
|
|
@@ -543,10 +539,12 @@ function Morpheus({
|
|
|
543
539
|
animatedSize: motionState.animatedSize,
|
|
544
540
|
visualStyle: motionState.visualStyle,
|
|
545
541
|
overflowVisible,
|
|
542
|
+
zIndex,
|
|
546
543
|
spring: motionState.activeSpring,
|
|
547
544
|
onMorphComplete: () => {
|
|
548
545
|
if (expanded) {
|
|
549
546
|
hasOpenedRef.current = true;
|
|
547
|
+
beforeCloseCalledRef.current = false;
|
|
550
548
|
setShellOverflowVisible(true);
|
|
551
549
|
return;
|
|
552
550
|
}
|
|
@@ -555,7 +553,13 @@ function Morpheus({
|
|
|
555
553
|
afterClose?.();
|
|
556
554
|
}
|
|
557
555
|
},
|
|
558
|
-
onMorphStart: () =>
|
|
556
|
+
onMorphStart: () => {
|
|
557
|
+
if (!expanded && hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
558
|
+
beforeCloseCalledRef.current = true;
|
|
559
|
+
beforeClose?.();
|
|
560
|
+
}
|
|
561
|
+
setShellOverflowVisible(false);
|
|
562
|
+
},
|
|
559
563
|
children: /* @__PURE__ */ jsx2(
|
|
560
564
|
MorphContentLayers,
|
|
561
565
|
{
|
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.4",
|
|
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",
|