@blurrow/ui 0.2.0 → 0.2.1
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/README.md +48 -36
- package/dist/Accordion.d.ts +17 -0
- package/dist/Accordion.js +138 -0
- package/dist/Accordion.js.map +1 -0
- package/dist/Alert.d.ts +13 -0
- package/dist/Alert.js +69 -0
- package/dist/Alert.js.map +1 -0
- package/dist/AuthorCard.d.ts +12 -0
- package/dist/AuthorCard.js +57 -0
- package/dist/AuthorCard.js.map +1 -0
- package/dist/Avatar.d.ts +21 -0
- package/dist/Avatar.js +96 -0
- package/dist/Avatar.js.map +1 -0
- package/dist/Badge.d.ts +12 -0
- package/dist/Badge.js +46 -0
- package/dist/Badge.js.map +1 -0
- package/dist/Banner.d.ts +15 -0
- package/dist/Banner.js +68 -0
- package/dist/Banner.js.map +1 -0
- package/dist/Breadcrumb.d.ts +13 -0
- package/dist/Breadcrumb.js +45 -0
- package/dist/Breadcrumb.js.map +1 -0
- package/dist/Button.d.ts +19 -0
- package/dist/Button.js +282 -0
- package/dist/Button.js.map +1 -0
- package/dist/ButtonGroup.d.ts +19 -0
- package/dist/ButtonGroup.js +62 -0
- package/dist/ButtonGroup.js.map +1 -0
- package/dist/Card.d.ts +40 -0
- package/dist/Card.js +175 -0
- package/dist/Card.js.map +1 -0
- package/dist/CardShell.d.ts +18 -0
- package/dist/CardShell.js +58 -0
- package/dist/CardShell.js.map +1 -0
- package/dist/Carousel.d.ts +22 -0
- package/dist/Carousel.js +247 -0
- package/dist/Carousel.js.map +1 -0
- package/dist/Checkbox.d.ts +15 -0
- package/dist/Checkbox.js +84 -0
- package/dist/Checkbox.js.map +1 -0
- package/dist/CloseButton.d.ts +12 -0
- package/dist/CloseButton.js +63 -0
- package/dist/CloseButton.js.map +1 -0
- package/dist/CodeBlock.d.ts +12 -0
- package/dist/CodeBlock.js +221 -0
- package/dist/CodeBlock.js.map +1 -0
- package/dist/ColorPicker.d.ts +24 -0
- package/dist/ColorPicker.js +366 -0
- package/dist/ColorPicker.js.map +1 -0
- package/dist/Combobox.d.ts +34 -0
- package/dist/Combobox.js +255 -0
- package/dist/Combobox.js.map +1 -0
- package/dist/CommandPalette.d.ts +25 -0
- package/dist/CommandPalette.js +304 -0
- package/dist/CommandPalette.js.map +1 -0
- package/dist/ContextMenu.d.ts +16 -0
- package/dist/ContextMenu.js +209 -0
- package/dist/ContextMenu.js.map +1 -0
- package/dist/DataList.d.ts +15 -0
- package/dist/DataList.js +42 -0
- package/dist/DataList.js.map +1 -0
- package/dist/DatePicker.d.ts +65 -0
- package/dist/DatePicker.js +391 -0
- package/dist/DatePicker.js.map +1 -0
- package/dist/DateRangePicker.d.ts +34 -0
- package/dist/DateRangePicker.js +180 -0
- package/dist/DateRangePicker.js.map +1 -0
- package/dist/Drawer.d.ts +17 -0
- package/dist/Drawer.js +132 -0
- package/dist/Drawer.js.map +1 -0
- package/dist/DropdownMenu.d.ts +23 -0
- package/dist/DropdownMenu.js +185 -0
- package/dist/DropdownMenu.js.map +1 -0
- package/dist/EmptyState.d.ts +13 -0
- package/dist/EmptyState.js +55 -0
- package/dist/EmptyState.js.map +1 -0
- package/dist/FieldMessage.d.ts +26 -0
- package/dist/FieldMessage.js +95 -0
- package/dist/FieldMessage.js.map +1 -0
- package/dist/FileUpload.d.ts +32 -0
- package/dist/FileUpload.js +253 -0
- package/dist/FileUpload.js.map +1 -0
- package/dist/Floating.d.ts +32 -0
- package/dist/Floating.js +95 -0
- package/dist/Floating.js.map +1 -0
- package/dist/HoverCard.d.ts +18 -0
- package/dist/HoverCard.js +90 -0
- package/dist/HoverCard.js.map +1 -0
- package/dist/Input.d.ts +17 -0
- package/dist/Input.js +96 -0
- package/dist/Input.js.map +1 -0
- package/dist/Kbd.d.ts +9 -0
- package/dist/Kbd.js +24 -0
- package/dist/Kbd.js.map +1 -0
- package/dist/Modal.d.ts +15 -0
- package/dist/Modal.js +109 -0
- package/dist/Modal.js.map +1 -0
- package/dist/NumberInput.d.ts +23 -0
- package/dist/NumberInput.js +162 -0
- package/dist/NumberInput.js.map +1 -0
- package/dist/OtpInput.d.ts +26 -0
- package/dist/OtpInput.js +173 -0
- package/dist/OtpInput.js.map +1 -0
- package/dist/Pagination.d.ts +11 -0
- package/dist/Pagination.js +148 -0
- package/dist/Pagination.js.map +1 -0
- package/dist/Popover.d.ts +13 -0
- package/dist/Popover.js +98 -0
- package/dist/Popover.js.map +1 -0
- package/dist/Progress.d.ts +12 -0
- package/dist/Progress.js +61 -0
- package/dist/Progress.js.map +1 -0
- package/dist/QuoteCard.d.ts +13 -0
- package/dist/QuoteCard.js +92 -0
- package/dist/QuoteCard.js.map +1 -0
- package/dist/Radio.d.ts +19 -0
- package/dist/Radio.js +161 -0
- package/dist/Radio.js.map +1 -0
- package/dist/RadioCards.d.ts +27 -0
- package/dist/RadioCards.js +205 -0
- package/dist/RadioCards.js.map +1 -0
- package/dist/ReactionBar.d.ts +15 -0
- package/dist/ReactionBar.js +92 -0
- package/dist/ReactionBar.js.map +1 -0
- package/dist/ReadingProgress.d.ts +8 -0
- package/dist/ReadingProgress.js +51 -0
- package/dist/ReadingProgress.js.map +1 -0
- package/dist/ReadingTime.d.ts +9 -0
- package/dist/ReadingTime.js +39 -0
- package/dist/ReadingTime.js.map +1 -0
- package/dist/Select.d.ts +20 -0
- package/dist/Select.js +244 -0
- package/dist/Select.js.map +1 -0
- package/dist/Separator.d.ts +14 -0
- package/dist/Separator.js +33 -0
- package/dist/Separator.js.map +1 -0
- package/dist/ShareButtons.d.ts +10 -0
- package/dist/ShareButtons.js +107 -0
- package/dist/ShareButtons.js.map +1 -0
- package/dist/Sidebar.d.ts +30 -0
- package/dist/Sidebar.js +140 -0
- package/dist/Sidebar.js.map +1 -0
- package/dist/Skeleton.d.ts +15 -0
- package/dist/Skeleton.js +67 -0
- package/dist/Skeleton.js.map +1 -0
- package/dist/Slider.d.ts +27 -0
- package/dist/Slider.js +182 -0
- package/dist/Slider.js.map +1 -0
- package/dist/Spinner.d.ts +12 -0
- package/dist/Spinner.js +24 -0
- package/dist/Spinner.js.map +1 -0
- package/dist/SpotlightCard.d.ts +17 -0
- package/dist/SpotlightCard.js +87 -0
- package/dist/SpotlightCard.js.map +1 -0
- package/dist/Stat.d.ts +19 -0
- package/dist/Stat.js +71 -0
- package/dist/Stat.js.map +1 -0
- package/dist/StatCard.d.ts +24 -0
- package/dist/StatCard.js +212 -0
- package/dist/StatCard.js.map +1 -0
- package/dist/Steps.d.ts +14 -0
- package/dist/Steps.js +94 -0
- package/dist/Steps.js.map +1 -0
- package/dist/Table.d.ts +21 -0
- package/dist/Table.js +93 -0
- package/dist/Table.js.map +1 -0
- package/dist/TableOfContents.d.ts +17 -0
- package/dist/TableOfContents.js +92 -0
- package/dist/TableOfContents.js.map +1 -0
- package/dist/Tabs.d.ts +17 -0
- package/dist/Tabs.js +123 -0
- package/dist/Tabs.js.map +1 -0
- package/dist/TagInput.d.ts +16 -0
- package/dist/TagInput.js +169 -0
- package/dist/TagInput.js.map +1 -0
- package/dist/Textarea.d.ts +13 -0
- package/dist/Textarea.js +74 -0
- package/dist/Textarea.js.map +1 -0
- package/dist/TimePicker.d.ts +28 -0
- package/dist/TimePicker.js +264 -0
- package/dist/TimePicker.js.map +1 -0
- package/dist/Timeline.d.ts +19 -0
- package/dist/Timeline.js +76 -0
- package/dist/Timeline.js.map +1 -0
- package/dist/Toast.d.ts +18 -0
- package/dist/Toast.js +318 -0
- package/dist/Toast.js.map +1 -0
- package/dist/Toggle.d.ts +13 -0
- package/dist/Toggle.js +84 -0
- package/dist/Toggle.js.map +1 -0
- package/dist/Tooltip.d.ts +13 -0
- package/dist/Tooltip.js +138 -0
- package/dist/Tooltip.js.map +1 -0
- package/dist/cli.js +172 -5
- package/dist/index.d.ts +68 -631
- package/dist/index.js +67 -5558
- package/dist/index.js.map +1 -1
- package/dist/registry/Accordion.tsx +6 -6
- package/dist/registry/Alert.tsx +13 -10
- package/dist/registry/AuthorCard.tsx +7 -7
- package/dist/registry/Avatar.tsx +5 -5
- package/dist/registry/Badge.tsx +12 -15
- package/dist/registry/Banner.tsx +15 -18
- package/dist/registry/Breadcrumb.tsx +3 -3
- package/dist/registry/Button.tsx +58 -69
- package/dist/registry/ButtonGroup.tsx +9 -9
- package/dist/registry/Card.tsx +15 -15
- package/dist/registry/CardShell.tsx +6 -6
- package/dist/registry/Carousel.tsx +19 -15
- package/dist/registry/Checkbox.tsx +14 -9
- package/dist/registry/CloseButton.tsx +2 -2
- package/dist/registry/CodeBlock.tsx +15 -15
- package/dist/registry/ColorPicker.tsx +404 -0
- package/dist/registry/Combobox.tsx +318 -0
- package/dist/registry/CommandPalette.tsx +31 -24
- package/dist/registry/ContextMenu.tsx +241 -0
- package/dist/registry/DataList.tsx +4 -4
- package/dist/registry/DatePicker.tsx +494 -0
- package/dist/registry/DateRangePicker.tsx +232 -0
- package/dist/registry/Drawer.tsx +17 -15
- package/dist/registry/DropdownMenu.tsx +39 -77
- package/dist/registry/EmptyState.tsx +8 -8
- package/dist/registry/FieldMessage.tsx +123 -0
- package/dist/registry/FileUpload.tsx +318 -0
- package/dist/registry/Floating.tsx +134 -0
- package/dist/registry/HoverCard.tsx +113 -0
- package/dist/registry/Input.tsx +54 -43
- package/dist/registry/Kbd.tsx +4 -5
- package/dist/registry/Modal.tsx +17 -15
- package/dist/registry/NumberInput.tsx +183 -0
- package/dist/registry/OtpInput.tsx +219 -0
- package/dist/registry/Pagination.tsx +15 -18
- package/dist/registry/Popover.tsx +40 -45
- package/dist/registry/Progress.tsx +4 -4
- package/dist/registry/QuoteCard.tsx +7 -7
- package/dist/registry/Radio.tsx +24 -21
- package/dist/registry/RadioCards.tsx +247 -0
- package/dist/registry/ReactionBar.tsx +5 -5
- package/dist/registry/ReadingProgress.tsx +2 -2
- package/dist/registry/ReadingTime.tsx +2 -2
- package/dist/registry/Select.tsx +28 -53
- package/dist/registry/Separator.tsx +52 -0
- package/dist/registry/ShareButtons.tsx +2 -2
- package/dist/registry/Sidebar.tsx +18 -18
- package/dist/registry/Skeleton.tsx +1 -1
- package/dist/registry/Slider.tsx +229 -0
- package/dist/registry/Spinner.tsx +32 -0
- package/dist/registry/SpotlightCard.tsx +6 -6
- package/dist/registry/Stat.tsx +4 -4
- package/dist/registry/StatCard.tsx +19 -19
- package/dist/registry/Steps.tsx +7 -7
- package/dist/registry/Table.tsx +12 -10
- package/dist/registry/TableOfContents.tsx +8 -8
- package/dist/registry/Tabs.tsx +6 -6
- package/dist/registry/TagInput.tsx +9 -9
- package/dist/registry/Textarea.tsx +5 -5
- package/dist/registry/TimePicker.tsx +315 -0
- package/dist/registry/Timeline.tsx +14 -14
- package/dist/registry/Toast.tsx +15 -12
- package/dist/registry/Toggle.tsx +7 -10
- package/dist/registry/Tooltip.tsx +21 -73
- package/dist/registry/styles.css +868 -62
- package/dist/registry/useFloatingPanel.ts +112 -0
- package/dist/styles.css +868 -62
- package/dist/useFloatingPanel.d.ts +24 -0
- package/dist/useFloatingPanel.js +89 -0
- package/dist/useFloatingPanel.js.map +1 -0
- package/dist/useModalBehavior.d.ts +15 -0
- package/dist/useModalBehavior.js +57 -0
- package/dist/useModalBehavior.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/Slider.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useId, useRef, useState } from \"react\";\nimport { FieldLabel } from \"./FieldMessage\";\n\ntype SliderValue = number | [number, number];\n\ninterface SliderProps<V extends SliderValue> {\n /** One number for a slider, two for a range */\n value?: V;\n defaultValue?: V;\n onChange?: (value: V) => void;\n /** Fires once the knob is let go, for work too heavy to do while dragging */\n onCommit?: (value: V) => void;\n min?: number;\n max?: number;\n step?: number;\n label?: string;\n /** Show the value beside the label; pass a function to format it */\n showValue?: boolean | ((value: V) => React.ReactNode);\n /** Small labels under the track, at these values */\n marks?: number[];\n disabled?: boolean;\n /** Name of a hidden input (two for a range: name[0], name[1]) */\n name?: string;\n className?: string;\n}\n\nconst clamp = (n: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, n));\nconst decimals = (n: number) => (String(n).split(\".\")[1] ?? \"\").length;\n\n/* A sunken track with the filled part in the inverse material, and a\n raised knob (or two, for a range) that you drag along it. The knob\n swells while held; arrows step, shift steps by ten, page keys by a\n tenth of the range, home and end jump to the ends. */\nexport function Slider<V extends SliderValue = number>({\n value,\n defaultValue,\n onChange,\n onCommit,\n min = 0,\n max = 100,\n step = 1,\n label,\n showValue = false,\n marks,\n disabled = false,\n name,\n className = \"\",\n}: SliderProps<V>) {\n const uid = useId();\n const trackRef = useRef<HTMLDivElement>(null);\n const knobs = useRef<(HTMLSpanElement | null)[]>([]);\n const isRange = Array.isArray(value ?? defaultValue);\n const [internal, setInternal] = useState<V>(() => (defaultValue ?? value ?? (min as V)) as V);\n const current = (value !== undefined ? value : internal) as V;\n const values: number[] = Array.isArray(current) ? [...current] : [current as number];\n const [dragging, setDragging] = useState<number | null>(null);\n const digits = decimals(step);\n\n const snap = (n: number) => Number(clamp(Math.round((n - min) / step) * step + min, min, max).toFixed(digits));\n const pct = (n: number) => ((n - min) / (max - min)) * 100;\n\n const emit = (next: number[], commit = false) => {\n const v = (isRange ? [next[0], next[1]] : next[0]) as V;\n setInternal(v);\n if (next.some((n, i) => n !== values[i])) onChange?.(v);\n if (commit) onCommit?.(v);\n };\n\n /* Each knob is kept on its own side of the other */\n const setAt = (i: number, n: number, commit = false) => {\n const next = [...values];\n next[i] = snap(n);\n if (isRange) next[i] = i === 0 ? Math.min(next[0], values[1]) : Math.max(next[1], values[0]);\n emit(next, commit);\n };\n\n const fromPointer = (clientX: number) => {\n const r = trackRef.current!.getBoundingClientRect();\n return min + clamp((clientX - r.left) / r.width, 0, 1) * (max - min);\n };\n\n const onPointerDown = (e: React.PointerEvent) => {\n if (disabled || e.button !== 0) return;\n e.preventDefault();\n const n = fromPointer(e.clientX);\n // Grab whichever knob is nearer; on a tie, the one that can move that way\n let i = 0;\n if (isRange) {\n const d0 = Math.abs(n - values[0]);\n const d1 = Math.abs(n - values[1]);\n i = d1 < d0 || (d0 === d1 && n > values[1]) ? 1 : 0;\n }\n (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);\n setDragging(i);\n knobs.current[i]?.focus();\n setAt(i, n);\n };\n\n const onKeyDown = (i: number) => (e: React.KeyboardEvent) => {\n const big = Math.max(step, (max - min) / 10);\n const moves: Record<string, number> = {\n ArrowRight: step * (e.shiftKey ? 10 : 1),\n ArrowUp: step * (e.shiftKey ? 10 : 1),\n ArrowLeft: -step * (e.shiftKey ? 10 : 1),\n ArrowDown: -step * (e.shiftKey ? 10 : 1),\n PageUp: big,\n PageDown: -big,\n };\n if (e.key in moves) {\n e.preventDefault();\n setAt(i, values[i] + moves[e.key], true);\n } else if (e.key === \"Home\") {\n e.preventDefault();\n setAt(i, min, true);\n } else if (e.key === \"End\") {\n e.preventDefault();\n setAt(i, max, true);\n }\n };\n\n const shown =\n typeof showValue === \"function\"\n ? showValue(current)\n : showValue\n ? values.join(\" – \")\n : null;\n\n const lo = isRange ? pct(values[0]) : 0;\n const hi = pct(values[isRange ? 1 : 0]);\n\n return (\n <div className={[\"flex w-full flex-col gap-2.5\", disabled ? \"opacity-40\" : \"\", className].join(\" \")}>\n {(label || shown !== null) && (\n <div className=\"flex items-center justify-between gap-4\">\n {label && <FieldLabel id={`${uid}-label`}>{label}</FieldLabel>}\n {shown !== null && <span className=\"text-[12.5px] tabular-nums text-ink-muted\">{shown}</span>}\n </div>\n )}\n\n <div\n className={[\"relative flex h-6 touch-none items-center\", disabled ? \"pointer-events-none\" : \"cursor-pointer\"].join(\" \")}\n onPointerDown={onPointerDown}\n onPointerMove={(e) => dragging !== null && setAt(dragging, fromPointer(e.clientX))}\n onPointerUp={() => {\n if (dragging !== null) onCommit?.(current);\n setDragging(null);\n }}\n onPointerCancel={() => setDragging(null)}\n >\n {/* The track: a groove cut into the surface */}\n <div\n ref={trackRef}\n className=\"relative h-2 w-full rounded-full\"\n style={{ background: \"var(--track)\", boxShadow: \"var(--track-shadow)\" }}\n >\n <div\n className=\"absolute inset-y-px rounded-full\"\n style={{\n left: `calc(${lo}% + ${isRange ? 0 : 1}px)`,\n width: `calc(${hi - lo}% - ${isRange ? 0 : 1}px)`,\n background: \"var(--face-inverse)\",\n boxShadow: \"var(--fill-shadow)\",\n transition: dragging === null ? \"left 200ms var(--ease-out-soft), width 200ms var(--ease-out-soft)\" : \"none\",\n }}\n />\n </div>\n\n {values.map((v, i) => (\n <span\n key={i}\n ref={(el) => {\n knobs.current[i] = el;\n }}\n role=\"slider\"\n tabIndex={disabled ? -1 : 0}\n aria-labelledby={label ? `${uid}-label` : undefined}\n aria-label={isRange ? (i === 0 ? \"minimum\" : \"maximum\") : label ? undefined : \"value\"}\n aria-valuemin={isRange && i === 1 ? values[0] : min}\n aria-valuemax={isRange && i === 0 ? values[1] : max}\n aria-valuenow={v}\n aria-disabled={disabled || undefined}\n onKeyDown={onKeyDown(i)}\n className=\"absolute top-1/2 h-5 w-5 rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ink focus-visible:ring-offset-2 focus-visible:ring-offset-page\"\n style={{\n left: `${pct(v)}%`,\n // the last-dragged knob of a range sits on top, so two at one spot can be pulled apart\n zIndex: dragging === i ? 2 : 1,\n background: \"var(--face)\",\n boxShadow: \"var(--knob-shadow)\",\n transform: `translate(-50%, -50%) scale(${dragging === i ? 1.15 : 1})`,\n transition:\n dragging === null\n ? \"left 200ms var(--ease-out-soft), transform 220ms var(--ease-spring)\"\n : \"transform 220ms var(--ease-spring)\",\n }}\n >\n {/* A small dimple on the knob's crown */}\n <span\n aria-hidden\n className=\"absolute left-1/2 top-1/2 h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full\"\n style={{ background: \"var(--track)\", boxShadow: \"var(--track-shadow)\" }}\n />\n </span>\n ))}\n </div>\n\n {marks && marks.length > 0 && (\n <div aria-hidden className=\"relative h-4\">\n {marks.map((m) => (\n <span\n key={m}\n className=\"absolute -translate-x-1/2 text-[11px] tabular-nums text-ink-faint\"\n style={{ left: `${pct(m)}%` }}\n >\n {m}\n </span>\n ))}\n </div>\n )}\n\n {name &&\n values.map((v, i) => (\n <input key={i} type=\"hidden\" name={isRange ? `${name}[${i}]` : name} value={v} />\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type SpinnerSize = "sm" | "md" | "lg";
|
|
4
|
+
interface SpinnerProps {
|
|
5
|
+
size?: SpinnerSize | number;
|
|
6
|
+
/** Read out to screen readers; the spinner is silent without it */
|
|
7
|
+
label?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
declare function Spinner({ size, label, className }: SpinnerProps): react_jsx_runtime.JSX.Element;
|
|
11
|
+
|
|
12
|
+
export { Spinner };
|
package/dist/Spinner.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// ../../components/ui/Spinner.tsx
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
var SIZES = { sm: 14, md: 18, lg: 24 };
|
|
4
|
+
function Spinner({ size = "md", label, className = "" }) {
|
|
5
|
+
const px = typeof size === "number" ? size : SIZES[size];
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
"span",
|
|
8
|
+
{
|
|
9
|
+
role: label ? "status" : void 0,
|
|
10
|
+
"aria-label": label,
|
|
11
|
+
"aria-hidden": label ? void 0 : true,
|
|
12
|
+
className: ["inline-flex shrink-0", className].join(" "),
|
|
13
|
+
style: { width: px, height: px },
|
|
14
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: px, height: px, fill: "none", className: "animate-spin", children: [
|
|
15
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9.5", stroke: "currentColor", strokeWidth: "2.5", opacity: "0.18" }),
|
|
16
|
+
/* @__PURE__ */ jsx("path", { d: "M21.5 12a9.5 9.5 0 0 0-9.5-9.5", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
|
|
17
|
+
] })
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
Spinner
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=Spinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/Spinner.tsx"],"sourcesContent":["import React from \"react\";\n\ntype SpinnerSize = \"sm\" | \"md\" | \"lg\";\n\ninterface SpinnerProps {\n size?: SpinnerSize | number;\n /** Read out to screen readers; the spinner is silent without it */\n label?: string;\n className?: string;\n}\n\nconst SIZES: Record<SpinnerSize, number> = { sm: 14, md: 18, lg: 24 };\n\n/* A short arc chasing round a faint ring. It takes the text colour, so\n inside a primary button it's light and on the page it's ink. */\nexport function Spinner({ size = \"md\", label, className = \"\" }: SpinnerProps) {\n const px = typeof size === \"number\" ? size : SIZES[size];\n return (\n <span\n role={label ? \"status\" : undefined}\n aria-label={label}\n aria-hidden={label ? undefined : true}\n className={[\"inline-flex shrink-0\", className].join(\" \")}\n style={{ width: px, height: px }}\n >\n <svg viewBox=\"0 0 24 24\" width={px} height={px} fill=\"none\" className=\"animate-spin\">\n <circle cx=\"12\" cy=\"12\" r=\"9.5\" stroke=\"currentColor\" strokeWidth=\"2.5\" opacity=\"0.18\" />\n <path d=\"M21.5 12a9.5 9.5 0 0 0-9.5-9.5\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" />\n </svg>\n </span>\n );\n}\n"],"mappings":";AAyBM,SACE,KADF;AAdN,IAAM,QAAqC,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAG;AAI7D,SAAS,QAAQ,EAAE,OAAO,MAAM,OAAO,YAAY,GAAG,GAAiB;AAC5E,QAAM,KAAK,OAAO,SAAS,WAAW,OAAO,MAAM,IAAI;AACvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,QAAQ,WAAW;AAAA,MACzB,cAAY;AAAA,MACZ,eAAa,QAAQ,SAAY;AAAA,MACjC,WAAW,CAAC,wBAAwB,SAAS,EAAE,KAAK,GAAG;AAAA,MACvD,OAAO,EAAE,OAAO,IAAI,QAAQ,GAAG;AAAA,MAE/B,+BAAC,SAAI,SAAQ,aAAY,OAAO,IAAI,QAAQ,IAAI,MAAK,QAAO,WAAU,gBACpE;AAAA,4BAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,OAAM,QAAO,gBAAe,aAAY,OAAM,SAAQ,QAAO;AAAA,QACvF,oBAAC,UAAK,GAAE,kCAAiC,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ;AAAA,SACzG;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface SpotlightCardProps {
|
|
5
|
+
title: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
eyebrow?: string;
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
actions?: React.ReactNode;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
theme?: "light" | "dark";
|
|
12
|
+
size?: "sm" | "md" | "lg";
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
declare function SpotlightCard({ title, subtitle, eyebrow, icon, actions, children, theme, size, className, }: SpotlightCardProps): react_jsx_runtime.JSX.Element;
|
|
16
|
+
|
|
17
|
+
export { SpotlightCard };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// ../../components/ui/SpotlightCard.tsx
|
|
2
|
+
import { CardShell } from "./CardShell.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var paddingBySize = {
|
|
5
|
+
// Feature cards pad 32px whatever their size; only the type scales
|
|
6
|
+
sm: "p-6",
|
|
7
|
+
md: "p-8",
|
|
8
|
+
lg: "p-8"
|
|
9
|
+
};
|
|
10
|
+
var titleSizeBySize = {
|
|
11
|
+
sm: "text-[20px]",
|
|
12
|
+
md: "text-[26px]",
|
|
13
|
+
lg: "text-[34px]"
|
|
14
|
+
};
|
|
15
|
+
function SpotlightCard({
|
|
16
|
+
title,
|
|
17
|
+
subtitle,
|
|
18
|
+
eyebrow,
|
|
19
|
+
icon,
|
|
20
|
+
actions,
|
|
21
|
+
children,
|
|
22
|
+
theme = "dark",
|
|
23
|
+
size = "md",
|
|
24
|
+
className = ""
|
|
25
|
+
}) {
|
|
26
|
+
const dark = theme === "dark";
|
|
27
|
+
return (
|
|
28
|
+
/* Built on CardShell: icon at the top of the face, words and actions
|
|
29
|
+
settled at the bottom — the same rhythm as Card and QuoteCard. */
|
|
30
|
+
/* @__PURE__ */ jsx(CardShell, { as: "article", theme, className, children: /* @__PURE__ */ jsxs("div", { className: ["relative flex-1 flex flex-col gap-3", paddingBySize[size]].join(" "), children: [
|
|
31
|
+
icon && /* @__PURE__ */ jsx(
|
|
32
|
+
"div",
|
|
33
|
+
{
|
|
34
|
+
className: [
|
|
35
|
+
"w-9 h-9 rounded-[11px] flex items-center justify-center mb-auto",
|
|
36
|
+
dark ? "text-white" : "text-ink-soft"
|
|
37
|
+
].join(" "),
|
|
38
|
+
style: {
|
|
39
|
+
background: dark ? "var(--tile-dark)" : "var(--tile)",
|
|
40
|
+
boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-shadow)"
|
|
41
|
+
},
|
|
42
|
+
children: icon
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
eyebrow && /* @__PURE__ */ jsx(
|
|
46
|
+
"p",
|
|
47
|
+
{
|
|
48
|
+
className: [
|
|
49
|
+
// Without an icon above to push it down, the copy still sits low
|
|
50
|
+
icon ? "pt-8" : "mt-auto",
|
|
51
|
+
"text-[14px] font-medium tracking-[-0.01em]",
|
|
52
|
+
dark ? "text-neutral-400" : "text-ink-muted"
|
|
53
|
+
].join(" "),
|
|
54
|
+
children: eyebrow
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
"h3",
|
|
59
|
+
{
|
|
60
|
+
className: [
|
|
61
|
+
titleSizeBySize[size],
|
|
62
|
+
!icon && !eyebrow ? "mt-auto" : "",
|
|
63
|
+
"font-medium leading-[1.12] tracking-[-0.03em] text-balance",
|
|
64
|
+
dark ? "text-white" : "text-ink"
|
|
65
|
+
].join(" "),
|
|
66
|
+
children: title
|
|
67
|
+
}
|
|
68
|
+
),
|
|
69
|
+
subtitle && /* @__PURE__ */ jsx(
|
|
70
|
+
"p",
|
|
71
|
+
{
|
|
72
|
+
className: [
|
|
73
|
+
"text-[15px] leading-[1.6] max-w-lg text-pretty",
|
|
74
|
+
dark ? "text-neutral-400" : "text-ink-body"
|
|
75
|
+
].join(" "),
|
|
76
|
+
children: subtitle
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
children,
|
|
80
|
+
actions && /* @__PURE__ */ jsx("div", { className: "pt-4 flex items-center gap-2", children: actions })
|
|
81
|
+
] }) })
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
SpotlightCard
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=SpotlightCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/SpotlightCard.tsx"],"sourcesContent":["import React from \"react\";\nimport { CardShell } from \"./CardShell\";\n\ninterface SpotlightCardProps {\n title: string;\n subtitle?: string;\n eyebrow?: string;\n icon?: React.ReactNode;\n actions?: React.ReactNode;\n children?: React.ReactNode;\n theme?: \"light\" | \"dark\";\n size?: \"sm\" | \"md\" | \"lg\";\n className?: string;\n}\n\nconst paddingBySize: Record<NonNullable<SpotlightCardProps[\"size\"]>, string> = {\n // Feature cards pad 32px whatever their size; only the type scales\n sm: \"p-6\",\n md: \"p-8\",\n lg: \"p-8\",\n};\n\nconst titleSizeBySize: Record<\n NonNullable<SpotlightCardProps[\"size\"]>,\n string\n> = {\n sm: \"text-[20px]\",\n md: \"text-[26px]\",\n lg: \"text-[34px]\",\n};\n\nexport function SpotlightCard({\n title,\n subtitle,\n eyebrow,\n icon,\n actions,\n children,\n theme = \"dark\",\n size = \"md\",\n className = \"\",\n}: SpotlightCardProps) {\n const dark = theme === \"dark\";\n\n return (\n /* Built on CardShell: icon at the top of the face, words and actions\n settled at the bottom — the same rhythm as Card and QuoteCard. */\n <CardShell as=\"article\" theme={theme} className={className}>\n {/* Headline sits low on the face, so tall cards breathe above it, not below */}\n <div className={[\"relative flex-1 flex flex-col gap-3\", paddingBySize[size]].join(\" \")}>\n {icon && (\n <div\n className={[\n \"w-9 h-9 rounded-[11px] flex items-center justify-center mb-auto\",\n dark ? \"text-white\" : \"text-ink-soft\",\n ].join(\" \")}\n style={{\n background: dark ? \"var(--tile-dark)\" : \"var(--tile)\",\n boxShadow: dark ? \"var(--tile-dark-shadow)\" : \"var(--tile-shadow)\",\n }}\n >\n {icon}\n </div>\n )}\n\n {eyebrow && (\n <p\n className={[\n // Without an icon above to push it down, the copy still sits low\n icon ? \"pt-8\" : \"mt-auto\",\n \"text-[14px] font-medium tracking-[-0.01em]\",\n dark ? \"text-neutral-400\" : \"text-ink-muted\",\n ].join(\" \")}\n >\n {eyebrow}\n </p>\n )}\n\n <h3\n className={[\n titleSizeBySize[size],\n !icon && !eyebrow ? \"mt-auto\" : \"\",\n \"font-medium leading-[1.12] tracking-[-0.03em] text-balance\",\n dark ? \"text-white\" : \"text-ink\",\n ].join(\" \")}\n >\n {title}\n </h3>\n\n {subtitle && (\n <p\n className={[\n \"text-[15px] leading-[1.6] max-w-lg text-pretty\",\n dark ? \"text-neutral-400\" : \"text-ink-body\",\n ].join(\" \")}\n >\n {subtitle}\n </p>\n )}\n\n {children}\n {actions && <div className=\"pt-4 flex items-center gap-2\">{actions}</div>}\n </div>\n </CardShell>\n );\n}\n"],"mappings":";AACA,SAAS,iBAAiB;AAgDpB,SAEI,KAFJ;AAlCN,IAAM,gBAAyE;AAAA;AAAA,EAE7E,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,IAAM,kBAGF;AAAA,EACF,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,YAAY;AACd,GAAuB;AACrB,QAAM,OAAO,UAAU;AAEvB;AAAA;AAAA;AAAA,IAGE,oBAAC,aAAU,IAAG,WAAU,OAAc,WAEpC,+BAAC,SAAI,WAAW,CAAC,uCAAuC,cAAc,IAAI,CAAC,EAAE,KAAK,GAAG,GAClF;AAAA,cACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,OAAO,eAAe;AAAA,UACxB,EAAE,KAAK,GAAG;AAAA,UACV,OAAO;AAAA,YACL,YAAY,OAAO,qBAAqB;AAAA,YACxC,WAAW,OAAO,4BAA4B;AAAA,UAChD;AAAA,UAEC;AAAA;AAAA,MACH;AAAA,MAGD,WACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA;AAAA,YAET,OAAO,SAAS;AAAA,YAChB;AAAA,YACA,OAAO,qBAAqB;AAAA,UAC9B,EAAE,KAAK,GAAG;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MAGF;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT,gBAAgB,IAAI;AAAA,YACpB,CAAC,QAAQ,CAAC,UAAU,YAAY;AAAA,YAChC;AAAA,YACA,OAAO,eAAe;AAAA,UACxB,EAAE,KAAK,GAAG;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MAEC,YACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,OAAO,qBAAqB;AAAA,UAC9B,EAAE,KAAK,GAAG;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MAGD;AAAA,MACA,WAAW,oBAAC,SAAI,WAAU,gCAAgC,mBAAQ;AAAA,OACrE,GACF;AAAA;AAEJ;","names":[]}
|
package/dist/Stat.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface StatProps {
|
|
5
|
+
value: number | string;
|
|
6
|
+
label: string;
|
|
7
|
+
prefix?: string;
|
|
8
|
+
suffix?: string;
|
|
9
|
+
trend?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
declare function Stat({ value, label, prefix, suffix, trend, className }: StatProps): react_jsx_runtime.JSX.Element;
|
|
13
|
+
interface StatRowProps {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
declare function StatRow({ children, className }: StatRowProps): react_jsx_runtime.JSX.Element;
|
|
18
|
+
|
|
19
|
+
export { Stat, StatRow };
|
package/dist/Stat.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// ../../components/ui/Stat.tsx
|
|
4
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function useCountUp(target, duration = 800) {
|
|
7
|
+
const [count, setCount] = useState(0);
|
|
8
|
+
const raf = useRef(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
11
|
+
raf.current = requestAnimationFrame(() => setCount(target));
|
|
12
|
+
return () => {
|
|
13
|
+
if (raf.current) cancelAnimationFrame(raf.current);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
let start = null;
|
|
17
|
+
const step = (now) => {
|
|
18
|
+
if (!start) start = now;
|
|
19
|
+
const p = Math.min((now - start) / duration, 1);
|
|
20
|
+
setCount(Math.round((1 - Math.pow(1 - p, 3)) * target));
|
|
21
|
+
if (p < 1) raf.current = requestAnimationFrame(step);
|
|
22
|
+
};
|
|
23
|
+
raf.current = requestAnimationFrame(step);
|
|
24
|
+
return () => {
|
|
25
|
+
if (raf.current) cancelAnimationFrame(raf.current);
|
|
26
|
+
};
|
|
27
|
+
}, [target, duration]);
|
|
28
|
+
return count;
|
|
29
|
+
}
|
|
30
|
+
function Stat({ value, label, prefix = "", suffix = "", trend, className = "" }) {
|
|
31
|
+
const isNum = typeof value === "number";
|
|
32
|
+
const counted = useCountUp(isNum ? value : 0);
|
|
33
|
+
const displayed = isNum ? counted : value;
|
|
34
|
+
return /* @__PURE__ */ jsxs("div", { className: ["flex flex-col gap-0.5", className].join(" "), children: [
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
|
|
36
|
+
/* @__PURE__ */ jsxs(
|
|
37
|
+
"span",
|
|
38
|
+
{
|
|
39
|
+
className: "text-[32px] font-semibold text-ink leading-none tabular-nums",
|
|
40
|
+
style: { letterSpacing: "-0.03em" },
|
|
41
|
+
children: [
|
|
42
|
+
prefix,
|
|
43
|
+
displayed,
|
|
44
|
+
suffix
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
),
|
|
48
|
+
trend && /* @__PURE__ */ jsx("span", { className: "text-[12px] text-ink-muted font-medium", children: trend })
|
|
49
|
+
] }),
|
|
50
|
+
/* @__PURE__ */ jsx("span", { className: "text-[12px] text-ink-muted", children: label })
|
|
51
|
+
] });
|
|
52
|
+
}
|
|
53
|
+
function StatRow({ children, className = "" }) {
|
|
54
|
+
const arr = React.Children.toArray(children);
|
|
55
|
+
return /* @__PURE__ */ jsx("div", { className: ["grid grid-cols-2 gap-6 sm:flex sm:divide-x sm:divide-line-strong sm:gap-0", className].join(" "), children: arr.map((child, i) => /* @__PURE__ */ jsx(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
className: [
|
|
59
|
+
"min-w-0",
|
|
60
|
+
i === 0 ? "sm:pr-6" : i === arr.length - 1 ? "sm:pl-6" : "sm:px-6"
|
|
61
|
+
].join(" "),
|
|
62
|
+
children: child
|
|
63
|
+
},
|
|
64
|
+
i
|
|
65
|
+
)) });
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
Stat,
|
|
69
|
+
StatRow
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=Stat.js.map
|
package/dist/Stat.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/Stat.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\n\ninterface StatProps {\n value: number | string;\n label: string;\n prefix?: string;\n suffix?: string;\n trend?: string;\n className?: string;\n}\n\nfunction useCountUp(target: number, duration = 800) {\n const [count, setCount] = useState(0);\n const raf = useRef<number | null>(null);\n\n useEffect(() => {\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n raf.current = requestAnimationFrame(() => setCount(target));\n return () => { if (raf.current) cancelAnimationFrame(raf.current); };\n }\n let start: number | null = null;\n const step = (now: number) => {\n if (!start) start = now;\n const p = Math.min((now - start) / duration, 1);\n setCount(Math.round((1 - Math.pow(1 - p, 3)) * target));\n if (p < 1) raf.current = requestAnimationFrame(step);\n };\n raf.current = requestAnimationFrame(step);\n return () => { if (raf.current) cancelAnimationFrame(raf.current); };\n }, [target, duration]);\n\n return count;\n}\n\nexport function Stat({ value, label, prefix = \"\", suffix = \"\", trend, className = \"\" }: StatProps) {\n const isNum = typeof value === \"number\";\n const counted = useCountUp(isNum ? (value as number) : 0);\n const displayed = isNum ? counted : value;\n\n return (\n <div className={[\"flex flex-col gap-0.5\", className].join(\" \")}>\n <div className=\"flex items-baseline gap-1.5\">\n <span\n className=\"text-[32px] font-semibold text-ink leading-none tabular-nums\"\n style={{ letterSpacing: \"-0.03em\" }}\n >\n {prefix}{displayed}{suffix}\n </span>\n {trend && (\n <span className=\"text-[12px] text-ink-muted font-medium\">{trend}</span>\n )}\n </div>\n <span className=\"text-[12px] text-ink-muted\">{label}</span>\n </div>\n );\n}\n\ninterface StatRowProps {\n children: React.ReactNode;\n className?: string;\n}\n\nexport function StatRow({ children, className = \"\" }: StatRowProps) {\n const arr = React.Children.toArray(children);\n return (\n <div className={[\"grid grid-cols-2 gap-6 sm:flex sm:divide-x sm:divide-line-strong sm:gap-0\", className].join(\" \")}>\n {arr.map((child, i) => (\n <div\n key={i}\n className={[\n \"min-w-0\",\n i === 0 ? \"sm:pr-6\" : i === arr.length - 1 ? \"sm:pl-6\" : \"sm:px-6\",\n ].join(\" \")}\n >\n {child}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":";;;AAEA,OAAO,SAAS,WAAW,QAAQ,gBAAgB;AA0C3C,SAOE,KAPF;AA/BR,SAAS,WAAW,QAAgB,WAAW,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,CAAC;AACpC,QAAM,MAAM,OAAsB,IAAI;AAEtC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,kCAAkC,EAAE,SAAS;AACjE,UAAI,UAAU,sBAAsB,MAAM,SAAS,MAAM,CAAC;AAC1D,aAAO,MAAM;AAAE,YAAI,IAAI,QAAS,sBAAqB,IAAI,OAAO;AAAA,MAAG;AAAA,IACrE;AACA,QAAI,QAAuB;AAC3B,UAAM,OAAO,CAAC,QAAgB;AAC5B,UAAI,CAAC,MAAO,SAAQ;AACpB,YAAM,IAAI,KAAK,KAAK,MAAM,SAAS,UAAU,CAAC;AAC9C,eAAS,KAAK,OAAO,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,MAAM,CAAC;AACtD,UAAI,IAAI,EAAG,KAAI,UAAU,sBAAsB,IAAI;AAAA,IACrD;AACA,QAAI,UAAU,sBAAsB,IAAI;AACxC,WAAO,MAAM;AAAE,UAAI,IAAI,QAAS,sBAAqB,IAAI,OAAO;AAAA,IAAG;AAAA,EACrE,GAAG,CAAC,QAAQ,QAAQ,CAAC;AAErB,SAAO;AACT;AAEO,SAAS,KAAK,EAAE,OAAO,OAAO,SAAS,IAAI,SAAS,IAAI,OAAO,YAAY,GAAG,GAAc;AACjG,QAAM,QAAQ,OAAO,UAAU;AAC/B,QAAM,UAAU,WAAW,QAAS,QAAmB,CAAC;AACxD,QAAM,YAAY,QAAQ,UAAU;AAEpC,SACE,qBAAC,SAAI,WAAW,CAAC,yBAAyB,SAAS,EAAE,KAAK,GAAG,GAC3D;AAAA,yBAAC,SAAI,WAAU,+BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,eAAe,UAAU;AAAA,UAEjC;AAAA;AAAA,YAAQ;AAAA,YAAW;AAAA;AAAA;AAAA,MACtB;AAAA,MACC,SACC,oBAAC,UAAK,WAAU,0CAA0C,iBAAM;AAAA,OAEpE;AAAA,IACA,oBAAC,UAAK,WAAU,8BAA8B,iBAAM;AAAA,KACtD;AAEJ;AAOO,SAAS,QAAQ,EAAE,UAAU,YAAY,GAAG,GAAiB;AAClE,QAAM,MAAM,MAAM,SAAS,QAAQ,QAAQ;AAC3C,SACE,oBAAC,SAAI,WAAW,CAAC,6EAA6E,SAAS,EAAE,KAAK,GAAG,GAC9G,cAAI,IAAI,CAAC,OAAO,MACf;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW;AAAA,QACT;AAAA,QACA,MAAM,IAAI,YAAY,MAAM,IAAI,SAAS,IAAI,YAAY;AAAA,MAC3D,EAAE,KAAK,GAAG;AAAA,MAET;AAAA;AAAA,IANI;AAAA,EAOP,CACD,GACH;AAEJ;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
type StatTheme = "light" | "dark";
|
|
5
|
+
type DeltaDirection = "up" | "down" | "neutral";
|
|
6
|
+
interface StatCardProps {
|
|
7
|
+
label: string;
|
|
8
|
+
value: number;
|
|
9
|
+
prefix?: string;
|
|
10
|
+
suffix?: string;
|
|
11
|
+
decimals?: number;
|
|
12
|
+
delta?: {
|
|
13
|
+
value: string;
|
|
14
|
+
direction: DeltaDirection;
|
|
15
|
+
};
|
|
16
|
+
hint?: string;
|
|
17
|
+
sparkline?: number[];
|
|
18
|
+
icon?: React.ReactNode;
|
|
19
|
+
theme?: StatTheme;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
declare function StatCard({ label, value, prefix, suffix, decimals, delta, hint, sparkline, icon, theme, className, }: StatCardProps): react_jsx_runtime.JSX.Element;
|
|
23
|
+
|
|
24
|
+
export { StatCard };
|
package/dist/StatCard.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// ../../components/ui/StatCard.tsx
|
|
4
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
5
|
+
import { CardShell } from "./CardShell.js";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
function useAnimatedNumber(target, duration) {
|
|
8
|
+
const [v, setV] = useState(0);
|
|
9
|
+
const prevRef = useRef(0);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const from = prevRef.current;
|
|
12
|
+
const delta = target - from;
|
|
13
|
+
if (delta === 0) return;
|
|
14
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
15
|
+
prevRef.current = target;
|
|
16
|
+
const id = requestAnimationFrame(() => setV(target));
|
|
17
|
+
return () => cancelAnimationFrame(id);
|
|
18
|
+
}
|
|
19
|
+
const start = performance.now();
|
|
20
|
+
let raf = 0;
|
|
21
|
+
const tick = (now) => {
|
|
22
|
+
const t = Math.min(1, (now - start) / duration);
|
|
23
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
24
|
+
setV(from + delta * eased);
|
|
25
|
+
if (t < 1) {
|
|
26
|
+
raf = requestAnimationFrame(tick);
|
|
27
|
+
} else {
|
|
28
|
+
prevRef.current = target;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
raf = requestAnimationFrame(tick);
|
|
32
|
+
return () => cancelAnimationFrame(raf);
|
|
33
|
+
}, [target, duration]);
|
|
34
|
+
return v;
|
|
35
|
+
}
|
|
36
|
+
function Sparkline({ data, dark }) {
|
|
37
|
+
const uid = useId();
|
|
38
|
+
if (data.length < 2) return null;
|
|
39
|
+
const W = 120;
|
|
40
|
+
const H = 44;
|
|
41
|
+
const P = 6;
|
|
42
|
+
const max = Math.max(...data);
|
|
43
|
+
const min = Math.min(...data);
|
|
44
|
+
const range = max - min || 1;
|
|
45
|
+
const step = (W - P * 2) / (data.length - 1);
|
|
46
|
+
const pts = data.map((d, i) => {
|
|
47
|
+
const x = P + i * step;
|
|
48
|
+
const y = H - P - (d - min) / range * (H - P * 2);
|
|
49
|
+
return [x, y];
|
|
50
|
+
});
|
|
51
|
+
const line = pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(1)},${y.toFixed(1)}`).join(" ");
|
|
52
|
+
const area = `M0,${pts[0][1].toFixed(1)} ${line.replace(/^M/, "L")} L${W},${pts[pts.length - 1][1].toFixed(1)} L${W},${H} L0,${H} Z`;
|
|
53
|
+
const stroke = dark ? "var(--spark-dark)" : "var(--spark)";
|
|
54
|
+
const gradId = `spark-fade-${uid.replace(/:/g, "")}`;
|
|
55
|
+
const [lx, ly] = pts[pts.length - 1];
|
|
56
|
+
return /* @__PURE__ */ jsxs(
|
|
57
|
+
"div",
|
|
58
|
+
{
|
|
59
|
+
"aria-hidden": true,
|
|
60
|
+
className: "relative h-14",
|
|
61
|
+
style: {
|
|
62
|
+
maskImage: "linear-gradient(90deg, transparent 0%, black 18%)",
|
|
63
|
+
WebkitMaskImage: "linear-gradient(90deg, transparent 0%, black 18%)"
|
|
64
|
+
},
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: "none", className: "absolute inset-0 w-full h-full overflow-visible", children: [
|
|
67
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
68
|
+
/* @__PURE__ */ jsx("stop", { offset: "0%", style: { stopColor: stroke }, stopOpacity: dark ? 0.2 : 0.08 }),
|
|
69
|
+
/* @__PURE__ */ jsx("stop", { offset: "100%", style: { stopColor: stroke }, stopOpacity: "0" })
|
|
70
|
+
] }) }),
|
|
71
|
+
/* @__PURE__ */ jsx("path", { d: area, fill: `url(#${gradId})` }),
|
|
72
|
+
/* @__PURE__ */ jsx(
|
|
73
|
+
"path",
|
|
74
|
+
{
|
|
75
|
+
d: line,
|
|
76
|
+
fill: "none",
|
|
77
|
+
style: { stroke },
|
|
78
|
+
strokeOpacity: dark ? 0.85 : 0.9,
|
|
79
|
+
strokeWidth: 1.5,
|
|
80
|
+
strokeLinecap: "round",
|
|
81
|
+
strokeLinejoin: "round",
|
|
82
|
+
vectorEffect: "non-scaling-stroke"
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
] }),
|
|
86
|
+
/* @__PURE__ */ jsx(
|
|
87
|
+
"span",
|
|
88
|
+
{
|
|
89
|
+
className: "absolute w-[7px] h-[7px] rounded-full -translate-x-1/2 -translate-y-1/2",
|
|
90
|
+
style: {
|
|
91
|
+
left: `${lx / W * 100}%`,
|
|
92
|
+
top: `${ly / H * 100}%`,
|
|
93
|
+
background: stroke,
|
|
94
|
+
boxShadow: dark ? "0 0 0 3px var(--spark-dark-halo)" : "0 0 0 3px var(--spark-halo)"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
var deltaTone = {
|
|
103
|
+
up: { light: "text-success", dark: "text-emerald-400", label: "up" },
|
|
104
|
+
down: { light: "text-danger", dark: "text-red-400", label: "down" },
|
|
105
|
+
neutral: { light: "text-ink-body", dark: "text-neutral-300", label: "unchanged" }
|
|
106
|
+
};
|
|
107
|
+
function DeltaArrow({ direction }) {
|
|
108
|
+
const d = direction === "up" ? "M12 19V5m0 0l-6 6m6-6l6 6" : direction === "down" ? "M12 5v14m0 0l-6-6m6 6l6-6" : "M5 12h14m0 0l-6-6m6 6l-6 6";
|
|
109
|
+
return /* @__PURE__ */ jsx("svg", { "aria-hidden": true, width: "10", height: "10", fill: "none", stroke: "currentColor", strokeWidth: "2.5", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d }) });
|
|
110
|
+
}
|
|
111
|
+
function StatCard({
|
|
112
|
+
label,
|
|
113
|
+
value,
|
|
114
|
+
prefix = "",
|
|
115
|
+
suffix = "",
|
|
116
|
+
decimals = 0,
|
|
117
|
+
delta,
|
|
118
|
+
hint,
|
|
119
|
+
sparkline,
|
|
120
|
+
icon,
|
|
121
|
+
theme = "light",
|
|
122
|
+
className = ""
|
|
123
|
+
}) {
|
|
124
|
+
const animated = useAnimatedNumber(value, 900);
|
|
125
|
+
const dark = theme === "dark";
|
|
126
|
+
const formatter = new Intl.NumberFormat("en-US", {
|
|
127
|
+
minimumFractionDigits: decimals,
|
|
128
|
+
maximumFractionDigits: decimals
|
|
129
|
+
});
|
|
130
|
+
const finalText = `${prefix}${formatter.format(value)}${suffix}`;
|
|
131
|
+
const badge = delta && /* @__PURE__ */ jsxs(
|
|
132
|
+
"span",
|
|
133
|
+
{
|
|
134
|
+
"aria-label": `${deltaTone[delta.direction].label} ${delta.value}`,
|
|
135
|
+
className: [
|
|
136
|
+
"inline-flex items-center gap-1 h-6 px-2 rounded-[7px] text-[12px] font-medium tabular-nums shrink-0",
|
|
137
|
+
dark ? deltaTone[delta.direction].dark : deltaTone[delta.direction].light
|
|
138
|
+
].join(" "),
|
|
139
|
+
style: {
|
|
140
|
+
background: dark ? "var(--tile-dark)" : "var(--tile)",
|
|
141
|
+
boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-shadow)"
|
|
142
|
+
},
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ jsx(DeltaArrow, { direction: delta.direction }),
|
|
145
|
+
delta.value
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
return /* @__PURE__ */ jsxs(CardShell, { theme, className, children: [
|
|
150
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 pt-5 flex items-center justify-between gap-3 min-h-11", children: [
|
|
151
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
|
|
152
|
+
icon && /* @__PURE__ */ jsx(
|
|
153
|
+
"div",
|
|
154
|
+
{
|
|
155
|
+
"aria-hidden": true,
|
|
156
|
+
className: [
|
|
157
|
+
"w-7 h-7 rounded-[9px] flex items-center justify-center shrink-0",
|
|
158
|
+
dark ? "text-neutral-100" : "text-ink-body"
|
|
159
|
+
].join(" "),
|
|
160
|
+
style: {
|
|
161
|
+
background: dark ? "var(--tile-dark)" : "var(--tile)",
|
|
162
|
+
boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-shadow)"
|
|
163
|
+
},
|
|
164
|
+
children: icon
|
|
165
|
+
}
|
|
166
|
+
),
|
|
167
|
+
/* @__PURE__ */ jsx(
|
|
168
|
+
"p",
|
|
169
|
+
{
|
|
170
|
+
className: [
|
|
171
|
+
"text-[14px] font-medium tracking-[-0.01em] truncate",
|
|
172
|
+
dark ? "text-neutral-300" : "text-ink-label"
|
|
173
|
+
].join(" "),
|
|
174
|
+
children: label
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
] }),
|
|
178
|
+
badge
|
|
179
|
+
] }),
|
|
180
|
+
/* @__PURE__ */ jsx("p", { className: "sr-only", children: finalText }),
|
|
181
|
+
/* @__PURE__ */ jsxs(
|
|
182
|
+
"p",
|
|
183
|
+
{
|
|
184
|
+
"aria-hidden": true,
|
|
185
|
+
className: [
|
|
186
|
+
"px-6 mt-5 text-[44px] font-semibold tabular-nums tracking-[-0.045em] leading-none",
|
|
187
|
+
dark ? "text-white" : "text-ink"
|
|
188
|
+
].join(" "),
|
|
189
|
+
children: [
|
|
190
|
+
prefix,
|
|
191
|
+
formatter.format(animated),
|
|
192
|
+
suffix && /* @__PURE__ */ jsx(
|
|
193
|
+
"span",
|
|
194
|
+
{
|
|
195
|
+
className: [
|
|
196
|
+
"ml-1 text-[17px] font-medium tracking-[-0.01em]",
|
|
197
|
+
dark ? "text-neutral-500" : "text-ink-faint"
|
|
198
|
+
].join(" "),
|
|
199
|
+
children: suffix.trim()
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
hint && /* @__PURE__ */ jsx("p", { className: ["px-6 mt-2.5 text-[13px] truncate", dark ? "text-neutral-500" : "text-ink-muted"].join(" "), children: hint }),
|
|
206
|
+
sparkline && sparkline.length > 1 ? /* @__PURE__ */ jsx("div", { className: "mt-auto pt-6", children: /* @__PURE__ */ jsx(Sparkline, { data: sparkline, dark }) }) : /* @__PURE__ */ jsx("div", { className: "pb-6" })
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
export {
|
|
210
|
+
StatCard
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=StatCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/StatCard.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useId, useRef, useState } from \"react\";\nimport { CardShell } from \"./CardShell\";\n\ntype StatTheme = \"light\" | \"dark\";\ntype DeltaDirection = \"up\" | \"down\" | \"neutral\";\n\ninterface StatCardProps {\n label: string;\n value: number;\n prefix?: string;\n suffix?: string;\n decimals?: number;\n delta?: { value: string; direction: DeltaDirection };\n hint?: string;\n sparkline?: number[];\n icon?: React.ReactNode;\n theme?: StatTheme;\n className?: string;\n}\n\nfunction useAnimatedNumber(target: number, duration: number) {\n const [v, setV] = useState(0);\n const prevRef = useRef(0);\n\n useEffect(() => {\n const from = prevRef.current;\n const delta = target - from;\n if (delta === 0) return;\n // Reduced motion: show the number, skip the count\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n prevRef.current = target;\n const id = requestAnimationFrame(() => setV(target));\n return () => cancelAnimationFrame(id);\n }\n const start = performance.now();\n let raf = 0;\n const tick = (now: number) => {\n const t = Math.min(1, (now - start) / duration);\n const eased = 1 - Math.pow(1 - t, 3);\n setV(from + delta * eased);\n if (t < 1) {\n raf = requestAnimationFrame(tick);\n } else {\n prevRef.current = target;\n }\n };\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [target, duration]);\n\n return v;\n}\n\nfunction Sparkline({ data, dark }: { data: number[]; dark?: boolean }) {\n const uid = useId();\n if (data.length < 2) return null;\n const W = 120;\n const H = 44;\n const P = 6;\n const max = Math.max(...data);\n const min = Math.min(...data);\n const range = max - min || 1;\n const step = (W - P * 2) / (data.length - 1);\n\n const pts = data.map((d, i) => {\n const x = P + i * step;\n const y = H - P - ((d - min) / range) * (H - P * 2);\n return [x, y] as const;\n });\n\n const line = pts\n .map(([x, y], i) => `${i === 0 ? \"M\" : \"L\"}${x.toFixed(1)},${y.toFixed(1)}`)\n .join(\" \");\n // The fill runs off both edges of the face so it reads as part of the surface\n const area = `M0,${pts[0][1].toFixed(1)} ${line.replace(/^M/, \"L\")} L${W},${pts[pts.length - 1][1].toFixed(1)} L${W},${H} L0,${H} Z`;\n\n const stroke = dark ? \"var(--spark-dark)\" : \"var(--spark)\";\n const gradId = `spark-fade-${uid.replace(/:/g, \"\")}`;\n const [lx, ly] = pts[pts.length - 1];\n\n return (\n <div\n aria-hidden\n className=\"relative h-14\"\n // Fades in from the left so the line arrives rather than starts at a hard edge\n style={{\n maskImage: \"linear-gradient(90deg, transparent 0%, black 18%)\",\n WebkitMaskImage: \"linear-gradient(90deg, transparent 0%, black 18%)\",\n }}\n >\n <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio=\"none\" className=\"absolute inset-0 w-full h-full overflow-visible\">\n <defs>\n <linearGradient id={gradId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" style={{ stopColor: stroke }} stopOpacity={dark ? 0.2 : 0.08} />\n <stop offset=\"100%\" style={{ stopColor: stroke }} stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n <path d={area} fill={`url(#${gradId})`} />\n <path\n d={line}\n fill=\"none\"\n style={{ stroke }}\n strokeOpacity={dark ? 0.85 : 0.9}\n strokeWidth={1.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n {/* The latest value, as a dot — drawn in HTML so it stays round when the SVG stretches */}\n <span\n className=\"absolute w-[7px] h-[7px] rounded-full -translate-x-1/2 -translate-y-1/2\"\n style={{\n left: `${(lx / W) * 100}%`,\n top: `${(ly / H) * 100}%`,\n background: stroke,\n boxShadow: dark ? \"0 0 0 3px var(--spark-dark-halo)\" : \"0 0 0 3px var(--spark-halo)\",\n }}\n />\n </div>\n );\n}\n\nconst deltaTone: Record<DeltaDirection, { light: string; dark: string; label: string }> = {\n up: { light: \"text-success\", dark: \"text-emerald-400\", label: \"up\" },\n down: { light: \"text-danger\", dark: \"text-red-400\", label: \"down\" },\n neutral: { light: \"text-ink-body\", dark: \"text-neutral-300\", label: \"unchanged\" },\n};\n\nfunction DeltaArrow({ direction }: { direction: DeltaDirection }) {\n const d =\n direction === \"up\" ? \"M12 19V5m0 0l-6 6m6-6l6 6\" : direction === \"down\" ? \"M12 5v14m0 0l-6-6m6 6l6-6\" : \"M5 12h14m0 0l-6-6m6 6l-6 6\";\n return (\n <svg aria-hidden width=\"10\" height=\"10\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d={d} />\n </svg>\n );\n}\n\n/* Built on CardShell; the trend line bleeds off the bottom of the face\n like a chart on a screen. */\nexport function StatCard({\n label,\n value,\n prefix = \"\",\n suffix = \"\",\n decimals = 0,\n delta,\n hint,\n sparkline,\n icon,\n theme = \"light\",\n className = \"\",\n}: StatCardProps) {\n const animated = useAnimatedNumber(value, 900);\n const dark = theme === \"dark\";\n\n const formatter = new Intl.NumberFormat(\"en-US\", {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n });\n const finalText = `${prefix}${formatter.format(value)}${suffix}`;\n\n const badge = delta && (\n <span\n aria-label={`${deltaTone[delta.direction].label} ${delta.value}`}\n className={[\n \"inline-flex items-center gap-1 h-6 px-2 rounded-[7px] text-[12px] font-medium tabular-nums shrink-0\",\n dark ? deltaTone[delta.direction].dark : deltaTone[delta.direction].light,\n ].join(\" \")}\n style={{\n background: dark ? \"var(--tile-dark)\" : \"var(--tile)\",\n boxShadow: dark ? \"var(--tile-dark-shadow)\" : \"var(--tile-shadow)\",\n }}\n >\n <DeltaArrow direction={delta.direction} />\n {delta.value}\n </span>\n );\n\n /* Label and change share the top line — what it is, and which way it's\n going. The number carries the card; the hint qualifies it in a\n quieter voice right underneath. */\n return (\n <CardShell theme={theme} className={className}>\n <div className=\"px-6 pt-5 flex items-center justify-between gap-3 min-h-11\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n {icon && (\n <div\n aria-hidden\n className={[\n \"w-7 h-7 rounded-[9px] flex items-center justify-center shrink-0\",\n dark ? \"text-neutral-100\" : \"text-ink-body\",\n ].join(\" \")}\n style={{\n background: dark ? \"var(--tile-dark)\" : \"var(--tile)\",\n boxShadow: dark ? \"var(--tile-dark-shadow)\" : \"var(--tile-shadow)\",\n }}\n >\n {icon}\n </div>\n )}\n <p\n className={[\n \"text-[14px] font-medium tracking-[-0.01em] truncate\",\n dark ? \"text-neutral-300\" : \"text-ink-label\",\n ].join(\" \")}\n >\n {label}\n </p>\n </div>\n {badge}\n </div>\n\n {/* Screen readers get the final value once, not every frame of the count */}\n <p className=\"sr-only\">{finalText}</p>\n <p\n aria-hidden\n className={[\n \"px-6 mt-5 text-[44px] font-semibold tabular-nums tracking-[-0.045em] leading-none\",\n dark ? \"text-white\" : \"text-ink\",\n ].join(\" \")}\n >\n {prefix}\n {formatter.format(animated)}\n {/* Units are context, not the headline — set them smaller and quieter */}\n {suffix && (\n <span\n className={[\n \"ml-1 text-[17px] font-medium tracking-[-0.01em]\",\n dark ? \"text-neutral-500\" : \"text-ink-faint\",\n ].join(\" \")}\n >\n {suffix.trim()}\n </span>\n )}\n </p>\n {hint && (\n <p className={[\"px-6 mt-2.5 text-[13px] truncate\", dark ? \"text-neutral-500\" : \"text-ink-muted\"].join(\" \")}>\n {hint}\n </p>\n )}\n\n {/* The trend line bleeds to the face's edges, like a chart on a screen */}\n {sparkline && sparkline.length > 1 ? (\n <div className=\"mt-auto pt-6\">\n <Sparkline data={sparkline} dark={dark} />\n </div>\n ) : (\n <div className=\"pb-6\" />\n )}\n </CardShell>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/Steps.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface StepItem {
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
interface StepsProps {
|
|
8
|
+
steps: StepItem[];
|
|
9
|
+
current: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
declare function Steps({ steps, current, className }: StepsProps): react_jsx_runtime.JSX.Element;
|
|
13
|
+
|
|
14
|
+
export { Steps };
|