@passui/react 0.1.45 → 0.1.46

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.
Files changed (140) hide show
  1. package/README.md +57 -46
  2. package/dist/types/src/components/accordion/accordion.d.ts +1 -1
  3. package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
  4. package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
  5. package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
  6. package/dist/types/src/components/popover/index.d.ts +41 -0
  7. package/dist/types/src/components/popover/popover-content.d.ts +32 -0
  8. package/dist/types/src/components/popover/popover-root.d.ts +22 -0
  9. package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
  10. package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
  11. package/dist/types/src/components/table/table.d.ts +1 -1
  12. package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
  13. package/dist/types/src/index.d.ts +1 -1
  14. package/dist/types/src/machines/popover.machine.d.ts +30 -0
  15. package/package.json +307 -307
  16. package/src/appearance/index.ts +2 -2
  17. package/src/appearance/platform.tsx +76 -60
  18. package/src/appearance/theme.tsx +409 -377
  19. package/src/components/accordion/accordion.tsx +58 -49
  20. package/src/components/ambient/ambient.tsx +25 -25
  21. package/src/components/ambient/index.ts +1 -1
  22. package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
  23. package/src/components/breadcrumb/breadcrumb.tsx +63 -57
  24. package/src/components/button/button-styles.ts +39 -32
  25. package/src/components/button/button.tsx +91 -84
  26. package/src/components/button/icon-button.tsx +27 -25
  27. package/src/components/button/pending-spinner.tsx +8 -8
  28. package/src/components/button/use-pending-indicator.ts +11 -11
  29. package/src/components/chip/chip.tsx +35 -30
  30. package/src/components/code-block/code-block.tsx +40 -39
  31. package/src/components/combo-box/combo-box.tsx +5 -5
  32. package/src/components/command/command.tsx +124 -94
  33. package/src/components/compact-date-picker/calendar-month.ts +75 -53
  34. package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
  35. package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
  36. package/src/components/compact-time-picker/time-value.ts +32 -29
  37. package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
  38. package/src/components/content-view/content-view.tsx +168 -159
  39. package/src/components/content-view/index.ts +1 -1
  40. package/src/components/context-menu/conceal-menu-source.ts +25 -25
  41. package/src/components/context-menu/context-menu-host.tsx +439 -402
  42. package/src/components/context-menu/context-menu-store.ts +125 -123
  43. package/src/components/context-menu/menu-navigation.ts +25 -18
  44. package/src/components/context-menu/menu-position.ts +33 -29
  45. package/src/components/context-menu/menu-reveal.ts +32 -32
  46. package/src/components/context-menu/use-context-menu.ts +63 -56
  47. package/src/components/conversation/conversation.tsx +265 -263
  48. package/src/components/conversation/index.ts +1 -1
  49. package/src/components/date-picker/date-picker.tsx +95 -94
  50. package/src/components/date-picker/date-value.ts +20 -10
  51. package/src/components/dialog/dialog-host.tsx +105 -103
  52. package/src/components/dialog/dialog-store.ts +37 -36
  53. package/src/components/dialog/dialog.tsx +192 -179
  54. package/src/components/dialog/index.ts +1 -1
  55. package/src/components/disclosure-group/disclosure-group.tsx +93 -86
  56. package/src/components/divider/divider.tsx +24 -22
  57. package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
  58. package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
  59. package/src/components/labeled-content/labeled-content.tsx +68 -68
  60. package/src/components/locale-switcher/index.ts +1 -1
  61. package/src/components/locale-switcher/locale-switcher.tsx +279 -251
  62. package/src/components/menu/menu.tsx +75 -58
  63. package/src/components/morph/index.ts +2 -2
  64. package/src/components/morph/morph.tsx +227 -201
  65. package/src/components/morph/use-morph-disclosure.ts +19 -19
  66. package/src/components/one-time-code/one-time-code.tsx +216 -200
  67. package/src/components/picker/picker-trigger.ts +16 -13
  68. package/src/components/picker/picker.tsx +371 -345
  69. package/src/components/popover/anchored-surface.tsx +253 -229
  70. package/src/components/popover/index.ts +50 -0
  71. package/src/components/popover/popover-content.tsx +334 -0
  72. package/src/components/popover/popover-fit.ts +58 -53
  73. package/src/components/popover/popover-root.tsx +97 -0
  74. package/src/components/popover/popover-trigger.tsx +71 -0
  75. package/src/components/profile-image/profile-image.tsx +80 -64
  76. package/src/components/progress-view/progress-view.tsx +61 -59
  77. package/src/components/progress-view/use-liquid-progress.ts +38 -38
  78. package/src/components/radio-group/radio-group.tsx +71 -68
  79. package/src/components/search-field/expanding-search-field.tsx +191 -177
  80. package/src/components/search-field/search-field.tsx +59 -50
  81. package/src/components/search-list/search-list.tsx +246 -236
  82. package/src/components/section/section.tsx +58 -58
  83. package/src/components/secure-field/secure-field.tsx +3 -3
  84. package/src/components/segmented-control/segmented-control.tsx +136 -136
  85. package/src/components/sheet/modal-environment.ts +71 -64
  86. package/src/components/sheet/sheet-portal.tsx +58 -53
  87. package/src/components/sheet/sheet.tsx +146 -141
  88. package/src/components/sheet/use-sheet-focus.ts +97 -82
  89. package/src/components/sidebar/sidebar.tsx +78 -78
  90. package/src/components/skeleton/skeleton.tsx +30 -21
  91. package/src/components/slider/slider.tsx +113 -104
  92. package/src/components/slosh-slider/liquid-step.ts +32 -31
  93. package/src/components/slosh-slider/slosh-slider.tsx +265 -228
  94. package/src/components/stat-tile/index.ts +1 -1
  95. package/src/components/stat-tile/stat-tile.tsx +79 -68
  96. package/src/components/status-indicator/status-indicator.tsx +40 -37
  97. package/src/components/stepper/rolling-digit.tsx +82 -68
  98. package/src/components/stepper/stepper.tsx +122 -113
  99. package/src/components/swatch-picker/swatch-picker.tsx +120 -120
  100. package/src/components/tab-bar/tab-bar.tsx +149 -148
  101. package/src/components/tab-view/tab-view.tsx +140 -137
  102. package/src/components/table/table.tsx +117 -98
  103. package/src/components/text-editor/text-editor.tsx +34 -29
  104. package/src/components/text-field/text-field.tsx +182 -180
  105. package/src/components/toast/toast-store.ts +127 -115
  106. package/src/components/toast/toaster.tsx +361 -347
  107. package/src/components/toggle/toggle.tsx +297 -282
  108. package/src/components/tooltip/tooltip-host.tsx +157 -150
  109. package/src/components/view-controller/index.ts +1 -1
  110. package/src/components/view-controller/view-controller.tsx +64 -53
  111. package/src/components/wake-slider/wake-slider.tsx +220 -205
  112. package/src/hooks/scroll/minimize-decision.ts +11 -7
  113. package/src/hooks/use-anchored-disclosure.ts +19 -19
  114. package/src/hooks/use-anchored-placement.ts +29 -25
  115. package/src/hooks/use-dismiss.ts +33 -29
  116. package/src/hooks/use-floating-dismiss.ts +24 -22
  117. package/src/hooks/use-media-query.ts +67 -60
  118. package/src/hooks/use-minimize-on-scroll.ts +69 -60
  119. package/src/hooks/use-visual-viewport.ts +63 -63
  120. package/src/hooks/viewport/types.ts +16 -16
  121. package/src/hooks/viewport/viewport-metrics.ts +16 -10
  122. package/src/icons/icons.tsx +440 -328
  123. package/src/index.ts +91 -91
  124. package/src/lib/anchored-placement.ts +70 -65
  125. package/src/lib/class-names.ts +14 -13
  126. package/src/lib/clipboard.ts +8 -7
  127. package/src/lib/date.ts +61 -60
  128. package/src/lib/floating-layers.ts +39 -24
  129. package/src/lib/haptics.ts +10 -10
  130. package/src/lib/language.ts +9 -9
  131. package/src/lib/range-value.ts +27 -21
  132. package/src/lib/screen-edges.ts +33 -30
  133. package/src/lib/shapes.ts +38 -34
  134. package/src/machines/popover.machine.ts +51 -0
  135. package/src/motion/transitions.ts +46 -43
  136. package/src/styles/code-block.css +69 -43
  137. package/src/styles/motion-controls.css +393 -191
  138. package/src/tokens.ts +1 -1
  139. package/dist/types/src/components/popover/popover.d.ts +0 -21
  140. package/src/components/popover/popover.tsx +0 -334
