@vamos-ui/react 0.2.0 → 0.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.
Files changed (82) hide show
  1. package/dist/blocks/site/_kit-1.d.ts +1 -0
  2. package/dist/blocks/site/_kit-1.js +2 -0
  3. package/dist/blocks/site/cta.js +2 -1
  4. package/dist/blocks/site/faq.js +2 -1
  5. package/dist/blocks/site/feature-grid.js +2 -1
  6. package/dist/blocks/site/gallery-section.js +3 -2
  7. package/dist/blocks/site/hero.js +12 -6
  8. package/dist/blocks/site/logo-strip.d.ts +5 -3
  9. package/dist/blocks/site/logo-strip.js +8 -7
  10. package/dist/blocks/site/media-text.js +2 -1
  11. package/dist/blocks/site/pricing-table.js +2 -1
  12. package/dist/blocks/site/process.js +2 -1
  13. package/dist/blocks/site/site-shell.d.ts +11 -0
  14. package/dist/blocks/site/site-shell.js +43 -16
  15. package/dist/blocks/site/stats-band.d.ts +3 -1
  16. package/dist/blocks/site/stats-band.js +11 -7
  17. package/dist/blocks/site/testimonials.js +2 -1
  18. package/dist/business/index.d.ts +1 -0
  19. package/dist/business/index.js +2 -0
  20. package/dist/business/signer-status.d.ts +53 -0
  21. package/dist/business/signer-status.js +76 -0
  22. package/dist/charts/bar-chart.js +5 -1
  23. package/dist/charts/combo-chart.js +1 -1
  24. package/dist/components/_qr-core.d.ts +26 -0
  25. package/dist/components/_qr-core.js +376 -0
  26. package/dist/components/backup-codes.d.ts +42 -0
  27. package/dist/components/backup-codes.js +67 -0
  28. package/dist/components/breadcrumb.d.ts +2 -2
  29. package/dist/components/breadcrumb.js +1 -1
  30. package/dist/components/command.d.ts +8 -2
  31. package/dist/components/command.js +7 -2
  32. package/dist/components/input.d.ts +1 -1
  33. package/dist/components/input.js +1 -1
  34. package/dist/components/pin-map.d.ts +58 -0
  35. package/dist/components/pin-map.js +167 -0
  36. package/dist/components/qr-code.d.ts +36 -0
  37. package/dist/components/qr-code.js +27 -0
  38. package/dist/components/record-detail-layout.d.ts +2 -2
  39. package/dist/components/record-detail-layout.js +1 -1
  40. package/dist/components/sidebar.js +8 -8
  41. package/dist/components/sla-timer.d.ts +75 -0
  42. package/dist/components/sla-timer.js +142 -0
  43. package/dist/components/slug-input.js +1 -1
  44. package/dist/components/tour.d.ts +60 -0
  45. package/dist/components/tour.js +139 -0
  46. package/dist/index.d.ts +5 -0
  47. package/dist/index.js +5 -0
  48. package/dist/lib/public.d.ts +1 -0
  49. package/dist/lib/public.js +1 -0
  50. package/dist/locale-data/core/holidays.d.ts +1 -1
  51. package/dist/locale-data/core/holidays.js +1 -1
  52. package/dist/media/gallery.js +3 -1
  53. package/dist/motion/core.d.ts +32 -0
  54. package/dist/motion/core.js +77 -0
  55. package/dist/motion/count-up.d.ts +29 -0
  56. package/dist/motion/count-up.js +92 -0
  57. package/dist/motion/index.d.ts +11 -0
  58. package/dist/motion/index.js +13 -0
  59. package/dist/motion/motion-scope.d.ts +17 -0
  60. package/dist/motion/motion-scope.js +16 -0
  61. package/dist/motion/page-transition.d.ts +33 -0
  62. package/dist/motion/page-transition.js +61 -0
  63. package/dist/motion/parallax.d.ts +37 -0
  64. package/dist/motion/parallax.js +90 -0
  65. package/dist/motion/pointer.d.ts +38 -0
  66. package/dist/motion/pointer.js +117 -0
  67. package/dist/motion/reveal.d.ts +72 -0
  68. package/dist/motion/reveal.js +133 -0
  69. package/dist/motion/scroll-shrink.d.ts +18 -0
  70. package/dist/motion/scroll-shrink.js +47 -0
  71. package/dist/motion/skeleton-swap.d.ts +17 -0
  72. package/dist/motion/skeleton-swap.js +43 -0
  73. package/dist/motion/text-reveal.d.ts +30 -0
  74. package/dist/motion/text-reveal.js +29 -0
  75. package/dist/motion/ticker.d.ts +35 -0
  76. package/dist/motion/ticker.js +16 -0
  77. package/dist/sortable/sortable-tree.d.ts +4 -0
  78. package/dist/sortable/sortable-tree.js +16 -4
  79. package/dist/styles/blocks.css +3 -3
  80. package/dist/styles/motion.css +389 -0
  81. package/dist/styles.css +2 -1
  82. package/package.json +5 -1
