@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.
- package/dist/blocks/block-image.js +1 -1
- package/dist/blocks/block-link.js +1 -1
- package/dist/blocks/index.d.ts +2 -2
- package/dist/blocks/index.js +2 -2
- package/dist/blocks/site/_kit-1.d.ts +1 -0
- package/dist/blocks/site/_kit-1.js +3 -1
- package/dist/blocks/site/announcement-bar.js +1 -1
- package/dist/blocks/site/article-layout.d.ts +1 -0
- package/dist/blocks/site/article-layout.js +2 -1
- package/dist/blocks/site/blog-list.d.ts +1 -0
- package/dist/blocks/site/blog-list.js +3 -2
- package/dist/blocks/site/contact-dock.js +3 -1
- package/dist/blocks/site/contact-form.js +1 -1
- package/dist/blocks/site/cta.d.ts +2 -2
- package/dist/blocks/site/cta.js +8 -5
- package/dist/blocks/site/faq.js +2 -1
- package/dist/blocks/site/feature-grid.d.ts +1 -0
- package/dist/blocks/site/feature-grid.js +7 -5
- package/dist/blocks/site/footer.d.ts +1 -1
- package/dist/blocks/site/footer.js +8 -7
- package/dist/blocks/site/gallery-section.d.ts +6 -1
- package/dist/blocks/site/gallery-section.js +6 -4
- package/dist/blocks/site/hero.d.ts +3 -2
- package/dist/blocks/site/hero.js +14 -6
- package/dist/blocks/site/logo-strip.d.ts +6 -3
- package/dist/blocks/site/logo-strip.js +8 -7
- package/dist/blocks/site/map-hours.d.ts +2 -2
- package/dist/blocks/site/map-hours.js +4 -2
- package/dist/blocks/site/media-text.d.ts +4 -1
- package/dist/blocks/site/media-text.js +6 -4
- package/dist/blocks/site/menu-list.d.ts +7 -3
- package/dist/blocks/site/menu-list.js +34 -7
- package/dist/blocks/site/navbar.d.ts +1 -0
- package/dist/blocks/site/navbar.js +6 -5
- package/dist/blocks/site/not-found.js +2 -1
- package/dist/blocks/site/page-intro.d.ts +1 -0
- package/dist/blocks/site/page-intro.js +2 -1
- package/dist/blocks/site/pricing-table.js +2 -1
- package/dist/blocks/site/process.d.ts +1 -0
- package/dist/blocks/site/process.js +4 -2
- package/dist/blocks/site/registry-1.d.ts +1 -1
- package/dist/blocks/site/registry-2.d.ts +5 -0
- package/dist/blocks/site/registry-2.js +2 -2
- package/dist/blocks/site/rich-text-section.js +2 -1
- package/dist/blocks/site/shared/heading-lines.d.ts +9 -0
- package/dist/blocks/site/shared/heading-lines.js +32 -0
- package/dist/blocks/site/shared/image-sizes.d.ts +25 -0
- package/dist/blocks/site/shared/image-sizes.js +37 -0
- package/dist/blocks/site/shared/island-keys.d.ts +1 -1
- package/dist/blocks/site/shared/island-keys.js +1 -1
- package/dist/blocks/site/shared/labels-2.d.ts +9 -1
- package/dist/blocks/site/shared/labels-2.js +24 -6
- package/dist/blocks/site/shared/open-now.d.ts +1 -1
- package/dist/blocks/site/shared/open-now.js +1 -1
- package/dist/blocks/site/shared/opening-hours.d.ts +18 -2
- package/dist/blocks/site/shared/opening-hours.js +36 -7
- package/dist/blocks/site/shared/tap.d.ts +9 -0
- package/dist/blocks/site/shared/tap.js +16 -0
- package/dist/blocks/site/site-shell.d.ts +11 -0
- package/dist/blocks/site/site-shell.js +43 -16
- package/dist/blocks/site/stats-band.d.ts +3 -1
- package/dist/blocks/site/stats-band.js +11 -7
- package/dist/blocks/site/team.d.ts +1 -1
- package/dist/blocks/site/team.js +4 -3
- package/dist/blocks/site/testimonials.d.ts +2 -2
- package/dist/blocks/site/testimonials.js +5 -4
- package/dist/business/index.d.ts +1 -0
- package/dist/business/index.js +2 -0
- package/dist/business/signer-status.d.ts +53 -0
- package/dist/business/signer-status.js +76 -0
- package/dist/charts/bar-chart.js +6 -2
- package/dist/charts/chart-shell.d.ts +9 -2
- package/dist/charts/chart-shell.js +10 -4
- package/dist/charts/chart.js +1 -1
- package/dist/charts/combo-chart.js +5 -7
- package/dist/charts/utils.d.ts +10 -0
- package/dist/charts/utils.js +46 -0
- package/dist/components/_qr-core.d.ts +26 -0
- package/dist/components/_qr-core.js +376 -0
- package/dist/components/activity-timeline.js +5 -5
- package/dist/components/aspect-ratio.d.ts +26 -0
- package/dist/components/aspect-ratio.js +42 -0
- package/dist/components/backup-codes.d.ts +42 -0
- package/dist/components/backup-codes.js +67 -0
- package/dist/components/breadcrumb.d.ts +2 -2
- package/dist/components/breadcrumb.js +1 -1
- package/dist/components/carousel.d.ts +12 -3
- package/dist/components/carousel.js +8 -5
- package/dist/components/collapsible.d.ts +36 -0
- package/dist/components/collapsible.js +37 -0
- package/dist/components/command.d.ts +8 -2
- package/dist/components/command.js +7 -2
- package/dist/components/image.d.ts +18 -2
- package/dist/components/image.js +12 -5
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/menubar.d.ts +96 -0
- package/dist/components/menubar.js +123 -0
- package/dist/components/pin-map.d.ts +58 -0
- package/dist/components/pin-map.js +167 -0
- package/dist/components/placeholder-art.d.ts +51 -0
- package/dist/components/placeholder-art.js +82 -0
- package/dist/components/qr-code.d.ts +36 -0
- package/dist/components/qr-code.js +27 -0
- package/dist/components/record-detail-layout.d.ts +2 -2
- package/dist/components/record-detail-layout.js +1 -1
- package/dist/components/sidebar.js +8 -8
- package/dist/components/sla-timer.d.ts +75 -0
- package/dist/components/sla-timer.js +142 -0
- package/dist/components/slug-input.js +1 -1
- package/dist/components/tour.d.ts +60 -0
- package/dist/components/tour.js +139 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/lib/public.d.ts +1 -0
- package/dist/lib/public.js +1 -0
- package/dist/lib/value-format.js +1 -1
- package/dist/locale-data/core/holidays.d.ts +1 -1
- package/dist/locale-data/core/holidays.js +1 -1
- package/dist/locales/ar.d.ts +2087 -0
- package/dist/locales/ar.js +2088 -0
- package/dist/locales/de.d.ts +2087 -0
- package/dist/locales/de.js +2088 -0
- package/dist/locales/en.d.ts +1 -0
- package/dist/locales/en.js +1 -0
- package/dist/locales/es.d.ts +2087 -0
- package/dist/locales/es.js +2087 -0
- package/dist/locales/fr.d.ts +2087 -0
- package/dist/locales/fr.js +2088 -0
- package/dist/locales/tr.d.ts +1 -0
- package/dist/locales/tr.js +1 -0
- package/dist/media/gallery.js +3 -1
- package/dist/media/media-library.js +1 -1
- package/dist/motion/core.d.ts +32 -0
- package/dist/motion/core.js +77 -0
- package/dist/motion/count-up.d.ts +29 -0
- package/dist/motion/count-up.js +92 -0
- package/dist/motion/index.d.ts +11 -0
- package/dist/motion/index.js +13 -0
- package/dist/motion/motion-scope.d.ts +17 -0
- package/dist/motion/motion-scope.js +16 -0
- package/dist/motion/page-transition.d.ts +33 -0
- package/dist/motion/page-transition.js +61 -0
- package/dist/motion/parallax.d.ts +37 -0
- package/dist/motion/parallax.js +90 -0
- package/dist/motion/pointer.d.ts +38 -0
- package/dist/motion/pointer.js +117 -0
- package/dist/motion/reveal.d.ts +72 -0
- package/dist/motion/reveal.js +133 -0
- package/dist/motion/scroll-shrink.d.ts +18 -0
- package/dist/motion/scroll-shrink.js +47 -0
- package/dist/motion/skeleton-swap.d.ts +17 -0
- package/dist/motion/skeleton-swap.js +43 -0
- package/dist/motion/text-reveal.d.ts +32 -0
- package/dist/motion/text-reveal.js +31 -0
- package/dist/motion/ticker.d.ts +35 -0
- package/dist/motion/ticker.js +16 -0
- package/dist/render/block-renderer.js +1 -1
- package/dist/sortable/sortable-tree.d.ts +4 -0
- package/dist/sortable/sortable-tree.js +16 -4
- package/dist/styles/blocks.css +21 -12
- package/dist/styles/editors.css +2 -1
- package/dist/styles/media.css +31 -5
- package/dist/styles/motion.css +389 -0
- package/dist/styles.css +41 -10
- 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";
|
package/dist/lib/public.d.ts
CHANGED
|
@@ -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";
|
package/dist/lib/public.js
CHANGED
|
@@ -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";
|
package/dist/lib/value-format.js
CHANGED
|
@@ -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":
|
|
@@ -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: "
|
|
11
|
+
* id: "northpoint",
|
|
12
12
|
* holidays: [{ date: "2026-12-24", name: "Company day off", kind: "bank" }],
|
|
13
13
|
* });
|
|
14
14
|
* ```
|