@seekalfred/primitives 0.6.0 → 0.7.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 +19 -11
- package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
- package/dist/activity-heatmap/activity-heatmap.js +699 -0
- package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
- package/dist/activity-heatmap/index.d.ts +2 -0
- package/dist/activity-heatmap/index.js +2 -0
- package/dist/alert/alert.d.ts +5 -1
- package/dist/alert/alert.js +68 -7
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +45 -18
- package/dist/alert-dialog/alert-dialog.js +128 -9
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/alert-dialog/index.d.ts +2 -2
- package/dist/alert-dialog/index.js +2 -2
- package/dist/animated-counter/animated-counter.d.ts +38 -0
- package/dist/animated-counter/animated-counter.js +230 -0
- package/dist/animated-counter/animated-counter.js.map +1 -0
- package/dist/animated-counter/index.d.ts +2 -0
- package/dist/animated-counter/index.js +2 -0
- package/dist/announcement-bar/announcement-bar.d.ts +67 -0
- package/dist/announcement-bar/announcement-bar.js +364 -0
- package/dist/announcement-bar/announcement-bar.js.map +1 -0
- package/dist/announcement-bar/index.d.ts +2 -0
- package/dist/announcement-bar/index.js +2 -0
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart.d.ts +51 -0
- package/dist/bar-chart/bar-chart.js +565 -0
- package/dist/bar-chart/bar-chart.js.map +1 -0
- package/dist/bar-chart/index.d.ts +2 -0
- package/dist/bar-chart/index.js +2 -0
- package/dist/breadcrumb/breadcrumb.d.ts +2 -2
- package/dist/breadcrumb/breadcrumb.js +12 -3
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/brush-chart/brush-chart.d.ts +64 -0
- package/dist/brush-chart/brush-chart.js +1056 -0
- package/dist/brush-chart/brush-chart.js.map +1 -0
- package/dist/brush-chart/index.d.ts +2 -0
- package/dist/brush-chart/index.js +2 -0
- package/dist/button/button.d.ts +2 -2
- package/dist/button/button.js +31 -21
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.js +3 -4
- package/dist/button-group/button-group.js.map +1 -1
- package/dist/card/card.js +1 -1
- package/dist/card/card.js.map +1 -1
- package/dist/card-stack/card-stack.d.ts +54 -0
- package/dist/card-stack/card-stack.js +492 -0
- package/dist/card-stack/card-stack.js.map +1 -0
- package/dist/card-stack/index.d.ts +2 -0
- package/dist/card-stack/index.js +2 -0
- package/dist/carousel/carousel.d.ts +56 -0
- package/dist/carousel/carousel.js +389 -0
- package/dist/carousel/carousel.js.map +1 -0
- package/dist/carousel/index.d.ts +2 -0
- package/dist/carousel/index.js +2 -0
- package/dist/checkbox/checkbox.js +4 -4
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +37 -0
- package/dist/chip-group/chip-group.js +148 -0
- package/dist/chip-group/chip-group.js.map +1 -0
- package/dist/chip-group/index.d.ts +2 -0
- package/dist/chip-group/index.js +2 -0
- package/dist/countdown/countdown.d.ts +45 -0
- package/dist/countdown/countdown.js +298 -0
- package/dist/countdown/countdown.js.map +1 -0
- package/dist/countdown/index.d.ts +2 -0
- package/dist/countdown/index.js +2 -0
- package/dist/date-picker/picker-parts.js +1 -1
- package/dist/date-picker/picker-parts.js.map +1 -1
- package/dist/dialog/dialog.js +1 -1
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +64 -0
- package/dist/donut-chart/donut-chart.js +576 -0
- package/dist/donut-chart/donut-chart.js.map +1 -0
- package/dist/donut-chart/index.d.ts +2 -0
- package/dist/donut-chart/index.js +2 -0
- package/dist/empty-state/empty-state.d.ts +21 -10
- package/dist/empty-state/empty-state.js +32 -26
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.js +1 -1
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +47 -26
- package/dist/filter-bar/filter-bar.js +272 -156
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +45 -0
- package/dist/gauge/gauge.js +219 -0
- package/dist/gauge/gauge.js.map +1 -0
- package/dist/gauge/index.d.ts +2 -0
- package/dist/gauge/index.js +2 -0
- package/dist/in-view-title/in-view-title.d.ts +33 -0
- package/dist/in-view-title/in-view-title.js +119 -0
- package/dist/in-view-title/in-view-title.js.map +1 -0
- package/dist/in-view-title/index.d.ts +2 -0
- package/dist/in-view-title/index.js +2 -0
- package/dist/index.d.ts +49 -6
- package/dist/index.js +48 -8
- package/dist/lib/cn.js +11 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/field-message.js +28 -10
- package/dist/lib/field-message.js.map +1 -1
- package/dist/lib/field-surface.js +9 -8
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flag-emoji.js +50 -0
- package/dist/lib/flag-emoji.js.map +1 -0
- package/dist/lib/flip.js +11 -3
- package/dist/lib/flip.js.map +1 -1
- package/dist/lib/menu-highlight.js +64 -0
- package/dist/lib/menu-highlight.js.map +1 -0
- package/dist/lib/motion.js +9 -2
- package/dist/lib/motion.js.map +1 -1
- package/dist/lib/rolling-value.js +1 -1
- package/dist/lib/rolling-value.js.map +1 -1
- package/dist/lib/spring-tween.js +500 -0
- package/dist/lib/spring-tween.js.map +1 -0
- package/dist/lib/swap-text.js +3 -1
- package/dist/lib/swap-text.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -0
- package/dist/line-chart/index.js +2 -0
- package/dist/line-chart/line-chart.d.ts +76 -0
- package/dist/line-chart/line-chart.js +611 -0
- package/dist/line-chart/line-chart.js.map +1 -0
- package/dist/mention-input/index.d.ts +2 -0
- package/dist/mention-input/index.js +2 -0
- package/dist/mention-input/mention-input.d.ts +98 -0
- package/dist/mention-input/mention-input.js +576 -0
- package/dist/mention-input/mention-input.js.map +1 -0
- package/dist/menu/menu.d.ts +2 -0
- package/dist/menu/menu.js +23 -12
- package/dist/menu/menu.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -0
- package/dist/money-input/index.js +2 -0
- package/dist/money-input/money-input.d.ts +65 -0
- package/dist/money-input/money-input.js +607 -0
- package/dist/money-input/money-input.js.map +1 -0
- package/dist/morph-select/index.d.ts +2 -0
- package/dist/morph-select/index.js +2 -0
- package/dist/morph-select/morph-select.d.ts +71 -0
- package/dist/morph-select/morph-select.js +693 -0
- package/dist/morph-select/morph-select.js.map +1 -0
- package/dist/multi-select/index.d.ts +2 -0
- package/dist/multi-select/index.js +2 -0
- package/dist/multi-select/multi-select.d.ts +36 -0
- package/dist/multi-select/multi-select.js +152 -0
- package/dist/multi-select/multi-select.js.map +1 -0
- package/dist/number-field/index.d.ts +2 -0
- package/dist/number-field/index.js +2 -0
- package/dist/number-field/number-field.d.ts +42 -0
- package/dist/number-field/number-field.js +199 -0
- package/dist/number-field/number-field.js.map +1 -0
- package/dist/onboarding-checklist/index.d.ts +2 -0
- package/dist/onboarding-checklist/index.js +2 -0
- package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
- package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
- package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
- package/dist/password-field/password-field.js +2 -2
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/phone-input/index.d.ts +3 -0
- package/dist/phone-input/index.js +3 -0
- package/dist/phone-input/phone-countries.d.ts +29 -0
- package/dist/phone-input/phone-countries.js +170 -0
- package/dist/phone-input/phone-countries.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +66 -0
- package/dist/phone-input/phone-input.js +440 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +12 -0
- package/dist/popover/popover.js +31 -7
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.js +1 -1
- package/dist/radio-group/radio-group.js +1 -1
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/reactions/index.d.ts +2 -0
- package/dist/reactions/index.js +2 -0
- package/dist/reactions/reactions.d.ts +50 -0
- package/dist/reactions/reactions.js +315 -0
- package/dist/reactions/reactions.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +3 -0
- package/dist/rich-text-editor/index.js +2 -0
- package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
- package/dist/rich-text-editor/rich-text-editor.js +909 -0
- package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
- package/dist/rich-text-editor/rich-text-engine.js +366 -0
- package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
- package/dist/rich-text-editor/rich-text-html.js +174 -0
- package/dist/rich-text-editor/rich-text-html.js.map +1 -0
- package/dist/rich-text-editor/rich-text-icons.js +43 -0
- package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
- package/dist/ridgeline/index.d.ts +2 -0
- package/dist/ridgeline/index.js +2 -0
- package/dist/ridgeline/ridgeline.d.ts +55 -0
- package/dist/ridgeline/ridgeline.js +701 -0
- package/dist/ridgeline/ridgeline.js.map +1 -0
- package/dist/scroll-area/index.d.ts +2 -2
- package/dist/scroll-area/index.js +2 -2
- package/dist/scroll-area/scroll-area.d.ts +68 -31
- package/dist/scroll-area/scroll-area.js +208 -59
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/evaluate.d.ts +14 -0
- package/dist/scrub-input/evaluate.js +72 -0
- package/dist/scrub-input/evaluate.js.map +1 -0
- package/dist/scrub-input/index.d.ts +3 -0
- package/dist/scrub-input/index.js +3 -0
- package/dist/scrub-input/scrub-input.d.ts +74 -0
- package/dist/scrub-input/scrub-input.js +486 -0
- package/dist/scrub-input/scrub-input.js.map +1 -0
- package/dist/search-field/search-field.d.ts +2 -1
- package/dist/search-field/search-field.js +4 -4
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +20 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/sheet/sheet.js +2 -1
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/shortcut-recorder/index.d.ts +3 -0
- package/dist/shortcut-recorder/index.js +3 -0
- package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
- package/dist/shortcut-recorder/shortcut-model.js +206 -0
- package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
- package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
- package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/slider/index.d.ts +2 -2
- package/dist/slider/slider.d.ts +46 -14
- package/dist/slider/slider.js +151 -67
- package/dist/slider/slider.js.map +1 -1
- package/dist/slope-chart/index.d.ts +2 -0
- package/dist/slope-chart/index.js +2 -0
- package/dist/slope-chart/slope-chart.d.ts +54 -0
- package/dist/slope-chart/slope-chart.js +568 -0
- package/dist/slope-chart/slope-chart.js.map +1 -0
- package/dist/sparkline/index.d.ts +2 -0
- package/dist/sparkline/index.js +2 -0
- package/dist/sparkline/sparkline.d.ts +46 -0
- package/dist/sparkline/sparkline.js +390 -0
- package/dist/sparkline/sparkline.js.map +1 -0
- package/dist/split-bar/index.d.ts +2 -0
- package/dist/split-bar/index.js +2 -0
- package/dist/split-bar/split-bar.d.ts +62 -0
- package/dist/split-bar/split-bar.js +513 -0
- package/dist/split-bar/split-bar.js.map +1 -0
- package/dist/split-button/split-button.d.ts +1 -1
- package/dist/split-button/split-button.js +12 -11
- package/dist/split-button/split-button.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +24 -4
- package/dist/stat-card/stat-card.js +114 -17
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/stepper/index.d.ts +2 -0
- package/dist/stepper/index.js +2 -0
- package/dist/stepper/stepper.d.ts +51 -0
- package/dist/stepper/stepper.js +215 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/streamgraph/index.d.ts +2 -0
- package/dist/streamgraph/index.js +2 -0
- package/dist/streamgraph/streamgraph.d.ts +70 -0
- package/dist/streamgraph/streamgraph.js +613 -0
- package/dist/streamgraph/streamgraph.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/swipe-actions/index.d.ts +2 -0
- package/dist/swipe-actions/index.js +2 -0
- package/dist/swipe-actions/swipe-actions.d.ts +67 -0
- package/dist/swipe-actions/swipe-actions.js +552 -0
- package/dist/swipe-actions/swipe-actions.js.map +1 -0
- package/dist/switch/switch.js +2 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +56 -4
- package/dist/table/table.js +340 -165
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +2 -2
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/tag-input/index.d.ts +2 -0
- package/dist/tag-input/index.js +2 -0
- package/dist/tag-input/tag-input.d.ts +40 -0
- package/dist/tag-input/tag-input.js +269 -0
- package/dist/tag-input/tag-input.js.map +1 -0
- package/dist/text-morph/index.d.ts +2 -0
- package/dist/text-morph/index.js +2 -0
- package/dist/text-morph/text-morph.d.ts +23 -0
- package/dist/text-morph/text-morph.js +21 -0
- package/dist/text-morph/text-morph.js.map +1 -0
- package/dist/text-reveal/index.d.ts +2 -0
- package/dist/text-reveal/index.js +2 -0
- package/dist/text-reveal/text-reveal.d.ts +27 -0
- package/dist/text-reveal/text-reveal.js +51 -0
- package/dist/text-reveal/text-reveal.js.map +1 -0
- package/dist/text-shimmer/index.d.ts +2 -0
- package/dist/text-shimmer/index.js +2 -0
- package/dist/text-shimmer/text-shimmer.d.ts +29 -0
- package/dist/text-shimmer/text-shimmer.js +29 -0
- package/dist/text-shimmer/text-shimmer.js.map +1 -0
- package/dist/time-picker/time-picker.js +2 -2
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/index.d.ts +2 -2
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +6 -0
- package/dist/toast/toast.js +82 -40
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +9 -2
- package/dist/tooltip/tooltip.js +7 -7
- package/dist/tooltip/tooltip.js.map +1 -1
- package/dist/treemap/index.d.ts +2 -0
- package/dist/treemap/index.js +2 -0
- package/dist/treemap/treemap.d.ts +53 -0
- package/dist/treemap/treemap.js +699 -0
- package/dist/treemap/treemap.js.map +1 -0
- package/dist/usage-meter/index.d.ts +2 -0
- package/dist/usage-meter/index.js +2 -0
- package/dist/usage-meter/usage-meter.d.ts +50 -0
- package/dist/usage-meter/usage-meter.js +440 -0
- package/dist/usage-meter/usage-meter.js.map +1 -0
- package/dist/waffle-chart/index.d.ts +2 -0
- package/dist/waffle-chart/index.js +2 -0
- package/dist/waffle-chart/waffle-chart.d.ts +57 -0
- package/dist/waffle-chart/waffle-chart.js +426 -0
- package/dist/waffle-chart/waffle-chart.js.map +1 -0
- package/package.json +165 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split-button.js","names":["AriaButton","Menu","MenuItem"],"sources":["../../src/split-button/split-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { isValidElement, useRef, type ReactNode } from \"react\";\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { MorphText } from \"../lib/morph-text.js\";\nimport { useMorphWidth } from \"../lib/motion.js\";\nimport { Menu, MenuItem, MenuTrigger } from \"../menu/menu.js\";\n\n/** One entry in the menu half. */\nexport interface SplitButtonAction {\n /** The item's text, and its identity. */\n label: string;\n /** Runs when the item is chosen. */\n onSelect?: () => void;\n isDisabled?: boolean;\n /** Tints the item
|
|
1
|
+
{"version":3,"file":"split-button.js","names":["AriaButton","Menu","MenuItem"],"sources":["../../src/split-button/split-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { isValidElement, useRef, type ReactNode } from \"react\";\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { MorphText } from \"../lib/morph-text.js\";\nimport { useMorphWidth } from \"../lib/motion.js\";\nimport { Menu, MenuItem, MenuTrigger } from \"../menu/menu.js\";\n\n/** One entry in the menu half. */\nexport interface SplitButtonAction {\n /** The item's text, and its identity. */\n label: string;\n /** Runs when the item is chosen. */\n onSelect?: () => void;\n isDisabled?: boolean;\n /** Tints the item red, and the menu's highlight with it. Pair it with a trash icon. */\n destructive?: boolean;\n /** An icon before the label. Mark it `aria-hidden`. */\n icon?: ReactNode;\n}\n\nexport interface SplitButtonProps {\n /** The main action's label. A plain string: it is split into letters to morph when it changes. */\n children: string;\n /** The menu half's items: the nearby alternatives to the main action. */\n actions: SplitButtonAction[];\n /** Runs when the main half is pressed. */\n onPress?: () => void;\n isDisabled?: boolean;\n /** An icon before the label. Swapping it for another icon morphs; re-rendering the same icon does not. Mark it `aria-hidden`. */\n icon?: ReactNode;\n /** `secondary` is the quiet, outlined pill. @default \"primary\" */\n variant?: \"primary\" | \"secondary\";\n /** Extra classes for the pill. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** Names the icon element, so swapping Copy for Check morphs while a re-render of the same icon stays still. */\nfunction iconKey(node: ReactNode): string {\n if (!isValidElement(node))\n return typeof node === \"string\" || typeof node === \"number\" ? String(node) : \"\";\n const type = node.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type?.displayName ?? type?.name ?? \"icon\");\n}\n\n/*\n * Class strings are static literals: Tailwind scans source text.\n *\n * Both halves share the focus outline, the tap-highlight reset and the\n * disabled look. The pill, not either half, scales on press (split-pill), and\n * the menu half never scales, so the menu opens from a still anchor.\n */\nconst half = [\n \"relative inline-flex items-center justify-center cursor-pointer select-none font-sans font-medium\",\n // Resets the browser's own button background, border and text colour: the pill\n // paints the surface and sets the colour, which the halves inherit.\n \"border-0 bg-transparent text-inherit\",\n \"[-webkit-tap-highlight-color:transparent]\",\n \"outline-none\",\n \"data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring)\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-button-disabled)\",\n \"motion-safe:transition-[background-color,color,opacity] motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n].join(\" \");\n\n// One fully rounded pill in the brand primary (orange, black text, as Button's\n// primary): both halves show the one fill, set apart by a subtle divider, so\n// they can never drift apart. Hover and press repaint a half with the primary\n// hover and pressed tokens, as Button does; the menu half keeps the rest fill while its menu is open.\nconst looks = {\n primary: {\n pill: \"bg-primary text-primary-foreground shadow-surface rounded-full\",\n main: \"h-(--density-control-h-md) ps-(--density-control-pad-md) pe-4 text-base rounded-s-full data-[hovered]:bg-primary-hover data-[pressed]:bg-primary-pressed\",\n trigger:\n \"h-(--density-control-h-md) w-11 rounded-e-full border-s-(length:--border-width) border-primary-foreground/20 aria-[expanded=false]:data-[hovered]:bg-primary-hover aria-[expanded=false]:data-[pressed]:bg-primary-pressed\",\n },\n secondary: {\n pill: \"bg-surface text-foreground border-(length:--border-width) border-border shadow-surface rounded-full\",\n main: \"h-(--density-control-h-sm) ps-(--density-control-pad-sm) pe-3 text-sm rounded-s-full data-[hovered]:bg-muted data-[pressed]:bg-muted\",\n trigger:\n \"h-(--density-control-h-sm) w-9 rounded-e-full border-s-(length:--border-width) border-border data-[hovered]:bg-muted data-[pressed]:bg-muted\",\n },\n} as const;\n\n/**\n * One pill, two actions: a main action and a menu of nearby alternatives.\n *\n * The main half morphs in place: change `children` (or `icon`) and the letters\n * that differ rise in while the old ones lift away, the icon swaps by scaling,\n * and the width springs to fit. The whole pill presses in for the main action;\n * the menu half answers in colour only, so the menu opens from a still anchor,\n * growing from its trigger, its items arriving one after another.\n *\n * @example\n * <SplitButton onPress={save} actions={[{ label: \"Save as draft\" }, { label: \"Delete\", destructive: true }]}>\n * Save\n * </SplitButton>\n */\nexport function SplitButton({\n children: label,\n actions,\n onPress,\n isDisabled,\n icon,\n variant = \"primary\",\n className,\n}: SplitButtonProps) {\n const contentRef = useRef<HTMLSpanElement>(null);\n const glyph = iconKey(icon);\n const look = looks[variant];\n useMorphWidth(contentRef, glyph + \"|\" + label);\n\n return (\n <div\n role=\"group\"\n className={cn(\"split-pill inline-flex items-stretch \" + look.pill, className)}\n >\n <AriaButton\n data-slot=\"split-main\"\n className={cn(half + \" \" + look.main)}\n isDisabled={isDisabled}\n onPress={onPress}\n >\n <span\n className=\"button-slot [--morph-clip:inset(-50%_calc(var(--spacing)*-2))]\"\n aria-hidden=\"true\"\n >\n <span\n ref={contentRef}\n className=\"inline-flex flex-none items-center gap-2 whitespace-nowrap [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-(--icon-sm)\"\n >\n {icon ? <IconSwap iconKey={glyph}>{icon}</IconSwap> : null}\n <MorphText text={label} morphWidth={false} />\n </span>\n </span>\n <span className=\"sr-only\" aria-live=\"polite\">\n {label}\n </span>\n </AriaButton>\n <MenuTrigger>\n <AriaButton\n data-slot=\"split-trigger\"\n aria-label={label + \" more actions\"}\n isDisabled={isDisabled}\n className={cn(\"group/trigger \" + half + \" \" + look.trigger)}\n >\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"size-4 motion-safe:transition-[rotate] motion-safe:duration-(--duration-gentle) motion-safe:ease-gentle group-aria-expanded/trigger:rotate-180\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </AriaButton>\n <Menu placement=\"bottom end\" offset={6} shouldFocusWrap>\n {actions.map((action) => (\n <MenuItem\n key={action.label}\n id={action.label}\n textValue={action.label}\n isDisabled={action.isDisabled}\n onAction={action.onSelect}\n destructive={action.destructive}\n >\n {action.icon ? (\n <span\n aria-hidden=\"true\"\n className={action.destructive ? \"inline-flex\" : \"inline-flex text-text-secondary\"}\n >\n {action.icon}\n </span>\n ) : null}\n {action.label}\n </MenuItem>\n ))}\n </Menu>\n </MenuTrigger>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAyCA,SAAS,QAAQ,MAAyB;CACxC,IAAI,CAAC,eAAe,IAAI,GACtB,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW,OAAO,IAAI,IAAI;CAC/E,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,MAAM,eAAe,MAAM,QAAQ;AAC/E;AASA,MAAM,OAAO;CACX;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,GAAG;AAMV,MAAM,QAAQ;CACZ,SAAS;EACP,MAAM;EACN,MAAM;EACN,SACE;CACJ;CACA,WAAW;EACT,MAAM;EACN,MAAM;EACN,SACE;CACJ;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,YAAY,EAC1B,UAAU,OACV,SACA,SACA,YACA,MACA,UAAU,WACV,aACmB;CACnB,MAAM,aAAa,OAAwB,IAAI;CAC/C,MAAM,QAAQ,QAAQ,IAAI;CAC1B,MAAM,OAAO,MAAM;CACnB,cAAc,YAAY,QAAQ,MAAM,KAAK;CAE7C,OACE,qBAAC,OAAD;EACE,MAAK;EACL,WAAW,GAAG,0CAA0C,KAAK,MAAM,SAAS;EAF9E,UAAA,CAIE,qBAACA,QAAD;GACE,aAAU;GACV,WAAW,GAAG,OAAO,MAAM,KAAK,IAAI;GACxB;GACH;GAJX,UAAA,CAME,oBAAC,QAAD;IACE,WAAU;IACV,eAAY;IAEZ,UAAA,qBAAC,QAAD;KACE,KAAK;KACL,WAAU;KAFZ,UAAA,CAIG,OAAO,oBAAC,UAAD;MAAU,SAAS;MAAQ,UAAA;KAAe,CAAA,IAAI,MACtD,oBAAC,WAAD;MAAW,MAAM;MAAO,YAAY;KAAQ,CAAA,CACxC;;GACF,CAAA,GACN,oBAAC,QAAD;IAAM,WAAU;IAAU,aAAU;IACjC,UAAA;GACG,CAAA,CACI;EACZ,CAAA,GAAA,qBAAC,aAAD,EAAA,UAAA,CACE,oBAACA,QAAD;GACE,aAAU;GACV,cAAY,QAAQ;GACR;GACZ,WAAW,GAAG,mBAAmB,OAAO,MAAM,KAAK,OAAO;GAE1D,UAAA,oBAAC,OAAD;IACE,SAAQ;IACR,MAAK;IACL,QAAO;IACP,aAAa;IACb,eAAc;IACd,gBAAe;IACf,eAAY;IACZ,WAAU;IAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,eAAgB,CAAA;GACrB,CAAA;EACK,CAAA,GACZ,oBAACC,QAAD;GAAM,WAAU;GAAa,QAAQ;GAAG,iBAAA;GACrC,UAAA,QAAQ,KAAK,WACZ,qBAACC,YAAD;IAEE,IAAI,OAAO;IACX,WAAW,OAAO;IAClB,YAAY,OAAO;IACnB,UAAU,OAAO;IACjB,aAAa,OAAO;IANtB,UAAA,CAQG,OAAO,OACN,oBAAC,QAAD;KACE,eAAY;KACZ,WAAW,OAAO,cAAc,gBAAgB;KAE/C,UAAA,OAAO;IACJ,CAAA,IACJ,MACH,OAAO,KACA;GAhBH,GAAA,OAAO,KAgBJ,CACX;EACG,CAAA,CACK,EAAA,CAAA,CACV;;AAET"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { StatCard, StatCardProps, StatCardTone, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
-
export { StatCard, type StatCardProps, type StatCardTone, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
|
1
|
+
import { StatCard, StatCardProps, StatCardTone, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
+
export { StatCard, type StatCardProps, type StatCardTone, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
package/dist/stat-card/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { StatCard, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
-
export { StatCard, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
|
1
|
+
import { StatCard, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
+
export { StatCard, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
|
@@ -8,7 +8,14 @@ import { ReactNode } from "react";
|
|
|
8
8
|
* without a shared card each screen picks its own label case, number size,
|
|
9
9
|
* colour and height, so the same figure looks different on two pages.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* The figure is a counter: a number rolls up to its value the first time the
|
|
12
|
+
* card scrolls into view, and when `value` changes the digits that changed roll
|
|
13
|
+
* to their new faces, the way the number moved. A label, a line of context or
|
|
14
|
+
* a change that is replaced rises in while the old copy lifts away. Reduced
|
|
15
|
+
* motion jumps and fades instead. A value that is not a plain number
|
|
16
|
+
* (a date, "N/A", a node) is shown as it is.
|
|
17
|
+
*
|
|
18
|
+
* Rules it holds:
|
|
12
19
|
*
|
|
13
20
|
* - The label is ONE line. A long label is cut with an ellipsis and the full
|
|
14
21
|
* text is on hover, so a row of cards keeps its numbers on one baseline
|
|
@@ -30,6 +37,8 @@ export declare const statCardVariants: (props?: ({
|
|
|
30
37
|
selected?: boolean | null | undefined;
|
|
31
38
|
interactive?: boolean | null | undefined;
|
|
32
39
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
40
|
+
/** The top row: the label on the left, the change pill on the right. */
|
|
41
|
+
export declare const statCardHeaderVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
33
42
|
export declare const statCardLabelVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
34
43
|
export declare const statCardValueVariants: (props?: ({
|
|
35
44
|
tone?: "neutral" | "warning" | "positive" | "negative" | "attention" | null | undefined;
|
|
@@ -39,10 +48,21 @@ export type StatCardTone = "neutral" | "positive" | "negative" | "warning" | "at
|
|
|
39
48
|
export interface StatCardProps {
|
|
40
49
|
/** What the figure is. One line; cut with an ellipsis, full text on hover. */
|
|
41
50
|
label: string;
|
|
42
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* The figure itself, already formatted. A plain number (`12400`), or a string
|
|
53
|
+
* made of a number with text around it (`"$12,400"`, `"42 days"`, `"98.5%"`),
|
|
54
|
+
* counts up on first view and rolls to each new value. Anything else is
|
|
55
|
+
* shown as it is.
|
|
56
|
+
*/
|
|
43
57
|
value: ReactNode;
|
|
44
|
-
/** One line of context under the figure. */
|
|
58
|
+
/** One line of context under the figure. A string that changes swaps in place. */
|
|
45
59
|
sub?: ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* A change shown as a pill beside the label, such as `"+12.4%"`. A leading `+`
|
|
62
|
+
* reads green and a leading `-` red; no sign is neutral. A new value swaps in
|
|
63
|
+
* place and the pill follows its width.
|
|
64
|
+
*/
|
|
65
|
+
change?: string;
|
|
46
66
|
/**
|
|
47
67
|
* What the figure means: `neutral` (default, and right for every zero),
|
|
48
68
|
* `positive`, `negative`, `warning` (part-way or waiting) or `attention`
|
|
@@ -71,7 +91,7 @@ export interface StatCardProps {
|
|
|
71
91
|
*
|
|
72
92
|
* @example
|
|
73
93
|
* <div className="grid gap-4 sm:grid-cols-3">
|
|
74
|
-
* <StatCard label="Open" value="$12,400" sub="18 invoices" />
|
|
94
|
+
* <StatCard label="Open" value="$12,400" sub="18 invoices" change="+4.2%" />
|
|
75
95
|
* <StatCard label="Overdue" value="$3,100" sub="4 invoices" tone="negative" />
|
|
76
96
|
* <StatCard label="Collected" value="$9,800" tone="positive" href="/payments" />
|
|
77
97
|
* </div>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { SwapText } from "../lib/swap-text.js";
|
|
4
|
+
import { AnimatedCounter } from "../animated-counter/animated-counter.js";
|
|
5
|
+
import { Badge } from "../badge/badge.js";
|
|
3
6
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import { forwardRef } from "react";
|
|
7
|
+
import { forwardRef, useState } from "react";
|
|
5
8
|
import { Button, Link, ToggleButton } from "react-aria-components";
|
|
6
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
10
|
//#region src/stat-card/stat-card.tsx
|
|
@@ -13,7 +16,14 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
13
16
|
* without a shared card each screen picks its own label case, number size,
|
|
14
17
|
* colour and height, so the same figure looks different on two pages.
|
|
15
18
|
*
|
|
16
|
-
*
|
|
19
|
+
* The figure is a counter: a number rolls up to its value the first time the
|
|
20
|
+
* card scrolls into view, and when `value` changes the digits that changed roll
|
|
21
|
+
* to their new faces, the way the number moved. A label, a line of context or
|
|
22
|
+
* a change that is replaced rises in while the old copy lifts away. Reduced
|
|
23
|
+
* motion jumps and fades instead. A value that is not a plain number
|
|
24
|
+
* (a date, "N/A", a node) is shown as it is.
|
|
25
|
+
*
|
|
26
|
+
* Rules it holds:
|
|
17
27
|
*
|
|
18
28
|
* - The label is ONE line. A long label is cut with an ellipsis and the full
|
|
19
29
|
* text is on hover, so a row of cards keeps its numbers on one baseline
|
|
@@ -32,8 +42,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
32
42
|
* simply never be emitted.
|
|
33
43
|
*/
|
|
34
44
|
const statCardVariants = cva([
|
|
35
|
-
"flex h-full min-w-0 flex-col p-
|
|
36
|
-
"rounded-
|
|
45
|
+
"flex h-full min-w-0 flex-col p-6 text-start",
|
|
46
|
+
"rounded-panel border-(length:--border-width) bg-surface text-foreground shadow-resting",
|
|
37
47
|
"box-border font-sans"
|
|
38
48
|
], {
|
|
39
49
|
variants: {
|
|
@@ -60,8 +70,10 @@ const statCardVariants = cva([
|
|
|
60
70
|
interactive: false
|
|
61
71
|
}
|
|
62
72
|
});
|
|
63
|
-
|
|
64
|
-
const
|
|
73
|
+
/** The top row: the label on the left, the change pill on the right. */
|
|
74
|
+
const statCardHeaderVariants = cva("mb-8 flex min-h-6 items-center justify-between gap-3");
|
|
75
|
+
const statCardLabelVariants = cva(["m-0 min-w-0 flex-1 text-sm leading-(--leading-normal) font-normal", "text-text-secondary"]);
|
|
76
|
+
const statCardValueVariants = cva("m-0 font-display text-h2 leading-none font-semibold tabular-nums break-words **:data-[slot=animated-counter]:text-inherit", {
|
|
65
77
|
variants: {
|
|
66
78
|
/** What the number MEANS. Colour follows from it; it is never picked directly. */
|
|
67
79
|
tone: {
|
|
@@ -73,38 +85,123 @@ tone: {
|
|
|
73
85
|
} },
|
|
74
86
|
defaultVariants: { tone: "neutral" }
|
|
75
87
|
});
|
|
76
|
-
const statCardSubVariants = cva("m-0 mt-
|
|
88
|
+
const statCardSubVariants = cva("m-0 mt-4 text-sm leading-(--leading-normal) text-muted-foreground");
|
|
89
|
+
/** The number in a piece of text, for the direction a swap moves: a larger one rises from below. */
|
|
90
|
+
const amountIn = (text) => Number(text.replace(/,/g, "").match(/-?\d+(?:\.\d+)?/)?.[0] ?? NaN);
|
|
91
|
+
/** 1 when the new text holds a number that did not shrink, -1 when it did. Updated during render. */
|
|
92
|
+
function useSwapDirection(text) {
|
|
93
|
+
const [shown, setShown] = useState({
|
|
94
|
+
text,
|
|
95
|
+
dir: 1
|
|
96
|
+
});
|
|
97
|
+
if (shown.text !== text) setShown({
|
|
98
|
+
text,
|
|
99
|
+
dir: amountIn(text ?? "") < amountIn(shown.text ?? "") ? -1 : 1
|
|
100
|
+
});
|
|
101
|
+
return shown.dir;
|
|
102
|
+
}
|
|
103
|
+
/** Copy that is replaced rises in from the side it moved toward and the old copy lifts away. */
|
|
104
|
+
function Swap({ text, className, children }) {
|
|
105
|
+
const dir = useSwapDirection(text);
|
|
106
|
+
return /* @__PURE__ */ jsx(SwapText, {
|
|
107
|
+
swapKey: text,
|
|
108
|
+
dir,
|
|
109
|
+
className: cn("block min-w-0", className),
|
|
110
|
+
children
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const FORMAT = {};
|
|
114
|
+
const format = (decimals) => FORMAT[decimals] ??= new Intl.NumberFormat("en-US", {
|
|
115
|
+
minimumFractionDigits: decimals,
|
|
116
|
+
maximumFractionDigits: decimals
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* A number with text around it, or null. Only a figure that the counter would
|
|
120
|
+
* draw back as the very same text qualifies ("12400" would come back as
|
|
121
|
+
* "12,400", so it is shown as written rather than quietly reformatted).
|
|
122
|
+
*/
|
|
123
|
+
function figureOf(value) {
|
|
124
|
+
if (typeof value === "number") {
|
|
125
|
+
if (!Number.isFinite(value)) return null;
|
|
126
|
+
return {
|
|
127
|
+
prefix: "",
|
|
128
|
+
value,
|
|
129
|
+
decimals: Math.min(4, (String(value).split(".")[1] ?? "").length),
|
|
130
|
+
suffix: ""
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (typeof value !== "string") return null;
|
|
134
|
+
const match = /^(\D*?)(-?\d[\d,]*(?:\.\d+)?)(\D*)$/.exec(value);
|
|
135
|
+
if (!match) return null;
|
|
136
|
+
const [, prefix = "", digits = "", suffix = ""] = match;
|
|
137
|
+
const decimals = (digits.split(".")[1] ?? "").length;
|
|
138
|
+
const number = Number(digits.replace(/,/g, ""));
|
|
139
|
+
if (!Number.isFinite(number) || `${prefix}${format(decimals).format(number)}${suffix}` !== value) return null;
|
|
140
|
+
return {
|
|
141
|
+
prefix,
|
|
142
|
+
value: number,
|
|
143
|
+
decimals,
|
|
144
|
+
suffix
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const changeTone = (change) => /^\+/.test(change) ? "success" : /^[-−]/.test(change) ? "danger" : "neutral";
|
|
77
148
|
/**
|
|
78
149
|
* One headline figure.
|
|
79
150
|
*
|
|
80
151
|
* @example
|
|
81
152
|
* <div className="grid gap-4 sm:grid-cols-3">
|
|
82
|
-
* <StatCard label="Open" value="$12,400" sub="18 invoices" />
|
|
153
|
+
* <StatCard label="Open" value="$12,400" sub="18 invoices" change="+4.2%" />
|
|
83
154
|
* <StatCard label="Overdue" value="$3,100" sub="4 invoices" tone="negative" />
|
|
84
155
|
* <StatCard label="Collected" value="$9,800" tone="positive" href="/payments" />
|
|
85
156
|
* </div>
|
|
86
157
|
*/
|
|
87
|
-
const StatCard = forwardRef(function StatCard({ label, value, sub, tone, isSelected, href, onPress, description, children, className, ...rest }, ref) {
|
|
158
|
+
const StatCard = forwardRef(function StatCard({ label, value, sub, change, tone, isSelected, href, onPress, description, children, className, ...rest }, ref) {
|
|
159
|
+
const figure = figureOf(value);
|
|
88
160
|
const body = /* @__PURE__ */ jsxs("span", {
|
|
89
161
|
className: "flex min-w-0 flex-1 flex-col",
|
|
90
162
|
title: description,
|
|
91
163
|
children: [
|
|
92
|
-
/* @__PURE__ */
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
164
|
+
/* @__PURE__ */ jsxs("span", {
|
|
165
|
+
className: statCardHeaderVariants(),
|
|
166
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
167
|
+
"data-slot": "stat-card-label",
|
|
168
|
+
className: cn(statCardLabelVariants(), "block"),
|
|
169
|
+
children: /* @__PURE__ */ jsx(Swap, {
|
|
170
|
+
text: label,
|
|
171
|
+
className: "*:max-w-full",
|
|
172
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
173
|
+
className: "block truncate",
|
|
174
|
+
title: label,
|
|
175
|
+
children: label
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
}), change ? /* @__PURE__ */ jsx(Badge, {
|
|
179
|
+
shape: "pill",
|
|
180
|
+
tone: changeTone(change),
|
|
181
|
+
appearance: changeTone(change) === "neutral" ? "outline" : "soft",
|
|
182
|
+
className: "tabular-nums",
|
|
183
|
+
children: change
|
|
184
|
+
}) : null]
|
|
97
185
|
}),
|
|
98
186
|
/* @__PURE__ */ jsx("span", {
|
|
99
187
|
"data-slot": "stat-card-value",
|
|
100
188
|
"data-tone": tone ?? "neutral",
|
|
101
189
|
className: cn(statCardValueVariants({ tone }), "block"),
|
|
102
|
-
children:
|
|
190
|
+
children: figure ? /* @__PURE__ */ jsx(AnimatedCounter, {
|
|
191
|
+
value: figure.value,
|
|
192
|
+
decimals: figure.decimals,
|
|
193
|
+
prefix: figure.prefix,
|
|
194
|
+
suffix: figure.suffix,
|
|
195
|
+
animateOnView: true
|
|
196
|
+
}) : value
|
|
103
197
|
}),
|
|
104
198
|
sub === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
105
199
|
"data-slot": "stat-card-sub",
|
|
106
200
|
className: cn(statCardSubVariants(), "block"),
|
|
107
|
-
children: sub
|
|
201
|
+
children: typeof sub === "string" ? /* @__PURE__ */ jsx(Swap, {
|
|
202
|
+
text: sub,
|
|
203
|
+
children: sub
|
|
204
|
+
}) : sub
|
|
108
205
|
}),
|
|
109
206
|
children
|
|
110
207
|
]
|
|
@@ -147,6 +244,6 @@ const StatCard = forwardRef(function StatCard({ label, value, sub, tone, isSelec
|
|
|
147
244
|
});
|
|
148
245
|
});
|
|
149
246
|
//#endregion
|
|
150
|
-
export { StatCard, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
|
247
|
+
export { StatCard, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
|
151
248
|
|
|
152
249
|
//# sourceMappingURL=stat-card.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-card.js","names":["AriaToggleButton","AriaButton","AriaLink"],"sources":["../../src/stat-card/stat-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode, type Ref } from \"react\";\nimport {\n Button as AriaButton,\n Link as AriaLink,\n ToggleButton as AriaToggleButton,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * StatCard: one headline figure with its label and a line of context. A row\n * of them is a dashboard's summary strip.\n *\n * It exists because a summary strip is on almost every overview screen, and\n * without a shared card each screen picks its own label case, number size,\n * colour and height, so the same figure looks different on two pages.\n *\n * Three rules it holds:\n *\n * - The label is ONE line. A long label is cut with an ellipsis and the full\n * text is on hover, so a row of cards keeps its numbers on one baseline\n * instead of one card's label wrapping and pushing its number down.\n * - The number's colour comes from `tone`, a fixed set of meanings, never a\n * colour. `neutral` is the default and is right for any figure without a\n * verdict in it, including every zero.\n * - Cards in a row are the same height (`h-full` in a grid or flex row).\n *\n * A card can be a link (`href`, React Aria Link, so a router provider routes\n * it), a button (`onPress`), or a filter toggle (`isSelected` + `onPress`, a\n * React Aria ToggleButton with `aria-pressed` and one shared selected look).\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const statCardVariants = cva(\n [\n \"flex h-full min-w-0 flex-col p-4 text-start\",\n \"rounded-lg border-(length:--border-width) bg-card text-card-foreground\",\n // box-border and font-sans are not boilerplate: the reset in\n // @seekalfred/styles is opt-in, so a consumer may load none.\n \"box-border font-sans\",\n ],\n {\n variants: {\n /** A filter card that is the one applied. */\n selected: {\n true: \"border-primary-strong\",\n false: \"border-border\",\n },\n /** Whether the card is a link or a button (hover and focus states). */\n interactive: {\n true: [\n \"cursor-pointer outline-none\",\n // A border, not a fill: a muted fill under the muted label fails contrast.\n \"data-[hovered]:not-data-[selected]:border-muted-foreground\",\n // outline-solid on the focus variant is load-bearing: outline-none\n // sets the outline style the width utility resolves from.\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n ],\n false: \"\",\n },\n },\n defaultVariants: { selected: false, interactive: false },\n },\n);\n\nexport const statCardLabelVariants = cva([\n \"m-0 truncate text-2xs leading-(--leading-normal) font-medium tracking-wide uppercase\",\n \"text-muted-foreground\",\n]);\n\nexport const statCardValueVariants = cva(\n // leading-* AFTER text-*: tailwind-merge treats a later text-* as owning\n // line-height and silently strips a leading-* that precedes it.\n \"m-0 mt-1 text-h4 leading-(--leading-snug) font-semibold tabular-nums break-words\",\n {\n variants: {\n /** What the number MEANS. Colour follows from it; it is never picked directly. */\n tone: {\n neutral: \"text-foreground\",\n positive: \"text-success-strong\",\n negative: \"text-destructive-strong\",\n warning: \"text-warning-strong\",\n attention: \"text-primary-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\nexport const statCardSubVariants = cva(\n \"m-0 mt-1 text-xs leading-(--leading-normal) text-muted-foreground\",\n);\n\nexport type StatCardTone = \"neutral\" | \"positive\" | \"negative\" | \"warning\" | \"attention\";\n\nexport interface StatCardProps {\n /** What the figure is. One line; cut with an ellipsis, full text on hover. */\n label: string;\n /** The figure itself, already formatted. */\n value: ReactNode;\n /** One line of context under the figure. */\n sub?: ReactNode;\n /**\n * What the figure means: `neutral` (default, and right for every zero),\n * `positive`, `negative`, `warning` (part-way or waiting) or `attention`\n * (a person has to look).\n */\n tone?: StatCardTone;\n /**\n * The card is a filter, and this says whether it is the one applied.\n * Renders a toggle button with `aria-pressed`. Pair with `onPress`.\n */\n isSelected?: boolean;\n /** Renders the card as a link to this URL. */\n href?: string;\n /** Renders the card as a button (or, with `isSelected`, a toggle). */\n onPress?: () => void;\n /** Hover text for the whole card. */\n description?: string;\n /** Anything under the context line. */\n children?: ReactNode;\n /** Layout classes for the card (margins, grid placement). */\n className?: string;\n \"aria-label\"?: string;\n}\n\n/**\n * One headline figure.\n *\n * @example\n * <div className=\"grid gap-4 sm:grid-cols-3\">\n * <StatCard label=\"Open\" value=\"$12,400\" sub=\"18 invoices\" />\n * <StatCard label=\"Overdue\" value=\"$3,100\" sub=\"4 invoices\" tone=\"negative\" />\n * <StatCard label=\"Collected\" value=\"$9,800\" tone=\"positive\" href=\"/payments\" />\n * </div>\n */\nexport const StatCard = forwardRef<HTMLElement, StatCardProps>(function StatCard(\n { label, value, sub, tone, isSelected, href, onPress, description, children, className, ...rest },\n ref,\n) {\n const body = (\n // The hover text sits on a box that fills the card: React Aria's Button\n // does not pass a `title` through to the element it renders.\n <span className=\"flex min-w-0 flex-1 flex-col\" title={description}>\n <span\n data-slot=\"stat-card-label\"\n className={cn(statCardLabelVariants(), \"block\")}\n title={label}\n >\n {label}\n </span>\n <span\n data-slot=\"stat-card-value\"\n data-tone={tone ?? \"neutral\"}\n className={cn(statCardValueVariants({ tone }), \"block\")}\n >\n {value}\n </span>\n {sub === undefined ? null : (\n <span data-slot=\"stat-card-sub\" className={cn(statCardSubVariants(), \"block\")}>\n {sub}\n </span>\n )}\n {children}\n </span>\n );\n const interactive = isSelected !== undefined || onPress !== undefined || href !== undefined;\n const classes = cn(statCardVariants({ selected: isSelected === true, interactive }), className);\n const common = { \"data-slot\": \"stat-card\", \"aria-label\": rest[\"aria-label\"] };\n\n if (isSelected !== undefined) {\n return (\n <AriaToggleButton\n ref={ref as Ref<HTMLButtonElement>}\n {...common}\n isSelected={isSelected}\n onChange={() => onPress?.()}\n className={classes}\n >\n {body}\n </AriaToggleButton>\n );\n }\n if (onPress) {\n return (\n <AriaButton\n ref={ref as Ref<HTMLButtonElement>}\n {...common}\n onPress={onPress}\n className={classes}\n >\n {body}\n </AriaButton>\n );\n }\n if (href !== undefined) {\n return (\n <AriaLink ref={ref as Ref<HTMLAnchorElement>} {...common} href={href} className={classes}>\n {body}\n </AriaLink>\n );\n }\n return (\n <div ref={ref as Ref<HTMLDivElement>} {...common} className={classes}>\n {body}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAa,mBAAmB,IAC9B;CACE;CACA;CAGA;AACF,GACA;CACE,UAAU;;EAER,UAAU;GACR,MAAM;GACN,OAAO;EACT;;EAEA,aAAa;GACX,MAAM;IACJ;IAEA;IAGA;IACA;IACA;IACA;GACF;GACA,OAAO;EACT;CACF;CACA,iBAAiB;EAAE,UAAU;EAAO,aAAa;CAAM;AACzD,CACF;AAEA,MAAa,wBAAwB,IAAI,CACvC,wFACA,uBACF,CAAC;AAED,MAAa,wBAAwB,IAGnC,oFACA;CACE,UAAU;;AAER,MAAM;EACJ,SAAS;EACT,UAAU;EACV,UAAU;EACV,SAAS;EACT,WAAW;CACb,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CACF;AAEA,MAAa,sBAAsB,IACjC,mEACF;;;;;;;;;;;AA6CA,MAAa,WAAW,WAAuC,SAAS,SACtE,EAAE,OAAO,OAAO,KAAK,MAAM,YAAY,MAAM,SAAS,aAAa,UAAU,WAAW,GAAG,QAC3F,KACA;CACA,MAAM,OAGJ,qBAAC,QAAD;EAAM,WAAU;EAA+B,OAAO;EAAtD,UAAA;GACE,oBAAC,QAAD;IACE,aAAU;IACV,WAAW,GAAG,sBAAsB,GAAG,OAAO;IAC9C,OAAO;IAEN,UAAA;GACG,CAAA;GACN,oBAAC,QAAD;IACE,aAAU;IACV,aAAW,QAAQ;IACnB,WAAW,GAAG,sBAAsB,EAAE,KAAK,CAAC,GAAG,OAAO;IAErD,UAAA;GACG,CAAA;GACL,QAAQ,KAAA,IAAY,OACnB,oBAAC,QAAD;IAAM,aAAU;IAAgB,WAAW,GAAG,oBAAoB,GAAG,OAAO;IACzE,UAAA;GACG,CAAA;GAEP;EACG;;CAGR,MAAM,UAAU,GAAG,iBAAiB;EAAE,UAAU,eAAe;EAAM,aADjD,eAAe,KAAA,KAAa,YAAY,KAAA,KAAa,SAAS,KAAA;CACD,CAAC,GAAG,SAAS;CAC9F,MAAM,SAAS;EAAE,aAAa;EAAa,cAAc,KAAK;CAAc;CAE5E,IAAI,eAAe,KAAA,GACjB,OACE,oBAACA,cAAD;EACO;EACL,GAAI;EACQ;EACZ,gBAAgB,UAAU;EAC1B,WAAW;EAEV,UAAA;CACe,CAAA;CAGtB,IAAI,SACF,OACE,oBAACC,QAAD;EACO;EACL,GAAI;EACK;EACT,WAAW;EAEV,UAAA;CACS,CAAA;CAGhB,IAAI,SAAS,KAAA,GACX,OACE,oBAACC,MAAD;EAAe;EAA+B,GAAI;EAAc;EAAM,WAAW;EAC9E,UAAA;CACO,CAAA;CAGd,OACE,oBAAC,OAAD;EAAU;EAA4B,GAAI;EAAQ,WAAW;EAC1D,UAAA;CACE,CAAA;AAET,CAAC"}
|
|
1
|
+
{"version":3,"file":"stat-card.js","names":["AriaToggleButton","AriaButton","AriaLink"],"sources":["../../src/stat-card/stat-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useState, type ReactNode, type Ref } from \"react\";\nimport {\n Button as AriaButton,\n Link as AriaLink,\n ToggleButton as AriaToggleButton,\n} from \"react-aria-components\";\n\nimport { AnimatedCounter } from \"../animated-counter/animated-counter.js\";\nimport { Badge } from \"../badge/badge.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * StatCard: one headline figure with its label and a line of context. A row\n * of them is a dashboard's summary strip.\n *\n * It exists because a summary strip is on almost every overview screen, and\n * without a shared card each screen picks its own label case, number size,\n * colour and height, so the same figure looks different on two pages.\n *\n * The figure is a counter: a number rolls up to its value the first time the\n * card scrolls into view, and when `value` changes the digits that changed roll\n * to their new faces, the way the number moved. A label, a line of context or\n * a change that is replaced rises in while the old copy lifts away. Reduced\n * motion jumps and fades instead. A value that is not a plain number\n * (a date, \"N/A\", a node) is shown as it is.\n *\n * Rules it holds:\n *\n * - The label is ONE line. A long label is cut with an ellipsis and the full\n * text is on hover, so a row of cards keeps its numbers on one baseline\n * instead of one card's label wrapping and pushing its number down.\n * - The number's colour comes from `tone`, a fixed set of meanings, never a\n * colour. `neutral` is the default and is right for any figure without a\n * verdict in it, including every zero.\n * - Cards in a row are the same height (`h-full` in a grid or flex row).\n *\n * A card can be a link (`href`, React Aria Link, so a router provider routes\n * it), a button (`onPress`), or a filter toggle (`isSelected` + `onPress`, a\n * React Aria ToggleButton with `aria-pressed` and one shared selected look).\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const statCardVariants = cva(\n [\n \"flex h-full min-w-0 flex-col p-6 text-start\",\n \"rounded-panel border-(length:--border-width) bg-surface text-foreground shadow-resting\",\n // box-border and font-sans are not boilerplate: the reset in\n // @seekalfred/styles is opt-in, so a consumer may load none.\n \"box-border font-sans\",\n ],\n {\n variants: {\n /** A filter card that is the one applied. */\n selected: {\n true: \"border-primary-strong\",\n false: \"border-border\",\n },\n /** Whether the card is a link or a button (hover and focus states). */\n interactive: {\n true: [\n \"cursor-pointer outline-none\",\n // A border, not a fill: a muted fill under the muted label fails contrast.\n \"data-[hovered]:not-data-[selected]:border-muted-foreground\",\n // outline-solid on the focus variant is load-bearing: outline-none\n // sets the outline style the width utility resolves from.\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n ],\n false: \"\",\n },\n },\n defaultVariants: { selected: false, interactive: false },\n },\n);\n\n/** The top row: the label on the left, the change pill on the right. */\nexport const statCardHeaderVariants = cva(\"mb-8 flex min-h-6 items-center justify-between gap-3\");\n\nexport const statCardLabelVariants = cva([\n \"m-0 min-w-0 flex-1 text-sm leading-(--leading-normal) font-normal\",\n \"text-text-secondary\",\n]);\n\nexport const statCardValueVariants = cva(\n // The counter sets its own colour; this makes it follow the tone instead.\n // leading-* AFTER text-*: tailwind-merge treats a later text-* as owning\n // line-height and silently strips a leading-* that precedes it.\n \"m-0 font-display text-h2 leading-none font-semibold tabular-nums break-words **:data-[slot=animated-counter]:text-inherit\",\n {\n variants: {\n /** What the number MEANS. Colour follows from it; it is never picked directly. */\n tone: {\n neutral: \"text-foreground\",\n positive: \"text-success-strong\",\n negative: \"text-destructive-strong\",\n warning: \"text-warning-strong\",\n attention: \"text-primary-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\nexport const statCardSubVariants = cva(\n \"m-0 mt-4 text-sm leading-(--leading-normal) text-muted-foreground\",\n);\n\nexport type StatCardTone = \"neutral\" | \"positive\" | \"negative\" | \"warning\" | \"attention\";\n\nexport interface StatCardProps {\n /** What the figure is. One line; cut with an ellipsis, full text on hover. */\n label: string;\n /**\n * The figure itself, already formatted. A plain number (`12400`), or a string\n * made of a number with text around it (`\"$12,400\"`, `\"42 days\"`, `\"98.5%\"`),\n * counts up on first view and rolls to each new value. Anything else is\n * shown as it is.\n */\n value: ReactNode;\n /** One line of context under the figure. A string that changes swaps in place. */\n sub?: ReactNode;\n /**\n * A change shown as a pill beside the label, such as `\"+12.4%\"`. A leading `+`\n * reads green and a leading `-` red; no sign is neutral. A new value swaps in\n * place and the pill follows its width.\n */\n change?: string;\n /**\n * What the figure means: `neutral` (default, and right for every zero),\n * `positive`, `negative`, `warning` (part-way or waiting) or `attention`\n * (a person has to look).\n */\n tone?: StatCardTone;\n /**\n * The card is a filter, and this says whether it is the one applied.\n * Renders a toggle button with `aria-pressed`. Pair with `onPress`.\n */\n isSelected?: boolean;\n /** Renders the card as a link to this URL. */\n href?: string;\n /** Renders the card as a button (or, with `isSelected`, a toggle). */\n onPress?: () => void;\n /** Hover text for the whole card. */\n description?: string;\n /** Anything under the context line. */\n children?: ReactNode;\n /** Layout classes for the card (margins, grid placement). */\n className?: string;\n \"aria-label\"?: string;\n}\n\n/** The number in a piece of text, for the direction a swap moves: a larger one rises from below. */\nconst amountIn = (text: string) => Number(text.replace(/,/g, \"\").match(/-?\\d+(?:\\.\\d+)?/)?.[0] ?? NaN);\n\n/** 1 when the new text holds a number that did not shrink, -1 when it did. Updated during render. */\nfunction useSwapDirection(text: string | undefined) {\n const [shown, setShown] = useState({ text, dir: 1 });\n if (shown.text !== text) {\n setShown({ text, dir: amountIn(text ?? \"\") < amountIn(shown.text ?? \"\") ? -1 : 1 });\n }\n return shown.dir;\n}\n\n/** Copy that is replaced rises in from the side it moved toward and the old copy lifts away. */\nfunction Swap({\n text,\n className,\n children,\n}: {\n text: string;\n className?: string;\n children: ReactNode;\n}) {\n const dir = useSwapDirection(text);\n return (\n <SwapText swapKey={text} dir={dir} className={cn(\"block min-w-0\", className)}>\n {children}\n </SwapText>\n );\n}\n\ninterface Figure {\n prefix: string;\n value: number;\n decimals: number;\n suffix: string;\n}\n\nconst FORMAT: Record<number, Intl.NumberFormat> = {};\nconst format = (decimals: number) =>\n (FORMAT[decimals] ??= new Intl.NumberFormat(\"en-US\", {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }));\n\n/**\n * A number with text around it, or null. Only a figure that the counter would\n * draw back as the very same text qualifies (\"12400\" would come back as\n * \"12,400\", so it is shown as written rather than quietly reformatted).\n */\nfunction figureOf(value: ReactNode): Figure | null {\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) return null;\n const decimals = Math.min(4, (String(value).split(\".\")[1] ?? \"\").length);\n return { prefix: \"\", value, decimals, suffix: \"\" };\n }\n if (typeof value !== \"string\") return null;\n const match = /^(\\D*?)(-?\\d[\\d,]*(?:\\.\\d+)?)(\\D*)$/.exec(value);\n if (!match) return null;\n const [, prefix = \"\", digits = \"\", suffix = \"\"] = match;\n const decimals = (digits.split(\".\")[1] ?? \"\").length;\n const number = Number(digits.replace(/,/g, \"\"));\n if (!Number.isFinite(number) || `${prefix}${format(decimals).format(number)}${suffix}` !== value) {\n return null;\n }\n return { prefix, value: number, decimals, suffix };\n}\n\nconst changeTone = (change: string) =>\n /^\\+/.test(change) ? \"success\" : /^[-−]/.test(change) ? \"danger\" : \"neutral\";\n\n/**\n * One headline figure.\n *\n * @example\n * <div className=\"grid gap-4 sm:grid-cols-3\">\n * <StatCard label=\"Open\" value=\"$12,400\" sub=\"18 invoices\" change=\"+4.2%\" />\n * <StatCard label=\"Overdue\" value=\"$3,100\" sub=\"4 invoices\" tone=\"negative\" />\n * <StatCard label=\"Collected\" value=\"$9,800\" tone=\"positive\" href=\"/payments\" />\n * </div>\n */\nexport const StatCard = forwardRef<HTMLElement, StatCardProps>(function StatCard(\n { label, value, sub, change, tone, isSelected, href, onPress, description, children, className, ...rest },\n ref,\n) {\n const figure = figureOf(value);\n const body = (\n // The hover text sits on a box that fills the card: React Aria's Button\n // does not pass a `title` through to the element it renders.\n <span className=\"flex min-w-0 flex-1 flex-col\" title={description}>\n <span className={statCardHeaderVariants()}>\n <span data-slot=\"stat-card-label\" className={cn(statCardLabelVariants(), \"block\")}>\n {/* *:max-w-full: the arriving copy is an inline-block, whose shrink-to-fit width is the whole unwrapped label; capped, the label truncates. */}\n <Swap text={label} className=\"*:max-w-full\">\n <span className=\"block truncate\" title={label}>\n {label}\n </span>\n </Swap>\n </span>\n {change ? (\n <Badge\n shape=\"pill\"\n tone={changeTone(change)}\n appearance={changeTone(change) === \"neutral\" ? \"outline\" : \"soft\"}\n className=\"tabular-nums\"\n >\n {change}\n </Badge>\n ) : null}\n </span>\n <span\n data-slot=\"stat-card-value\"\n data-tone={tone ?? \"neutral\"}\n className={cn(statCardValueVariants({ tone }), \"block\")}\n >\n {figure ? (\n <AnimatedCounter\n value={figure.value}\n decimals={figure.decimals}\n prefix={figure.prefix}\n suffix={figure.suffix}\n animateOnView\n />\n ) : (\n value\n )}\n </span>\n {sub === undefined ? null : (\n <span data-slot=\"stat-card-sub\" className={cn(statCardSubVariants(), \"block\")}>\n {typeof sub === \"string\" ? <Swap text={sub}>{sub}</Swap> : sub}\n </span>\n )}\n {children}\n </span>\n );\n const interactive = isSelected !== undefined || onPress !== undefined || href !== undefined;\n const classes = cn(statCardVariants({ selected: isSelected === true, interactive }), className);\n const common = { \"data-slot\": \"stat-card\", \"aria-label\": rest[\"aria-label\"] };\n\n if (isSelected !== undefined) {\n return (\n <AriaToggleButton\n ref={ref as Ref<HTMLButtonElement>}\n {...common}\n isSelected={isSelected}\n onChange={() => onPress?.()}\n className={classes}\n >\n {body}\n </AriaToggleButton>\n );\n }\n if (onPress) {\n return (\n <AriaButton\n ref={ref as Ref<HTMLButtonElement>}\n {...common}\n onPress={onPress}\n className={classes}\n >\n {body}\n </AriaButton>\n );\n }\n if (href !== undefined) {\n return (\n <AriaLink ref={ref as Ref<HTMLAnchorElement>} {...common} href={href} className={classes}>\n {body}\n </AriaLink>\n );\n }\n return (\n <div ref={ref as Ref<HTMLDivElement>} {...common} className={classes}>\n {body}\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/stepper/stepper.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Stepper: a steps indicator for onboarding, checkout and setup flows. Place it
|
|
4
|
+
* above (horizontal) or beside (vertical) the step content and drive it with
|
|
5
|
+
* `current`; set `current` to `steps.length` once every step is done.
|
|
6
|
+
*
|
|
7
|
+
* Moving on fills the connector behind the step that was finished (a jump
|
|
8
|
+
* across several steps fills or drains them one after another), the number turns
|
|
9
|
+
* into a check that draws itself, and a ring grows out behind the new current
|
|
10
|
+
* step. A step marked `error` morphs its marker into an alert. A changed
|
|
11
|
+
* description rises in while the old one lifts away, and the steps below follow
|
|
12
|
+
* the new height. Nothing animates on first render.
|
|
13
|
+
*
|
|
14
|
+
* Pass `onStepSelect` to let people return to completed steps by click or
|
|
15
|
+
* keyboard (arrow keys move between the steps they can reach). Without it the
|
|
16
|
+
* stepper is a read-only indicator. A horizontal stepper folds to markers on
|
|
17
|
+
* its own below 30rem and names the current step underneath.
|
|
18
|
+
*/
|
|
19
|
+
export type StepperOrientation = "horizontal" | "vertical";
|
|
20
|
+
export type StepperStatus = "complete" | "current" | "upcoming" | "error";
|
|
21
|
+
export interface StepperStep {
|
|
22
|
+
/** Stable key, so each step keeps its own motion when the list changes. */
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
/** A short hint under the label. */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Marks the step as failed: the marker morphs into an alert and this message replaces the description. */
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface StepperProps {
|
|
31
|
+
steps: StepperStep[];
|
|
32
|
+
/** Index of the step in progress. `steps.length` marks the whole flow complete. */
|
|
33
|
+
current: number;
|
|
34
|
+
/** @default "horizontal" */
|
|
35
|
+
orientation?: StepperOrientation;
|
|
36
|
+
/** Called with the index of a completed step when it is chosen. Without it the stepper is a read-only indicator. */
|
|
37
|
+
onStepSelect?: (index: number) => void;
|
|
38
|
+
/** `current` shows only the active step's description, for tight spaces. Errors always show. @default "all" */
|
|
39
|
+
details?: "all" | "current";
|
|
40
|
+
/** Markers only; labels stay available to assistive tech. A horizontal stepper switches to this below 30rem on its own and shows the current step's label underneath. */
|
|
41
|
+
compact?: boolean;
|
|
42
|
+
/** Accessible name for the stepper. @default "Progress" */
|
|
43
|
+
label?: string;
|
|
44
|
+
/** Announced, and shown in the compact caption, once every step is complete. @default "All steps complete" */
|
|
45
|
+
completeLabel?: string;
|
|
46
|
+
/** Extra classes for the stepper. Merged last. */
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
export declare const Stepper: import("react").ForwardRefExoticComponent<StepperProps & import("react").RefAttributes<HTMLElement>>;
|
|
50
|
+
//#endregion
|
|
51
|
+
//# sourceMappingURL=stepper.d.ts.map
|