@nexub/react 2.1.0 → 2.3.0

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.
@@ -83,7 +83,9 @@ var Pagination = React.forwardRef(
83
83
  "aria-current": p === page ? "page" : void 0,
84
84
  className: cn(
85
85
  pressable,
86
- "inline-flex h-11 min-w-11 px-2 items-center justify-center text-label-lg",
86
+ // tabular-nums on the button itself: Tailwind 4's preflight lets buttons
87
+ // inherit it from the nav, Tailwind 3's (UA font reset) does not.
88
+ "inline-flex h-11 min-w-11 px-2 items-center justify-center text-label-lg tabular-nums",
87
89
  p === page ? "bg-[var(--color-primary)] text-[var(--color-on-primary)]" : "bg-transparent text-[var(--color-on-surface)]"
88
90
  ),
89
91
  children: p
@@ -0,0 +1,468 @@
1
+ 'use client';
2
+
3
+ // src/components/Motion/Fade.tsx
4
+ import * as React2 from "react";
5
+
6
+ // src/components/Motion/useMotion.ts
7
+ import * as React from "react";
8
+
9
+ // src/hooks/useMediaQuery.ts
10
+ import { useState, useEffect } from "react";
11
+ function useMediaQuery(query) {
12
+ const [matches, setMatches] = useState(false);
13
+ useEffect(() => {
14
+ if (typeof window === "undefined") return;
15
+ const mql = window.matchMedia(query);
16
+ const handler = (e) => setMatches(e.matches);
17
+ mql.addEventListener("change", handler);
18
+ setMatches(mql.matches);
19
+ return () => mql.removeEventListener("change", handler);
20
+ }, [query]);
21
+ return matches;
22
+ }
23
+
24
+ // src/components/Motion/useMotion.ts
25
+ var DURATION_MS = {
26
+ instant: 0,
27
+ fast: 150,
28
+ normal: 200,
29
+ slow: 300,
30
+ slower: 500
31
+ };
32
+ var SPRING_SPEED = {
33
+ fast: "fast",
34
+ normal: "default",
35
+ slow: "slow",
36
+ slower: "slow"
37
+ };
38
+ var SPRING_MS = {
39
+ spatial: { fast: 224, default: 317, slow: 484 },
40
+ effects: { fast: 150, default: 231, slow: 327 },
41
+ expressive: { fast: 364, default: 435, slow: 600 }
42
+ };
43
+ var FALLBACK_MARGIN_MS = 50;
44
+ var spring = (property, family, speed) => `${property} var(--motion-spring-${family}-${speed}-duration) var(--motion-spring-${family}-${speed}-easing)`;
45
+ function usePrefersReducedMotion() {
46
+ return useMediaQuery("(prefers-reduced-motion: reduce)");
47
+ }
48
+ function useMotion({
49
+ visible,
50
+ duration = "normal",
51
+ easing,
52
+ unmountOnExit = false
53
+ }) {
54
+ const [phase, setPhase] = React.useState(
55
+ visible ? "entered" : "exited"
56
+ );
57
+ const reduced = usePrefersReducedMotion();
58
+ const rafRef = React.useRef(null);
59
+ const isMounted = React.useRef(true);
60
+ React.useEffect(() => {
61
+ isMounted.current = true;
62
+ return () => {
63
+ isMounted.current = false;
64
+ if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
65
+ };
66
+ }, []);
67
+ const prevVisible = React.useRef(visible);
68
+ React.useEffect(() => {
69
+ if (prevVisible.current === visible) return;
70
+ prevVisible.current = visible;
71
+ if (visible) {
72
+ setPhase("entering");
73
+ rafRef.current = requestAnimationFrame(() => {
74
+ rafRef.current = requestAnimationFrame(() => {
75
+ if (isMounted.current) setPhase("entered");
76
+ });
77
+ });
78
+ } else {
79
+ setPhase("exiting");
80
+ }
81
+ }, [visible]);
82
+ const legacy = easing !== void 0;
83
+ const exitMs = duration === "instant" ? 0 : legacy ? DURATION_MS[duration] : SPRING_MS.effects.fast;
84
+ React.useEffect(() => {
85
+ if (phase !== "exiting") return;
86
+ const t = setTimeout(() => setPhase("exited"), exitMs + FALLBACK_MARGIN_MS);
87
+ return () => clearTimeout(t);
88
+ }, [phase, exitMs]);
89
+ const handleTransitionEnd = React.useCallback((e) => {
90
+ if (!isMounted.current) return;
91
+ if (e && e.target !== e.currentTarget) return;
92
+ setPhase((prev) => prev === "exiting" ? "exited" : prev);
93
+ }, []);
94
+ const mounted = unmountOnExit ? phase !== "exited" : true;
95
+ let transitionStyle;
96
+ if (duration === "instant") {
97
+ transitionStyle = { transition: "none" };
98
+ } else if (legacy) {
99
+ transitionStyle = {
100
+ transitionDuration: `var(--duration-${duration})`,
101
+ transitionTimingFunction: `var(--ease-${easing})`,
102
+ transitionProperty: "opacity, transform"
103
+ };
104
+ } else if (phase === "exiting" || phase === "exited") {
105
+ transitionStyle = {
106
+ transition: [spring("opacity", "effects", "fast"), spring("transform", "effects", "fast")].join(", ")
107
+ };
108
+ } else {
109
+ const speed = SPRING_SPEED[duration];
110
+ transitionStyle = {
111
+ transition: [spring("opacity", "effects", speed), spring("transform", "spatial", speed)].join(", ")
112
+ };
113
+ }
114
+ return { phase, mounted, reduced, transitionStyle, handleTransitionEnd };
115
+ }
116
+
117
+ // src/components/Motion/Fade.tsx
118
+ import { jsx } from "react/jsx-runtime";
119
+ var Fade = React2.forwardRef(function Fade2({
120
+ in: visible,
121
+ duration = "normal",
122
+ easing,
123
+ unmountOnExit = false,
124
+ children,
125
+ style,
126
+ as: Tag = "div",
127
+ ...rest
128
+ }, ref) {
129
+ const { phase, mounted, transitionStyle, handleTransitionEnd } = useMotion({
130
+ visible,
131
+ duration,
132
+ easing,
133
+ unmountOnExit
134
+ });
135
+ if (!mounted) return null;
136
+ const opacity = phase === "entered" ? 1 : 0;
137
+ return /* @__PURE__ */ jsx(
138
+ Tag,
139
+ {
140
+ ref,
141
+ onTransitionEnd: handleTransitionEnd,
142
+ style: {
143
+ opacity,
144
+ willChange: "opacity",
145
+ ...transitionStyle,
146
+ ...style
147
+ },
148
+ ...rest,
149
+ children
150
+ }
151
+ );
152
+ });
153
+ Fade.displayName = "Fade";
154
+
155
+ // src/components/Motion/Slide.tsx
156
+ import * as React3 from "react";
157
+ import { jsx as jsx2 } from "react/jsx-runtime";
158
+ var directionTransform = {
159
+ up: { from: "translateY(12px)", to: "translateY(0)" },
160
+ down: { from: "translateY(-12px)", to: "translateY(0)" },
161
+ left: { from: "translateX(12px)", to: "translateX(0)" },
162
+ right: { from: "translateX(-12px)", to: "translateX(0)" }
163
+ };
164
+ var Slide = React3.forwardRef(function Slide2({
165
+ in: visible,
166
+ duration = "normal",
167
+ easing,
168
+ unmountOnExit = false,
169
+ direction = "up",
170
+ distance,
171
+ children,
172
+ style,
173
+ as: Tag = "div",
174
+ ...rest
175
+ }, ref) {
176
+ const { phase, mounted, reduced, transitionStyle, handleTransitionEnd } = useMotion({
177
+ visible,
178
+ duration,
179
+ easing,
180
+ unmountOnExit
181
+ });
182
+ if (!mounted) return null;
183
+ const isEntered = phase === "entered";
184
+ const dt = directionTransform[direction];
185
+ let fromTransform = dt.from;
186
+ if (distance) {
187
+ if (direction === "up") fromTransform = `translateY(${distance})`;
188
+ if (direction === "down") fromTransform = `translateY(-${distance})`;
189
+ if (direction === "left") fromTransform = `translateX(${distance})`;
190
+ if (direction === "right") fromTransform = `translateX(-${distance})`;
191
+ }
192
+ return /* @__PURE__ */ jsx2(
193
+ Tag,
194
+ {
195
+ ref,
196
+ onTransitionEnd: handleTransitionEnd,
197
+ style: {
198
+ opacity: isEntered ? 1 : 0,
199
+ transform: isEntered || reduced ? dt.to : fromTransform,
200
+ willChange: "opacity, transform",
201
+ ...transitionStyle,
202
+ ...style
203
+ },
204
+ ...rest,
205
+ children
206
+ }
207
+ );
208
+ });
209
+ Slide.displayName = "Slide";
210
+
211
+ // src/components/Motion/Scale.tsx
212
+ import * as React4 from "react";
213
+ import { jsx as jsx3 } from "react/jsx-runtime";
214
+ var Scale = React4.forwardRef(function Scale2({
215
+ in: visible,
216
+ duration = "fast",
217
+ easing,
218
+ unmountOnExit = false,
219
+ initialScale = 0.95,
220
+ children,
221
+ style,
222
+ as: Tag = "div",
223
+ ...rest
224
+ }, ref) {
225
+ const { phase, mounted, reduced, transitionStyle, handleTransitionEnd } = useMotion({
226
+ visible,
227
+ duration,
228
+ easing,
229
+ unmountOnExit
230
+ });
231
+ if (!mounted) return null;
232
+ const isEntered = phase === "entered";
233
+ return /* @__PURE__ */ jsx3(
234
+ Tag,
235
+ {
236
+ ref,
237
+ onTransitionEnd: handleTransitionEnd,
238
+ style: {
239
+ opacity: isEntered ? 1 : 0,
240
+ transform: isEntered || reduced ? "scale(1)" : `scale(${initialScale})`,
241
+ transformOrigin: "top center",
242
+ willChange: "opacity, transform",
243
+ ...transitionStyle,
244
+ ...style
245
+ },
246
+ ...rest,
247
+ children
248
+ }
249
+ );
250
+ });
251
+ Scale.displayName = "Scale";
252
+
253
+ // src/components/Motion/Collapse.tsx
254
+ import * as React5 from "react";
255
+ import { jsx as jsx4 } from "react/jsx-runtime";
256
+ var Collapse = React5.forwardRef(function Collapse2({
257
+ in: visible,
258
+ duration = "normal",
259
+ easing,
260
+ unmountOnExit = false,
261
+ collapsedHeight = 0,
262
+ children,
263
+ style,
264
+ as: Tag = "div",
265
+ ...rest
266
+ }, ref) {
267
+ const innerRef = React5.useRef(null);
268
+ const reduced = usePrefersReducedMotion();
269
+ const legacy = easing !== void 0;
270
+ const settleMs = duration === "instant" || reduced ? 0 : legacy ? DURATION_MS[duration] : SPRING_MS.spatial[SPRING_SPEED[duration]];
271
+ const [height, setHeight] = React5.useState(visible ? "auto" : 0);
272
+ const [overflow, setOverflow] = React5.useState(
273
+ visible ? "visible" : "hidden"
274
+ );
275
+ const [mounted, setMounted] = React5.useState(unmountOnExit ? visible : true);
276
+ const prevVisible = React5.useRef(visible);
277
+ React5.useEffect(() => {
278
+ const changed = prevVisible.current !== visible;
279
+ prevVisible.current = visible;
280
+ if (!changed) return;
281
+ let raf1 = 0;
282
+ let raf2 = 0;
283
+ if (visible) {
284
+ setMounted(true);
285
+ raf1 = requestAnimationFrame(() => {
286
+ const el = innerRef.current;
287
+ if (el) {
288
+ setHeight(el.scrollHeight);
289
+ setOverflow("hidden");
290
+ }
291
+ });
292
+ } else {
293
+ const el = innerRef.current;
294
+ if (el) {
295
+ setHeight(el.scrollHeight);
296
+ setOverflow("hidden");
297
+ raf1 = requestAnimationFrame(() => {
298
+ raf2 = requestAnimationFrame(() => {
299
+ setHeight(typeof collapsedHeight === "number" ? collapsedHeight : 0);
300
+ });
301
+ });
302
+ }
303
+ }
304
+ return () => {
305
+ cancelAnimationFrame(raf1);
306
+ cancelAnimationFrame(raf2);
307
+ };
308
+ }, [visible, collapsedHeight]);
309
+ const finish = React5.useCallback(() => {
310
+ if (visible) {
311
+ setHeight("auto");
312
+ setOverflow("visible");
313
+ } else if (unmountOnExit) {
314
+ setMounted(false);
315
+ }
316
+ }, [visible, unmountOnExit]);
317
+ const handleTransitionEnd = React5.useCallback(
318
+ (e) => {
319
+ if (e.target !== e.currentTarget || e.propertyName !== "max-height") return;
320
+ finish();
321
+ },
322
+ [finish]
323
+ );
324
+ const firstRender = React5.useRef(true);
325
+ React5.useEffect(() => {
326
+ if (firstRender.current) {
327
+ firstRender.current = false;
328
+ return;
329
+ }
330
+ const t = setTimeout(finish, settleMs + 100);
331
+ return () => clearTimeout(t);
332
+ }, [visible, settleMs, finish]);
333
+ if (!mounted) return null;
334
+ const maxHeight = height === "auto" ? "none" : `${height}px`;
335
+ return /* @__PURE__ */ jsx4(
336
+ Tag,
337
+ {
338
+ ref,
339
+ style: {
340
+ maxHeight,
341
+ overflow,
342
+ transition: duration === "instant" || reduced ? "none" : legacy ? `max-height var(--duration-${duration}) var(--ease-${easing})` : spring("max-height", "spatial", SPRING_SPEED[duration]),
343
+ ...style
344
+ },
345
+ onTransitionEnd: handleTransitionEnd,
346
+ ...rest,
347
+ children: /* @__PURE__ */ jsx4("div", { ref: innerRef, children })
348
+ }
349
+ );
350
+ });
351
+ Collapse.displayName = "Collapse";
352
+
353
+ // src/components/Motion/ListTransition.tsx
354
+ import * as React6 from "react";
355
+ import { jsx as jsx5, jsxs } from "react/jsx-runtime";
356
+ var EXIT_FALLBACK_MS = SPRING_MS.effects.default + 50;
357
+ function keyed(children) {
358
+ const out = [];
359
+ React6.Children.forEach(children, (child, i) => {
360
+ const key = React6.isValidElement(child) && child.key != null ? child.key : `__nx-${i}`;
361
+ out.push({ key, node: child });
362
+ });
363
+ return out;
364
+ }
365
+ function merge(prev, next) {
366
+ const before = new Map(prev.map((p) => [p.key, p]));
367
+ const nextKeys = new Set(next.map((n) => n.key));
368
+ const result = next.map((n) => {
369
+ const old = before.get(n.key);
370
+ return { ...n, state: !old || old.state === "exit" ? "enter" : old.state };
371
+ });
372
+ prev.forEach((p, i) => {
373
+ if (nextKeys.has(p.key)) return;
374
+ let at = 0;
375
+ for (let j = i - 1; j >= 0; j--) {
376
+ const idx = result.findIndex((r) => r.key === prev[j].key);
377
+ if (idx !== -1) {
378
+ at = idx + 1;
379
+ break;
380
+ }
381
+ }
382
+ result.splice(at, 0, { ...p, state: "exit" });
383
+ });
384
+ return result;
385
+ }
386
+ var ListTransition = React6.forwardRef(function ListTransition2({ as: Tag = "div", itemAs: Item = "div", appear = false, children, ...rest }, ref) {
387
+ const next = keyed(children);
388
+ const [entries, setEntries] = React6.useState(
389
+ () => next.map((n) => ({ ...n, state: appear ? "enter" : "idle" }))
390
+ );
391
+ const [prevChildren, setPrevChildren] = React6.useState(children);
392
+ let rendered = entries;
393
+ if (children !== prevChildren) {
394
+ rendered = merge(entries, next);
395
+ setPrevChildren(children);
396
+ setEntries(rendered);
397
+ }
398
+ const remove = React6.useCallback((key) => {
399
+ setEntries((list) => list.filter((e) => !(e.key === key && e.state === "exit")));
400
+ }, []);
401
+ const settle = React6.useCallback((key) => {
402
+ setEntries((list) => list.map((e) => e.key === key && e.state === "enter" ? { ...e, state: "idle" } : e));
403
+ }, []);
404
+ const exiting = rendered.filter((e) => e.state === "exit").map((e) => e.key).join("|");
405
+ React6.useEffect(() => {
406
+ if (!exiting) return;
407
+ const keys = rendered.filter((e) => e.state === "exit").map((e) => e.key);
408
+ const t = setTimeout(() => keys.forEach(remove), EXIT_FALLBACK_MS);
409
+ return () => clearTimeout(t);
410
+ }, [exiting, remove]);
411
+ return /* @__PURE__ */ jsx5(Tag, { ref, ...rest, children: rendered.map((e) => /* @__PURE__ */ jsx5(
412
+ Item,
413
+ {
414
+ className: "nx-list-item",
415
+ "data-state": e.state === "idle" ? void 0 : e.state,
416
+ "aria-hidden": e.state === "exit" ? true : void 0,
417
+ onAnimationEnd: (ev) => {
418
+ if (ev.target !== ev.currentTarget) return;
419
+ if (e.state === "exit") remove(e.key);
420
+ else if (e.state === "enter") settle(e.key);
421
+ },
422
+ children: /* @__PURE__ */ jsx5("div", { className: "nx-list-item-inner", children: e.node })
423
+ },
424
+ e.key
425
+ )) });
426
+ });
427
+ ListTransition.displayName = "ListTransition";
428
+ var SkeletonSwap = React6.forwardRef(function SkeletonSwap2({ loading, skeleton, children, className, ...rest }, ref) {
429
+ const [phase, setPhase] = React6.useState(loading ? "loading" : "done");
430
+ const [prevLoading, setPrevLoading] = React6.useState(loading);
431
+ let current = phase;
432
+ if (loading !== prevLoading) {
433
+ current = loading ? "loading" : "out";
434
+ setPrevLoading(loading);
435
+ setPhase(current);
436
+ }
437
+ React6.useEffect(() => {
438
+ if (current !== "out") return;
439
+ const t = setTimeout(() => setPhase("done"), SPRING_MS.effects.default + 50);
440
+ return () => clearTimeout(t);
441
+ }, [current]);
442
+ return /* @__PURE__ */ jsxs("div", { ref, className: className ? `nx-swap ${className}` : "nx-swap", "aria-busy": loading || void 0, ...rest, children: [
443
+ current !== "loading" && /* @__PURE__ */ jsx5("div", { className: current === "out" ? "nx-swap-in" : void 0, children }),
444
+ current !== "done" && /* @__PURE__ */ jsx5(
445
+ "div",
446
+ {
447
+ className: current === "out" ? "nx-swap-out" : void 0,
448
+ "aria-hidden": "true",
449
+ onAnimationEnd: (e) => {
450
+ if (e.target === e.currentTarget) setPhase("done");
451
+ },
452
+ children: skeleton
453
+ }
454
+ )
455
+ ] });
456
+ });
457
+ SkeletonSwap.displayName = "SkeletonSwap";
458
+
459
+ export {
460
+ useMediaQuery,
461
+ usePrefersReducedMotion,
462
+ Fade,
463
+ Slide,
464
+ Scale,
465
+ Collapse,
466
+ ListTransition,
467
+ SkeletonSwap
468
+ };
@@ -101,7 +101,7 @@ var Footer = React.forwardRef(
101
101
  {
102
102
  ref,
103
103
  className: cn(
104
- "mt-auto border-t border-[var(--color-outline-variant)] p-4",
104
+ "sticky bottom-0 mt-auto shrink-0 border-t border-[var(--color-outline-variant)] bg-[var(--color-surface-container-low)] p-4",
105
105
  className
106
106
  ),
107
107
  ...props
@@ -1546,7 +1546,7 @@ var ClassLockOverlay = React2.forwardRef(
1546
1546
  var Overlay3 = React2.forwardRef(
1547
1547
  ({ className, ...props }, ref) => {
1548
1548
  const { scrollLock, open, modal } = React2.useContext(DrawerContext2);
1549
- const classes = cn("fixed inset-0 z-[var(--z-modal)]", scrim, className);
1549
+ const classes = cn("nx-drawer-overlay fixed inset-0 z-[var(--z-modal)]", scrim, className);
1550
1550
  if (scrollLock === "class") {
1551
1551
  if (!modal) return null;
1552
1552
  return /* @__PURE__ */ jsx(ClassLockOverlay, { ref, open, className: cn("pointer-events-auto", classes), ...props });
@@ -1567,7 +1567,7 @@ var Content3 = React2.forwardRef(({ className, children, side = "right", surface
1567
1567
  {
1568
1568
  ref,
1569
1569
  className: cn(
1570
- "fixed z-[var(--z-modal)] flex flex-col bg-[var(--color-surface-elevated)] text-[var(--color-on-surface)] shadow-elevation-3 focus:outline-none",
1570
+ "nx-drawer fixed z-[var(--z-modal)] flex flex-col bg-[var(--color-surface-elevated)] text-[var(--color-on-surface)] shadow-elevation-3 focus:outline-none",
1571
1571
  sideMap[side],
1572
1572
  surface === "glass" && glassSurfaceClass,
1573
1573
  className
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  Drawer,
4
4
  Sheet
5
- } from "../../chunk-UVWQPDOV.js";
5
+ } from "../../chunk-6ALDF3XF.js";
6
6
  import "../../chunk-47UGX37F.js";
7
7
  import "../../chunk-5FWJXSXT.js";
8
8
  import "../../chunk-DEIBF3P7.js";
@@ -5,8 +5,8 @@ import {
5
5
  import "../../chunk-JLUCMNVS.js";
6
6
  import "../../chunk-NHUQLIHL.js";
7
7
  import "../../chunk-F7ZA2VA2.js";
8
- import "../../chunk-R3SUPKKI.js";
9
8
  import "../../chunk-5W3AOOYS.js";
9
+ import "../../chunk-R3SUPKKI.js";
10
10
  import "../../chunk-VMSWIC3O.js";
11
11
  import "../../chunk-Q7G5WGYV.js";
12
12
  export {
@@ -2,12 +2,18 @@
2
2
  import {
3
3
  Collapse,
4
4
  Fade,
5
+ ListTransition,
5
6
  Scale,
6
- Slide
7
- } from "../../chunk-FS4XKWFA.js";
7
+ SkeletonSwap,
8
+ Slide,
9
+ usePrefersReducedMotion
10
+ } from "../../chunk-4GVGS5N4.js";
8
11
  export {
9
12
  Collapse,
10
13
  Fade,
14
+ ListTransition,
11
15
  Scale,
12
- Slide
16
+ SkeletonSwap,
17
+ Slide,
18
+ usePrefersReducedMotion
13
19
  };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  Pagination
4
- } from "../../chunk-AKV4GMIY.js";
4
+ } from "../../chunk-2NJQBJWH.js";
5
5
  import "../../chunk-5W3AOOYS.js";
6
6
  import "../../chunk-VMSWIC3O.js";
7
7
  import "../../chunk-Q7G5WGYV.js";
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  Sidebar
4
- } from "../../chunk-MX6NYEOW.js";
4
+ } from "../../chunk-5HEMUXSR.js";
5
5
  import "../../chunk-VMSWIC3O.js";
6
6
  import "../../chunk-Q7G5WGYV.js";
7
7
  export {
package/dist/index.d.ts CHANGED
@@ -1733,13 +1733,14 @@ interface MotionBaseProps {
1733
1733
  /** Whether the component is visible / mounted. */
1734
1734
  in: boolean;
1735
1735
  /**
1736
- * Token-based duration. Maps to `--duration-{value}`.
1736
+ * Speed on the spring tokens: `fast` → fast, `normal` → default,
1737
+ * `slow`/`slower` → slow springs. `instant` turns motion off.
1737
1738
  * @default 'normal'
1738
1739
  */
1739
1740
  duration?: MotionDuration;
1740
1741
  /**
1741
- * Token-based easing. Maps to `--ease-{value}`.
1742
- * @default 'out'
1742
+ * Legacy: set it to use the old `--duration-*` + `--ease-{value}` tokens
1743
+ * instead of springs. Omit it to ride the spring tokens.
1743
1744
  */
1744
1745
  easing?: MotionEasing;
1745
1746
  /**
@@ -1776,7 +1777,8 @@ interface SlideProps extends MotionBaseProps, React.HTMLAttributes<HTMLDivElemen
1776
1777
  as?: React.ElementType;
1777
1778
  }
1778
1779
  /**
1779
- * Slide — animates opacity + translate from a given direction.
1780
+ * Slide — animates opacity + translate from a given direction on the spring
1781
+ * tokens. Reduced motion keeps the fade and drops the travel.
1780
1782
  *
1781
1783
  * ```tsx
1782
1784
  * <Slide in={isOpen} direction="up">
@@ -1796,7 +1798,8 @@ interface ScaleProps extends MotionBaseProps, React.HTMLAttributes<HTMLDivElemen
1796
1798
  as?: React.ElementType;
1797
1799
  }
1798
1800
  /**
1799
- * Scale — animates opacity + scale(x) from `initialScale` to 1.
1801
+ * Scale — animates opacity + scale(x) from `initialScale` to 1 on the spring
1802
+ * tokens. Reduced motion keeps the fade and drops the scale.
1800
1803
  *
1801
1804
  * ```tsx
1802
1805
  * <Scale in={isOpen} initialScale={0.9}>
@@ -1808,8 +1811,8 @@ declare const Scale: React.ForwardRefExoticComponent<ScaleProps & React.RefAttri
1808
1811
 
1809
1812
  interface CollapseProps extends Omit<MotionBaseProps, 'easing'>, React.HTMLAttributes<HTMLDivElement> {
1810
1813
  /**
1811
- * Easing for the height transition.
1812
- * @default 'out'
1814
+ * Legacy: set it to use `--duration-*` + `--ease-{value}` instead of the
1815
+ * spatial spring for the height transition.
1813
1816
  */
1814
1817
  easing?: MotionBaseProps['easing'];
1815
1818
  /**
@@ -1821,7 +1824,8 @@ interface CollapseProps extends Omit<MotionBaseProps, 'easing'>, React.HTMLAttri
1821
1824
  as?: React.ElementType;
1822
1825
  }
1823
1826
  /**
1824
- * Collapse — animates `max-height` to reveal / hide content.
1827
+ * Collapse — animates `max-height` to reveal / hide content on the spatial
1828
+ * spring. Reduced motion snaps the height instead of animating it.
1825
1829
  * Height is measured from the content ref so you don't need to
1826
1830
  * provide an explicit height.
1827
1831
  *
@@ -1833,4 +1837,53 @@ interface CollapseProps extends Omit<MotionBaseProps, 'easing'>, React.HTMLAttri
1833
1837
  */
1834
1838
  declare const Collapse: React.ForwardRefExoticComponent<CollapseProps & React.RefAttributes<HTMLDivElement>>;
1835
1839
 
1836
- export { Accordion, Alert, AlertDialog, type AlertProps, Amount, type AmountProps, AppShell, AspectRatio, type AspectRatioProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, Banner, type BannerProps, Box, type BoxProps, Breadcrumb, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, type ButtonStatus, Callout, type CalloutProps, Card, CardLink, type CardLinkProps, type CardProps, CheckList, CheckListItem, type CheckListItemProps, type CheckListProps, Checkbox, CheckboxCard, type CheckboxCardProps, type CheckboxProps, Chip, type ChipProps, Citation, type CitationMessages, type CitationProps, type CitationSource, Code, type CodeProps, Collapse, type CollapseProps, Collapsible, Combobox, CommandPalette, type CommandPaletteRootProps, Container, type ContainerProps, ContextMenu, CountryBadge, type CountryBadgeProps, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, DateRangePicker, type DateRangePickerProps, Dialog, Drawer, type DrawerRootProps, DropdownMenu, EmptyState, ErrorState, type ErrorStateProps, EyebrowChip, type EyebrowChipProps, Fade, type FadeProps, type Feedback, FileUpload, type FileUploadRootProps, FilterChip, type FilterChipProps, Flag, type FlagProps, Flex, type FlexProps, FormError, type FormErrorProps, FormField, type FormFieldContextValue, type FormFieldRootProps, GradientDivider, type GradientDividerProps, Grid, type GridProps, Heading, type HeadingProps, HelperText, type HelperTextProps, HoverCard, IconButton, type IconButtonProps, Input, type InputProps, Kbd, type KbdProps, KeyboardShortcut, type KeyboardShortcutProps, Label, type LabelProps, Link, type LinkProps, Logo, type LogoProps, MediaCard, type MediaCardProps, type MediaCardRatio, Menubar, MessageActions, type MessageActionsProps, MessageBubble, type MessageBubbleProps, type MessageFrom, type MessageStrings, MoneyInput, type MoneyInputProps, type MotionBaseProps, type MotionDuration, type MotionEasing, NavigationMenu, NavigationRail, type NavigationRailItemProps, type NavigationRailRootProps, NexubProvider, type NexubProviderProps, NumberInput, type NumberInputProps, NumberTicker, type NumberTickerPart, type NumberTickerProps, PageHeader, type PageHeaderProps, Pagination, type PaginationProps, PasswordInput, type PasswordInputProps, Popover, Progress, type ProgressProps, PromptInput, type PromptInputMessages, type PromptInputProps, type PromptInputStatus, RadioCard, RadioCardGroup, type RadioCardGroupProps, type RadioCardProps, RadioGroup, Rating, type RatingProps, Scale, type ScaleProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, Section, type SectionProps, SegmentedControl, type SegmentedControlItemProps, type SegmentedControlProps, Select, Separator, type SeparatorProps, Sheet, Sidebar, type SidebarGroupProps, type SidebarHeaderProps, type SidebarItemProps, type SidebarRootProps, Skeleton, type SkeletonProps, Slide, type SlideDirection, type SlideProps, Slider, type SliderProps, SourceChip, type SourceChipProps, SourceList, type SourceListProps, Spinner, type SpinnerProps, SplitButton, type SplitButtonProps, Stack, type StackProps, StatusPill, type StatusPillProps, StreamingText, type StreamingTextProps, Switch, type SwitchProps, Table, type TableRootProps, Tabs, Tag, type TagProps, Text, TextGradient, type TextGradientProps, type TextProps, Textarea, type TextareaProps, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThemeScript, type ThemeScriptProps, ThinkingIndicator, type ThinkingIndicatorMessages, type ThinkingIndicatorProps, Toast, type ToastItem, Toaster, type ToasterProps, Toggle, ToggleGroup, type ToggleProps, Toolbar, type ToolbarRootProps, Tooltip, Provider$1 as TooltipProvider, TopNav, type TopNavItemProps, type TopNavRootProps, type UndoableToastOptions, addonVariants, alertVariants, avatarGroupVariants, avatarVariants, badgeVariants, bannerVariants, buttonVariants, calloutVariants, cardVariants, chipVariants, cn, containerVariants, defaultMessageStrings, headingVariants, iconButtonVariants, inputVariants, inputWrapperVariants, labelVariants, linkVariants, logoVariants, progressFillVariants, progressTrackVariants, ratingVariants, safeUrl, sectionVariants, separatorVariants, skeletonVariants, spinnerVariants, startViewTransition, statusPillVariants, tagVariants, textVariants, textareaVariants, toast, toggleVariants, useControllableState, useDisclosure, useIsomorphicLayoutEffect, useLocale, useMediaQuery, useMergedRefs, useNexubMessages, usePointerSheen, useTheme, useToastStore };
1840
+ interface ListTransitionProps extends React.HTMLAttributes<HTMLElement> {
1841
+ /** List element. @default 'div' */
1842
+ as?: React.ElementType;
1843
+ /** Element wrapping each item, e.g. `'li'` inside a `'ul'`. @default 'div' */
1844
+ itemAs?: React.ElementType;
1845
+ /** Animate the items present on first render too. @default false */
1846
+ appear?: boolean;
1847
+ /** Keyed children. Items without a key are rendered but never animated. */
1848
+ children?: React.ReactNode;
1849
+ }
1850
+ /**
1851
+ * ListTransition — animates keyed items in and out of a list.
1852
+ * New items grow in from zero height and fade in; removed items stay in place
1853
+ * while they collapse and fade out, then unmount. Uses CSS keyframes on the
1854
+ * spring tokens (`nx-list-item` in `@nexub/themes/base`); reduced motion keeps
1855
+ * only the fade.
1856
+ *
1857
+ * Space items with padding inside each item, not `gap`, so a collapsing item
1858
+ * takes its spacing with it.
1859
+ *
1860
+ * ```tsx
1861
+ * <ListTransition as="ul" itemAs="li">
1862
+ * {items.map((item) => <Row key={item.id} item={item} />)}
1863
+ * </ListTransition>
1864
+ * ```
1865
+ */
1866
+ declare const ListTransition: React.ForwardRefExoticComponent<ListTransitionProps & React.RefAttributes<HTMLElement>>;
1867
+ interface SkeletonSwapProps extends React.HTMLAttributes<HTMLDivElement> {
1868
+ /** While true the skeleton shows; flipping to false crossfades to the content. */
1869
+ loading: boolean;
1870
+ /** Placeholder shown while loading, usually `Skeleton`s shaped like the content. */
1871
+ skeleton: React.ReactNode;
1872
+ children?: React.ReactNode;
1873
+ }
1874
+ /**
1875
+ * SkeletonSwap — crossfades from a skeleton to the loaded content in place,
1876
+ * instead of popping. Sets `aria-busy` while loading.
1877
+ *
1878
+ * ```tsx
1879
+ * <SkeletonSwap loading={isLoading} skeleton={<Skeleton shape="rect" height={120} />}>
1880
+ * <Card>…</Card>
1881
+ * </SkeletonSwap>
1882
+ * ```
1883
+ */
1884
+ declare const SkeletonSwap: React.ForwardRefExoticComponent<SkeletonSwapProps & React.RefAttributes<HTMLDivElement>>;
1885
+
1886
+ /** True when the user asked the OS for reduced motion. False during SSR. */
1887
+ declare function usePrefersReducedMotion(): boolean;
1888
+
1889
+ export { Accordion, Alert, AlertDialog, type AlertProps, Amount, type AmountProps, AppShell, AspectRatio, type AspectRatioProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, Banner, type BannerProps, Box, type BoxProps, Breadcrumb, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, type ButtonStatus, Callout, type CalloutProps, Card, CardLink, type CardLinkProps, type CardProps, CheckList, CheckListItem, type CheckListItemProps, type CheckListProps, Checkbox, CheckboxCard, type CheckboxCardProps, type CheckboxProps, Chip, type ChipProps, Citation, type CitationMessages, type CitationProps, type CitationSource, Code, type CodeProps, Collapse, type CollapseProps, Collapsible, Combobox, CommandPalette, type CommandPaletteRootProps, Container, type ContainerProps, ContextMenu, CountryBadge, type CountryBadgeProps, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, DateRangePicker, type DateRangePickerProps, Dialog, Drawer, type DrawerRootProps, DropdownMenu, EmptyState, ErrorState, type ErrorStateProps, EyebrowChip, type EyebrowChipProps, Fade, type FadeProps, type Feedback, FileUpload, type FileUploadRootProps, FilterChip, type FilterChipProps, Flag, type FlagProps, Flex, type FlexProps, FormError, type FormErrorProps, FormField, type FormFieldContextValue, type FormFieldRootProps, GradientDivider, type GradientDividerProps, Grid, type GridProps, Heading, type HeadingProps, HelperText, type HelperTextProps, HoverCard, IconButton, type IconButtonProps, Input, type InputProps, Kbd, type KbdProps, KeyboardShortcut, type KeyboardShortcutProps, Label, type LabelProps, Link, type LinkProps, ListTransition, type ListTransitionProps, Logo, type LogoProps, MediaCard, type MediaCardProps, type MediaCardRatio, Menubar, MessageActions, type MessageActionsProps, MessageBubble, type MessageBubbleProps, type MessageFrom, type MessageStrings, MoneyInput, type MoneyInputProps, type MotionBaseProps, type MotionDuration, type MotionEasing, NavigationMenu, NavigationRail, type NavigationRailItemProps, type NavigationRailRootProps, NexubProvider, type NexubProviderProps, NumberInput, type NumberInputProps, NumberTicker, type NumberTickerPart, type NumberTickerProps, PageHeader, type PageHeaderProps, Pagination, type PaginationProps, PasswordInput, type PasswordInputProps, Popover, Progress, type ProgressProps, PromptInput, type PromptInputMessages, type PromptInputProps, type PromptInputStatus, RadioCard, RadioCardGroup, type RadioCardGroupProps, type RadioCardProps, RadioGroup, Rating, type RatingProps, Scale, type ScaleProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, Section, type SectionProps, SegmentedControl, type SegmentedControlItemProps, type SegmentedControlProps, Select, Separator, type SeparatorProps, Sheet, Sidebar, type SidebarGroupProps, type SidebarHeaderProps, type SidebarItemProps, type SidebarRootProps, Skeleton, type SkeletonProps, SkeletonSwap, type SkeletonSwapProps, Slide, type SlideDirection, type SlideProps, Slider, type SliderProps, SourceChip, type SourceChipProps, SourceList, type SourceListProps, Spinner, type SpinnerProps, SplitButton, type SplitButtonProps, Stack, type StackProps, StatusPill, type StatusPillProps, StreamingText, type StreamingTextProps, Switch, type SwitchProps, Table, type TableRootProps, Tabs, Tag, type TagProps, Text, TextGradient, type TextGradientProps, type TextProps, Textarea, type TextareaProps, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThemeScript, type ThemeScriptProps, ThinkingIndicator, type ThinkingIndicatorMessages, type ThinkingIndicatorProps, Toast, type ToastItem, Toaster, type ToasterProps, Toggle, ToggleGroup, type ToggleProps, Toolbar, type ToolbarRootProps, Tooltip, Provider$1 as TooltipProvider, TopNav, type TopNavItemProps, type TopNavRootProps, type UndoableToastOptions, addonVariants, alertVariants, avatarGroupVariants, avatarVariants, badgeVariants, bannerVariants, buttonVariants, calloutVariants, cardVariants, chipVariants, cn, containerVariants, defaultMessageStrings, headingVariants, iconButtonVariants, inputVariants, inputWrapperVariants, labelVariants, linkVariants, logoVariants, progressFillVariants, progressTrackVariants, ratingVariants, safeUrl, sectionVariants, separatorVariants, skeletonVariants, spinnerVariants, startViewTransition, statusPillVariants, tagVariants, textVariants, textareaVariants, toast, toggleVariants, useControllableState, useDisclosure, useIsomorphicLayoutEffect, useLocale, useMediaQuery, useMergedRefs, useNexubMessages, usePointerSheen, usePrefersReducedMotion, useTheme, useToastStore };
package/dist/index.js CHANGED
@@ -35,20 +35,20 @@ import {
35
35
  Toolbar
36
36
  } from "./chunk-2ZBJOUJ7.js";
37
37
  import "./chunk-XYXMPHND.js";
38
- import {
39
- StreamingText
40
- } from "./chunk-5QEARXY3.js";
41
38
  import {
42
39
  StatusPill,
43
40
  statusPillVariants
44
41
  } from "./chunk-77LYWSJW.js";
42
+ import {
43
+ StreamingText
44
+ } from "./chunk-5QEARXY3.js";
45
45
  import {
46
46
  Switch
47
47
  } from "./chunk-BTYDDKRQ.js";
48
+ import "./chunk-4B4XWDAH.js";
48
49
  import {
49
50
  Tabs
50
51
  } from "./chunk-CKFRMCZ7.js";
51
- import "./chunk-4B4XWDAH.js";
52
52
  import {
53
53
  Tag,
54
54
  tagVariants
@@ -69,7 +69,7 @@ import {
69
69
  } from "./chunk-CUOASXI7.js";
70
70
  import {
71
71
  Sidebar
72
- } from "./chunk-MX6NYEOW.js";
72
+ } from "./chunk-5HEMUXSR.js";
73
73
  import {
74
74
  Skeleton,
75
75
  skeletonVariants
@@ -128,13 +128,13 @@ import {
128
128
  } from "./chunk-UCNA2ICU.js";
129
129
  import {
130
130
  Pagination
131
- } from "./chunk-AKV4GMIY.js";
132
- import {
133
- Popover
134
- } from "./chunk-NORCPKRD.js";
131
+ } from "./chunk-2NJQBJWH.js";
135
132
  import {
136
133
  PasswordInput
137
134
  } from "./chunk-TSBBWYDO.js";
135
+ import {
136
+ Popover
137
+ } from "./chunk-NORCPKRD.js";
138
138
  import {
139
139
  Progress,
140
140
  progressFillVariants,
@@ -147,6 +147,9 @@ import {
147
147
  import {
148
148
  MediaCard
149
149
  } from "./chunk-3QD76BKT.js";
150
+ import {
151
+ Menubar
152
+ } from "./chunk-VF6RWEKX.js";
150
153
  import {
151
154
  MessageActions,
152
155
  MessageBubble,
@@ -156,9 +159,6 @@ import {
156
159
  Provider,
157
160
  Tooltip
158
161
  } from "./chunk-XQDD76EF.js";
159
- import {
160
- Menubar
161
- } from "./chunk-VF6RWEKX.js";
162
162
  import {
163
163
  MoneyInput
164
164
  } from "./chunk-6FRG6NMM.js";
@@ -171,9 +171,13 @@ import {
171
171
  import {
172
172
  Collapse,
173
173
  Fade,
174
+ ListTransition,
174
175
  Scale,
175
- Slide
176
- } from "./chunk-FS4XKWFA.js";
176
+ SkeletonSwap,
177
+ Slide,
178
+ useMediaQuery,
179
+ usePrefersReducedMotion
180
+ } from "./chunk-4GVGS5N4.js";
177
181
  import {
178
182
  NavigationMenu
179
183
  } from "./chunk-BEOHZKPQ.js";
@@ -210,10 +214,10 @@ import {
210
214
  FilterChip
211
215
  } from "./chunk-NN7SNECM.js";
212
216
  import "./chunk-BV3U3ATL.js";
213
- import "./chunk-ADO2DB6Q.js";
214
217
  import {
215
218
  Flex
216
219
  } from "./chunk-7MSIOYJZ.js";
220
+ import "./chunk-ADO2DB6Q.js";
217
221
  import {
218
222
  FormField
219
223
  } from "./chunk-G4RLUGE3.js";
@@ -247,7 +251,7 @@ import {
247
251
  import {
248
252
  Drawer,
249
253
  Sheet
250
- } from "./chunk-UVWQPDOV.js";
254
+ } from "./chunk-6ALDF3XF.js";
251
255
  import "./chunk-HI7GBSW2.js";
252
256
  import {
253
257
  DropdownMenu
@@ -363,17 +367,6 @@ import {
363
367
  import {
364
368
  Accordion
365
369
  } from "./chunk-T7RLPFGX.js";
366
- import {
367
- Alert,
368
- alertVariants
369
- } from "./chunk-TXIJMUD5.js";
370
- import {
371
- Amount
372
- } from "./chunk-ZYR3R36N.js";
373
- import {
374
- NumberTicker
375
- } from "./chunk-2BAXCPQF.js";
376
- import "./chunk-R3SUPKKI.js";
377
370
  import {
378
371
  AlertDialog
379
372
  } from "./chunk-TSN6HYVY.js";
@@ -384,9 +377,20 @@ import {
384
377
  } from "./chunk-22BGPRJZ.js";
385
378
  import "./chunk-5W3AOOYS.js";
386
379
  import "./chunk-DEIBF3P7.js";
380
+ import {
381
+ Amount
382
+ } from "./chunk-ZYR3R36N.js";
383
+ import {
384
+ NumberTicker
385
+ } from "./chunk-2BAXCPQF.js";
386
+ import "./chunk-R3SUPKKI.js";
387
387
  import {
388
388
  AppShell
389
389
  } from "./chunk-56FERRWW.js";
390
+ import {
391
+ Alert,
392
+ alertVariants
393
+ } from "./chunk-TXIJMUD5.js";
390
394
  import {
391
395
  AspectRatio
392
396
  } from "./chunk-S2CTBFLN.js";
@@ -422,21 +426,6 @@ function useDisclosure(initialOpen = false) {
422
426
  const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
423
427
  return { isOpen, open, close, toggle };
424
428
  }
425
-
426
- // src/hooks/useMediaQuery.ts
427
- import { useState as useState2, useEffect } from "react";
428
- function useMediaQuery(query) {
429
- const [matches, setMatches] = useState2(false);
430
- useEffect(() => {
431
- if (typeof window === "undefined") return;
432
- const mql = window.matchMedia(query);
433
- const handler = (e) => setMatches(e.matches);
434
- mql.addEventListener("change", handler);
435
- setMatches(mql.matches);
436
- return () => mql.removeEventListener("change", handler);
437
- }, [query]);
438
- return matches;
439
- }
440
429
  export {
441
430
  Accordion,
442
431
  Alert,
@@ -497,6 +486,7 @@ export {
497
486
  KeyboardShortcut,
498
487
  Label,
499
488
  Link,
489
+ ListTransition,
500
490
  Logo,
501
491
  MediaCard,
502
492
  Menubar,
@@ -528,6 +518,7 @@ export {
528
518
  Sheet,
529
519
  Sidebar,
530
520
  Skeleton,
521
+ SkeletonSwap,
531
522
  Slide,
532
523
  Slider,
533
524
  SourceChip,
@@ -600,6 +591,7 @@ export {
600
591
  useMergedRefs,
601
592
  useNexubMessages,
602
593
  usePointerSheen,
594
+ usePrefersReducedMotion,
603
595
  useTheme,
604
596
  useToastStore
605
597
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexub/react",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "React component library for Nexub Design System — 90+ accessible, themeable UI components built on Radix UI primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -68,39 +68,39 @@
68
68
  "date-fns": "^4.4.0",
69
69
  "react-day-picker": "^9.14.0",
70
70
  "tailwind-merge": "^2.6.1",
71
- "@nexub/core": "2.1.0",
72
- "@nexub/icons": "2.1.0"
71
+ "@nexub/core": "2.3.0",
72
+ "@nexub/icons": "2.3.0"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "react": ">=18 <20",
76
76
  "react-dom": ">=18 <20",
77
- "@nexub/tokens": "^2.1.0"
77
+ "@nexub/tokens": "^2.3.0"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@playwright/test": "1.64.0",
81
81
  "@storybook/react": "10.6.3",
82
82
  "@storybook/react-vite": "10.6.3",
83
+ "@tailwindcss/vite": "^4.3.3",
83
84
  "@testing-library/jest-dom": "^6.9.1",
84
85
  "@testing-library/react": "^16.3.3",
85
86
  "@testing-library/user-event": "^14.6.8",
86
87
  "@types/node": "^26.6.5",
87
88
  "@types/react": "^19.3.0",
88
89
  "@types/react-dom": "^19.3.0",
89
- "autoprefixer": "^10.6.1",
90
90
  "axe-core": "^4.14.0",
91
91
  "jsdom": "^30.1.3",
92
92
  "postcss": "^8.5.31",
93
93
  "react": "^19.3.1",
94
94
  "react-dom": "^19.3.1",
95
95
  "storybook": "10.6.3",
96
- "tailwindcss": "^3.4.19",
96
+ "tailwindcss": "^4.3.3",
97
97
  "tsup": "^8.5.1",
98
98
  "typescript": "^5.9.3",
99
99
  "vaul": "1.1.2",
100
100
  "vite": "^7.3.7",
101
101
  "vitest": "^5.0.5",
102
- "@nexub/themes": "2.1.0",
103
- "@nexub/tokens": "2.1.0"
102
+ "@nexub/themes": "2.3.0",
103
+ "@nexub/tokens": "2.3.0"
104
104
  },
105
105
  "scripts": {
106
106
  "build": "tsup && node scripts/check-dts.mjs",
@@ -1,308 +0,0 @@
1
- 'use client';
2
-
3
- // src/components/Motion/Fade.tsx
4
- import * as React2 from "react";
5
-
6
- // src/components/Motion/useMotion.ts
7
- import * as React from "react";
8
- var DURATION_MS = {
9
- instant: 0,
10
- fast: 150,
11
- normal: 200,
12
- slow: 300,
13
- slower: 500
14
- };
15
- var FALLBACK_MARGIN_MS = 50;
16
- function useMotion({
17
- visible,
18
- duration = "normal",
19
- easing = "out",
20
- unmountOnExit = false
21
- }) {
22
- const [phase, setPhase] = React.useState(
23
- visible ? "entered" : "exited"
24
- );
25
- const rafRef = React.useRef(null);
26
- const isMounted = React.useRef(true);
27
- React.useEffect(() => {
28
- isMounted.current = true;
29
- return () => {
30
- isMounted.current = false;
31
- if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
32
- };
33
- }, []);
34
- const prevVisible = React.useRef(visible);
35
- React.useEffect(() => {
36
- if (prevVisible.current === visible) return;
37
- prevVisible.current = visible;
38
- if (visible) {
39
- setPhase("entering");
40
- rafRef.current = requestAnimationFrame(() => {
41
- rafRef.current = requestAnimationFrame(() => {
42
- if (isMounted.current) setPhase("entered");
43
- });
44
- });
45
- } else {
46
- setPhase("exiting");
47
- }
48
- }, [visible]);
49
- React.useEffect(() => {
50
- if (phase !== "exiting") return;
51
- const t = setTimeout(() => setPhase("exited"), DURATION_MS[duration] + FALLBACK_MARGIN_MS);
52
- return () => clearTimeout(t);
53
- }, [phase, duration]);
54
- const handleTransitionEnd = React.useCallback((e) => {
55
- if (!isMounted.current) return;
56
- if (e && e.target !== e.currentTarget) return;
57
- setPhase((prev) => prev === "exiting" ? "exited" : prev);
58
- }, []);
59
- const mounted = unmountOnExit ? phase !== "exited" : true;
60
- const transitionStyle = {
61
- transitionDuration: `var(--duration-${duration})`,
62
- transitionTimingFunction: `var(--ease-${easing})`,
63
- transitionProperty: "opacity, transform"
64
- };
65
- return { phase, mounted, transitionStyle, handleTransitionEnd };
66
- }
67
-
68
- // src/components/Motion/Fade.tsx
69
- import { jsx } from "react/jsx-runtime";
70
- var Fade = React2.forwardRef(function Fade2({
71
- in: visible,
72
- duration = "normal",
73
- easing = "out",
74
- unmountOnExit = false,
75
- children,
76
- style,
77
- as: Tag = "div",
78
- ...rest
79
- }, ref) {
80
- const { phase, mounted, transitionStyle, handleTransitionEnd } = useMotion({
81
- visible,
82
- duration,
83
- easing,
84
- unmountOnExit
85
- });
86
- if (!mounted) return null;
87
- const opacity = phase === "entered" ? 1 : 0;
88
- return /* @__PURE__ */ jsx(
89
- Tag,
90
- {
91
- ref,
92
- onTransitionEnd: handleTransitionEnd,
93
- style: {
94
- opacity,
95
- willChange: "opacity",
96
- ...transitionStyle,
97
- ...style
98
- },
99
- ...rest,
100
- children
101
- }
102
- );
103
- });
104
- Fade.displayName = "Fade";
105
-
106
- // src/components/Motion/Slide.tsx
107
- import * as React3 from "react";
108
- import { jsx as jsx2 } from "react/jsx-runtime";
109
- var directionTransform = {
110
- up: { from: "translateY(12px)", to: "translateY(0)" },
111
- down: { from: "translateY(-12px)", to: "translateY(0)" },
112
- left: { from: "translateX(12px)", to: "translateX(0)" },
113
- right: { from: "translateX(-12px)", to: "translateX(0)" }
114
- };
115
- var Slide = React3.forwardRef(function Slide2({
116
- in: visible,
117
- duration = "normal",
118
- easing = "out",
119
- unmountOnExit = false,
120
- direction = "up",
121
- distance,
122
- children,
123
- style,
124
- as: Tag = "div",
125
- ...rest
126
- }, ref) {
127
- const { phase, mounted, transitionStyle, handleTransitionEnd } = useMotion({
128
- visible,
129
- duration,
130
- easing,
131
- unmountOnExit
132
- });
133
- if (!mounted) return null;
134
- const isEntered = phase === "entered";
135
- const dt = directionTransform[direction];
136
- let fromTransform = dt.from;
137
- if (distance) {
138
- if (direction === "up") fromTransform = `translateY(${distance})`;
139
- if (direction === "down") fromTransform = `translateY(-${distance})`;
140
- if (direction === "left") fromTransform = `translateX(${distance})`;
141
- if (direction === "right") fromTransform = `translateX(-${distance})`;
142
- }
143
- return /* @__PURE__ */ jsx2(
144
- Tag,
145
- {
146
- ref,
147
- onTransitionEnd: handleTransitionEnd,
148
- style: {
149
- opacity: isEntered ? 1 : 0,
150
- transform: isEntered ? dt.to : fromTransform,
151
- willChange: "opacity, transform",
152
- ...transitionStyle,
153
- ...style
154
- },
155
- ...rest,
156
- children
157
- }
158
- );
159
- });
160
- Slide.displayName = "Slide";
161
-
162
- // src/components/Motion/Scale.tsx
163
- import * as React4 from "react";
164
- import { jsx as jsx3 } from "react/jsx-runtime";
165
- var Scale = React4.forwardRef(function Scale2({
166
- in: visible,
167
- duration = "fast",
168
- easing = "out-expo",
169
- unmountOnExit = false,
170
- initialScale = 0.95,
171
- children,
172
- style,
173
- as: Tag = "div",
174
- ...rest
175
- }, ref) {
176
- const { phase, mounted, transitionStyle, handleTransitionEnd } = useMotion({
177
- visible,
178
- duration,
179
- easing,
180
- unmountOnExit
181
- });
182
- if (!mounted) return null;
183
- const isEntered = phase === "entered";
184
- return /* @__PURE__ */ jsx3(
185
- Tag,
186
- {
187
- ref,
188
- onTransitionEnd: handleTransitionEnd,
189
- style: {
190
- opacity: isEntered ? 1 : 0,
191
- transform: isEntered ? "scale(1)" : `scale(${initialScale})`,
192
- transformOrigin: "top center",
193
- willChange: "opacity, transform",
194
- ...transitionStyle,
195
- ...style
196
- },
197
- ...rest,
198
- children
199
- }
200
- );
201
- });
202
- Scale.displayName = "Scale";
203
-
204
- // src/components/Motion/Collapse.tsx
205
- import * as React5 from "react";
206
- import { jsx as jsx4 } from "react/jsx-runtime";
207
- var Collapse = React5.forwardRef(function Collapse2({
208
- in: visible,
209
- duration = "normal",
210
- easing = "out",
211
- unmountOnExit = false,
212
- collapsedHeight = 0,
213
- children,
214
- style,
215
- as: Tag = "div",
216
- ...rest
217
- }, ref) {
218
- const innerRef = React5.useRef(null);
219
- const [height, setHeight] = React5.useState(visible ? "auto" : 0);
220
- const [overflow, setOverflow] = React5.useState(
221
- visible ? "visible" : "hidden"
222
- );
223
- const [mounted, setMounted] = React5.useState(unmountOnExit ? visible : true);
224
- const prevVisible = React5.useRef(visible);
225
- React5.useEffect(() => {
226
- const changed = prevVisible.current !== visible;
227
- prevVisible.current = visible;
228
- if (!changed) return;
229
- let raf1 = 0;
230
- let raf2 = 0;
231
- if (visible) {
232
- setMounted(true);
233
- raf1 = requestAnimationFrame(() => {
234
- const el = innerRef.current;
235
- if (el) {
236
- setHeight(el.scrollHeight);
237
- setOverflow("hidden");
238
- }
239
- });
240
- } else {
241
- const el = innerRef.current;
242
- if (el) {
243
- setHeight(el.scrollHeight);
244
- setOverflow("hidden");
245
- raf1 = requestAnimationFrame(() => {
246
- raf2 = requestAnimationFrame(() => {
247
- setHeight(typeof collapsedHeight === "number" ? collapsedHeight : 0);
248
- });
249
- });
250
- }
251
- }
252
- return () => {
253
- cancelAnimationFrame(raf1);
254
- cancelAnimationFrame(raf2);
255
- };
256
- }, [visible, collapsedHeight]);
257
- const finish = React5.useCallback(() => {
258
- if (visible) {
259
- setHeight("auto");
260
- setOverflow("visible");
261
- } else if (unmountOnExit) {
262
- setMounted(false);
263
- }
264
- }, [visible, unmountOnExit]);
265
- const handleTransitionEnd = React5.useCallback(
266
- (e) => {
267
- if (e.target !== e.currentTarget || e.propertyName !== "max-height") return;
268
- finish();
269
- },
270
- [finish]
271
- );
272
- const firstRender = React5.useRef(true);
273
- React5.useEffect(() => {
274
- if (firstRender.current) {
275
- firstRender.current = false;
276
- return;
277
- }
278
- const t = setTimeout(finish, DURATION_MS[duration] + 100);
279
- return () => clearTimeout(t);
280
- }, [visible, duration, finish]);
281
- if (!mounted) return null;
282
- const maxHeight = height === "auto" ? "none" : `${height}px`;
283
- return /* @__PURE__ */ jsx4(
284
- Tag,
285
- {
286
- ref,
287
- style: {
288
- maxHeight,
289
- overflow,
290
- transitionProperty: "max-height",
291
- transitionDuration: `var(--duration-${duration})`,
292
- transitionTimingFunction: `var(--ease-${easing})`,
293
- ...style
294
- },
295
- onTransitionEnd: handleTransitionEnd,
296
- ...rest,
297
- children: /* @__PURE__ */ jsx4("div", { ref: innerRef, children })
298
- }
299
- );
300
- });
301
- Collapse.displayName = "Collapse";
302
-
303
- export {
304
- Fade,
305
- Slide,
306
- Scale,
307
- Collapse
308
- };