@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":"alert-dialog.js","names":[],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type ReactNode } from \"react\";\n\nimport { Dialog, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * A PRESET over `Dialog`, not a second implementation. The legacy\n * `alert-dialog.tsx` is a 157-line clone of `dialog.tsx` with three\n * differences, and keeping two copies is how they drift -- the legacy pair\n * already disagree about `leading-none` on their titles for no stated\n * reason.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `closeButton={false}`. An X in the corner is a third answer to a\n * two-answer question, and it is ambiguous: it reads as \"cancel\" but\n * looks like \"dismiss\".\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there.\n *\n * NO `AlertDialogAction` OR `AlertDialogCancel`. The legacy shipped both,\n * with `AlertDialogAction` defaulting to the gradient primary button -- and\n * BOTH call sites override it back to destructive by hand\n * (`data-table.tsx:146`, `calendar-view.tsx:638`). A default that every\n * caller fights is not a default. Use a `Button` and say which one it is:\n * `slot=\"close\"` cancels, and the confirm button picks its own variant.\n */\nexport interface AlertDialogProps\n extends Omit<DialogProps, \"role\" | \"closeButton\" | \"closeLabel\" | \"isDismissable\"> {\n /**\n * What is about to happen, and whether it can be undone.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n props,\n ref,\n) {\n return (\n <Dialog {...props} ref={ref} role=\"alertdialog\" closeButton={false} isDismissable={false} />\n );\n});\n"],"mappings":";;;;;AAuDA,MAAa,cAAc,WAA0C,SAAS,YAC5E,OACA,KACA;CACA,OACE,oBAAC,QAAD;EAAQ,GAAI;EAAY;EAAK,MAAK;EAAc,aAAa;EAAO,eAAe;CAAQ,CAAA;AAE/F,CAAC"}
|
|
1
|
+
{"version":3,"file":"alert-dialog.js","names":["Button","AriaModalOverlay","AriaModal","AriaDialog","AriaHeading","AriaButton","AriaText"],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Dialog as AriaDialog,\n Heading as AriaHeading,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Text as AriaText,\n} from \"react-aria-components\";\n\nimport { Button, type ButtonProps } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { dialogOverlayVariants, dialogPanelVariants, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * Built from `Dialog`'s own overlay and panel styles and the same React Aria\n * parts, but with its own layout: a header (title, a grey subtitle and a\n * circular close button), a thin divider, the body text, then the actions.\n * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this\n * shares the tokens and the motion classes instead, so the two cannot drift\n * on radius, scrim or entrance.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - THE CLOSE (X) IS CANCEL. It is `slot=\"close\"`, the same dismiss wiring as\n * a Cancel button, so it goes through the same `onOpenChange(false)` and\n * focus returns to the trigger. It is not a third answer: it is the\n * header's copy of the second one.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there. It is the body text above the actions.\n *\n * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary\n * button (white, light border) and cancels through `slot=\"close\"`;\n * `AlertDialogAction` is the primary button painted destructive red, because a\n * confirmation here is a destructive one. The legacy `AlertDialogAction`\n * defaulted to the gradient primary and BOTH call sites overrode it back to\n * destructive by hand (`data-table.tsx:146`, `calendar-view.tsx:638`); the red\n * is the default now, so nobody fights it. Plain `Button`s still work in the\n * actions slot for anything else.\n */\nexport interface AlertDialogProps extends Omit<\n DialogProps,\n \"role\" | \"closeButton\" | \"isDismissable\"\n> {\n /**\n * What is about to happen, and whether it can be undone. Rendered as the body text\n * between the divider and the actions.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** A short grey line under the title, in the header. */\n subtitle?: ReactNode;\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nconst alertDialogActionsVariants = cva(\"flex flex-wrap items-center justify-end gap-2 px-8 pb-8\");\n\n/**\n * The cancel action: the secondary button, wired to dismiss the dialog.\n *\n * @example\n * <AlertDialogCancel>Cancel</AlertDialogCancel>\n */\nexport const AlertDialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogCancel(props, ref) {\n return <Button ref={ref} slot=\"close\" variant=\"secondary\" size=\"sm\" {...props} />;\n },\n);\n\n/**\n * The confirm action: the primary button in the destructive red.\n *\n * It is the primary button with its fill, hover, pressed and label roles\n * pointed at the destructive tokens, so it keeps the primary's press motion\n * and shadow. Variables on the element, rather than a competing background\n * class, so no stylesheet order decides which colour wins.\n *\n * @example\n * <AlertDialogAction onPress={remove}>Delete</AlertDialogAction>\n */\nexport const AlertDialogAction = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogAction({ className, ...props }, ref) {\n return (\n <Button\n ref={ref}\n variant=\"primary\"\n size=\"sm\"\n className={cnAll(\n \"[--color-primary:var(--color-destructive)] [--color-primary-hover:var(--color-destructive-hover)] [--color-primary-pressed:var(--color-destructive-pressed)] [--color-primary-foreground:var(--color-destructive-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst alertDialogHeaderVariants = cva(\n \"flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6\",\n);\n\nconst alertDialogTitleVariants = cva(\n \"m-0 text-lg leading-(--leading-tight) font-semibold text-foreground\",\n);\n\nconst alertDialogCloseVariants = cva([\n \"inline-flex size-9 shrink-0 items-center justify-center\",\n \"rounded-circle box-border cursor-pointer\",\n \"border-(length:--border-width) border-border bg-surface p-0 text-text-secondary\",\n // Fill on hover, a quick press to .96 and a spring release (see calendar-nav-motion).\n \"calendar-nav-motion\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"outline-none\",\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 \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]);\n\n/** A string swaps as a whole when its text changes while the dialog is open. */\nfunction Swappable({ value }: { value: ReactNode }) {\n if (typeof value !== \"string\") return <>{value}</>;\n return (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n );\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n {\n title,\n subtitle,\n beforeTitle,\n description,\n children,\n className,\n closeLabel = \"Close\",\n overlayClassName,\n entrance = \"rise\",\n ...props\n },\n ref,\n) {\n return (\n <AriaModalOverlay\n {...props}\n isDismissable={false}\n data-slot=\"dialog-overlay\"\n className={cnAll(\n dialogOverlayVariants(),\n entrance === \"rise\" && \"dialog-overlay-motion\",\n overlayClassName,\n )}\n >\n <AriaModal className=\"contents\">\n <AriaDialog\n ref={ref}\n role=\"alertdialog\"\n data-slot=\"dialog\"\n className={cnAll(\n dialogPanelVariants(),\n // The panel has no padding of its own: the header, body and actions pad themselves,\n // so the divider can run edge to edge.\n \"gap-0 p-0\",\n entrance === \"rise\" && \"dialog-panel-motion\",\n className,\n )}\n >\n {beforeTitle}\n <div data-slot=\"alert-dialog-header\" className={alertDialogHeaderVariants()}>\n <div className=\"min-w-0 flex-1\">\n {/* slot=\"title\" is what React Aria wires aria-labelledby to. */}\n <AriaHeading\n slot=\"title\"\n data-slot=\"dialog-title\"\n className={alertDialogTitleVariants()}\n >\n <Swappable value={title} />\n </AriaHeading>\n {subtitle == null ? null : (\n <p\n data-slot=\"alert-dialog-subtitle\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n <Swappable value={subtitle} />\n </p>\n )}\n </div>\n {/* slot=\"close\" is the same dismiss wiring as a Cancel button: one handler, focus back to the trigger. */}\n <AriaButton\n slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"alert-dialog-close\"\n className={alertDialogCloseVariants()}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </div>\n <AriaText\n slot=\"description\"\n elementType=\"p\"\n data-slot=\"dialog-description\"\n className=\"m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground\"\n >\n <Swappable value={description} />\n </AriaText>\n {children == null ? null : (\n <div data-slot=\"alert-dialog-actions\" className={alertDialogActionsVariants()}>\n {children}\n </div>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n});\n"],"mappings":";;;;;;;;;;AA4EA,MAAM,6BAA6B,IAAI,yDAAyD;;;;;;;AAQhG,MAAa,oBAAoB,WAC/B,SAAS,kBAAkB,OAAO,KAAK;CACrC,OAAO,oBAACA,UAAD;EAAa;EAAK,MAAK;EAAQ,SAAQ;EAAY,MAAK;EAAK,GAAI;CAAQ,CAAA;AAClF,CACF;;;;;;;;;;;;AAaA,MAAa,oBAAoB,WAC/B,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAS,KAAK;CACvD,OACE,oBAACA,UAAD;EACO;EACL,SAAQ;EACR,MAAK;EACL,WAAW,MACT,iOACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CACF;AAEA,MAAM,4BAA4B,IAChC,sFACF;AAEA,MAAM,2BAA2B,IAC/B,qEACF;AAEA,MAAM,2BAA2B,IAAI;CACnC;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAS,UAAU,EAAE,SAA+B;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,oBAAA,YAAA,EAAA,UAAG,MAAQ,CAAA;CACjD,OACE,oBAAC,UAAD;EAAU,SAAS;EAAO,WAAU;EACjC,UAAA;CACO,CAAA;AAEd;AAEA,MAAa,cAAc,WAA0C,SAAS,YAC5E,EACE,OACA,UACA,aACA,aACA,UACA,WACA,aAAa,SACb,kBACA,WAAW,QACX,GAAG,SAEL,KACA;CACA,OACE,oBAACC,cAAD;EACE,GAAI;EACJ,eAAe;EACf,aAAU;EACV,WAAW,MACT,sBAAsB,GACtB,aAAa,UAAU,yBACvB,gBACF;EAEA,UAAA,oBAACC,OAAD;GAAW,WAAU;GACnB,UAAA,qBAACC,QAAD;IACO;IACL,MAAK;IACL,aAAU;IACV,WAAW,MACT,oBAAoB,GAGpB,aACA,aAAa,UAAU,uBACvB,SACF;IAXF,UAAA;KAaG;KACD,qBAAC,OAAD;MAAK,aAAU;MAAsB,WAAW,0BAA0B;MAA1E,UAAA,CACE,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CAEE,oBAACC,SAAD;QACE,MAAK;QACL,aAAU;QACV,WAAW,yBAAyB;QAEpC,UAAA,oBAAC,WAAD,EAAW,OAAO,MAAQ,CAAA;OACf,CAAA,GACZ,YAAY,OAAO,OAClB,oBAAC,KAAD;QACE,aAAU;QACV,WAAU;QAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,SAAW,CAAA;OAC5B,CAAA,CAEF;MAEL,CAAA,GAAA,oBAACC,QAAD;OACE,MAAK;OACL,cAAY;OACZ,aAAU;OACV,WAAW,yBAAyB;OAEpC,UAAA,oBAAC,OAAD;QACE,eAAY;QACZ,SAAQ;QACR,MAAK;QACL,QAAO;QACP,aAAY;QACZ,eAAc;QAEd,UAAA,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA;OAC3B,CAAA;MACK,CAAA,CACT;;KACL,oBAACC,MAAD;MACE,MAAK;MACL,aAAY;MACZ,aAAU;MACV,WAAU;MAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,YAAc,CAAA;KACxB,CAAA;KACT,YAAY,OAAO,OAClB,oBAAC,OAAD;MAAK,aAAU;MAAuB,WAAW,2BAA2B;MACzE;KACE,CAAA;IAEG;;EACH,CAAA;CACK,CAAA;AAEtB,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { AlertDialog, AlertDialogProps } from "./alert-dialog.js";
|
|
2
|
-
export { AlertDialog, type AlertDialogProps };
|
|
1
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogProps } from "./alert-dialog.js";
|
|
2
|
+
export { AlertDialog, AlertDialogAction, AlertDialogCancel, type AlertDialogProps };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { AlertDialog } from "./alert-dialog.js";
|
|
2
|
-
export { AlertDialog };
|
|
1
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel } from "./alert-dialog.js";
|
|
2
|
+
export { AlertDialog, AlertDialogAction, AlertDialogCancel };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/animated-counter/animated-counter.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* AnimatedCounter: a number whose digits are wheels. When `value` changes each
|
|
4
|
+
* digit that changed rolls to its new face, turning the way the whole number
|
|
5
|
+
* moved (up when it grew, down when it shrank, wrapping 9 to 0 like an
|
|
6
|
+
* odometer) while the digits that did not change stay still. A gained or lost
|
|
7
|
+
* digit, or separator, opens or closes its width instead of snapping, so 999
|
|
8
|
+
* becoming 1,000 keeps the ones column the ones column. A changed `label` rises
|
|
9
|
+
* in while the old one lifts away.
|
|
10
|
+
*
|
|
11
|
+
* With `animateOnView` every digit starts at 0 and rolls up to its value, one
|
|
12
|
+
* after another, the first time the counter scrolls into view.
|
|
13
|
+
*
|
|
14
|
+
* The digits are a picture: assistive technology reads the formatted number
|
|
15
|
+
* once, in a polite live region, so a change is announced as text and never as
|
|
16
|
+
* a spinning wheel. Nothing animates on first render, and reduced motion jumps.
|
|
17
|
+
*/
|
|
18
|
+
export interface AnimatedCounterProps {
|
|
19
|
+
/** The number to show. Changing it rolls the digits that changed. */
|
|
20
|
+
value: number;
|
|
21
|
+
/** A caption above the number. */
|
|
22
|
+
label?: string;
|
|
23
|
+
/** Text before the number, such as a currency symbol. */
|
|
24
|
+
prefix?: string;
|
|
25
|
+
/** Text after the number, such as a unit. */
|
|
26
|
+
suffix?: string;
|
|
27
|
+
/** Digits after the decimal point. @default 0 */
|
|
28
|
+
decimals?: number;
|
|
29
|
+
/** Roll every digit up from zero the first time the counter scrolls into view. */
|
|
30
|
+
animateOnView?: boolean;
|
|
31
|
+
/** Formatting locale. Fixed by default so server and client render the same digits. @default "en-US" */
|
|
32
|
+
locale?: string;
|
|
33
|
+
/** Extra classes for the counter. Merged last. */
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare const AnimatedCounter: import("react").ForwardRefExoticComponent<AnimatedCounterProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
37
|
+
//#endregion
|
|
38
|
+
//# sourceMappingURL=animated-counter.d.ts.map
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { mergeRefs } from "../lib/merge-refs.js";
|
|
4
|
+
import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from "../lib/motion.js";
|
|
5
|
+
import { SwapText } from "../lib/swap-text.js";
|
|
6
|
+
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/animated-counter/animated-counter.tsx
|
|
9
|
+
const FACES = [
|
|
10
|
+
0,
|
|
11
|
+
1,
|
|
12
|
+
2,
|
|
13
|
+
3,
|
|
14
|
+
4,
|
|
15
|
+
5,
|
|
16
|
+
6,
|
|
17
|
+
7,
|
|
18
|
+
8,
|
|
19
|
+
9
|
|
20
|
+
];
|
|
21
|
+
/** Stagger between digits on the first reveal (ms), at most 250 in all. */
|
|
22
|
+
const REVEAL_STAGGER_MS = 35;
|
|
23
|
+
const REVEAL_MAX_DELAY_MS = 250;
|
|
24
|
+
/** How long the reveal takes at most: the longest delay, the wheel at 1.2 times the smooth spring, and a margin. */
|
|
25
|
+
const REVEAL_SETTLE_MS = 1200;
|
|
26
|
+
/** The longest a width exit takes (the morph spring, 736ms) plus a margin, for the backstop that removes a node whose animation never ends. */
|
|
27
|
+
const EXIT_BACKSTOP_MS = 900;
|
|
28
|
+
/** Split a formatted number into parts keyed by place value, so 999 to 1,000 keeps the ones column the ones column. */
|
|
29
|
+
function partsFor(value, decimals, locale) {
|
|
30
|
+
const parts = new Intl.NumberFormat(locale, {
|
|
31
|
+
minimumFractionDigits: decimals,
|
|
32
|
+
maximumFractionDigits: decimals,
|
|
33
|
+
numberingSystem: "latn"
|
|
34
|
+
}).formatToParts(value);
|
|
35
|
+
let place = parts.reduce((count, part) => count + (part.type === "integer" ? part.value.length : 0), 0);
|
|
36
|
+
let fraction = 0;
|
|
37
|
+
let order = 0;
|
|
38
|
+
return parts.flatMap((part, index) => {
|
|
39
|
+
if (part.type === "integer") return [...part.value].map((char) => ({
|
|
40
|
+
key: `i${--place}`,
|
|
41
|
+
digit: Number(char),
|
|
42
|
+
order: order++
|
|
43
|
+
}));
|
|
44
|
+
if (part.type === "fraction") return [...part.value].map((char) => ({
|
|
45
|
+
key: `f${fraction++}`,
|
|
46
|
+
digit: Number(char),
|
|
47
|
+
order: order++
|
|
48
|
+
}));
|
|
49
|
+
return [{
|
|
50
|
+
key: part.type === "group" ? `g${place}` : part.type === "decimal" ? "d" : `${part.type}${index}`,
|
|
51
|
+
text: part.value
|
|
52
|
+
}];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const same = (a, b) => a.key === b.key && ("digit" in a ? "digit" in b && a.digit === b.digit && a.order === b.order : "text" in b && a.text === b.text);
|
|
56
|
+
/** The entries after `parts` arrive: gone ones stay (leaving) where they stood, new ones are fresh. */
|
|
57
|
+
function merge(previous, parts) {
|
|
58
|
+
const before = new Map(previous.map((entry) => [entry.part.key, entry]));
|
|
59
|
+
const result = parts.map((part) => {
|
|
60
|
+
const old = before.get(part.key);
|
|
61
|
+
return {
|
|
62
|
+
part,
|
|
63
|
+
leaving: false,
|
|
64
|
+
fresh: old ? old.fresh && !old.leaving : true
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
const live = new Set(parts.map((part) => part.key));
|
|
68
|
+
previous.forEach((entry, index) => {
|
|
69
|
+
if (live.has(entry.part.key)) return;
|
|
70
|
+
let anchor = -1;
|
|
71
|
+
for (let back = index - 1; back >= 0 && anchor < 0; back--) anchor = result.findIndex((item) => item.part.key === previous[back].part.key);
|
|
72
|
+
result.splice(anchor + 1, 0, {
|
|
73
|
+
...entry,
|
|
74
|
+
leaving: true
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
/** True once the node has been at least 60% on screen (or at once where IntersectionObserver is missing). */
|
|
80
|
+
function useSeen(node, enabled) {
|
|
81
|
+
const [seen, setSeen] = useState(false);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const target = node.current;
|
|
84
|
+
if (!enabled || seen || !target || typeof IntersectionObserver === "undefined") return;
|
|
85
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
86
|
+
if (entry?.isIntersecting) setSeen(true);
|
|
87
|
+
}, { threshold: .6 });
|
|
88
|
+
observer.observe(target);
|
|
89
|
+
return () => observer.disconnect();
|
|
90
|
+
}, [
|
|
91
|
+
node,
|
|
92
|
+
enabled,
|
|
93
|
+
seen
|
|
94
|
+
]);
|
|
95
|
+
return seen || enabled && typeof IntersectionObserver === "undefined";
|
|
96
|
+
}
|
|
97
|
+
/** Width presence: opens when it arrives after the first render, closes while it leaves, and is removed once closed. */
|
|
98
|
+
function Slot({ entry, onGone, children }) {
|
|
99
|
+
const { key } = entry.part;
|
|
100
|
+
const gone = useCallback(() => onGone(key), [onGone, key]);
|
|
101
|
+
useExitBackstop(entry.leaving, gone, EXIT_BACKSTOP_MS);
|
|
102
|
+
useIsomorphicLayoutEffect(() => {
|
|
103
|
+
if (entry.leaving && prefersReducedMotion()) gone();
|
|
104
|
+
}, [entry.leaving, gone]);
|
|
105
|
+
return /* @__PURE__ */ jsx("span", {
|
|
106
|
+
"data-state": entry.leaving ? "leaving" : entry.fresh ? "entering" : void 0,
|
|
107
|
+
onAnimationEnd: (event) => {
|
|
108
|
+
if (entry.leaving && event.target === event.currentTarget) gone();
|
|
109
|
+
},
|
|
110
|
+
className: "counter-slot",
|
|
111
|
+
children
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** One digit wheel. It remembers how far it has turned, so it always keeps going the way the whole number moved. */
|
|
115
|
+
function Column({ digit, direction, phase, delay }) {
|
|
116
|
+
const armed = phase === "armed";
|
|
117
|
+
const [wheel, setWheel] = useState(() => {
|
|
118
|
+
const start = armed ? 0 : digit;
|
|
119
|
+
return {
|
|
120
|
+
digit: start,
|
|
121
|
+
turns: start
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
let { turns } = wheel;
|
|
125
|
+
if (!armed && wheel.digit !== digit) {
|
|
126
|
+
const back = direction < 0 && phase === "settled";
|
|
127
|
+
turns = wheel.turns + (back ? -((wheel.digit - digit + 10) % 10) : (digit - wheel.digit + 10) % 10);
|
|
128
|
+
setWheel({
|
|
129
|
+
digit,
|
|
130
|
+
turns
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const style = { "--wheel": turns };
|
|
134
|
+
if (phase === "revealing") {
|
|
135
|
+
style.transitionDelay = `${delay}ms`;
|
|
136
|
+
style.transitionDuration = "calc(var(--duration-smooth) * 1.2)";
|
|
137
|
+
}
|
|
138
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
139
|
+
style,
|
|
140
|
+
className: "counter-column",
|
|
141
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
142
|
+
className: "counter-sizer",
|
|
143
|
+
children: "0"
|
|
144
|
+
}), FACES.map((face) => /* @__PURE__ */ jsx("span", {
|
|
145
|
+
style: { "--face": face },
|
|
146
|
+
className: "counter-glyph",
|
|
147
|
+
children: face
|
|
148
|
+
}, face))]
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const AnimatedCounter = forwardRef(function AnimatedCounter({ value, label, prefix = "", suffix = "", decimals = 0, animateOnView = false, locale = "en-US", className }, ref) {
|
|
152
|
+
const root = useRef(null);
|
|
153
|
+
const refs = useMemo(() => mergeRefs(ref, root), [ref]);
|
|
154
|
+
const seen = useSeen(root, animateOnView);
|
|
155
|
+
const [phase, setPhase] = useState(animateOnView ? "armed" : "settled");
|
|
156
|
+
if (phase === "armed" && seen) setPhase("revealing");
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (phase !== "revealing") return;
|
|
159
|
+
const timer = setTimeout(() => setPhase("settled"), prefersReducedMotion() ? 0 : REVEAL_SETTLE_MS);
|
|
160
|
+
return () => clearTimeout(timer);
|
|
161
|
+
}, [phase]);
|
|
162
|
+
const [previous, setPrevious] = useState(value);
|
|
163
|
+
const [direction, setDirection] = useState(1);
|
|
164
|
+
if (value !== previous) {
|
|
165
|
+
setPrevious(value);
|
|
166
|
+
setDirection(value > previous ? 1 : -1);
|
|
167
|
+
}
|
|
168
|
+
const parts = partsFor(value, decimals, locale);
|
|
169
|
+
const [entries, setEntries] = useState(() => parts.map((part) => ({
|
|
170
|
+
part,
|
|
171
|
+
leaving: false,
|
|
172
|
+
fresh: false
|
|
173
|
+
})));
|
|
174
|
+
const live = entries.filter((entry) => !entry.leaving);
|
|
175
|
+
if (live.length !== parts.length || live.some((entry, index) => !same(entry.part, parts[index]))) setEntries(merge(entries, parts));
|
|
176
|
+
const finish = useCallback((key) => setEntries((list) => list.filter((entry) => !(entry.leaving && entry.part.key === key))), []);
|
|
177
|
+
const text = `${prefix}${parts.map((part) => "text" in part ? part.text : part.digit).join("")}${suffix}`;
|
|
178
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
179
|
+
ref: refs,
|
|
180
|
+
"data-slot": "animated-counter",
|
|
181
|
+
className: cn("animated-counter box-border font-display text-h2 leading-none font-semibold tabular-nums text-foreground", className),
|
|
182
|
+
children: [
|
|
183
|
+
label ? /* @__PURE__ */ jsx("span", {
|
|
184
|
+
className: "counter-label",
|
|
185
|
+
children: /* @__PURE__ */ jsx(SwapText, {
|
|
186
|
+
swapKey: label,
|
|
187
|
+
motion: "rise",
|
|
188
|
+
className: "block",
|
|
189
|
+
children: label
|
|
190
|
+
})
|
|
191
|
+
}) : null,
|
|
192
|
+
/* @__PURE__ */ jsx("span", {
|
|
193
|
+
className: "sr-only",
|
|
194
|
+
"aria-live": "polite",
|
|
195
|
+
children: text
|
|
196
|
+
}),
|
|
197
|
+
/* @__PURE__ */ jsxs("span", {
|
|
198
|
+
className: "counter-value",
|
|
199
|
+
"aria-hidden": "true",
|
|
200
|
+
children: [
|
|
201
|
+
prefix ? /* @__PURE__ */ jsx("span", {
|
|
202
|
+
className: "counter-symbol",
|
|
203
|
+
children: prefix
|
|
204
|
+
}) : null,
|
|
205
|
+
entries.map((entry) => /* @__PURE__ */ jsx(Slot, {
|
|
206
|
+
entry,
|
|
207
|
+
onGone: finish,
|
|
208
|
+
children: "digit" in entry.part ? /* @__PURE__ */ jsx(Column, {
|
|
209
|
+
digit: entry.part.digit,
|
|
210
|
+
direction,
|
|
211
|
+
phase,
|
|
212
|
+
delay: Math.min(entry.part.order * REVEAL_STAGGER_MS, REVEAL_MAX_DELAY_MS)
|
|
213
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
214
|
+
className: "counter-symbol",
|
|
215
|
+
children: entry.part.text
|
|
216
|
+
})
|
|
217
|
+
}, entry.part.key)),
|
|
218
|
+
suffix ? /* @__PURE__ */ jsx("span", {
|
|
219
|
+
className: "counter-symbol",
|
|
220
|
+
children: suffix
|
|
221
|
+
}) : null
|
|
222
|
+
]
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
//#endregion
|
|
228
|
+
export { AnimatedCounter };
|
|
229
|
+
|
|
230
|
+
//# sourceMappingURL=animated-counter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animated-counter.js","names":[],"sources":["../../src/animated-counter/animated-counter.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * AnimatedCounter: a number whose digits are wheels. When `value` changes each\n * digit that changed rolls to its new face, turning the way the whole number\n * moved (up when it grew, down when it shrank, wrapping 9 to 0 like an\n * odometer) while the digits that did not change stay still. A gained or lost\n * digit, or separator, opens or closes its width instead of snapping, so 999\n * becoming 1,000 keeps the ones column the ones column. A changed `label` rises\n * in while the old one lifts away.\n *\n * With `animateOnView` every digit starts at 0 and rolls up to its value, one\n * after another, the first time the counter scrolls into view.\n *\n * The digits are a picture: assistive technology reads the formatted number\n * once, in a polite live region, so a change is announced as text and never as\n * a spinning wheel. Nothing animates on first render, and reduced motion jumps.\n */\nexport interface AnimatedCounterProps {\n /** The number to show. Changing it rolls the digits that changed. */\n value: number;\n /** A caption above the number. */\n label?: string;\n /** Text before the number, such as a currency symbol. */\n prefix?: string;\n /** Text after the number, such as a unit. */\n suffix?: string;\n /** Digits after the decimal point. @default 0 */\n decimals?: number;\n /** Roll every digit up from zero the first time the counter scrolls into view. */\n animateOnView?: boolean;\n /** Formatting locale. Fixed by default so server and client render the same digits. @default \"en-US\" */\n locale?: string;\n /** Extra classes for the counter. Merged last. */\n className?: string;\n}\n\ntype Part = { key: string; digit: number; order: number } | { key: string; text: string };\n\ninterface Entry {\n part: Part;\n /** Kept on screen while its width closes. */\n leaving: boolean;\n /** Appeared after the first render, so its width opens. */\n fresh: boolean;\n}\n\nconst FACES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as const;\n/** Stagger between digits on the first reveal (ms), at most 250 in all. */\nconst REVEAL_STAGGER_MS = 35;\nconst REVEAL_MAX_DELAY_MS = 250;\n/** How long the reveal takes at most: the longest delay, the wheel at 1.2 times the smooth spring, and a margin. */\nconst REVEAL_SETTLE_MS = 1200;\n/** The longest a width exit takes (the morph spring, 736ms) plus a margin, for the backstop that removes a node whose animation never ends. */\nconst EXIT_BACKSTOP_MS = 900;\n\n/** Split a formatted number into parts keyed by place value, so 999 to 1,000 keeps the ones column the ones column. */\nfunction partsFor(value: number, decimals: number, locale: string): Part[] {\n const parts = new Intl.NumberFormat(locale, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n numberingSystem: \"latn\",\n }).formatToParts(value);\n let place = parts.reduce((count, part) => count + (part.type === \"integer\" ? part.value.length : 0), 0);\n let fraction = 0;\n let order = 0;\n return parts.flatMap((part, index): Part[] => {\n if (part.type === \"integer\")\n return [...part.value].map((char) => ({ key: `i${--place}`, digit: Number(char), order: order++ }));\n if (part.type === \"fraction\")\n return [...part.value].map((char) => ({ key: `f${fraction++}`, digit: Number(char), order: order++ }));\n const key = part.type === \"group\" ? `g${place}` : part.type === \"decimal\" ? \"d\" : `${part.type}${index}`;\n return [{ key, text: part.value }];\n });\n}\n\nconst same = (a: Part, b: Part) =>\n a.key === b.key && (\"digit\" in a ? \"digit\" in b && a.digit === b.digit && a.order === b.order : \"text\" in b && a.text === b.text);\n\n/** The entries after `parts` arrive: gone ones stay (leaving) where they stood, new ones are fresh. */\nfunction merge(previous: Entry[], parts: Part[]): Entry[] {\n const before = new Map(previous.map((entry) => [entry.part.key, entry]));\n const result: Entry[] = parts.map((part) => {\n const old = before.get(part.key);\n return { part, leaving: false, fresh: old ? old.fresh && !old.leaving : true };\n });\n const live = new Set(parts.map((part) => part.key));\n previous.forEach((entry, index) => {\n if (live.has(entry.part.key)) return;\n let anchor = -1;\n for (let back = index - 1; back >= 0 && anchor < 0; back--) {\n anchor = result.findIndex((item) => item.part.key === previous[back]!.part.key);\n }\n result.splice(anchor + 1, 0, { ...entry, leaving: true });\n });\n return result;\n}\n\n/** True once the node has been at least 60% on screen (or at once where IntersectionObserver is missing). */\nfunction useSeen(node: { current: HTMLElement | null }, enabled: boolean) {\n const [seen, setSeen] = useState(false);\n useEffect(() => {\n const target = node.current;\n if (!enabled || seen || !target || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry?.isIntersecting) setSeen(true);\n },\n { threshold: 0.6 },\n );\n observer.observe(target);\n return () => observer.disconnect();\n }, [node, enabled, seen]);\n return seen || (enabled && typeof IntersectionObserver === \"undefined\");\n}\n\n/** Width presence: opens when it arrives after the first render, closes while it leaves, and is removed once closed. */\nfunction Slot({\n entry,\n onGone,\n children,\n}: {\n entry: Entry;\n onGone: (key: string) => void;\n children: ReactNode;\n}) {\n const { key } = entry.part;\n const gone = useCallback(() => onGone(key), [onGone, key]);\n useExitBackstop(entry.leaving, gone, EXIT_BACKSTOP_MS);\n // Reduced motion removes it at once, and an animation that is switched off never ends.\n useIsomorphicLayoutEffect(() => {\n if (entry.leaving && prefersReducedMotion()) gone();\n }, [entry.leaving, gone]);\n return (\n <span\n data-state={entry.leaving ? \"leaving\" : entry.fresh ? \"entering\" : undefined}\n onAnimationEnd={(event) => {\n if (entry.leaving && event.target === event.currentTarget) gone();\n }}\n className=\"counter-slot\"\n >\n {children}\n </span>\n );\n}\n\n/** One digit wheel. It remembers how far it has turned, so it always keeps going the way the whole number moved. */\nfunction Column({\n digit,\n direction,\n phase,\n delay,\n}: {\n digit: number;\n direction: number;\n phase: \"armed\" | \"revealing\" | \"settled\";\n delay: number;\n}) {\n const armed = phase === \"armed\";\n const [wheel, setWheel] = useState(() => {\n const start = armed ? 0 : digit;\n return { digit: start, turns: start };\n });\n let { turns } = wheel;\n if (!armed && wheel.digit !== digit) {\n // The first reveal always counts up from 0; afterwards a smaller number turns the wheel back.\n const back = direction < 0 && phase === \"settled\";\n turns = wheel.turns + (back ? -((wheel.digit - digit + 10) % 10) : (digit - wheel.digit + 10) % 10);\n setWheel({ digit, turns });\n }\n const style: CSSProperties & Record<string, string | number> = { \"--wheel\": turns };\n // The first reveal rolls on a slower spring (1.2 times the smooth one), each digit a beat after the last.\n if (phase === \"revealing\") {\n style.transitionDelay = `${delay}ms`;\n style.transitionDuration = \"calc(var(--duration-smooth) * 1.2)\";\n }\n return (\n <span style={style} className=\"counter-column\">\n <span className=\"counter-sizer\">0</span>\n {FACES.map((face) => (\n <span key={face} style={{ \"--face\": face } as CSSProperties} className=\"counter-glyph\">\n {face}\n </span>\n ))}\n </span>\n );\n}\n\nexport const AnimatedCounter = forwardRef<HTMLSpanElement, AnimatedCounterProps>(function AnimatedCounter(\n { value, label, prefix = \"\", suffix = \"\", decimals = 0, animateOnView = false, locale = \"en-US\", className },\n ref,\n) {\n const root = useRef<HTMLSpanElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, root), [ref]);\n const seen = useSeen(root, animateOnView);\n const [phase, setPhase] = useState<\"armed\" | \"revealing\" | \"settled\">(animateOnView ? \"armed\" : \"settled\");\n if (phase === \"armed\" && seen) setPhase(\"revealing\");\n useEffect(() => {\n if (phase !== \"revealing\") return;\n const timer = setTimeout(() => setPhase(\"settled\"), prefersReducedMotion() ? 0 : REVEAL_SETTLE_MS);\n return () => clearTimeout(timer);\n }, [phase]);\n\n const [previous, setPrevious] = useState(value);\n const [direction, setDirection] = useState(1);\n if (value !== previous) {\n setPrevious(value);\n setDirection(value > previous ? 1 : -1);\n }\n\n const parts = partsFor(value, decimals, locale);\n const [entries, setEntries] = useState<Entry[]>(() =>\n parts.map((part) => ({ part, leaving: false, fresh: false })),\n );\n const live = entries.filter((entry) => !entry.leaving);\n if (live.length !== parts.length || live.some((entry, index) => !same(entry.part, parts[index]!))) {\n setEntries(merge(entries, parts));\n }\n const finish = useCallback(\n (key: string) =>\n setEntries((list) => list.filter((entry) => !(entry.leaving && entry.part.key === key))),\n [],\n );\n\n const text = `${prefix}${parts.map((part) => (\"text\" in part ? part.text : part.digit)).join(\"\")}${suffix}`;\n return (\n <span\n ref={refs}\n data-slot=\"animated-counter\"\n className={cn(\n \"animated-counter box-border font-display text-h2 leading-none font-semibold tabular-nums text-foreground\",\n className,\n )}\n >\n {label ? (\n <span className=\"counter-label\">\n <SwapText swapKey={label} motion=\"rise\" className=\"block\">\n {label}\n </SwapText>\n </span>\n ) : null}\n <span className=\"sr-only\" aria-live=\"polite\">\n {text}\n </span>\n <span className=\"counter-value\" aria-hidden=\"true\">\n {prefix ? <span className=\"counter-symbol\">{prefix}</span> : null}\n {entries.map((entry) => (\n <Slot key={entry.part.key} entry={entry} onGone={finish}>\n {\"digit\" in entry.part ? (\n <Column\n digit={entry.part.digit}\n direction={direction}\n phase={phase}\n delay={Math.min(entry.part.order * REVEAL_STAGGER_MS, REVEAL_MAX_DELAY_MS)}\n />\n ) : (\n <span className=\"counter-symbol\">{entry.part.text}</span>\n )}\n </Slot>\n ))}\n {suffix ? <span className=\"counter-symbol\">{suffix}</span> : null}\n </span>\n </span>\n );\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/announcement-bar/announcement-bar.d.ts
|
|
3
|
+
export interface AnnouncementAction {
|
|
4
|
+
label: string;
|
|
5
|
+
/** Renders a link. Without it the action is a button. */
|
|
6
|
+
href?: string;
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface Announcement {
|
|
10
|
+
/** Stable id, reported to callbacks. */
|
|
11
|
+
id: string;
|
|
12
|
+
message: ReactNode;
|
|
13
|
+
action?: AnnouncementAction;
|
|
14
|
+
/** Counts down to a moment, for example the end of a sale. */
|
|
15
|
+
countdown?: {
|
|
16
|
+
to: Date | string | number;
|
|
17
|
+
label?: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* AnnouncementBar: a slim bar for the top of a page.
|
|
22
|
+
*
|
|
23
|
+
* It can rotate several messages: the next one rises in from below (the
|
|
24
|
+
* previous one from above) while the bar follows the new height. Rotation pauses
|
|
25
|
+
* on hover, keyboard focus, a hidden tab and, with the `controls`, a Pause
|
|
26
|
+
* button whose ring is the clock; it is off under reduced motion. Messages can
|
|
27
|
+
* carry a call to action and a live countdown whose digits roll. Dismissing
|
|
28
|
+
* collapses the bar's height so the page below eases up, and with an `id` the
|
|
29
|
+
* dismissal is remembered in `localStorage`. While rotation runs the message is
|
|
30
|
+
* not announced on each change; when it holds, changes are announced politely.
|
|
31
|
+
*/
|
|
32
|
+
export interface AnnouncementBarProps {
|
|
33
|
+
messages: Announcement[];
|
|
34
|
+
/** Remembers dismissal under this id. Change the id to show a new campaign to everyone again. */
|
|
35
|
+
id?: string;
|
|
36
|
+
/** Whether the bar is shown. Leave it out to let the component manage it. */
|
|
37
|
+
open?: boolean;
|
|
38
|
+
/** @default true */
|
|
39
|
+
defaultOpen?: boolean;
|
|
40
|
+
onOpenChange?: (open: boolean) => void;
|
|
41
|
+
/** Index of the visible message. */
|
|
42
|
+
index?: number;
|
|
43
|
+
/** @default 0 */
|
|
44
|
+
defaultIndex?: number;
|
|
45
|
+
onIndexChange?: (index: number) => void;
|
|
46
|
+
/** Milliseconds each message stays before the next one. @default 6000 */
|
|
47
|
+
interval?: number;
|
|
48
|
+
/** Rotate automatically. Off under reduced motion. @default true */
|
|
49
|
+
autoPlay?: boolean;
|
|
50
|
+
/** Show previous, next and pause controls when there are several messages. By default only the close button shows. @default false */
|
|
51
|
+
controls?: boolean;
|
|
52
|
+
/** @default true */
|
|
53
|
+
dismissible?: boolean;
|
|
54
|
+
/** `inverted` is the foreground colour as the fill. @default "neutral" */
|
|
55
|
+
tone?: "neutral" | "inverted";
|
|
56
|
+
onAction?: (announcement: Announcement) => void;
|
|
57
|
+
onCountdownEnd?: (announcement: Announcement) => void;
|
|
58
|
+
/** Accessible name of the region. @default "Announcements" */
|
|
59
|
+
label?: string;
|
|
60
|
+
/** Extra classes for the bar. Merged last. */
|
|
61
|
+
className?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Forgets a remembered dismissal so the bar with this id shows again. */
|
|
64
|
+
export declare function clearAnnouncementDismissal(id: string): void;
|
|
65
|
+
export declare const AnnouncementBar: import("react").ForwardRefExoticComponent<AnnouncementBarProps & import("react").RefAttributes<HTMLElement>>;
|
|
66
|
+
//#endregion
|
|
67
|
+
//# sourceMappingURL=announcement-bar.d.ts.map
|