react-morpheus 0.1.4 → 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 CHANGED
@@ -2,23 +2,51 @@
2
2
 
3
3
  A controlled React component for morphing one UI state into another.
4
4
 
5
- `Morpheus` does not manage its own open state. Keep the `expanded` boolean in
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.
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.
12
35
 
13
36
  ```tsx
14
- import { Morpheus, MorphAnchor } from "react-morpheus";
37
+ import { useState } from "react";
38
+ import {
39
+ Morpheus,
40
+ MorphAnchor,
41
+ morphSpringPresets,
42
+ } from "react-morpheus";
15
43
  import "react-morpheus/style.css";
16
44
 
17
45
  function Example() {
18
46
  const [expanded, setExpanded] = useState(false);
19
47
  const source = (
20
48
  <button type="button" onClick={() => setExpanded(true)}>
21
- Open
49
+ Open menu
22
50
  </button>
23
51
  );
24
52
 
@@ -28,30 +56,97 @@ function Example() {
28
56
  anchor={MorphAnchor.TopMiddle}
29
57
  expanded={expanded}
30
58
  onClose={() => setExpanded(false)}
31
- beforeClose={() => console.log("closing")}
32
- afterClose={() => console.log("closed")}
33
- collapsedContent={source}
34
- expandedContent={<div>Expanded content</div>}
59
+ overlayColor="#0f172a"
60
+ overlayOpacity={0.18}
61
+ overlayBlur={3}
35
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
+ }
36
74
  />
37
75
  );
38
76
  }
39
77
  ```
40
78
 
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`.
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
44
118
 
45
119
  Do not place Morpheus inside a container that clips overflow, such as
46
120
  `overflow: hidden`, `overflow: clip`, or scroll containers. The expanded surface
47
121
  is positioned from the local source element and can extend outside its original
48
122
  container while animating.
49
123
 
50
- ## Scripts
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`.
51
129
 
52
- - `bun run build` builds declarations and bundled ESM output.
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.
53
139
  - `bun run check` runs TypeScript without emitting files.
54
140
  - `bun run publish:npm` builds and publishes to npm.
55
141
  - `bun run release:patch` bumps the patch version, builds, tags, and publishes.
56
142
  - `bun run release:minor` bumps the minor version, builds, tags, and publishes.
57
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
151
+
152
+ Released under the [MIT License](https://github.com/shivekkhurana/react-morpheus/blob/master/LICENSE).
package/dist/index.d.ts CHANGED
@@ -117,6 +117,7 @@ type MorphShellProps = {
117
117
  };
118
118
  type MorphContentLayersProps = {
119
119
  expanded: boolean;
120
+ settledState: "collapsed" | "expanded" | null;
120
121
  collapsedContent: ReactNode;
121
122
  expandedContent: ReactNode;
122
123
  collapsedLayerSize: PanelSize;
package/dist/index.js CHANGED
@@ -391,6 +391,7 @@ function MorphShell({
391
391
  }
392
392
  function MorphContentLayers({
393
393
  expanded,
394
+ settledState,
394
395
  collapsedContent,
395
396
  expandedContent,
396
397
  collapsedLayerSize,
@@ -423,6 +424,7 @@ function MorphContentLayers({
423
424
  opacity: sourceOpacityTransition
424
425
  },
425
426
  style: {
427
+ display: expanded && settledState === "expanded" ? "none" : void 0,
426
428
  width: collapsedLayerSize.width,
427
429
  height: collapsedLayerSize.height,
428
430
  pointerEvents: expanded ? "none" : "auto",
@@ -448,6 +450,7 @@ function MorphContentLayers({
448
450
  opacity: targetOpacityTransition
449
451
  },
450
452
  style: {
453
+ display: !expanded && settledState === "collapsed" ? "none" : void 0,
451
454
  width: expandedLayerSize.width,
452
455
  height: expandedLayerSize.height,
453
456
  pointerEvents: expanded ? "auto" : "none",
@@ -477,6 +480,9 @@ function Morpheus({
477
480
  const measurements = useMorphMeasurements();
478
481
  const [animationEnabled, setAnimationEnabled] = useState2(false);
479
482
  const [shellOverflowVisible, setShellOverflowVisible] = useState2(false);
483
+ const [settledState, setSettledState] = useState2(
484
+ expanded ? "expanded" : "collapsed"
485
+ );
480
486
  const hasOpenedRef = useRef2(expanded);
481
487
  const beforeCloseCalledRef = useRef2(false);
482
488
  useEffect(() => {
@@ -545,9 +551,11 @@ function Morpheus({
545
551
  if (expanded) {
546
552
  hasOpenedRef.current = true;
547
553
  beforeCloseCalledRef.current = false;
554
+ setSettledState("expanded");
548
555
  setShellOverflowVisible(true);
549
556
  return;
550
557
  }
558
+ setSettledState("collapsed");
551
559
  if (hasOpenedRef.current) {
552
560
  hasOpenedRef.current = false;
553
561
  afterClose?.();
@@ -559,11 +567,13 @@ function Morpheus({
559
567
  beforeClose?.();
560
568
  }
561
569
  setShellOverflowVisible(false);
570
+ setSettledState(expanded ? "collapsed" : "expanded");
562
571
  },
563
572
  children: /* @__PURE__ */ jsx2(
564
573
  MorphContentLayers,
565
574
  {
566
575
  expanded,
576
+ settledState,
567
577
  collapsedContent,
568
578
  expandedContent,
569
579
  collapsedLayerSize: motionState.collapsedLayerSize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-morpheus",
3
- "version": "0.1.4",
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",