@vamos-ui/react 0.2.0 → 0.4.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 (166) hide show
  1. package/dist/blocks/block-image.js +1 -1
  2. package/dist/blocks/block-link.js +1 -1
  3. package/dist/blocks/index.d.ts +2 -2
  4. package/dist/blocks/index.js +2 -2
  5. package/dist/blocks/site/_kit-1.d.ts +1 -0
  6. package/dist/blocks/site/_kit-1.js +3 -1
  7. package/dist/blocks/site/announcement-bar.js +1 -1
  8. package/dist/blocks/site/article-layout.d.ts +1 -0
  9. package/dist/blocks/site/article-layout.js +2 -1
  10. package/dist/blocks/site/blog-list.d.ts +1 -0
  11. package/dist/blocks/site/blog-list.js +3 -2
  12. package/dist/blocks/site/contact-dock.js +3 -1
  13. package/dist/blocks/site/contact-form.js +1 -1
  14. package/dist/blocks/site/cta.d.ts +2 -2
  15. package/dist/blocks/site/cta.js +8 -5
  16. package/dist/blocks/site/faq.js +2 -1
  17. package/dist/blocks/site/feature-grid.d.ts +1 -0
  18. package/dist/blocks/site/feature-grid.js +7 -5
  19. package/dist/blocks/site/footer.d.ts +1 -1
  20. package/dist/blocks/site/footer.js +8 -7
  21. package/dist/blocks/site/gallery-section.d.ts +6 -1
  22. package/dist/blocks/site/gallery-section.js +6 -4
  23. package/dist/blocks/site/hero.d.ts +3 -2
  24. package/dist/blocks/site/hero.js +14 -6
  25. package/dist/blocks/site/logo-strip.d.ts +6 -3
  26. package/dist/blocks/site/logo-strip.js +8 -7
  27. package/dist/blocks/site/map-hours.d.ts +2 -2
  28. package/dist/blocks/site/map-hours.js +4 -2
  29. package/dist/blocks/site/media-text.d.ts +4 -1
  30. package/dist/blocks/site/media-text.js +6 -4
  31. package/dist/blocks/site/menu-list.d.ts +7 -3
  32. package/dist/blocks/site/menu-list.js +34 -7
  33. package/dist/blocks/site/navbar.d.ts +1 -0
  34. package/dist/blocks/site/navbar.js +6 -5
  35. package/dist/blocks/site/not-found.js +2 -1
  36. package/dist/blocks/site/page-intro.d.ts +1 -0
  37. package/dist/blocks/site/page-intro.js +2 -1
  38. package/dist/blocks/site/pricing-table.js +2 -1
  39. package/dist/blocks/site/process.d.ts +1 -0
  40. package/dist/blocks/site/process.js +4 -2
  41. package/dist/blocks/site/registry-1.d.ts +1 -1
  42. package/dist/blocks/site/registry-2.d.ts +5 -0
  43. package/dist/blocks/site/registry-2.js +2 -2
  44. package/dist/blocks/site/rich-text-section.js +2 -1
  45. package/dist/blocks/site/shared/heading-lines.d.ts +9 -0
  46. package/dist/blocks/site/shared/heading-lines.js +32 -0
  47. package/dist/blocks/site/shared/image-sizes.d.ts +25 -0
  48. package/dist/blocks/site/shared/image-sizes.js +37 -0
  49. package/dist/blocks/site/shared/island-keys.d.ts +1 -1
  50. package/dist/blocks/site/shared/island-keys.js +1 -1
  51. package/dist/blocks/site/shared/labels-2.d.ts +9 -1
  52. package/dist/blocks/site/shared/labels-2.js +24 -6
  53. package/dist/blocks/site/shared/open-now.d.ts +1 -1
  54. package/dist/blocks/site/shared/open-now.js +1 -1
  55. package/dist/blocks/site/shared/opening-hours.d.ts +18 -2
  56. package/dist/blocks/site/shared/opening-hours.js +36 -7
  57. package/dist/blocks/site/shared/tap.d.ts +9 -0
  58. package/dist/blocks/site/shared/tap.js +16 -0
  59. package/dist/blocks/site/site-shell.d.ts +11 -0
  60. package/dist/blocks/site/site-shell.js +43 -16
  61. package/dist/blocks/site/stats-band.d.ts +3 -1
  62. package/dist/blocks/site/stats-band.js +11 -7
  63. package/dist/blocks/site/team.d.ts +1 -1
  64. package/dist/blocks/site/team.js +4 -3
  65. package/dist/blocks/site/testimonials.d.ts +2 -2
  66. package/dist/blocks/site/testimonials.js +5 -4
  67. package/dist/business/index.d.ts +1 -0
  68. package/dist/business/index.js +2 -0
  69. package/dist/business/signer-status.d.ts +53 -0
  70. package/dist/business/signer-status.js +76 -0
  71. package/dist/charts/bar-chart.js +6 -2
  72. package/dist/charts/chart-shell.d.ts +9 -2
  73. package/dist/charts/chart-shell.js +10 -4
  74. package/dist/charts/chart.js +1 -1
  75. package/dist/charts/combo-chart.js +5 -7
  76. package/dist/charts/utils.d.ts +10 -0
  77. package/dist/charts/utils.js +46 -0
  78. package/dist/components/_qr-core.d.ts +26 -0
  79. package/dist/components/_qr-core.js +376 -0
  80. package/dist/components/activity-timeline.js +5 -5
  81. package/dist/components/aspect-ratio.d.ts +26 -0
  82. package/dist/components/aspect-ratio.js +42 -0
  83. package/dist/components/backup-codes.d.ts +42 -0
  84. package/dist/components/backup-codes.js +67 -0
  85. package/dist/components/breadcrumb.d.ts +2 -2
  86. package/dist/components/breadcrumb.js +1 -1
  87. package/dist/components/carousel.d.ts +12 -3
  88. package/dist/components/carousel.js +8 -5
  89. package/dist/components/collapsible.d.ts +36 -0
  90. package/dist/components/collapsible.js +37 -0
  91. package/dist/components/command.d.ts +8 -2
  92. package/dist/components/command.js +7 -2
  93. package/dist/components/image.d.ts +18 -2
  94. package/dist/components/image.js +12 -5
  95. package/dist/components/input.d.ts +1 -1
  96. package/dist/components/input.js +1 -1
  97. package/dist/components/menubar.d.ts +96 -0
  98. package/dist/components/menubar.js +123 -0
  99. package/dist/components/pin-map.d.ts +58 -0
  100. package/dist/components/pin-map.js +167 -0
  101. package/dist/components/placeholder-art.d.ts +51 -0
  102. package/dist/components/placeholder-art.js +82 -0
  103. package/dist/components/qr-code.d.ts +36 -0
  104. package/dist/components/qr-code.js +27 -0
  105. package/dist/components/record-detail-layout.d.ts +2 -2
  106. package/dist/components/record-detail-layout.js +1 -1
  107. package/dist/components/sidebar.js +8 -8
  108. package/dist/components/sla-timer.d.ts +75 -0
  109. package/dist/components/sla-timer.js +142 -0
  110. package/dist/components/slug-input.js +1 -1
  111. package/dist/components/tour.d.ts +60 -0
  112. package/dist/components/tour.js +139 -0
  113. package/dist/index.d.ts +9 -0
  114. package/dist/index.js +9 -0
  115. package/dist/lib/public.d.ts +1 -0
  116. package/dist/lib/public.js +1 -0
  117. package/dist/lib/value-format.js +1 -1
  118. package/dist/locale-data/core/holidays.d.ts +1 -1
  119. package/dist/locale-data/core/holidays.js +1 -1
  120. package/dist/locales/ar.d.ts +2087 -0
  121. package/dist/locales/ar.js +2088 -0
  122. package/dist/locales/de.d.ts +2087 -0
  123. package/dist/locales/de.js +2088 -0
  124. package/dist/locales/en.d.ts +1 -0
  125. package/dist/locales/en.js +1 -0
  126. package/dist/locales/es.d.ts +2087 -0
  127. package/dist/locales/es.js +2087 -0
  128. package/dist/locales/fr.d.ts +2087 -0
  129. package/dist/locales/fr.js +2088 -0
  130. package/dist/locales/tr.d.ts +1 -0
  131. package/dist/locales/tr.js +1 -0
  132. package/dist/media/gallery.js +3 -1
  133. package/dist/media/media-library.js +1 -1
  134. package/dist/motion/core.d.ts +32 -0
  135. package/dist/motion/core.js +77 -0
  136. package/dist/motion/count-up.d.ts +29 -0
  137. package/dist/motion/count-up.js +92 -0
  138. package/dist/motion/index.d.ts +11 -0
  139. package/dist/motion/index.js +13 -0
  140. package/dist/motion/motion-scope.d.ts +17 -0
  141. package/dist/motion/motion-scope.js +16 -0
  142. package/dist/motion/page-transition.d.ts +33 -0
  143. package/dist/motion/page-transition.js +61 -0
  144. package/dist/motion/parallax.d.ts +37 -0
  145. package/dist/motion/parallax.js +90 -0
  146. package/dist/motion/pointer.d.ts +38 -0
  147. package/dist/motion/pointer.js +117 -0
  148. package/dist/motion/reveal.d.ts +72 -0
  149. package/dist/motion/reveal.js +133 -0
  150. package/dist/motion/scroll-shrink.d.ts +18 -0
  151. package/dist/motion/scroll-shrink.js +47 -0
  152. package/dist/motion/skeleton-swap.d.ts +17 -0
  153. package/dist/motion/skeleton-swap.js +43 -0
  154. package/dist/motion/text-reveal.d.ts +32 -0
  155. package/dist/motion/text-reveal.js +31 -0
  156. package/dist/motion/ticker.d.ts +35 -0
  157. package/dist/motion/ticker.js +16 -0
  158. package/dist/render/block-renderer.js +1 -1
  159. package/dist/sortable/sortable-tree.d.ts +4 -0
  160. package/dist/sortable/sortable-tree.js +16 -4
  161. package/dist/styles/blocks.css +21 -12
  162. package/dist/styles/editors.css +2 -1
  163. package/dist/styles/media.css +31 -5
  164. package/dist/styles/motion.css +389 -0
  165. package/dist/styles.css +41 -10
  166. package/package.json +5 -1