@@ -1,240 +1,264 @@
1
- import { springs } from "@passui/tokens/motion"
2
- import { useMediaQuery } from "../../hooks/use-media-query"
3
- import { forwardRef, type HTMLAttributes, useLayoutEffect, useRef, useState } from "react"
4
- import { useAnchoredPlacement } from "../../hooks/use-anchored-placement"
5
- import { FLOATING_LAYER } from "../../hooks/use-dismiss"
6
- import { useFloatingDismiss } from "../../hooks/use-floating-dismiss"
7
- import { cn } from "../../lib/class-names"
8
- import { registerFloatingLayer } from "../../lib/floating-layers"
9
- import { concealMenuSource } from "../context-menu/conceal-menu-source"
10
- import { menuSurfaceReveal } from "../context-menu/menu-reveal"
1
+ import { springs } from "@passui/tokens/motion";
2
+ import { useMediaQuery } from "../../hooks/use-media-query";
3
+ import {
4
+ forwardRef,
5
+ type HTMLAttributes,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { useAnchoredPlacement } from "../../hooks/use-anchored-placement";
11
+ import { FLOATING_LAYER } from "../../hooks/use-dismiss";
12
+ import { useFloatingDismiss } from "../../hooks/use-floating-dismiss";
13
+ import { cn } from "../../lib/class-names";
14
+ import { registerFloatingLayer } from "../../lib/floating-layers";
15
+ import { concealMenuSource } from "../context-menu/conceal-menu-source";
16
+ import { menuSurfaceReveal } from "../context-menu/menu-reveal";
11
17
 
12
18
  interface AnchoredSurfaceProps extends HTMLAttributes<HTMLDivElement> {
13
- open: boolean
14
- onExited: () => void
15
- anchor: HTMLElement
16
- width: number
17
- needed: number
18
- onClose: (restoreFocus: boolean) => void
19
- contentClassName?: string
19
+ open: boolean;
20
+ onExited: () => void;
21
+ anchor: HTMLElement;
22
+ width: number;
23
+ needed: number;
24
+ onClose: (restoreFocus: boolean) => void;
25
+ contentClassName?: string;
20
26
  }
21
27
 
22
- /** Shared material and lifetime for portaled pickers; their content owns selection. */
28
+ /**
29
+ * Shared material and lifetime for portaled
30
+ * pickers; their content owns
31
+ * selection.
32
+ * */
23
33
  export const AnchoredSurface = forwardRef<HTMLDivElement, AnchoredSurfaceProps>(
24
- function AnchoredSurface(
25
- {
26
- open: present,
27
- onExited: remove,
28
- anchor,
29
- width,
30
- needed,
31
- onClose,
32
- children,
33
- className,
34
- contentClassName,
35
- ...props
36
- },
37
- forwardedRef
38
- ) {
39
- const root = useRef<HTMLDivElement>(null)
40
- const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)")
41
- const material = useRef<HTMLDivElement>(null)
42
- const content = useRef<HTMLDivElement>(null)
43
- const animated = useRef<{
44
- values: number[]
45
- velocities: number[]
46
- opacity: number
47
- materialOpacity: number
48
- } | null>(null)
49
- const placement = useAnchoredPlacement(anchor, width, needed, true)
50
- const [geometry, setGeometry] = useState<{
51
- source: ReturnType<typeof menuSurfaceReveal>
52
- width: number
53
- height: number
54
- radius: number
55
- } | null>(null)
34
+ function AnchoredSurface(
35
+ {
36
+ open: present,
37
+ onExited: remove,
38
+ anchor,
39
+ width,
40
+ needed,
41
+ onClose,
42
+ children,
43
+ className,
44
+ contentClassName,
45
+ ...props
46
+ },
47
+ forwardedRef,
48
+ ) {
49
+ const root = useRef<HTMLDivElement>(null);
50
+ const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
51
+ const material = useRef<HTMLDivElement>(null);
52
+ const content = useRef<HTMLDivElement>(null);
53
+ const animated = useRef<{
54
+ values: number[];
55
+ velocities: number[];
56
+ opacity: number;
57
+ materialOpacity: number;
58
+ } | null>(null);
59
+ const placement = useAnchoredPlacement(anchor, width, needed, true);
60
+ const [geometry, setGeometry] = useState<{
61
+ source: ReturnType<typeof menuSurfaceReveal>;
62
+ width: number;
63
+ height: number;
64
+ radius: number;
65
+ } | null>(null);
56
66
 
57
- // biome-ignore lint/correctness/useExhaustiveDependencies: Placement can move without resizing; remeasure the source-relative reveal.
58
- useLayoutEffect(() => {
59
- const panel = root.current
60
- if (!panel || !present) return
61
- const measure = () => {
62
- const target = panel.getBoundingClientRect()
63
- const source = anchor.getBoundingClientRect()
64
- const radius = Number.parseFloat(getComputedStyle(panel).borderTopLeftRadius) || 0
65
- const reveal = menuSurfaceReveal(source, target)
66
- setGeometry((current) =>
67
- current &&
68
- current.width === target.width &&
69
- current.height === target.height &&
70
- current.radius === radius &&
71
- current.source.x === reveal.x &&
72
- current.source.y === reveal.y &&
73
- current.source.width === reveal.width &&
74
- current.source.height === reveal.height
75
- ? current
76
- : { source: reveal, width: target.width, height: target.height, radius }
77
- )
78
- }
79
- measure()
80
- const observer = new ResizeObserver(measure)
81
- observer.observe(panel)
82
- return () => observer.disconnect()
83
- }, [anchor, present, placement.left, placement.top, placement.bottom, placement.width])
67
+ // biome-ignore lint/correctness/useExhaustiveDependencies: Placement can move without resizing; remeasure the source-relative reveal.
68
+ useLayoutEffect(() => {
69
+ const panel = root.current;
70
+ if (!panel || !present) return;
71
+ const measure = () => {
72
+ const target = panel.getBoundingClientRect();
73
+ const source = anchor.getBoundingClientRect();
74
+ const radius =
75
+ Number.parseFloat(getComputedStyle(panel).borderTopLeftRadius) || 0;
76
+ const reveal = menuSurfaceReveal(source, target);
77
+ setGeometry((current) =>
78
+ current &&
79
+ current.width === target.width &&
80
+ current.height === target.height &&
81
+ current.radius === radius &&
82
+ current.source.x === reveal.x &&
83
+ current.source.y === reveal.y &&
84
+ current.source.width === reveal.width &&
85
+ current.source.height === reveal.height
86
+ ? current
87
+ : {
88
+ source: reveal,
89
+ width: target.width,
90
+ height: target.height,
91
+ radius,
92
+ },
93
+ );
94
+ };
95
+ measure();
96
+ const observer = new ResizeObserver(measure);
97
+ observer.observe(panel);
98
+ return () => observer.disconnect();
99
+ }, [
100
+ anchor,
101
+ present,
102
+ placement.left,
103
+ placement.top,
104
+ placement.bottom,
105
+ placement.width,
106
+ ]);
84
107
 
85
- useLayoutEffect(() => {
86
- if (!root.current) return
87
- const unregister = registerFloatingLayer(root.current, anchor)
88
- return unregister
89
- }, [anchor])
108
+ useLayoutEffect(() => {
109
+ if (!root.current) return;
110
+ const unregister = registerFloatingLayer(root.current, anchor);
111
+ return unregister;
112
+ }, [anchor]);
90
113
 
91
- useLayoutEffect(() => {
92
- if (!present) return
93
- const restore = concealMenuSource(anchor, false)
94
- return () => {
95
- restore()
96
- }
97
- }, [anchor, present])
114
+ useLayoutEffect(() => {
115
+ if (!present) return;
116
+ const restore = concealMenuSource(anchor, false);
117
+ return () => {
118
+ restore();
119
+ };
120
+ }, [anchor, present]);
98
121
 
99
- useLayoutEffect(() => {
100
- if (!geometry || !material.current || !content.current) {
101
- if (!present) remove?.()
102
- return
103
- }
104
- const pane = material.current,
105
- body = content.current
106
- const source = [
107
- geometry.source.x,
108
- geometry.source.y,
109
- geometry.source.width,
110
- geometry.source.height,
111
- geometry.source.borderRadius,
112
- ]
113
- const target = present
114
- ? [0, 0, geometry.width, geometry.height, geometry.radius]
115
- : source
116
- const state = animated.current ?? {
117
- values: [...source],
118
- velocities: source.map(() => 0),
119
- opacity: 0,
120
- materialOpacity: 1,
121
- }
122
- animated.current = state
123
- let frame = 0,
124
- previous = 0
125
- const paint = () => {
126
- const [x, y, width, height, radius] = state.values
127
- pane.style.transform = `translate(${x}px, ${y}px)`
128
- pane.style.width = `${Math.max(0, width)}px`
129
- pane.style.height = `${Math.max(0, height)}px`
130
- pane.style.borderRadius = `${Math.max(0, radius)}px`
131
- pane.style.opacity = String(state.materialOpacity)
132
- body.style.opacity = String(state.opacity)
133
- }
134
- const tick = (now: number) => {
135
- const dt = previous ? Math.min(0.032, (now - previous) / 1000) : 1 / 60
136
- previous = now
137
- const { stiffness, damping, mass } = springs.smooth
138
- let resting = true
139
- for (let index = 0; index < target.length; index++) {
140
- state.velocities[index] +=
141
- (((target[index] - state.values[index]) * stiffness -
142
- state.velocities[index] * damping) /
143
- mass) *
144
- dt
145
- state.values[index] += state.velocities[index] * dt
146
- if (
147
- Math.abs(target[index] - state.values[index]) > 0.05 ||
148
- Math.abs(state.velocities[index]) > 0.05
149
- )
150
- resting = false
151
- }
152
- const progress = Math.max(
153
- 0,
154
- Math.min(
155
- 1,
156
- 1 -
157
- Math.abs(geometry.height - state.values[3]) /
158
- Math.max(1, Math.abs(geometry.height - source[3]))
159
- )
160
- )
161
- const opacity = present ? Math.max(0, (progress - 0.25) / 0.75) : 0
162
- state.opacity += (opacity - state.opacity) * (1 - Math.exp(-dt / 0.04))
163
- state.materialOpacity +=
164
- ((present ? 1 : 0) - state.materialOpacity) * (1 - Math.exp(-dt / 0.04))
165
- paint()
166
- if (
167
- resting &&
168
- Math.abs(opacity - state.opacity) < 0.01 &&
169
- Math.abs((present ? 1 : 0) - state.materialOpacity) < 0.01
170
- ) {
171
- state.values = [...target]
172
- state.velocities.fill(0)
173
- state.opacity = present ? 1 : 0
174
- state.materialOpacity = present ? 1 : 0
175
- paint()
176
- if (!present) remove?.()
177
- return
178
- }
179
- frame = requestAnimationFrame(tick)
180
- }
181
- if (reducedMotion) {
182
- state.values = [...target]
183
- state.velocities.fill(0)
184
- state.opacity = present ? 1 : 0
185
- state.materialOpacity = present ? 1 : 0
186
- paint()
187
- if (!present) remove?.()
188
- } else {
189
- paint()
190
- frame = requestAnimationFrame(tick)
191
- }
192
- return () => cancelAnimationFrame(frame)
193
- }, [geometry, present, remove, reducedMotion])
122
+ useLayoutEffect(() => {
123
+ if (!geometry || !material.current || !content.current) {
124
+ if (!present) remove?.();
125
+ return;
126
+ }
127
+ const pane = material.current;
128
+ const body = content.current;
129
+ const source = [
130
+ geometry.source.x,
131
+ geometry.source.y,
132
+ geometry.source.width,
133
+ geometry.source.height,
134
+ geometry.source.borderRadius,
135
+ ];
136
+ const target = present
137
+ ? [0, 0, geometry.width, geometry.height, geometry.radius]
138
+ : source;
139
+ const state = animated.current ?? {
140
+ values: [...source],
141
+ velocities: source.map(() => 0),
142
+ opacity: 0,
143
+ materialOpacity: 1,
144
+ };
145
+ animated.current = state;
146
+ let frame = 0;
147
+ let previous = 0;
148
+ const paint = () => {
149
+ const [x, y, width, height, radius] = state.values;
150
+ pane.style.transform = `translate(${x}px, ${y}px)`;
151
+ pane.style.width = `${Math.max(0, width)}px`;
152
+ pane.style.height = `${Math.max(0, height)}px`;
153
+ pane.style.borderRadius = `${Math.max(0, radius)}px`;
154
+ pane.style.opacity = String(state.materialOpacity);
155
+ body.style.opacity = String(state.opacity);
156
+ };
157
+ const tick = (now: number) => {
158
+ const dt = previous ? Math.min(0.032, (now - previous) / 1000) : 1 / 60;
159
+ previous = now;
160
+ const { stiffness, damping, mass } = springs.smooth;
161
+ let resting = true;
162
+ for (let index = 0; index < target.length; index++) {
163
+ state.velocities[index] +=
164
+ (((target[index] - state.values[index]) * stiffness -
165
+ state.velocities[index] * damping) /
166
+ mass) *
167
+ dt;
168
+ state.values[index] += state.velocities[index] * dt;
169
+ if (
170
+ Math.abs(target[index] - state.values[index]) > 0.05 ||
171
+ Math.abs(state.velocities[index]) > 0.05
172
+ )
173
+ resting = false;
174
+ }
175
+ const progress = Math.max(
176
+ 0,
177
+ Math.min(
178
+ 1,
179
+ 1 -
180
+ Math.abs(geometry.height - state.values[3]) /
181
+ Math.max(1, Math.abs(geometry.height - source[3])),
182
+ ),
183
+ );
184
+ const opacity = present ? Math.max(0, (progress - 0.25) / 0.75) : 0;
185
+ state.opacity += (opacity - state.opacity) * (1 - Math.exp(-dt / 0.04));
186
+ state.materialOpacity +=
187
+ ((present ? 1 : 0) - state.materialOpacity) *
188
+ (1 - Math.exp(-dt / 0.04));
189
+ paint();
190
+ if (
191
+ resting &&
192
+ Math.abs(opacity - state.opacity) < 0.01 &&
193
+ Math.abs((present ? 1 : 0) - state.materialOpacity) < 0.01
194
+ ) {
195
+ state.values = [...target];
196
+ state.velocities.fill(0);
197
+ state.opacity = present ? 1 : 0;
198
+ state.materialOpacity = present ? 1 : 0;
199
+ paint();
200
+ if (!present) remove?.();
201
+ return;
202
+ }
203
+ frame = requestAnimationFrame(tick);
204
+ };
205
+ if (reducedMotion) {
206
+ state.values = [...target];
207
+ state.velocities.fill(0);
208
+ state.opacity = present ? 1 : 0;
209
+ state.materialOpacity = present ? 1 : 0;
210
+ paint();
211
+ if (!present) remove?.();
212
+ } else {
213
+ paint();
214
+ frame = requestAnimationFrame(tick);
215
+ }
216
+ return () => cancelAnimationFrame(frame);
217
+ }, [geometry, present, remove, reducedMotion]);
194
218
 
195
- useFloatingDismiss(root, anchor, (restore) => {
196
- if (present) onClose(restore)
197
- })
219
+ useFloatingDismiss(root, anchor, (restore) => {
220
+ if (present) onClose(restore);
221
+ });
198
222
 
199
- return (
200
- <div
201
- {...props}
202
- ref={(node) => {
203
- root.current = node
204
- if (typeof forwardedRef === "function") forwardedRef(node)
205
- else if (forwardedRef) forwardedRef.current = node
206
- }}
207
- {...{ [FLOATING_LAYER]: "" }}
208
- inert={!present}
209
- className={cn(
210
- "fixed z-[80] isolate flex min-w-0 flex-col rounded-3xl outline-none",
211
- !present && "pointer-events-none",
212
- className
213
- )}
214
- style={{
215
- left: placement.left,
216
- top: placement.top,
217
- bottom: placement.bottom,
218
- width: placement.width,
219
- maxHeight: placement.maxHeight ?? 420,
220
- }}
221
- >
222
- <div
223
- ref={material}
224
- aria-hidden="true"
225
- className="glass-menu pointer-events-none absolute top-0 left-0 -z-10"
226
- />
227
- <div
228
- ref={content}
229
- style={{ opacity: 0 }}
230
- className={cn(
231
- "flex min-h-0 flex-col overflow-y-auto overscroll-contain",
232
- contentClassName
233
- )}
234
- >
235
- {children}
236
- </div>
237
- </div>
238
- )
239
- }
240
- )
223
+ return (
224
+ <div
225
+ {...props}
226
+ ref={(node) => {
227
+ root.current = node;
228
+ if (typeof forwardedRef === "function") forwardedRef(node);
229
+ else if (forwardedRef) forwardedRef.current = node;
230
+ }}
231
+ {...{ [FLOATING_LAYER]: "" }}
232
+ inert={!present}
233
+ className={cn(
234
+ "fixed isolate z-[80] flex min-w-0 flex-col rounded-3xl outline-none",
235
+ !present && "pointer-events-none",
236
+ className,
237
+ )}
238
+ style={{
239
+ left: placement.left,
240
+ top: placement.top,
241
+ bottom: placement.bottom,
242
+ width: placement.width,
243
+ maxHeight: placement.maxHeight ?? 420,
244
+ }}
245
+ >
246
+ <div
247
+ ref={material}
248
+ aria-hidden="true"
249
+ className="glass-menu pointer-events-none absolute top-0 left-0 -z-10"
250
+ />
251
+ <div
252
+ ref={content}
253
+ style={{ opacity: 0 }}
254
+ className={cn(
255
+ "flex min-h-0 flex-col overflow-y-auto overscroll-contain",
256
+ contentClassName,
257
+ )}
258
+ >
259
+ {children}
260
+ </div>
261
+ </div>
262
+ );
263
+ },
264
+ );
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Popover — a glass surface that springs open from its trigger.
3
+ *
4
+ * Compound API (the only API):
5
+ *
6
+ * ```tsx
7
+ * <Popover>
8
+ * <Popover.Trigger render={(props) => <Button {...props}>Open</Button>} />
9
+ * <Popover.Content origin="top-left" className="top-full left-0 mt-2 w-64 p-4">
10
+ * …content…
11
+ * </Popover.Content>
12
+ * </Popover>
13
+ * ```
14
+ *
15
+ * The `Popover.Trigger` clones its child (or uses a `render` prop) and wires
16
+ * up aria-haspopup, aria-expanded, aria-controls, click + keyboard toggling.
17
+ * The `Popover.Content` portals out of the authored layout, morphs its glass
18
+ * surface from the trigger's rect, and keeps itself inside the viewport as
19
+ * it resizes or scrolls. `origin` picks the corner it grows from; pass
20
+ * `placement="center"` for a modal center-screen variant.
21
+ *
22
+ * State is driven by a small pure machine (`popover.machine.ts`) that
23
+ * translates directly to a Rust enum — see the architecture plan.
24
+ */
25
+ export { PopoverRoot as Root } from "./popover-root";
26
+ export {
27
+ PopoverTrigger as Trigger,
28
+ type PopoverTriggerRenderProps,
29
+ } from "./popover-trigger";
30
+ export {
31
+ PopoverContent as Content,
32
+ type PopoverOrigin,
33
+ } from "./popover-content";
34
+
35
+ import { PopoverRoot } from "./popover-root";
36
+ import { PopoverTrigger } from "./popover-trigger";
37
+ import { PopoverContent } from "./popover-content";
38
+
39
+ /**
40
+ * Compound Popover. Use as `<Popover>` with `.Trigger` and `.Content` parts,
41
+ * or import `Root` / `Trigger` / `Content` directly from the barrel.
42
+ */
43
+ export const Popover = Object.assign(PopoverRoot, {
44
+ Trigger: PopoverTrigger,
45
+ Content: PopoverContent,
46
+ });
47
+
48
+ export type { PopoverRootProps } from "./popover-root";
49
+ export type { PopoverTriggerProps } from "./popover-trigger";
50
+ export type { PopoverContentProps } from "./popover-content";