@@ -0,0 +1,72 @@
1
+ import * as React from "react";
2
+ export type RevealEffect = "fade" | "rise" | "blur" | "scale" | "clip";
3
+ export declare const useIsoLayoutEffect: typeof React.useLayoutEffect;
4
+ /**
5
+ * `true` when the component mounted on the client after hydration (a page switch, a tab, a
6
+ * replay); `false` when it was server-rendered and is being hydrated. Server-rendered content
7
+ * is already on screen, so it is never hidden again; fresh content may start hidden.
8
+ */
9
+ export declare function useFreshMount(): boolean;
10
+ export interface UseRevealOptions {
11
+ /** Reveal once and stop observing. Default true. With false it hides again when scrolled back above it. */
12
+ once?: boolean;
13
+ /** IntersectionObserver rootMargin. Default `"0px 0px -8% 0px"` (a little before the bottom edge). */
14
+ rootMargin?: string;
15
+ /** IntersectionObserver threshold. Default 0. */
16
+ threshold?: number;
17
+ /** The children animate (Stagger): wait for the last child before settling. */
18
+ stagger?: boolean;
19
+ /** Skip the animation; the element just shows. */
20
+ disabled?: boolean;
21
+ /** Called once the element starts revealing. */
22
+ onReveal?: () => void;
23
+ }
24
+ /**
25
+ * Scroll-reveal state machine on any element: `data-reveal-state` goes
26
+ * `pending → in → done`. CSS (motion.css) draws the states, so the hook only flips an attribute.
27
+ *
28
+ * - Server-rendered content that is already in view stays as it is (no flash, CLS 0).
29
+ * - Server-rendered content below the fold is hidden just before paint and revealed on scroll.
30
+ * - Freshly mounted content starts hidden and reveals when it enters the viewport.
31
+ * - Reduced motion, `data-motion="off"` or no IntersectionObserver → shown at once.
32
+ *
33
+ * Returns the props for the first render (spread them on the element).
34
+ */
35
+ export declare function useReveal<T extends HTMLElement>(ref: React.RefObject<T | null>, { once, rootMargin, threshold, stagger, disabled, onReveal }?: UseRevealOptions): {
36
+ readonly "data-reveal-state": "pending" | undefined;
37
+ };
38
+ interface RevealBaseProps extends React.HTMLAttributes<HTMLDivElement>, Omit<UseRevealOptions, "stagger"> {
39
+ /** fade · rise (default) · blur · scale · clip (wipes up from the bottom edge). */
40
+ effect?: RevealEffect;
41
+ /** Delay before it starts, in ms. */
42
+ delay?: number;
43
+ /** Override the duration, in ms. Default: the motion token for the level (`--motion-reveal`). */
44
+ duration?: number;
45
+ /** Render the single child instead of a `div` (keeps your `ul`, `section`, `Container`…). */
46
+ asChild?: boolean;
47
+ }
48
+ export type RevealProps = RevealBaseProps;
49
+ /**
50
+ * Shows its content when it scrolls into view — opacity and transform only, so nothing moves
51
+ * (CLS 0). Server-rendered content is never hidden before hydration; reduced motion and
52
+ * `data-motion="off"` show it at once.
53
+ *
54
+ * ```tsx
55
+ * <Reveal effect="rise" delay={80}><Card>…</Card></Reveal>
56
+ * ```
57
+ */
58
+ export declare const Reveal: React.ForwardRefExoticComponent<RevealBaseProps & React.RefAttributes<HTMLDivElement>>;
59
+ export interface StaggerProps extends RevealBaseProps {
60
+ /** Time between two children, in ms. Default: the level's `--motion-stagger` (60 ms subtle, 90 ms full). */
61
+ step?: number;
62
+ }
63
+ /**
64
+ * Reveals its direct children one after another (the 13th and later share the last slot, so a
65
+ * long list never keeps people waiting). Use `asChild` on a list to keep its semantics.
66
+ *
67
+ * ```tsx
68
+ * <Stagger asChild step={70}><ul className="grid grid-cols-3">{plans.map(…)}</ul></Stagger>
69
+ * ```
70
+ */
71
+ export declare const Stagger: React.ForwardRefExoticComponent<StaggerProps & React.RefAttributes<HTMLDivElement>>;
72
+ export {};
@@ -0,0 +1,133 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Slot } from "radix-ui";
5
+ import { cn } from "../lib/cn.js";
6
+ import { inViewport, ioSupported, motionAllowed, observe, transitionEnd } from "./core.js";
7
+ const noopSubscribe = () => () => { };
8
+ export const useIsoLayoutEffect = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
9
+ /**
10
+ * `true` when the component mounted on the client after hydration (a page switch, a tab, a
11
+ * replay); `false` when it was server-rendered and is being hydrated. Server-rendered content
12
+ * is already on screen, so it is never hidden again; fresh content may start hidden.
13
+ */
14
+ export function useFreshMount() {
15
+ const hydrating = React.useSyncExternalStore(noopSubscribe, () => false, () => true);
16
+ const [fresh] = React.useState(!hydrating);
17
+ return fresh;
18
+ }
19
+ /**
20
+ * Scroll-reveal state machine on any element: `data-reveal-state` goes
21
+ * `pending → in → done`. CSS (motion.css) draws the states, so the hook only flips an attribute.
22
+ *
23
+ * - Server-rendered content that is already in view stays as it is (no flash, CLS 0).
24
+ * - Server-rendered content below the fold is hidden just before paint and revealed on scroll.
25
+ * - Freshly mounted content starts hidden and reveals when it enters the viewport.
26
+ * - Reduced motion, `data-motion="off"` or no IntersectionObserver → shown at once.
27
+ *
28
+ * Returns the props for the first render (spread them on the element).
29
+ */
30
+ export function useReveal(ref, { once = true, rootMargin, threshold, stagger, disabled, onReveal } = {}) {
31
+ const fresh = useFreshMount();
32
+ const onRevealRef = React.useRef(onReveal);
33
+ onRevealRef.current = onReveal;
34
+ useIsoLayoutEffect(() => {
35
+ const el = ref.current;
36
+ if (!el)
37
+ return;
38
+ let timer;
39
+ let frame = 0;
40
+ const set = (s) => el.setAttribute("data-reveal-state", s);
41
+ const show = () => {
42
+ if (el.getAttribute("data-reveal-state") !== "pending")
43
+ return;
44
+ set("in");
45
+ onRevealRef.current?.();
46
+ clearTimeout(timer);
47
+ // After the transition, drop the reveal styles so the element's own transitions
48
+ // (hover, focus) are its own again.
49
+ timer = setTimeout(() => el.getAttribute("data-reveal-state") === "in" && set("done"), transitionEnd(el, stagger) + 60);
50
+ };
51
+ if (disabled || !motionAllowed(el)) {
52
+ set("done");
53
+ return;
54
+ }
55
+ if (!ioSupported()) {
56
+ if (fresh)
57
+ frame = requestAnimationFrame(() => show());
58
+ else
59
+ set("done");
60
+ return () => cancelAnimationFrame(frame);
61
+ }
62
+ if (!fresh && inViewport(el)) {
63
+ set("done");
64
+ if (once)
65
+ return;
66
+ }
67
+ else
68
+ set("pending");
69
+ const stop = observe(el, (entry) => {
70
+ if (entry.isIntersecting) {
71
+ show();
72
+ if (once)
73
+ stop();
74
+ }
75
+ else if (!once && entry.boundingClientRect.top > 0)
76
+ set("pending");
77
+ }, { rootMargin, threshold });
78
+ return () => {
79
+ stop();
80
+ clearTimeout(timer);
81
+ };
82
+ // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ }, [fresh, once, rootMargin, threshold, stagger, disabled]);
84
+ return { "data-reveal-state": fresh && !disabled ? "pending" : undefined };
85
+ }
86
+ function useComposedRef(...refs) {
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ return React.useCallback((node) => {
89
+ for (const r of refs) {
90
+ if (typeof r === "function")
91
+ r(node);
92
+ else if (r)
93
+ r.current = node;
94
+ }
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ }, refs);
97
+ }
98
+ const timing = (delay, duration, extra) => ({
99
+ ...(delay ? { "--vm-delay": `${delay}ms` } : null),
100
+ ...(duration ? { "--vm-duration": `${duration}ms` } : null),
101
+ ...extra,
102
+ });
103
+ /**
104
+ * Shows its content when it scrolls into view — opacity and transform only, so nothing moves
105
+ * (CLS 0). Server-rendered content is never hidden before hydration; reduced motion and
106
+ * `data-motion="off"` show it at once.
107
+ *
108
+ * ```tsx
109
+ * <Reveal effect="rise" delay={80}><Card>…</Card></Reveal>
110
+ * ```
111
+ */
112
+ export const Reveal = React.forwardRef(function Reveal({ effect = "rise", delay, duration, once, rootMargin, threshold, disabled, onReveal, asChild, className, style, ...props }, ref) {
113
+ const inner = React.useRef(null);
114
+ const setRef = useComposedRef(ref, inner);
115
+ const initial = useReveal(inner, { once, rootMargin, threshold, disabled, onReveal });
116
+ const Comp = asChild ? Slot.Root : "div";
117
+ return _jsx(Comp, { ref: setRef, "data-reveal": effect, ...initial, className: cn("vm-reveal", className), style: { ...timing(delay, duration), ...style }, ...props });
118
+ });
119
+ /**
120
+ * Reveals its direct children one after another (the 13th and later share the last slot, so a
121
+ * long list never keeps people waiting). Use `asChild` on a list to keep its semantics.
122
+ *
123
+ * ```tsx
124
+ * <Stagger asChild step={70}><ul className="grid grid-cols-3">{plans.map(…)}</ul></Stagger>
125
+ * ```
126
+ */
127
+ export const Stagger = React.forwardRef(function Stagger({ effect = "rise", step, delay, duration, once, rootMargin, threshold, disabled, onReveal, asChild, className, style, ...props }, ref) {
128
+ const inner = React.useRef(null);
129
+ const setRef = useComposedRef(ref, inner);
130
+ const initial = useReveal(inner, { once, rootMargin, threshold, disabled, onReveal, stagger: true });
131
+ const Comp = asChild ? Slot.Root : "div";
132
+ return (_jsx(Comp, { ref: setRef, "data-reveal": effect, "data-reveal-stagger": "", ...initial, className: cn("vm-stagger", className), style: { ...timing(delay, duration, step ? { "--vm-step": `${step}ms` } : undefined), ...style }, ...props }));
133
+ });
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ export interface UseScrollShrinkOptions {
3
+ /** Scroll distance in px after which the header counts as scrolled. Default 24. */
4
+ threshold?: number;
5
+ }
6
+ /**
7
+ * Sticky header shrink: sets `data-scrolled` on the element once the page has scrolled past
8
+ * `threshold` (passive listener, rAF-throttled, no re-render). Pair it with the `vm-shrink`
9
+ * class, which tightens the bar with transforms only, so the content below never moves (CLS 0).
10
+ * Returns whether it is scrolled, for anything else you want to switch.
11
+ *
12
+ * ```tsx
13
+ * const ref = React.useRef<HTMLElement>(null);
14
+ * useScrollShrink(ref);
15
+ * <header ref={ref} className="vm-shrink sticky top-0">…</header>
16
+ * ```
17
+ */
18
+ export declare function useScrollShrink(ref: React.RefObject<HTMLElement | null>, { threshold }?: UseScrollShrinkOptions): boolean;
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ /**
4
+ * Sticky header shrink: sets `data-scrolled` on the element once the page has scrolled past
5
+ * `threshold` (passive listener, rAF-throttled, no re-render). Pair it with the `vm-shrink`
6
+ * class, which tightens the bar with transforms only, so the content below never moves (CLS 0).
7
+ * Returns whether it is scrolled, for anything else you want to switch.
8
+ *
9
+ * ```tsx
10
+ * const ref = React.useRef<HTMLElement>(null);
11
+ * useScrollShrink(ref);
12
+ * <header ref={ref} className="vm-shrink sticky top-0">…</header>
13
+ * ```
14
+ */
15
+ export function useScrollShrink(ref, { threshold = 24 } = {}) {
16
+ const [scrolled, setScrolled] = React.useState(false);
17
+ React.useEffect(() => {
18
+ let raf = 0;
19
+ let last = null;
20
+ const update = () => {
21
+ raf = 0;
22
+ const next = window.scrollY > threshold;
23
+ if (next === last)
24
+ return;
25
+ last = next;
26
+ const el = ref.current;
27
+ if (el) {
28
+ if (next)
29
+ el.setAttribute("data-scrolled", "");
30
+ else
31
+ el.removeAttribute("data-scrolled");
32
+ }
33
+ setScrolled(next);
34
+ };
35
+ const onScroll = () => {
36
+ if (!raf)
37
+ raf = requestAnimationFrame(update);
38
+ };
39
+ update();
40
+ window.addEventListener("scroll", onScroll, { passive: true });
41
+ return () => {
42
+ cancelAnimationFrame(raf);
43
+ window.removeEventListener("scroll", onScroll);
44
+ };
45
+ }, [ref, threshold]);
46
+ return scrolled;
47
+ }
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ export interface SkeletonSwapProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ /** While true the skeleton shows; when it turns false the content cross-fades in over it. */
4
+ loading: boolean;
5
+ /** The placeholder. Give it the content's size so the box does not jump when it leaves. */
6
+ skeleton: React.ReactNode;
7
+ }
8
+ /**
9
+ * Skeleton → content without a flash: both share one grid cell, the content fades in while the
10
+ * skeleton fades out, then the skeleton unmounts. `aria-busy` while loading. Content that is
11
+ * there from the first render never animates; reduced motion swaps at once.
12
+ *
13
+ * ```tsx
14
+ * <SkeletonSwap loading={isLoading} skeleton={<InvoiceSkeleton />}><InvoiceTable rows={rows} /></SkeletonSwap>
15
+ * ```
16
+ */
17
+ export declare const SkeletonSwap: React.ForwardRefExoticComponent<SkeletonSwapProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { motionAllowed } from "./core.js";
6
+ import { useIsoLayoutEffect } from "./reveal.js";
7
+ /**
8
+ * Skeleton → content without a flash: both share one grid cell, the content fades in while the
9
+ * skeleton fades out, then the skeleton unmounts. `aria-busy` while loading. Content that is
10
+ * there from the first render never animates; reduced motion swaps at once.
11
+ *
12
+ * ```tsx
13
+ * <SkeletonSwap loading={isLoading} skeleton={<InvoiceSkeleton />}><InvoiceTable rows={rows} /></SkeletonSwap>
14
+ * ```
15
+ */
16
+ export const SkeletonSwap = React.forwardRef(function SkeletonSwap({ loading, skeleton, className, children, ...props }, ref) {
17
+ const inner = React.useRef(null);
18
+ const [prevLoading, setPrevLoading] = React.useState(loading);
19
+ const [leaving, setLeaving] = React.useState(false);
20
+ // Derived from the previous prop (React's "adjust state while rendering" pattern): the frame
21
+ // that flips loading → false already renders both layers, so nothing blinks.
22
+ if (prevLoading !== loading) {
23
+ setPrevLoading(loading);
24
+ setLeaving(!loading);
25
+ }
26
+ useIsoLayoutEffect(() => {
27
+ if (!leaving)
28
+ return;
29
+ if (!motionAllowed(inner.current)) {
30
+ setLeaving(false);
31
+ return;
32
+ }
33
+ const t = setTimeout(() => setLeaving(false), 700); // safety net if animationend never fires
34
+ return () => clearTimeout(t);
35
+ }, [leaving]);
36
+ return (_jsxs("div", { ref: (node) => {
37
+ inner.current = node;
38
+ if (typeof ref === "function")
39
+ ref(node);
40
+ else if (ref)
41
+ ref.current = node;
42
+ }, "aria-busy": loading || undefined, "data-loading": loading ? "" : undefined, className: cn("vm-swap", className), ...props, children: [loading ? null : (_jsx("div", { className: "vm-swap-content", "data-entering": leaving ? "" : undefined, children: children })), loading || leaving ? (_jsx("div", { className: "vm-swap-skeleton", "aria-hidden": !loading || undefined, "data-leaving": loading ? undefined : "", onAnimationEnd: (e) => e.target === e.currentTarget && setLeaving(false), children: skeleton })) : null] }));
43
+ });
@@ -0,0 +1,30 @@
1
+ import * as React from "react";
2
+ import { type UseRevealOptions } from "./reveal.js";
3
+ type TextTag = "h1" | "h2" | "h3" | "h4" | "p" | "span" | "div" | "blockquote";
4
+ export interface TextRevealProps extends Omit<React.HTMLAttributes<HTMLElement>, "children">, Omit<UseRevealOptions, "stagger"> {
5
+ /** The text. In `by="line"` mode, `\n` separates lines. */
6
+ children: string;
7
+ /** Element to render. Default `"h2"`. */
8
+ as?: TextTag;
9
+ /** Split into words (default) or lines. */
10
+ by?: "word" | "line";
11
+ /**
12
+ * mask: each unit rises from behind its own baseline (default) · fade · blur.
13
+ */
14
+ effect?: "mask" | "fade" | "blur";
15
+ /** ms between two units. Default 45 (words) / 110 (lines). */
16
+ step?: number;
17
+ /** Delay before the first unit, in ms. */
18
+ delay?: number;
19
+ }
20
+ /**
21
+ * A heading that reveals word by word (or line by line) as it scrolls into view. Assistive
22
+ * tech gets one plain string: the split copy is `aria-hidden`, the real text is a visually
23
+ * hidden twin. Transform and opacity only; reduced motion shows it at once.
24
+ *
25
+ * ```tsx
26
+ * <TextReveal as="h2">Small rooms, slow mornings, nothing to scroll.</TextReveal>
27
+ * ```
28
+ */
29
+ export declare const TextReveal: React.ForwardRefExoticComponent<TextRevealProps & React.RefAttributes<HTMLElement>>;
30
+ export {};
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { useReveal } from "./reveal.js";
6
+ /**
7
+ * A heading that reveals word by word (or line by line) as it scrolls into view. Assistive
8
+ * tech gets one plain string: the split copy is `aria-hidden`, the real text is a visually
9
+ * hidden twin. Transform and opacity only; reduced motion shows it at once.
10
+ *
11
+ * ```tsx
12
+ * <TextReveal as="h2">Small rooms, slow mornings, nothing to scroll.</TextReveal>
13
+ * ```
14
+ */
15
+ export const TextReveal = React.forwardRef(function TextReveal({ children, as: Tag = "h2", by = "word", effect = "mask", step, delay, once, rootMargin, threshold, disabled, onReveal, className, style, ...props }, ref) {
16
+ const inner = React.useRef(null);
17
+ const initial = useReveal(inner, { once, rootMargin, threshold, disabled, onReveal });
18
+ const text = String(children);
19
+ const units = by === "line" ? text.split("\n") : text.split(/\s+/).filter(Boolean);
20
+ const gap = step ?? (by === "line" ? 110 : 45);
21
+ const Comp = Tag;
22
+ return (_jsxs(Comp, { ref: (node) => {
23
+ inner.current = node;
24
+ if (typeof ref === "function")
25
+ ref(node);
26
+ else if (ref)
27
+ ref.current = node;
28
+ }, "data-reveal": effect === "mask" ? "text" : effect, "data-text-reveal": by, ...initial, className: cn("vm-text-reveal", className), style: { "--vm-step": `${gap}ms`, ...(delay ? { "--vm-delay": `${delay}ms` } : null), ...style }, ...props, children: [_jsx("span", { className: "sr-only", children: by === "line" ? units.join(" ") : text }), _jsx("span", { "aria-hidden": "true", className: "vm-text-reveal-visual", children: units.map((u, i) => (_jsxs(React.Fragment, { children: [_jsx("span", { className: "vm-tr-mask", children: _jsx("span", { className: "vm-tr-unit", style: { "--vm-i": i }, children: u }) }), by === "word" && i < units.length - 1 ? " " : null] }, i))) })] }));
29
+ });
@@ -0,0 +1,35 @@
1
+ import * as React from "react";
2
+ export interface TickerProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ /** Seconds for one full loop. Default 40. */
4
+ duration?: number;
5
+ /** Which way the strip travels (mirrored in RTL). Default `"start"`. */
6
+ direction?: "start" | "end";
7
+ /** Element for each copy of the items, e.g. `"ul"` when the children are `li`. Default `"div"`. */
8
+ as?: "div" | "ul" | "ol";
9
+ /** Space between items (any CSS length). Default `clamp(2.5rem, 6cqi, 4.5rem)`. */
10
+ gap?: string;
11
+ /** Fade the start and end edges. Default true. */
12
+ fade?: boolean;
13
+ /**
14
+ * A small pause control (a native checkbox — works without JavaScript). Moving content that
15
+ * lasts longer than five seconds needs one (WCAG 2.2.2). Default true.
16
+ */
17
+ controls?: boolean;
18
+ /** Accessible name of the pause control. Default `"Pause animation"`. */
19
+ pauseLabel?: string;
20
+ /** Names the list of items (it lands on the first copy, e.g. the `ul`). */
21
+ "aria-label"?: string;
22
+ /** Classes for each copy of the items. */
23
+ groupClassName?: string;
24
+ }
25
+ /**
26
+ * The motion kit's marquee: an endless strip of logos or words that follows the motion level. Pure CSS (server component): the items are rendered
27
+ * twice and the track slides by one copy. The copy is `aria-hidden` and `inert`, so assistive
28
+ * tech and the keyboard meet every item once. Pauses on hover and focus and with its pause
29
+ * control; under reduced motion or `data-motion="off"` it becomes a static, wrapped row.
30
+ *
31
+ * ```tsx
32
+ * <Ticker as="ul" duration={30}>{logos.map((l) => <li key={l.name}>…</li>)}</Ticker>
33
+ * ```
34
+ */
35
+ export declare const Ticker: React.ForwardRefExoticComponent<TickerProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ /**
5
+ * The motion kit's marquee: an endless strip of logos or words that follows the motion level. Pure CSS (server component): the items are rendered
6
+ * twice and the track slides by one copy. The copy is `aria-hidden` and `inert`, so assistive
7
+ * tech and the keyboard meet every item once. Pauses on hover and focus and with its pause
8
+ * control; under reduced motion or `data-motion="off"` it becomes a static, wrapped row.
9
+ *
10
+ * ```tsx
11
+ * <Ticker as="ul" duration={30}>{logos.map((l) => <li key={l.name}>…</li>)}</Ticker>
12
+ * ```
13
+ */
14
+ export const Ticker = React.forwardRef(function Ticker({ duration = 40, direction = "start", as: Group = "div", gap, fade = true, controls = true, pauseLabel = "Pause animation", groupClassName, className, style, children, "aria-label": ariaLabel, ...props }, ref) {
15
+ return (_jsxs("div", { ref: ref, "data-direction": direction, "data-fade": fade ? "" : undefined, className: cn("vm-marquee", className), style: { "--vm-marquee-duration": `${duration}s`, ...(gap ? { "--vm-marquee-gap": gap } : null), ...style }, ...props, children: [_jsx("div", { className: "vm-marquee-viewport", children: _jsxs("div", { className: "vm-marquee-track", children: [_jsx(Group, { className: cn("vm-marquee-group", groupClassName), "aria-label": ariaLabel, children: children }), _jsx(Group, { className: cn("vm-marquee-group", groupClassName), "aria-hidden": "true", inert: true, "data-marquee-copy": "", children: children })] }) }), controls ? (_jsxs("label", { className: "vm-marquee-toggle", children: [_jsx("input", { type: "checkbox", className: "vm-marquee-pause sr-only", "aria-label": pauseLabel }), _jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", className: "vm-marquee-icon-pause", children: [_jsx("rect", { x: "4", y: "3", width: "2.5", height: "10", rx: "0.5" }), _jsx("rect", { x: "9.5", y: "3", width: "2.5", height: "10", rx: "0.5" })] }), _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", className: "vm-marquee-icon-play", children: _jsx("path", { d: "M5 3.2v9.6a.5.5 0 0 0 .77.42l7.2-4.8a.5.5 0 0 0 0-.84l-7.2-4.8A.5.5 0 0 0 5 3.2Z" }) })] })) : null] }));
16
+ });
@@ -83,6 +83,10 @@ export interface SortableTreeProps<N extends SortableTreeNode> {
83
83
  moveMenu?: boolean;
84
84
  /** Shown when `nodes` is empty. */
85
85
  empty?: React.ReactNode;
86
+ /** Id of the selected row (`aria-selected`). Only used together with `onSelect`. */
87
+ selected?: string | null;
88
+ /** Click or Enter on a row selects it (Space still picks it up for a keyboard move). */
89
+ onSelect?: (node: N) => void;
86
90
  labels?: Partial<SortableTreeLabels>;
87
91
  "aria-label"?: string;
88
92
  "aria-labelledby"?: string;
@@ -110,7 +110,7 @@ const allParents = (nodes) => {
110
110
  * ```
111
111
  */
112
112
  export function SortableTree(props) {
113
- const { nodes: nodesProp, onMove, getLabel = defaultLabel, maxDepth, canDrop, disabled, expanded: expandedProp, defaultExpanded, onExpandedChange, indent = 24, guides = true, size = "md", renderItem, renderActions, moveMenu = true, empty, labels, className, ...aria } = props;
113
+ const { nodes: nodesProp, onMove, getLabel = defaultLabel, maxDepth, canDrop, disabled, expanded: expandedProp, defaultExpanded, onExpandedChange, indent = 24, guides = true, size = "md", renderItem, renderActions, moveMenu = true, empty, selected, onSelect, labels, className, ...aria } = props;
114
114
  const t = useLabels("sortableTree", sortableTreeLabels, labels);
115
115
  const { dir } = useLocale();
116
116
  const reducedMotion = useReducedMotion();
@@ -325,6 +325,11 @@ export function SortableTree(props) {
325
325
  announce(t("invalid", { item: labelOf(f.id), reason: op.ok }));
326
326
  return;
327
327
  }
328
+ if (e.key === "Enter" && onSelect && !e.altKey && !e.ctrlKey && !e.metaKey) {
329
+ e.preventDefault();
330
+ onSelect(f.node);
331
+ return;
332
+ }
328
333
  if ((e.shiftKey && e.key === "F10") || e.key === "ContextMenu") {
329
334
  e.preventDefault();
330
335
  setMenuId(f.id);
@@ -388,7 +393,7 @@ export function SortableTree(props) {
388
393
  const me = info.get(f.id);
389
394
  const isActive = f.id === activeId;
390
395
  const depth = isActive && projection ? projection.depth : f.depth;
391
- return (_jsx(TreeRow, { item: f, depth: depth, info: me, t: t, label: labelOf(f.id), expanded: openSet.has(f.id), onToggle: () => setOpen(f.id, !openSet.has(f.id)), tabbable: tabId === f.id, onFocus: () => setFocusedId(f.id), onKeyDown: onRowKeyDown, rowRef: (el) => {
396
+ return (_jsx(TreeRow, { item: f, depth: depth, info: me, t: t, label: labelOf(f.id), expanded: openSet.has(f.id), onToggle: () => setOpen(f.id, !openSet.has(f.id)), tabbable: tabId === f.id, onFocus: () => setFocusedId(f.id), onKeyDown: onRowKeyDown, selected: onSelect ? selected === f.id : undefined, onSelect: onSelect ? () => onSelect(f.node) : undefined, rowRef: (el) => {
392
397
  if (el)
393
398
  rowRefs.current.set(f.id, el);
394
399
  else
@@ -434,10 +439,17 @@ function TreeRow(p) {
434
439
  s.setNodeRef(el);
435
440
  s.setActivatorNodeRef(el);
436
441
  p.rowRef(el);
437
- }, role: "treeitem", "aria-level": item.depth + 1, "aria-setsize": info.siblings, "aria-posinset": info.index + 1, "aria-expanded": hasKids ? expanded : undefined, "aria-labelledby": labelId, "aria-disabled": disabled || undefined, tabIndex: tabbable ? 0 : -1, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown Alt+ArrowLeft Alt+ArrowRight Shift+F10", ...attrs, ...(disabled ? {} : pointerListeners), onKeyDown: (e) => p.onKeyDown(e, item, disabled ? undefined : dndKeyDown), onFocus: (e) => {
442
+ }, role: "treeitem", "aria-level": item.depth + 1, "aria-setsize": info.siblings, "aria-posinset": info.index + 1, "aria-expanded": hasKids ? expanded : undefined, "aria-labelledby": labelId, "aria-disabled": disabled || undefined, "aria-selected": p.selected, tabIndex: tabbable ? 0 : -1, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown Alt+ArrowLeft Alt+ArrowRight Shift+F10", ...attrs, ...(disabled ? {} : pointerListeners), onKeyDown: (e) => p.onKeyDown(e, item, disabled ? undefined : dndKeyDown), onFocus: (e) => {
438
443
  if (e.target === e.currentTarget)
439
444
  p.onFocus();
440
- }, "data-slot": "sortable-tree-item", "data-depth": depth, "data-dragging": s.isDragging ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-disabled": disabled ? "" : undefined, style: { transform: CSS.Translate.toString(s.transform), transition: reducedMotion ? undefined : s.transition, paddingInlineStart: depth * indent, "--depth-px": `${depth * indent}px` }, className: cn("vui-tree-row group/row relative flex min-w-0 items-center gap-1 rounded-ui-sm pe-1 text-fg outline-none", "transition-colors hover:bg-hover focus-visible:bg-hover focus-visible:shadow-[inset_0_0_0_2px_var(--ring)]", size === "sm" ? "h-8 text-[13px]" : "h-9 text-sm", !disabled && "cursor-grab active:cursor-grabbing"), children: [guides && !s.isDragging
445
+ }, onClick: p.onSelect
446
+ ? (e) => {
447
+ // The expand toggle, row actions and the Move menu keep their own click.
448
+ if (e.target.closest('button, a, input, [data-slot="sortable-tree-toggle"]'))
449
+ return;
450
+ p.onSelect?.();
451
+ }
452
+ : undefined, "data-slot": "sortable-tree-item", "data-selected": p.selected ? "" : undefined, "data-depth": depth, "data-dragging": s.isDragging ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-disabled": disabled ? "" : undefined, style: { transform: CSS.Translate.toString(s.transform), transition: reducedMotion ? undefined : s.transition, paddingInlineStart: depth * indent, "--depth-px": `${depth * indent}px` }, className: cn("vui-tree-row group/row relative flex min-w-0 items-center gap-1 rounded-ui-sm pe-1 text-fg outline-none", "transition-colors hover:bg-hover focus-visible:bg-hover focus-visible:shadow-[inset_0_0_0_2px_var(--ring)]", size === "sm" ? "h-8 text-[13px]" : "h-9 text-sm", !disabled && "cursor-grab active:cursor-grabbing"), children: [guides && !s.isDragging
441
453
  ? Array.from({ length: depth }, (_, l) => (_jsx("span", { "aria-hidden": true, "data-slot": "sortable-tree-guide", className: "pointer-events-none absolute inset-y-0 w-px bg-line", style: { insetInlineStart: l * indent + 12 } }, l)))
442
454
  : null, s.isDragging ? _jsx("span", { "aria-hidden": true, "data-slot": "sortable-tree-indicator", className: "vui-tree-indicator" }) : null, _jsx("span", { className: "inline-flex size-6 shrink-0 items-center justify-center", children: hasKids ? (_jsx("span", { "aria-hidden": true, "data-slot": "sortable-tree-toggle", onClick: onToggle, onMouseDown: (e) => e.stopPropagation(), title: expanded ? t("collapse") : t("expand"), className: "inline-flex size-6 cursor-pointer items-center justify-center rounded-ui-sm text-fg-subtle hover:bg-hover hover:text-fg", children: _jsx(ChevronRight, { className: cn("size-3.5 transition-transform motion-reduce:transition-none rtl:-scale-x-100", expanded && "rotate-90 rtl:rotate-90") }) })) : null }), disabled ? (_jsx("span", { "aria-hidden": true, className: "w-4 shrink-0" })) : (_jsx(GripVertical, { "aria-hidden": true, className: "size-4 shrink-0 text-fg-subtle opacity-50 transition-opacity group-hover/row:opacity-100 group-focus-visible/row:opacity-100" })), _jsx("span", { id: labelId, className: "flex min-w-0 flex-1 items-center gap-2 ps-1", children: p.renderItem ? p.renderItem(item.node, { depth, isDragging: s.isDragging, overlay: false, expanded, childCount }) : _jsx("span", { className: "truncate", children: label }) }), p.actions, p.menu] }));
443
455
  }
@@ -259,7 +259,7 @@
259
259
  }
260
260
  [data-density="compact"] .vb-navbar,
261
261
  .vb-navbar[data-density="compact"] { --vb-nav-h: 3.75rem; }
262
- [data-material="glass"] .vb-navbar { --vb-nav-fill: color-mix(in oklch, var(--scene) 72%, transparent); }
262
+ [data-material="glass"] .vb-navbar { --vb-nav-fill: color-mix(in oklch, var(--scene) 90%, transparent); }
263
263
  .vb-navbar[data-variant="rail"],
264
264
  .vb-navbar[data-variant="minimal"] { border-block-end-color: transparent; }
265
265
  .vb-navbar-row { min-height: var(--vb-nav-h); }
@@ -540,8 +540,8 @@
540
540
  }
541
541
  .vb-navbar[data-sticky]:not([data-overlay]) { background-color: var(--scene); }
542
542
  [data-material="glass"] .vb-navbar[data-sticky] {
543
- -webkit-backdrop-filter: blur(var(--blur, 0px)) saturate(160%);
544
- backdrop-filter: blur(var(--blur, 0px)) saturate(160%);
543
+ -webkit-backdrop-filter: blur(max(var(--blur, 0px), 14px)) saturate(160%);
544
+ backdrop-filter: blur(max(var(--blur, 0px), 14px)) saturate(160%);
545
545
  }
546
546
  }
547
547
  @supports not (animation-timeline: scroll()) {