@@ -0,0 +1,142 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cva } from "class-variance-authority";
5
+ import { AlarmClock, CircleCheck, CircleX, Pause, Timer } from "lucide-react";
6
+ import { cn } from "../lib/cn.js";
7
+ import { useLabels } from "../lib/labels.js";
8
+ import { useFormat } from "../lib/locale.js";
9
+ export const slaTimerLabels = {
10
+ dueIn: "Due in {time}",
11
+ overdue: "Overdue by {time}",
12
+ met: "Met with {time} to spare",
13
+ missed: "Missed by {time}",
14
+ paused: "Paused with {time} left",
15
+ pausedOverdue: "Paused, {time} over",
16
+ stateOnTrack: "On track",
17
+ stateAtRisk: "At risk",
18
+ stateBreached: "Breached",
19
+ stateMet: "Met",
20
+ stateMissed: "Missed",
21
+ statePaused: "Paused",
22
+ };
23
+ const ms = (v) => (v instanceof Date ? v.getTime() : typeof v === "number" ? v : Date.parse(v));
24
+ /**
25
+ * Pure SLA state: `on-track`, `at-risk` (less than `warnAt` of the window or `warnMinutes`
26
+ * left), `breached`, and the final `met` / `missed` once `completedAt` is set.
27
+ */
28
+ export function slaState({ due, start, completedAt, pausedAt, now, warnAt = 0.25, warnMinutes }) {
29
+ const dueMs = ms(due);
30
+ const startMs = start != null ? ms(start) : null;
31
+ const end = completedAt != null ? ms(completedAt) : pausedAt != null ? ms(pausedAt) : now;
32
+ const remaining = dueMs - end;
33
+ const used = startMs != null && dueMs > startMs ? (end - startMs) / (dueMs - startMs) : null;
34
+ if (completedAt != null)
35
+ return { state: remaining >= 0 ? "met" : "missed", remaining, used };
36
+ if (pausedAt != null)
37
+ return { state: "paused", remaining, used };
38
+ if (remaining < 0)
39
+ return { state: "breached", remaining, used };
40
+ const threshold = warnMinutes != null ? warnMinutes * 60_000 : startMs != null ? (dueMs - startMs) * warnAt : 30 * 60_000;
41
+ return { state: remaining <= threshold ? "at-risk" : "on-track", remaining, used };
42
+ }
43
+ const slaTimerVariants = cva("inline-flex items-center gap-1.5 whitespace-nowrap font-medium tabular", {
44
+ variants: {
45
+ variant: {
46
+ /** Tinted pill — list rows, inbox items. */
47
+ badge: "h-6 rounded-full px-2 text-xs [&_svg]:size-3.5",
48
+ /** Text with an icon — headers, side panels. */
49
+ inline: "text-[13px] [&_svg]:size-4",
50
+ },
51
+ state: {
52
+ "on-track": "",
53
+ "at-risk": "",
54
+ breached: "",
55
+ met: "",
56
+ missed: "",
57
+ paused: "",
58
+ },
59
+ },
60
+ compoundVariants: [
61
+ { variant: "badge", state: "on-track", class: "bg-selected text-fg-muted" },
62
+ { variant: "badge", state: "at-risk", class: "tone-warning tone-soft" },
63
+ { variant: "badge", state: "breached", class: "tone-danger tone-soft" },
64
+ { variant: "badge", state: "met", class: "tone-success tone-soft" },
65
+ { variant: "badge", state: "missed", class: "tone-danger tone-soft" },
66
+ { variant: "badge", state: "paused", class: "border border-dashed border-field-border text-fg-muted" },
67
+ { variant: "inline", state: "on-track", class: "text-fg" },
68
+ { variant: "inline", state: "at-risk", class: "tone-warning [color:color-mix(in_srgb,var(--tone)_var(--tone-ink-mix,100%),var(--fg))]" },
69
+ { variant: "inline", state: "breached", class: "tone-danger [color:color-mix(in_srgb,var(--tone)_var(--tone-ink-mix,100%),var(--fg))]" },
70
+ { variant: "inline", state: "met", class: "tone-success [color:color-mix(in_srgb,var(--tone)_var(--tone-ink-mix,100%),var(--fg))]" },
71
+ { variant: "inline", state: "missed", class: "tone-danger [color:color-mix(in_srgb,var(--tone)_var(--tone-ink-mix,100%),var(--fg))]" },
72
+ { variant: "inline", state: "paused", class: "text-fg-muted" },
73
+ ],
74
+ defaultVariants: { variant: "badge", state: "on-track" },
75
+ });
76
+ const icons = {
77
+ "on-track": Timer,
78
+ "at-risk": AlarmClock,
79
+ breached: AlarmClock,
80
+ met: CircleCheck,
81
+ missed: CircleX,
82
+ paused: Pause,
83
+ };
84
+ const barColor = {
85
+ "on-track": "bg-accent",
86
+ "at-risk": "bg-warning",
87
+ breached: "bg-danger",
88
+ met: "bg-success",
89
+ missed: "bg-danger",
90
+ paused: "bg-fg-subtle",
91
+ };
92
+ /** Ticks once per second near the deadline, every 15 s otherwise; static when `now` is passed. */
93
+ function useNow(fixed, remaining, live) {
94
+ const [now, setNow] = React.useState(() => fixed ?? Date.now());
95
+ React.useEffect(() => {
96
+ if (fixed != null || !live)
97
+ return;
98
+ let id = 0;
99
+ const tick = () => {
100
+ setNow(Date.now());
101
+ const left = Math.abs(remaining());
102
+ id = window.setTimeout(tick, left < 3_600_000 ? 1000 - (Date.now() % 1000) : 15_000);
103
+ };
104
+ tick();
105
+ return () => window.clearTimeout(id);
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ }, [fixed, live]);
108
+ return fixed ?? now;
109
+ }
110
+ /**
111
+ * Countdown to a service-level deadline — first response, resolution, delivery window.
112
+ * Colour follows the state (on track → at risk → breached), and it freezes as met/missed
113
+ * once `completedAt` is set. `role="timer"` with a full sentence for screen readers.
114
+ *
115
+ * ```tsx
116
+ * <SlaTimer label="First response" start={ticket.createdAt} due={ticket.firstResponseDue} />
117
+ * <SlaTimer variant="bar" label="Resolution" start={t.createdAt} due={t.resolveDue} completedAt={t.solvedAt} />
118
+ * ```
119
+ */
120
+ export const SlaTimer = React.forwardRef(function SlaTimer({ due, start, completedAt, pausedAt, now: fixedNow, warnAt, warnMinutes, variant = "badge", label, seconds = true, hideIcon, labels, className, ...props }, ref) {
121
+ const t = useLabels("slaTimer", slaTimerLabels, labels);
122
+ const f = useFormat();
123
+ const frozen = completedAt != null || pausedAt != null;
124
+ const now = useNow(fixedNow, () => ms(due) - Date.now(), !frozen);
125
+ const m = slaState({ due, start, completedAt, pausedAt, now, warnAt, warnMinutes });
126
+ const abs = Math.abs(m.remaining);
127
+ const time = f.duration(Math.floor(abs / 1000), { smallest: seconds && abs < 3_600_000 ? "s" : "min", maxParts: 2 });
128
+ const sentence = m.state === "met" ? t("met", { time })
129
+ : m.state === "missed" ? t("missed", { time })
130
+ : m.state === "paused" ? t(m.remaining >= 0 ? "paused" : "pausedOverdue", { time })
131
+ : m.state === "breached" ? t("overdue", { time })
132
+ : t("dueIn", { time });
133
+ const stateName = t({ "on-track": "stateOnTrack", "at-risk": "stateAtRisk", breached: "stateBreached", met: "stateMet", missed: "stateMissed", paused: "statePaused" }[m.state]);
134
+ const spoken = `${label ? `${label}: ` : ""}${sentence}`;
135
+ const Icon = icons[m.state];
136
+ const visible = m.state === "breached" || m.state === "missed" ? `−${time}` : m.state === "met" ? stateName : time;
137
+ if (variant === "bar") {
138
+ const fill = m.used == null ? (m.remaining < 0 ? 1 : 0) : Math.max(0, Math.min(1, m.used));
139
+ return (_jsxs("span", { ref: ref, role: "timer", "aria-label": spoken, suppressHydrationWarning: true, "data-slot": "sla-timer", "data-state": m.state, className: cn("grid w-full min-w-40 gap-1.5", className), ...props, children: [_jsxs("span", { "aria-hidden": "true", className: "flex items-baseline justify-between gap-3 text-[13px]", children: [_jsx("span", { className: "truncate text-fg-muted", children: label ?? stateName }), _jsx("span", { suppressHydrationWarning: true, className: cn("font-medium tabular", slaTimerVariants({ variant: "inline", state: m.state })), children: visible })] }), _jsx("span", { "aria-hidden": "true", className: "relative h-1 overflow-hidden rounded-full bg-track", children: _jsx("span", { suppressHydrationWarning: true, className: cn("absolute inset-0 origin-left rounded-full transition-transform duration-500 motion-reduce:transition-none rtl:origin-right", barColor[m.state]), style: { transform: `scaleX(${fill})` } }) })] }));
140
+ }
141
+ return (_jsxs("span", { ref: ref, role: "timer", "aria-label": spoken, title: spoken, suppressHydrationWarning: true, "data-slot": "sla-timer", "data-state": m.state, className: cn(slaTimerVariants({ variant, state: m.state }), className), ...props, children: [hideIcon ? null : _jsx(Icon, { "aria-hidden": true }), _jsx("span", { "aria-hidden": "true", suppressHydrationWarning: true, children: visible })] }));
142
+ });
@@ -140,7 +140,7 @@ export const SlugInput = React.forwardRef(function SlugInput(props, forwardedRef
140
140
  const lockControl = !locked && source !== undefined ? (_jsx("span", { "data-slot": "slug-input-auto", title: t("autoHint"), className: "me-1.5 inline-flex h-5 items-center rounded-full bg-accent-soft px-2 text-[11px] font-medium text-accent-ink", children: t("auto") })) : canRegenerate && !disabled ? (_jsx("button", { type: "button", "data-slot": "slug-input-regenerate", onClick: regenerate, "aria-label": t("regenerate"), title: t("regenerate"), className: "me-1 inline-flex size-7 items-center justify-center rounded-ui-sm text-fg-subtle transition-colors hover:bg-hover hover:text-fg focus-ring [&_svg]:size-3.5", children: _jsx(RefreshCw, { "aria-hidden": true, className: "rtl:-scale-x-100" }) })) : null;
141
141
  const message = check.state === "error" ? (_jsxs("p", { id: messageId, "data-slot": "slug-input-message", className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-danger", children: [_jsx("span", { children: check.message }), check.suggestion ? (_jsx("button", { type: "button", onClick: () => applySuggestion(check.suggestion), className: "rounded-ui-sm font-mono text-[11.5px] font-medium text-fg underline decoration-line underline-offset-[3px] transition-colors hover:decoration-fg focus-ring", children: t("useSuggestion", { slug: check.suggestion }) })) : null] })) : null;
142
142
  const live = (_jsx("span", { className: "sr-only", "aria-live": "polite", children: check.state === "checking" ? t("checking") : check.state === "ok" ? t("available") : check.state === "error" ? check.message : "" }));
143
- const input = (extra) => (_jsx(WithoutFieldName, { children: _jsx(Input, { ref: ref, ...engine.inputProps, id: id, size: size, disabled: disabled, invalid: isInvalid, dir: "ltr", spellCheck: false, autoCapitalize: "off", autoComplete: "off", placeholder: placeholder ?? (source !== undefined && !source ? t("empty") : undefined), "aria-label": ariaLabel ?? (field ? undefined : t("slug")), "aria-describedby": mergeIds(describedByProp, message ? messageId : undefined), addonStart: prefix ? _jsx("span", { className: "max-w-[16rem] truncate font-mono text-[12px] text-fg-subtle", title: prefix, children: prefix }) : undefined, inputClassName: "font-mono text-[13px]", "data-slot": "slug-input-control", trailing: status || lockControl || extra?.trailingExtra ? (_jsxs("span", { className: "flex items-center gap-1", children: [status ? _jsx("span", { className: "flex size-5 items-center justify-center", children: status }) : null, lockControl, extra?.trailingExtra] })) : undefined, onKeyDown: (e) => {
143
+ const input = (extra) => (_jsx(WithoutFieldName, { children: _jsx(Input, { ref: ref, ...engine.inputProps, id: id, size: size, disabled: disabled, invalid: isInvalid, dir: "ltr", spellCheck: false, autoCapitalize: "off", autoComplete: "off", placeholder: placeholder ?? (source !== undefined && !source ? t("empty") : undefined), "aria-label": ariaLabel ?? (field ? undefined : t("slug")), "aria-describedby": mergeIds(describedByProp, message ? messageId : undefined), addonStart: prefix ? _jsx("span", { className: "max-w-[16rem] truncate font-mono text-[12px] text-fg-subtle max-sm:max-w-[9rem]", title: prefix, children: prefix }) : undefined, inputClassName: "font-mono text-[13px]", "data-slot": "slug-input-control", trailing: status || lockControl || extra?.trailingExtra ? (_jsxs("span", { className: "flex items-center gap-1", children: [status ? _jsx("span", { className: "flex size-5 items-center justify-center", children: status }) : null, lockControl, extra?.trailingExtra] })) : undefined, onKeyDown: (e) => {
144
144
  extra?.onKeyDown?.(e);
145
145
  if (!e.defaultPrevented)
146
146
  engine.inputProps.onKeyDown(e);
@@ -0,0 +1,60 @@
1
+ import * as React from "react";
2
+ export declare const tourLabels: {
3
+ next: string;
4
+ back: string;
5
+ done: string;
6
+ skip: string;
7
+ progress: string;
8
+ };
9
+ export type TourLabels = typeof tourLabels;
10
+ export interface TourStep {
11
+ /** Stable key (defaults to the index). */
12
+ id?: string;
13
+ /**
14
+ * What to highlight: a CSS selector (`"[data-tour=new-order]"`), a ref, or a function.
15
+ * Missing or not found → the card shows centred without a spotlight.
16
+ */
17
+ target?: string | React.RefObject<Element | null> | (() => Element | null);
18
+ title: React.ReactNode;
19
+ description?: React.ReactNode;
20
+ /** Extra body under the description (an illustration, a shortcut hint). */
21
+ content?: React.ReactNode;
22
+ side?: "top" | "right" | "bottom" | "left";
23
+ align?: "start" | "center" | "end";
24
+ /** Space between the target and the spotlight edge, px. Default `6`. */
25
+ padding?: number;
26
+ /** Spotlight corner radius, px. Default `10`. */
27
+ radius?: number;
28
+ }
29
+ export interface TourProps {
30
+ steps: TourStep[];
31
+ open?: boolean;
32
+ defaultOpen?: boolean;
33
+ onOpenChange?: (open: boolean) => void;
34
+ /** Current step index (controlled). */
35
+ step?: number;
36
+ defaultStep?: number;
37
+ onStepChange?: (step: number) => void;
38
+ /** "Done" on the last step. The tour closes afterwards. */
39
+ onComplete?: () => void;
40
+ /** Closed early (skip button or Escape) on step `index`. */
41
+ onSkip?: (index: number) => void;
42
+ /** Clicking the dimmed page closes the tour. Default `false`. */
43
+ dismissible?: boolean;
44
+ /** Card class (width, etc.). */
45
+ className?: string;
46
+ labels?: Partial<TourLabels>;
47
+ }
48
+ /**
49
+ * Guided product tour: dims the page, cuts a spotlight around each target and anchors a step
50
+ * card to it. Back / Next / Done, arrow keys between steps, Escape to skip. The card is a modal
51
+ * dialog labelled by the step title; focus returns to where it was when the tour ends.
52
+ *
53
+ * ```tsx
54
+ * <Tour open={open} onOpenChange={setOpen} steps={[
55
+ * { target: "[data-tour=search]", title: "Search everything", description: "Press ⌘K from any page." },
56
+ * { target: "[data-tour=new]", title: "Create", description: "Orders, customers and products start here.", side: "left" },
57
+ * ]} />
58
+ * ```
59
+ */
60
+ export declare function Tour({ steps, open: openProp, defaultOpen, onOpenChange, step: stepProp, defaultStep, onStepChange, onComplete, onSkip, dismissible, className, labels }: TourProps): React.JSX.Element | null;
@@ -0,0 +1,139 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Popover as PopoverPrimitive } from "radix-ui";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "../lib/cn.js";
7
+ import { useLabels } from "../lib/labels.js";
8
+ import { useControllableState } from "../lib/use-controllable-state.js";
9
+ import { useReducedMotion } from "../lib/use-reduced-motion.js";
10
+ import { usePortalContainer } from "../lib/theme-scope.js";
11
+ import { Layer } from "../lib/layer.js";
12
+ import { Button } from "./button.js";
13
+ export const tourLabels = {
14
+ next: "Next",
15
+ back: "Back",
16
+ done: "Done",
17
+ skip: "Skip tour",
18
+ progress: "Step {current} of {total}",
19
+ };
20
+ function resolveTarget(target) {
21
+ if (!target || typeof document === "undefined")
22
+ return null;
23
+ if (typeof target === "string")
24
+ return document.querySelector(target);
25
+ if (typeof target === "function")
26
+ return target();
27
+ return target.current;
28
+ }
29
+ const sameBox = (a, b) => a === b || (!!a && !!b && a.top === b.top && a.left === b.left && a.width === b.width && a.height === b.height);
30
+ /**
31
+ * Guided product tour: dims the page, cuts a spotlight around each target and anchors a step
32
+ * card to it. Back / Next / Done, arrow keys between steps, Escape to skip. The card is a modal
33
+ * dialog labelled by the step title; focus returns to where it was when the tour ends.
34
+ *
35
+ * ```tsx
36
+ * <Tour open={open} onOpenChange={setOpen} steps={[
37
+ * { target: "[data-tour=search]", title: "Search everything", description: "Press ⌘K from any page." },
38
+ * { target: "[data-tour=new]", title: "Create", description: "Orders, customers and products start here.", side: "left" },
39
+ * ]} />
40
+ * ```
41
+ */
42
+ export function Tour({ steps, open: openProp, defaultOpen = false, onOpenChange, step: stepProp, defaultStep = 0, onStepChange, onComplete, onSkip, dismissible = false, className, labels }) {
43
+ const t = useLabels("tour", tourLabels, labels);
44
+ const [open, setOpen] = useControllableState({ value: openProp, defaultValue: defaultOpen, onChange: onOpenChange, name: "Tour" });
45
+ const [rawIndex, setIndex] = useControllableState({ value: stepProp, defaultValue: defaultStep, onChange: onStepChange, name: "Tour" });
46
+ const index = Math.max(0, Math.min(steps.length - 1, rawIndex));
47
+ const current = steps[index];
48
+ const reduced = useReducedMotion();
49
+ const container = usePortalContainer();
50
+ const titleId = React.useId();
51
+ const descId = React.useId();
52
+ const [box, setBox] = React.useState(null);
53
+ const boxRef = React.useRef(null);
54
+ const contentRef = React.useRef(null);
55
+ const pad = current?.padding ?? 6;
56
+ const radius = current?.radius ?? 10;
57
+ // Track the target's position every frame while open (layout shifts, scrolling, resizes).
58
+ React.useEffect(() => {
59
+ if (!open || !current)
60
+ return;
61
+ let frame = 0;
62
+ let scrolled = false;
63
+ const measure = () => {
64
+ const el = resolveTarget(current.target);
65
+ let next = null;
66
+ if (el) {
67
+ const r = el.getBoundingClientRect();
68
+ if (r.width || r.height)
69
+ next = { top: r.top - pad, left: r.left - pad, width: r.width + pad * 2, height: r.height + pad * 2 };
70
+ if (!scrolled) {
71
+ scrolled = true;
72
+ const vh = window.innerHeight;
73
+ const vw = window.innerWidth;
74
+ if (r.top < 0 || r.bottom > vh || r.left < 0 || r.right > vw)
75
+ el.scrollIntoView({ block: "center", inline: "nearest", behavior: reduced ? "auto" : "smooth" });
76
+ }
77
+ el.setAttribute("data-tour-active", "");
78
+ }
79
+ if (!sameBox(boxRef.current, next)) {
80
+ boxRef.current = next;
81
+ setBox(next);
82
+ }
83
+ frame = requestAnimationFrame(measure);
84
+ };
85
+ measure();
86
+ return () => {
87
+ cancelAnimationFrame(frame);
88
+ resolveTarget(current.target)?.removeAttribute("data-tour-active");
89
+ };
90
+ }, [open, current, pad, reduced]);
91
+ // The card anchors to the spotlight, or to the viewport centre when there is no target.
92
+ const anchorRef = React.useRef({
93
+ getBoundingClientRect: () => {
94
+ const b = boxRef.current;
95
+ if (b)
96
+ return DOMRect.fromRect({ x: b.left, y: b.top, width: b.width, height: b.height });
97
+ return DOMRect.fromRect({ x: window.innerWidth / 2, y: window.innerHeight * 0.3, width: 0, height: 0 });
98
+ },
99
+ });
100
+ if (!current)
101
+ return null;
102
+ const last = index === steps.length - 1;
103
+ const close = (completed) => {
104
+ setOpen(false);
105
+ if (completed)
106
+ onComplete?.();
107
+ else
108
+ onSkip?.(index);
109
+ };
110
+ const go = (to) => {
111
+ if (to < 0)
112
+ return;
113
+ if (to >= steps.length)
114
+ return close(true);
115
+ setIndex(to);
116
+ };
117
+ const onKeyDown = (e) => {
118
+ const tag = e.target.tagName;
119
+ if (tag === "INPUT" || tag === "TEXTAREA" || e.target.isContentEditable)
120
+ return;
121
+ const rtl = getComputedStyle(e.currentTarget).direction === "rtl";
122
+ if (e.key === (rtl ? "ArrowLeft" : "ArrowRight")) {
123
+ e.preventDefault();
124
+ go(index + 1);
125
+ }
126
+ else if (e.key === (rtl ? "ArrowRight" : "ArrowLeft")) {
127
+ e.preventDefault();
128
+ go(index - 1);
129
+ }
130
+ };
131
+ const transition = reduced ? undefined : "top 280ms cubic-bezier(.2,.7,.2,1), left 280ms cubic-bezier(.2,.7,.2,1), width 280ms cubic-bezier(.2,.7,.2,1), height 280ms cubic-bezier(.2,.7,.2,1)";
132
+ return (_jsxs(PopoverPrimitive.Root, { open: open, onOpenChange: (o) => (!o ? close(false) : setOpen(true)), modal: true, children: [_jsx(PopoverPrimitive.Anchor, { virtualRef: anchorRef }), open ? (_jsx(PopoverPrimitive.Portal, { container: container, children: _jsx("div", { "data-slot": "tour-overlay", "aria-hidden": "true", className: "ui-fade pointer-events-auto fixed inset-0 z-(--z-overlay)", children: box ? (_jsx("div", { "data-slot": "tour-spotlight", className: "absolute outline-2 outline-offset-0 outline-[var(--ring)]", style: { top: box.top, left: box.left, width: box.width, height: box.height, borderRadius: radius, boxShadow: "0 0 0 200vmax var(--overlay)", transition } })) : (_jsx("div", { className: "absolute inset-0 bg-overlay" })) }) })) : null, _jsx(PopoverPrimitive.Portal, { container: container, children: _jsx(Layer, { asChild: true, children: _jsxs(PopoverPrimitive.Content, { ref: contentRef, "data-slot": "tour-card", side: box ? (current.side ?? "bottom") : "bottom", align: box ? (current.align ?? "center") : "center", sideOffset: box ? 12 : 0, collisionPadding: 16, updatePositionStrategy: "always", "aria-labelledby": titleId, "aria-describedby": current.description ? descId : undefined, onOpenAutoFocus: (e) => {
133
+ e.preventDefault();
134
+ contentRef.current?.focus();
135
+ }, onInteractOutside: (e) => {
136
+ if (!dismissible)
137
+ e.preventDefault();
138
+ }, onKeyDown: onKeyDown, className: cn("surface-popover ui-pop z-(--z-popover) w-80 max-w-[calc(100vw-2rem)] rounded-ui-lg p-4 outline-none", className), children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsxs("div", { className: "grid min-w-0 flex-1 gap-1", children: [_jsx("p", { className: "text-[11px] font-medium tracking-[0.06em] text-fg-subtle uppercase tabular", children: t("progress", { current: index + 1, total: steps.length }) }), _jsx("h2", { id: titleId, className: "text-[15px] leading-snug font-semibold tracking-[-0.01em] text-fg", children: current.title })] }), _jsx(PopoverPrimitive.Close, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": t("skip"), title: t("skip"), className: "-me-1.5 -mt-1 shrink-0", children: _jsx(X, { "aria-hidden": "true" }) }) })] }), current.description ? (_jsx("p", { id: descId, className: "mt-2 text-[13px] leading-relaxed text-fg-muted", children: current.description })) : null, current.content ? _jsx("div", { className: "mt-3", children: current.content }) : null, _jsxs("div", { className: "mt-4 flex items-center gap-2", children: [_jsx("ol", { "aria-hidden": "true", className: "me-auto flex items-center gap-1", children: steps.map((s, i) => (_jsx("li", { className: cn("h-1.5 rounded-full transition-[width,background-color] duration-300 motion-reduce:transition-none", i === index ? "w-4 bg-accent" : i < index ? "w-1.5 bg-fg-muted" : "w-1.5 bg-track") }, s.id ?? i))) }), index > 0 ? (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => go(index - 1), children: t("back") })) : null, _jsx(Button, { size: "sm", variant: "primary", onClick: () => go(index + 1), children: last ? t("done") : t("next") })] })] }, current.id ?? index) }) })] }));
139
+ }
package/dist/index.d.ts CHANGED
@@ -5,9 +5,11 @@ export * from "./components/address-field.js";
5
5
  export * from "./components/alert-dialog.js";
6
6
  export * from "./components/alert.js";
7
7
  export * from "./components/app-shell.js";
8
+ export * from "./components/aspect-ratio.js";
8
9
  export * from "./components/attachment-list.js";
9
10
  export * from "./components/audit-log.js";
10
11
  export * from "./components/avatar.js";
12
+ export * from "./components/backup-codes.js";
11
13
  export * from "./components/badge.js";
12
14
  export * from "./components/breadcrumb.js";
13
15
  export * from "./components/button-group.js";
@@ -16,6 +18,7 @@ export * from "./components/calendar.js";
16
18
  export * from "./components/card.js";
17
19
  export * from "./components/carousel.js";
18
20
  export * from "./components/checkbox.js";
21
+ export * from "./components/collapsible.js";
19
22
  export * from "./components/color-picker.js";
20
23
  export * from "./components/combobox.js";
21
24
  export * from "./components/command.js";
@@ -42,14 +45,18 @@ export * from "./components/input.js";
42
45
  export * from "./components/item.js";
43
46
  export * from "./components/kbd.js";
44
47
  export * from "./components/list-layout.js";
48
+ export * from "./components/menubar.js";
45
49
  export * from "./components/native-select.js";
46
50
  export * from "./components/navigation-menu.js";
47
51
  export * from "./components/notification-center.js";
48
52
  export * from "./components/page-header.js";
49
53
  export * from "./components/pagination.js";
50
54
  export * from "./components/phone-input.js";
55
+ export * from "./components/pin-map.js";
56
+ export * from "./components/placeholder-art.js";
51
57
  export * from "./components/popover.js";
52
58
  export * from "./components/progress.js";
59
+ export * from "./components/qr-code.js";
53
60
  export * from "./components/radio-group.js";
54
61
  export * from "./components/record-detail-layout.js";
55
62
  export * from "./components/record-picker.js";
@@ -62,6 +69,7 @@ export * from "./components/sheet.js";
62
69
  export * from "./components/sidebar.js";
63
70
  export * from "./components/signature-pad.js";
64
71
  export * from "./components/skeleton.js";
72
+ export * from "./components/sla-timer.js";
65
73
  export * from "./components/slider.js";
66
74
  export * from "./components/slug-input.js";
67
75
  export * from "./components/sparkline.js";
@@ -79,6 +87,7 @@ export * from "./components/toast.js";
79
87
  export * from "./components/toggle.js";
80
88
  export * from "./components/tooltip.js";
81
89
  export * from "./components/top-bar.js";
90
+ export * from "./components/tour.js";
82
91
  export * from "./components/tree-select.js";
83
92
  export * from "./components/tree-view.js";
84
93
  export * from "./components/typography.js";
package/dist/index.js CHANGED
@@ -6,9 +6,11 @@ export * from "./components/address-field.js";
6
6
  export * from "./components/alert-dialog.js";
7
7
  export * from "./components/alert.js";
8
8
  export * from "./components/app-shell.js";
9
+ export * from "./components/aspect-ratio.js";
9
10
  export * from "./components/attachment-list.js";
10
11
  export * from "./components/audit-log.js";
11
12
  export * from "./components/avatar.js";
13
+ export * from "./components/backup-codes.js";
12
14
  export * from "./components/badge.js";
13
15
  export * from "./components/breadcrumb.js";
14
16
  export * from "./components/button-group.js";
@@ -17,6 +19,7 @@ export * from "./components/calendar.js";
17
19
  export * from "./components/card.js";
18
20
  export * from "./components/carousel.js";
19
21
  export * from "./components/checkbox.js";
22
+ export * from "./components/collapsible.js";
20
23
  export * from "./components/color-picker.js";
21
24
  export * from "./components/combobox.js";
22
25
  export * from "./components/command.js";
@@ -43,14 +46,18 @@ export * from "./components/input.js";
43
46
  export * from "./components/item.js";
44
47
  export * from "./components/kbd.js";
45
48
  export * from "./components/list-layout.js";
49
+ export * from "./components/menubar.js";
46
50
  export * from "./components/native-select.js";
47
51
  export * from "./components/navigation-menu.js";
48
52
  export * from "./components/notification-center.js";
49
53
  export * from "./components/page-header.js";
50
54
  export * from "./components/pagination.js";
51
55
  export * from "./components/phone-input.js";
56
+ export * from "./components/pin-map.js";
57
+ export * from "./components/placeholder-art.js";
52
58
  export * from "./components/popover.js";
53
59
  export * from "./components/progress.js";
60
+ export * from "./components/qr-code.js";
54
61
  export * from "./components/radio-group.js";
55
62
  export * from "./components/record-detail-layout.js";
56
63
  export * from "./components/record-picker.js";
@@ -63,6 +70,7 @@ export * from "./components/sheet.js";
63
70
  export * from "./components/sidebar.js";
64
71
  export * from "./components/signature-pad.js";
65
72
  export * from "./components/skeleton.js";
73
+ export * from "./components/sla-timer.js";
66
74
  export * from "./components/slider.js";
67
75
  export * from "./components/slug-input.js";
68
76
  export * from "./components/sparkline.js";
@@ -80,6 +88,7 @@ export * from "./components/toast.js";
80
88
  export * from "./components/toggle.js";
81
89
  export * from "./components/tooltip.js";
82
90
  export * from "./components/top-bar.js";
91
+ export * from "./components/tour.js";
83
92
  export * from "./components/tree-select.js";
84
93
  export * from "./components/tree-view.js";
85
94
  export * from "./components/typography.js";
@@ -20,3 +20,4 @@ export { useAsyncList, type AsyncLoader, type AsyncPage, type AsyncListState, ty
20
20
  export { parseColor, toHex, formatColor, contrastRatio, contrastLevel, readableOn, type ColorFormat } from "./color.js";
21
21
  export { FieldContext, useFieldContext, useFieldControl, FieldContextReset, focusFirstInvalid, type FieldContextValue, type ControlA11yProps } from "./field-context.js";
22
22
  export { ChipRemoveButton, LocalizedText, LocaleCount, type ChipRemoveButtonProps } from "./islands.js";
23
+ export { CopyValueButton, type CopyValueButtonProps } from "./data-islands.js";
@@ -31,3 +31,4 @@ export { parseColor, toHex, formatColor, contrastRatio, contrastLevel, readableO
31
31
  export { FieldContext, useFieldContext, useFieldControl, FieldContextReset, focusFirstInvalid } from "./field-context.js";
32
32
  // Client islands for server-compatible components
33
33
  export { ChipRemoveButton, LocalizedText, LocaleCount } from "./islands.js";
34
+ export { CopyValueButton } from "./data-islands.js";
@@ -25,7 +25,7 @@ export function formatValue(f, value, spec = "number", extra) {
25
25
  case "compact":
26
26
  return f.compact(n, opts);
27
27
  case "compact-currency":
28
- return f.currency(n, currency ?? f.currencyCode, { notation: "compact", maximumFractionDigits: 1, ...opts });
28
+ return f.currency(n, currency ?? f.currencyCode, { notation: "compact", minimumFractionDigits: 0, maximumFractionDigits: 1, ...opts });
29
29
  case "percent":
30
30
  return f.percent(n, opts);
31
31
  case "duration":
@@ -14,7 +14,7 @@ export interface CreateHolidayProviderOptions {
14
14
  *
15
15
  * ```ts
16
16
  * const company = createHolidayProvider({
17
- * id: "acme",
17
+ * id: "northpoint",
18
18
  * holidays: [{ date: "2026-12-24", name: "Company day off", kind: "bank" }],
19
19
  * });
20
20
  * ```
@@ -8,7 +8,7 @@ const regionMatch = (h, region) => !h.region || (region !== undefined && h.regio
8
8
  *
9
9
  * ```ts
10
10
  * const company = createHolidayProvider({
11
- * id: "acme",
11
+ * id: "northpoint",
12
12
  * holidays: [{ date: "2026-12-24", name: "Company day off", kind: "bank" }],
13
13
  * });
14
14
  * ```