react-morpheus 0.1.1 → 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 +32 -17
- package/dist/index.d.ts +6 -4
- package/dist/index.js +44 -24
- package/dist/style.css +2 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -3,11 +3,16 @@
|
|
|
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
|
-
|
|
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.
|
|
8
12
|
|
|
9
13
|
```tsx
|
|
10
14
|
import { Morpheus, MorphAnchor } from "react-morpheus";
|
|
15
|
+
import "react-morpheus/style.css";
|
|
11
16
|
|
|
12
17
|
function Example() {
|
|
13
18
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -18,25 +23,35 @@ function Example() {
|
|
|
18
23
|
);
|
|
19
24
|
|
|
20
25
|
return (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
/>
|
|
32
37
|
);
|
|
33
38
|
}
|
|
34
39
|
```
|
|
35
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
|
+
|
|
36
50
|
## Scripts
|
|
37
51
|
|
|
38
52
|
- `bun run build` builds declarations and bundled ESM output.
|
|
39
|
-
- `bun run
|
|
40
|
-
- `bun run
|
|
41
|
-
- `bun run
|
|
42
|
-
- `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,14 +77,16 @@ type MorphProps = {
|
|
|
77
77
|
direction: MorphDirection;
|
|
78
78
|
anchor?: MorphAnchor | undefined;
|
|
79
79
|
expanded: boolean;
|
|
80
|
-
onOpen?: (() => void) | undefined;
|
|
81
80
|
onClose?: (() => void) | undefined;
|
|
81
|
+
beforeClose?: (() => void) | undefined;
|
|
82
|
+
afterClose?: (() => void) | undefined;
|
|
82
83
|
collapsedContent: ReactNode;
|
|
83
84
|
expandedContent: ReactNode;
|
|
84
85
|
className?: string;
|
|
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 = {
|
|
@@ -93,6 +95,7 @@ type MorphOverlayProps = {
|
|
|
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;
|
|
@@ -126,14 +130,12 @@ type MorphContentLayersProps = {
|
|
|
126
130
|
sourceOpacityTransition: Transition;
|
|
127
131
|
targetTransition: Transition;
|
|
128
132
|
targetOpacityTransition: Transition;
|
|
129
|
-
onOpen?: (() => void) | undefined;
|
|
130
133
|
};
|
|
131
134
|
type MorphLayerInteractivityProps = {
|
|
132
|
-
"aria-hidden": boolean;
|
|
133
135
|
inert?: boolean;
|
|
134
136
|
};
|
|
135
137
|
|
|
136
|
-
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;
|
|
137
139
|
|
|
138
140
|
declare const defaultAnchorByDirection: Record<MorphDirection, MorphAnchor>;
|
|
139
141
|
declare const morphSpringPresets: {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/component.tsx
|
|
2
2
|
import { motion as motion2 } from "framer-motion";
|
|
3
|
-
import { useEffect, useState as useState2 } from "react";
|
|
3
|
+
import { useEffect, useRef as useRef2, useState as useState2 } from "react";
|
|
4
4
|
|
|
5
5
|
// src/measurement.ts
|
|
6
6
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
@@ -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,
|
|
@@ -294,14 +294,15 @@ function MorphOverlay({
|
|
|
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,7 +316,8 @@ 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
322
|
onClick: onClose
|
|
321
323
|
}
|
|
@@ -324,7 +326,7 @@ function MorphOverlay({
|
|
|
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
|
|
@@ -399,8 +403,7 @@ function MorphContentLayers({
|
|
|
399
403
|
sourceTransition,
|
|
400
404
|
sourceOpacityTransition,
|
|
401
405
|
targetTransition,
|
|
402
|
-
targetOpacityTransition
|
|
403
|
-
onOpen
|
|
406
|
+
targetOpacityTransition
|
|
404
407
|
}) {
|
|
405
408
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
406
409
|
/* @__PURE__ */ jsx2(
|
|
@@ -419,7 +422,6 @@ function MorphContentLayers({
|
|
|
419
422
|
...sourceTransition,
|
|
420
423
|
opacity: sourceOpacityTransition
|
|
421
424
|
},
|
|
422
|
-
onClick: expanded ? void 0 : onOpen,
|
|
423
425
|
style: {
|
|
424
426
|
width: collapsedLayerSize.width,
|
|
425
427
|
height: collapsedLayerSize.height,
|
|
@@ -460,19 +462,23 @@ function Morpheus({
|
|
|
460
462
|
direction,
|
|
461
463
|
anchor,
|
|
462
464
|
expanded,
|
|
463
|
-
onOpen,
|
|
464
465
|
onClose,
|
|
466
|
+
beforeClose,
|
|
467
|
+
afterClose,
|
|
465
468
|
collapsedContent,
|
|
466
469
|
expandedContent,
|
|
467
|
-
className = "relative inline-block align-top",
|
|
470
|
+
className = "rmorph:relative rmorph:inline-block rmorph:align-top",
|
|
468
471
|
overlayColor = "#05070a",
|
|
469
472
|
overlayOpacity = 0.54,
|
|
470
473
|
overlayBlur = 0,
|
|
474
|
+
overlayZIndex: zIndex = 1e3,
|
|
471
475
|
spring = defaultPanelSpring
|
|
472
476
|
}) {
|
|
473
477
|
const measurements = useMorphMeasurements();
|
|
474
478
|
const [animationEnabled, setAnimationEnabled] = useState2(false);
|
|
475
479
|
const [shellOverflowVisible, setShellOverflowVisible] = useState2(false);
|
|
480
|
+
const hasOpenedRef = useRef2(expanded);
|
|
481
|
+
const beforeCloseCalledRef = useRef2(false);
|
|
476
482
|
useEffect(() => {
|
|
477
483
|
if (!measurements.ready) {
|
|
478
484
|
return;
|
|
@@ -499,7 +505,8 @@ function Morpheus({
|
|
|
499
505
|
onClose,
|
|
500
506
|
color: overlayColor,
|
|
501
507
|
opacity: overlayOpacity,
|
|
502
|
-
blur: overlayBlur
|
|
508
|
+
blur: overlayBlur,
|
|
509
|
+
zIndex
|
|
503
510
|
}
|
|
504
511
|
),
|
|
505
512
|
/* @__PURE__ */ jsxs("div", { className, "aria-live": "polite", children: [
|
|
@@ -512,7 +519,7 @@ function Morpheus({
|
|
|
512
519
|
expandedContent
|
|
513
520
|
}
|
|
514
521
|
),
|
|
515
|
-
/* @__PURE__ */ jsx2("div", { className: "relative mx-auto block w-fit align-top", children: shouldRenderStaticSource ? /* @__PURE__ */ jsx2("div", { className: "inline-block align-top",
|
|
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: [
|
|
516
523
|
/* @__PURE__ */ jsx2(
|
|
517
524
|
"div",
|
|
518
525
|
{
|
|
@@ -532,13 +539,27 @@ function Morpheus({
|
|
|
532
539
|
animatedSize: motionState.animatedSize,
|
|
533
540
|
visualStyle: motionState.visualStyle,
|
|
534
541
|
overflowVisible,
|
|
542
|
+
zIndex,
|
|
535
543
|
spring: motionState.activeSpring,
|
|
536
544
|
onMorphComplete: () => {
|
|
537
545
|
if (expanded) {
|
|
546
|
+
hasOpenedRef.current = true;
|
|
547
|
+
beforeCloseCalledRef.current = false;
|
|
538
548
|
setShellOverflowVisible(true);
|
|
549
|
+
return;
|
|
539
550
|
}
|
|
551
|
+
if (hasOpenedRef.current) {
|
|
552
|
+
hasOpenedRef.current = false;
|
|
553
|
+
afterClose?.();
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
onMorphStart: () => {
|
|
557
|
+
if (!expanded && hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
558
|
+
beforeCloseCalledRef.current = true;
|
|
559
|
+
beforeClose?.();
|
|
560
|
+
}
|
|
561
|
+
setShellOverflowVisible(false);
|
|
540
562
|
},
|
|
541
|
-
onMorphStart: () => setShellOverflowVisible(false),
|
|
542
563
|
children: /* @__PURE__ */ jsx2(
|
|
543
564
|
MorphContentLayers,
|
|
544
565
|
{
|
|
@@ -555,8 +576,7 @@ function Morpheus({
|
|
|
555
576
|
sourceTransition: motionState.sourceTransition,
|
|
556
577
|
sourceOpacityTransition: motionState.sourceOpacityTransition,
|
|
557
578
|
targetTransition: motionState.targetTransition,
|
|
558
|
-
targetOpacityTransition: motionState.targetOpacityTransition
|
|
559
|
-
onOpen
|
|
579
|
+
targetOpacityTransition: motionState.targetOpacityTransition
|
|
560
580
|
}
|
|
561
581
|
)
|
|
562
582
|
}
|
package/dist/style.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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}}}.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,10 +1,12 @@
|
|
|
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",
|
|
7
|
-
"sideEffects":
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"**/*.css"
|
|
9
|
+
],
|
|
8
10
|
"files": [
|
|
9
11
|
"dist",
|
|
10
12
|
"README.md"
|
|
@@ -12,16 +14,22 @@
|
|
|
12
14
|
"main": "./dist/index.js",
|
|
13
15
|
"module": "./dist/index.js",
|
|
14
16
|
"types": "./dist/index.d.ts",
|
|
17
|
+
"style": "./dist/style.css",
|
|
15
18
|
"exports": {
|
|
16
19
|
".": {
|
|
17
20
|
"types": "./dist/index.d.ts",
|
|
18
21
|
"import": "./dist/index.js"
|
|
19
22
|
},
|
|
23
|
+
"./style.css": "./dist/style.css",
|
|
20
24
|
"./package.json": "./package.json"
|
|
21
25
|
},
|
|
22
26
|
"scripts": {
|
|
23
|
-
"
|
|
27
|
+
"prepare": "bun run build",
|
|
28
|
+
"build": "bun run build:js && bun run build:css",
|
|
29
|
+
"build:js": "tsup src/index.ts --format esm --dts --external react --external react-dom --external framer-motion --clean",
|
|
30
|
+
"build:css": "tailwindcss -i src/style.css -o dist/style.css --minify",
|
|
24
31
|
"check": "tsc --noEmit",
|
|
32
|
+
"publish:npm": "bun run scripts/publish.ts",
|
|
25
33
|
"release:minor": "bun run scripts/release.ts minor",
|
|
26
34
|
"release:major": "bun run scripts/release.ts major",
|
|
27
35
|
"release:patch": "bun run scripts/release.ts patch"
|
|
@@ -32,12 +40,14 @@
|
|
|
32
40
|
"react-dom": "^19.0.0"
|
|
33
41
|
},
|
|
34
42
|
"devDependencies": {
|
|
43
|
+
"@tailwindcss/cli": "^4.3.3",
|
|
35
44
|
"@types/bun": "^1.3.14",
|
|
36
45
|
"@types/react": "^19.2.17",
|
|
37
46
|
"@types/react-dom": "^19.2.3",
|
|
38
47
|
"framer-motion": "^12.42.2",
|
|
39
48
|
"react": "^19.2.7",
|
|
40
49
|
"react-dom": "^19.2.7",
|
|
50
|
+
"tailwindcss": "^4.3.3",
|
|
41
51
|
"tsup": "^8.5.1",
|
|
42
52
|
"typescript": "^6.0.3"
|
|
43
53
|
},
|