@seekalfred/primitives 0.6.0 → 0.7.3
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 +214 -15
- 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 +53 -4
- package/dist/alert/alert.js +100 -11
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +58 -18
- package/dist/alert-dialog/alert-dialog.js +152 -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 +1 -1
- 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/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- 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 +77 -0
- package/dist/carousel/carousel.js +415 -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/copy-button/copy-button.d.ts +18 -2
- package/dist/copy-button/copy-button.js +29 -8
- package/dist/copy-button/copy-button.js.map +1 -1
- 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 +86 -0
- package/dist/donut-chart/donut-chart.js +593 -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 +42 -10
- package/dist/empty-state/empty-state.js +44 -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 +50 -26
- package/dist/filter-bar/filter-bar.js +317 -157
- 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 +53 -10
- package/dist/index.js +52 -12
- package/dist/input/input.js +4 -4
- package/dist/input/input.js.map +1 -1
- 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-shell.js +10 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +31 -9
- 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/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.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 +116 -0
- package/dist/line-chart/line-chart.js +740 -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 +86 -0
- package/dist/money-input/money-input.js +613 -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 +89 -0
- package/dist/morph-select/morph-select.js +712 -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 +77 -0
- package/dist/multi-select/multi-select.js +209 -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.d.ts +16 -0
- package/dist/password-field/password-field.js +39 -10
- 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/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +51 -2
- package/dist/popover/popover.js +59 -8
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +16 -2
- package/dist/progress/progress.js +16 -9
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +110 -10
- 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 +212 -62
- 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 +24 -2
- package/dist/search-field/search-field.js +24 -9
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +28 -14
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +22 -10
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.js.map +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 +58 -5
- package/dist/stat-card/stat-card.js +144 -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 +55 -3
- 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.d.ts +36 -1
- package/dist/time-picker/time-picker.js +35 -7
- 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 +7 -1
- 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 +80 -0
- package/dist/usage-meter/usage-meter.js +458 -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
|
@@ -3,10 +3,10 @@ import { cn, cnAll } from "../lib/cn.js";
|
|
|
3
3
|
import { useIsomorphicLayoutEffect } from "../lib/motion.js";
|
|
4
4
|
import { labelVariants } from "../label/label.js";
|
|
5
5
|
import { RollingValue } from "../lib/rolling-value.js";
|
|
6
|
-
import { selectTriggerVariants, selectVariants } from "../select/select.js";
|
|
6
|
+
import { selectErrorClass, selectTriggerVariants, selectVariants } from "../select/select.js";
|
|
7
7
|
import { cva } from "class-variance-authority";
|
|
8
8
|
import { forwardRef, useMemo, useRef, useState } from "react";
|
|
9
|
-
import { Button, Label, ListBox, ListBoxItem, Popover, Select, SelectValue, Text, useLocale } from "react-aria-components";
|
|
9
|
+
import { Button, FieldError, Label, ListBox, ListBoxItem, Popover, Select, SelectValue, Text, useLocale } from "react-aria-components";
|
|
10
10
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { Time } from "@internationalized/date";
|
|
12
12
|
//#region src/time-picker/time-picker.tsx
|
|
@@ -27,14 +27,27 @@ import { Time } from "@internationalized/date";
|
|
|
27
27
|
const timePickerListVariants = cva([
|
|
28
28
|
"picker-pop w-(--trigger-width) origin-top overflow-hidden",
|
|
29
29
|
"[--pop-in-y:-6px] [--pop-in-scale:0.97] [--pop-out-y:-4px] [--pop-out-scale:0.98] [--pop-out-duration:120ms]",
|
|
30
|
-
"rounded-
|
|
30
|
+
"rounded-panel border-(length:--border-width) border-border p-1",
|
|
31
31
|
"bg-card text-card-foreground shadow-floating",
|
|
32
32
|
"box-border font-sans text-sm leading-(--leading-normal)"
|
|
33
33
|
]);
|
|
34
|
+
/**
|
|
35
|
+
* Keyboard focus on an INVALID trigger. The shared field surface shows focus by
|
|
36
|
+
* darkening the border, but an invalid field keeps its red border while focused,
|
|
37
|
+
* so a keyboard user tabbing onto an invalid picker saw no change at all
|
|
38
|
+
* (WCAG 2.4.7). The red border stays (it is the state) and the focus ring is
|
|
39
|
+
* added outside it. Only while invalid: a valid trigger is unchanged.
|
|
40
|
+
*/
|
|
41
|
+
const timePickerInvalidFocusClass = [
|
|
42
|
+
"group-data-[invalid]:data-[focus-visible]:outline-solid",
|
|
43
|
+
"group-data-[invalid]:data-[focus-visible]:outline-(length:--focus-ring-width)",
|
|
44
|
+
"group-data-[invalid]:data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
45
|
+
"group-data-[invalid]:data-[focus-visible]:outline-(--color-ring)"
|
|
46
|
+
].join(" ");
|
|
34
47
|
/** A time. The highlight changes almost at once (the arrows move it often); a dot marks the chosen one. */
|
|
35
48
|
const timePickerItemVariants = cva([
|
|
36
49
|
"relative flex min-h-9 cursor-pointer select-none items-center justify-between gap-2",
|
|
37
|
-
"rounded-
|
|
50
|
+
"rounded-control px-3 py-1.5 tabular-nums outline-none",
|
|
38
51
|
"text-sm leading-(--leading-normal) text-card-foreground",
|
|
39
52
|
"data-[focused]:bg-muted",
|
|
40
53
|
"motion-safe:transition-colors motion-safe:duration-80 motion-safe:ease-settle",
|
|
@@ -102,8 +115,14 @@ function CenteredListBox({ children }) {
|
|
|
102
115
|
*
|
|
103
116
|
* @example
|
|
104
117
|
* <TimePicker label="Start time" value={time} onChange={setTime} minuteStep={30} />
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* // With react-hook-form's Controller
|
|
121
|
+
* <TimePicker label="Sync time" id={field.name} name={field.name} value={field.value ?? null}
|
|
122
|
+
* onChange={field.onChange} onBlur={field.onBlur}
|
|
123
|
+
* isInvalid={!!fieldState.error} errorMessage={fieldState.error?.message} />
|
|
105
124
|
*/
|
|
106
|
-
const TimePicker = forwardRef(function TimePicker({ label, labelHidden, description, placeholder = "Select a time", value, defaultValue = null, onChange, minuteStep = 15, hourCycle = 12, size = "md", isDisabled, className }, ref) {
|
|
125
|
+
const TimePicker = forwardRef(function TimePicker({ label, labelHidden, description, placeholder = "Select a time", value, defaultValue = null, onChange, minuteStep = 15, hourCycle = 12, size = "md", isDisabled, id, name, isInvalid, errorMessage, onBlur, className }, ref) {
|
|
107
126
|
const { locale } = useLocale();
|
|
108
127
|
const [inner, setInner] = useState(defaultValue);
|
|
109
128
|
const current = value === void 0 ? inner : value;
|
|
@@ -140,6 +159,10 @@ const TimePicker = forwardRef(function TimePicker({ label, labelHidden, descript
|
|
|
140
159
|
onChange?.(time);
|
|
141
160
|
},
|
|
142
161
|
isDisabled,
|
|
162
|
+
id,
|
|
163
|
+
name,
|
|
164
|
+
isInvalid,
|
|
165
|
+
onBlur,
|
|
143
166
|
placeholder,
|
|
144
167
|
className: cn(selectVariants(), className),
|
|
145
168
|
children: [
|
|
@@ -151,7 +174,7 @@ const TimePicker = forwardRef(function TimePicker({ label, labelHidden, descript
|
|
|
151
174
|
/* @__PURE__ */ jsxs(Button, {
|
|
152
175
|
ref,
|
|
153
176
|
"data-slot": "time-picker-trigger",
|
|
154
|
-
className: selectTriggerVariants({ size }),
|
|
177
|
+
className: cnAll(selectTriggerVariants({ size }), timePickerInvalidFocusClass),
|
|
155
178
|
children: [
|
|
156
179
|
/* @__PURE__ */ jsx(ClockGlyph, {}),
|
|
157
180
|
/* @__PURE__ */ jsx(RollingValue, {
|
|
@@ -177,6 +200,11 @@ const TimePicker = forwardRef(function TimePicker({ label, labelHidden, descript
|
|
|
177
200
|
className: descriptionClass,
|
|
178
201
|
children: description
|
|
179
202
|
}) : null,
|
|
203
|
+
errorMessage == null ? null : /* @__PURE__ */ jsx(FieldError, {
|
|
204
|
+
"data-slot": "time-picker-error",
|
|
205
|
+
className: selectErrorClass,
|
|
206
|
+
children: errorMessage
|
|
207
|
+
}),
|
|
180
208
|
/* @__PURE__ */ jsx(Popover, {
|
|
181
209
|
"data-slot": "time-picker-popover",
|
|
182
210
|
className: timePickerListVariants(),
|
|
@@ -196,6 +224,6 @@ const TimePicker = forwardRef(function TimePicker({ label, labelHidden, descript
|
|
|
196
224
|
});
|
|
197
225
|
});
|
|
198
226
|
//#endregion
|
|
199
|
-
export { TimePicker, timePickerItemVariants, timePickerListVariants };
|
|
227
|
+
export { TimePicker, timePickerInvalidFocusClass, timePickerItemVariants, timePickerListVariants };
|
|
200
228
|
|
|
201
229
|
//# sourceMappingURL=time-picker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-picker.js","names":["AriaListBox","AriaSelect","AriaLabel","AriaButton","AriaSelectValue","AriaText","AriaPopover","AriaListBoxItem"],"sources":["../../src/time-picker/time-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { Time } from \"@internationalized/date\";\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useMemo, useRef, useState } from \"react\";\nimport {\n Button as AriaButton,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxItem as AriaListBoxItem,\n Popover as AriaPopover,\n Select as AriaSelect,\n SelectValue as AriaSelectValue,\n Text as AriaText,\n useLocale,\n type TimeValue,\n} from \"react-aria-components\";\n\nimport { labelVariants } from \"../label/label.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { RollingValue } from \"../lib/rolling-value.js\";\nimport { selectTriggerVariants, selectVariants } from \"../select/select.js\";\n\n/**\n * TimePicker, a field that shows a time and opens a list of times to pick from.\n *\n * Values are `TimeValue` objects from `@internationalized/date`, as Calendar and\n * DatePicker use `DateValue` (ADR 0006), not \"HH:MM\" strings: a time of day has\n * no time zone or format of its own. Unlike those two, this one builds a `Time`\n * at runtime to hand back, so the package is a real (peer) dependency here and\n * not only a type.\n *\n * The control is a React Aria `Select`, so the list is a listbox with typeahead,\n * arrow keys and a roving highlight; the motion (the value rolling like a clock\n * face, the chevron, the list growing from the field) sits on top of it.\n */\n\n/** The list: it grows from the field, and the highlight follows the pointer and the arrows. */\nexport const timePickerListVariants = cva([\n \"picker-pop w-(--trigger-width) origin-top overflow-hidden\",\n // A little less offset than a date picker: this list sits close under its field.\n \"[--pop-in-y:-6px] [--pop-in-scale:0.97] [--pop-out-y:-4px] [--pop-out-scale:0.98] [--pop-out-duration:120ms]\",\n \"rounded-md border-(length:--border-width) border-border p-1\",\n \"bg-card text-card-foreground shadow-floating\",\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n]);\n\n/** A time. The highlight changes almost at once (the arrows move it often); a dot marks the chosen one. */\nexport const timePickerItemVariants = cva([\n \"relative flex min-h-9 cursor-pointer select-none items-center justify-between gap-2\",\n \"rounded-2xs px-3 py-1.5 tabular-nums outline-none\",\n \"text-sm leading-(--leading-normal) text-card-foreground\",\n \"data-[focused]:bg-muted\",\n \"motion-safe:transition-colors motion-safe:duration-80 motion-safe:ease-settle\",\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-disabled)\",\n]);\n\nconst descriptionClass = \"text-sm leading-(--leading-normal) text-muted-foreground\";\n\nconst pad = (value: number) => String(value).padStart(2, \"0\");\nconst keyOf = (minutes: number) => `${pad(Math.floor(minutes / 60))}:${pad(minutes % 60)}`;\nconst minutesOf = (value: TimeValue) => value.hour * 60 + value.minute;\n\nfunction ClockGlyph() {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-muted-foreground\"\n >\n <circle cx=\"8\" cy=\"8\" r=\"5.5\" />\n <path d=\"M8 5v3l2 1.5\" />\n </svg>\n );\n}\n\n/** The chevron turns half a turn while the list is open, on the gentle spring. */\nfunction Chevron() {\n return (\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 strokeLinejoin=\"round\"\n className=\"text-muted-foreground motion-safe:transition-[rotate] motion-safe:duration-(--duration-gentle) motion-safe:ease-gentle group-aria-expanded/trigger:rotate-180\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n );\n}\n\n/**\n * The list of times. It opens centred on the chosen time (or at the top with\n * none), before the first frame, so there is no scroll to watch; the arrow keys\n * then keep the highlight in view, which React Aria does.\n */\nfunction CenteredListBox({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null);\n useIsomorphicLayoutEffect(() => {\n const list = ref.current;\n const chosen = list?.querySelector<HTMLElement>('[role=\"option\"][aria-selected=\"true\"]');\n if (!list || !chosen) return;\n list.scrollTop = chosen.offsetTop - (list.clientHeight - chosen.offsetHeight) / 2;\n }, []);\n return (\n <AriaListBox\n ref={ref}\n data-slot=\"time-picker-list\"\n className=\"relative max-h-62.5 overflow-y-auto overscroll-contain outline-none\"\n >\n {children}\n </AriaListBox>\n );\n}\n\nexport interface TimePickerProps {\n /** What is being chosen, and the control's accessible name. Required, as on Select. */\n label: string;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /** Help text under the control, named in `aria-describedby`. */\n description?: string;\n /** Shown while no time is chosen. @default \"Select a time\" */\n placeholder?: string;\n /** The chosen time. Controlled: pass `null` for none. */\n value?: TimeValue | null;\n /** The starting time when uncontrolled. @default null */\n defaultValue?: TimeValue | null;\n /** Called with the chosen time, as a `Time`. */\n onChange?: (value: TimeValue) => void;\n /** Minutes between the times in the list. @default 15 */\n minuteStep?: 1 | 5 | 10 | 15 | 30;\n /** 12-hour (9:00 AM) or 24-hour (09:00) times. @default 12 */\n hourCycle?: 12 | 24;\n /** Control scale of the trigger. @default \"md\" */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Disable the control. */\n isDisabled?: boolean;\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n}\n\n/**\n * A time picker.\n *\n * @example\n * <TimePicker label=\"Start time\" value={time} onChange={setTime} minuteStep={30} />\n */\nexport const TimePicker = forwardRef<HTMLButtonElement, TimePickerProps>(function TimePicker(\n {\n label,\n labelHidden,\n description,\n placeholder = \"Select a time\",\n value,\n defaultValue = null,\n onChange,\n minuteStep = 15,\n hourCycle = 12,\n size = \"md\",\n isDisabled,\n className,\n },\n ref,\n) {\n const { locale } = useLocale();\n const [inner, setInner] = useState<TimeValue | null>(defaultValue);\n const current = value === undefined ? inner : value;\n const minutes = current ? minutesOf(current) : null;\n const key = minutes === null ? null : keyOf(minutes);\n\n const display = useMemo(() => {\n const format = new Intl.DateTimeFormat(locale, {\n hour: \"numeric\",\n minute: \"2-digit\",\n hourCycle: hourCycle === 24 ? \"h23\" : \"h12\",\n timeZone: \"UTC\",\n });\n return (total: number) =>\n format\n .format(new Date(Date.UTC(2000, 0, 1, Math.floor(total / 60), total % 60)))\n .replace(/\\u202f/g, \" \");\n }, [locale, hourCycle]);\n\n // The list: every step of the day, plus the current time if it falls between steps.\n const options = useMemo(() => {\n const all = Array.from({ length: Math.ceil(1440 / minuteStep) }, (_, i) => i * minuteStep);\n if (minutes !== null && !all.includes(minutes)) all.push(minutes);\n return all.sort((a, b) => a - b);\n }, [minuteStep, minutes]);\n\n // Which way the time moved, so the shown value rolls like a clock face.\n const [previous, setPrevious] = useState(minutes);\n const [dir, setDir] = useState(1);\n if (previous !== minutes) {\n setPrevious(minutes);\n setDir(minutes !== null && previous !== null && minutes < previous ? -1 : 1);\n }\n\n return (\n <AriaSelect\n data-slot=\"time-picker\"\n selectedKey={key}\n onSelectionChange={(next) => {\n if (next == null) return;\n const [hour = 0, minute = 0] = String(next).split(\":\").map(Number);\n const time = new Time(hour, minute);\n if (value === undefined) setInner(time);\n onChange?.(time);\n }}\n isDisabled={isDisabled}\n placeholder={placeholder}\n className={cn(selectVariants(), className)}\n >\n <AriaLabel\n data-slot=\"time-picker-label\"\n className={cnAll(labelVariants(), labelHidden && \"sr-only\")}\n >\n {label}\n </AriaLabel>\n <AriaButton ref={ref} data-slot=\"time-picker-trigger\" className={selectTriggerVariants({ size })}>\n <ClockGlyph />\n <RollingValue\n srValue={\n <AriaSelectValue data-slot=\"time-picker-value\" className=\"sr-only\">\n {({ isPlaceholder, selectedText }) => (isPlaceholder ? placeholder : selectedText)}\n </AriaSelectValue>\n }\n swapKey={key ?? \"placeholder\"}\n dir={key === null ? -1 : dir}\n >\n {minutes === null ? (\n <span data-slot=\"time-picker-placeholder\" className=\"text-muted-foreground\">\n {placeholder}\n </span>\n ) : (\n display(minutes)\n )}\n </RollingValue>\n <Chevron />\n </AriaButton>\n {description ? (\n <AriaText slot=\"description\" data-slot=\"time-picker-description\" className={descriptionClass}>\n {description}\n </AriaText>\n ) : null}\n <AriaPopover data-slot=\"time-picker-popover\" className={timePickerListVariants()}>\n <CenteredListBox>\n {options.map((total) => (\n <AriaListBoxItem\n key={keyOf(total)}\n id={keyOf(total)}\n textValue={display(total)}\n data-slot=\"time-picker-item\"\n className={timePickerItemVariants()}\n >\n {({ isSelected }) => (\n <>\n {display(total)}\n {isSelected ? (\n <span\n aria-hidden=\"true\"\n data-slot=\"time-picker-dot\"\n className=\"size-1.5 rounded-full bg-foreground\"\n />\n ) : null}\n </>\n )}\n </AriaListBoxItem>\n ))}\n </CenteredListBox>\n </AriaPopover>\n </AriaSelect>\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"time-picker.js","names":["AriaListBox","AriaSelect","AriaLabel","AriaButton","AriaSelectValue","AriaText","AriaFieldError","AriaPopover","AriaListBoxItem"],"sources":["../../src/time-picker/time-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { Time } from \"@internationalized/date\";\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useMemo, useRef, useState, type FocusEvent, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n FieldError as AriaFieldError,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxItem as AriaListBoxItem,\n Popover as AriaPopover,\n Select as AriaSelect,\n SelectValue as AriaSelectValue,\n Text as AriaText,\n useLocale,\n type TimeValue,\n} from \"react-aria-components\";\n\nimport { labelVariants } from \"../label/label.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { RollingValue } from \"../lib/rolling-value.js\";\nimport { selectErrorClass, selectTriggerVariants, selectVariants } from \"../select/select.js\";\n\n/**\n * TimePicker, a field that shows a time and opens a list of times to pick from.\n *\n * Values are `TimeValue` objects from `@internationalized/date`, as Calendar and\n * DatePicker use `DateValue` (ADR 0006), not \"HH:MM\" strings: a time of day has\n * no time zone or format of its own. Unlike those two, this one builds a `Time`\n * at runtime to hand back, so the package is a real (peer) dependency here and\n * not only a type.\n *\n * The control is a React Aria `Select`, so the list is a listbox with typeahead,\n * arrow keys and a roving highlight; the motion (the value rolling like a clock\n * face, the chevron, the list growing from the field) sits on top of it.\n */\n\n/** The list: it grows from the field, and the highlight follows the pointer and the arrows. */\nexport const timePickerListVariants = cva([\n \"picker-pop w-(--trigger-width) origin-top overflow-hidden\",\n // A little less offset than a date picker: this list sits close under its field.\n \"[--pop-in-y:-6px] [--pop-in-scale:0.97] [--pop-out-y:-4px] [--pop-out-scale:0.98] [--pop-out-duration:120ms]\",\n \"rounded-panel border-(length:--border-width) border-border p-1\",\n \"bg-card text-card-foreground shadow-floating\",\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n]);\n\n/**\n * Keyboard focus on an INVALID trigger. The shared field surface shows focus by\n * darkening the border, but an invalid field keeps its red border while focused,\n * so a keyboard user tabbing onto an invalid picker saw no change at all\n * (WCAG 2.4.7). The red border stays (it is the state) and the focus ring is\n * added outside it. Only while invalid: a valid trigger is unchanged.\n */\nexport const timePickerInvalidFocusClass = [\n \"group-data-[invalid]:data-[focus-visible]:outline-solid\",\n \"group-data-[invalid]:data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"group-data-[invalid]:data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"group-data-[invalid]:data-[focus-visible]:outline-(--color-ring)\",\n].join(\" \");\n\n/** A time. The highlight changes almost at once (the arrows move it often); a dot marks the chosen one. */\nexport const timePickerItemVariants = cva([\n \"relative flex min-h-9 cursor-pointer select-none items-center justify-between gap-2\",\n \"rounded-control px-3 py-1.5 tabular-nums outline-none\",\n \"text-sm leading-(--leading-normal) text-card-foreground\",\n \"data-[focused]:bg-muted\",\n \"motion-safe:transition-colors motion-safe:duration-80 motion-safe:ease-settle\",\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-disabled)\",\n]);\n\nconst descriptionClass = \"text-sm leading-(--leading-normal) text-muted-foreground\";\n\nconst pad = (value: number) => String(value).padStart(2, \"0\");\nconst keyOf = (minutes: number) => `${pad(Math.floor(minutes / 60))}:${pad(minutes % 60)}`;\nconst minutesOf = (value: TimeValue) => value.hour * 60 + value.minute;\n\nfunction ClockGlyph() {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-muted-foreground\"\n >\n <circle cx=\"8\" cy=\"8\" r=\"5.5\" />\n <path d=\"M8 5v3l2 1.5\" />\n </svg>\n );\n}\n\n/** The chevron turns half a turn while the list is open, on the gentle spring. */\nfunction Chevron() {\n return (\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 strokeLinejoin=\"round\"\n className=\"text-muted-foreground motion-safe:transition-[rotate] motion-safe:duration-(--duration-gentle) motion-safe:ease-gentle group-aria-expanded/trigger:rotate-180\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n );\n}\n\n/**\n * The list of times. It opens centred on the chosen time (or at the top with\n * none), before the first frame, so there is no scroll to watch; the arrow keys\n * then keep the highlight in view, which React Aria does.\n */\nfunction CenteredListBox({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null);\n useIsomorphicLayoutEffect(() => {\n const list = ref.current;\n const chosen = list?.querySelector<HTMLElement>('[role=\"option\"][aria-selected=\"true\"]');\n if (!list || !chosen) return;\n list.scrollTop = chosen.offsetTop - (list.clientHeight - chosen.offsetHeight) / 2;\n }, []);\n return (\n <AriaListBox\n ref={ref}\n data-slot=\"time-picker-list\"\n className=\"relative max-h-62.5 overflow-y-auto overscroll-contain outline-none\"\n >\n {children}\n </AriaListBox>\n );\n}\n\nexport interface TimePickerProps {\n /** What is being chosen, and the control's accessible name. Required, as on Select. */\n label: string;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /** Help text under the control, named in `aria-describedby`. */\n description?: string;\n /** Shown while no time is chosen. @default \"Select a time\" */\n placeholder?: string;\n /**\n * The chosen time. Controlled: pass `null` for none. `undefined` means\n * UNCONTROLLED, so going from a time to `undefined` keeps the last time on\n * screen: to clear a controlled picker (a form reset) pass `null`, for example\n * `value={field.value ?? null}`.\n */\n value?: TimeValue | null;\n /** The starting time when uncontrolled. @default null */\n defaultValue?: TimeValue | null;\n /** Called with the chosen time, as a `Time`. */\n onChange?: (value: TimeValue) => void;\n /** Minutes between the times in the list. @default 15 */\n minuteStep?: 1 | 5 | 10 | 15 | 30;\n /** 12-hour (9:00 AM) or 24-hour (09:00) times. @default 12 */\n hourCycle?: 12 | 24;\n /** Control scale of the trigger. @default \"md\" */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Disable the control. */\n isDisabled?: boolean;\n /**\n * The trigger's `id`, so a `<label htmlFor>` outside the picker (or a form\n * library that focuses a field by id) reaches the real control.\n */\n id?: string;\n /**\n * Submits the time with a native form, as \"HH:MM\" in 24-hour time (the same\n * string an `<input type=\"time\">` submits). While no time is chosen the field\n * submits an empty string under this name, as an empty `<input type=\"time\">` does.\n */\n name?: string;\n /**\n * Marks the time as wrong: the trigger takes the invalid border. Pair it with\n * `errorMessage`, or the state is shown in colour only.\n */\n isInvalid?: boolean;\n /**\n * Why the time is wrong, under the control. It shows only while `isInvalid`\n * is set, and React Aria names it in the trigger's `aria-describedby`.\n */\n errorMessage?: ReactNode;\n /** Called when focus leaves the trigger, such as react-hook-form's `field.onBlur`. */\n onBlur?: (event: FocusEvent<Element>) => void;\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n}\n\n/**\n * A time picker.\n *\n * @example\n * <TimePicker label=\"Start time\" value={time} onChange={setTime} minuteStep={30} />\n *\n * @example\n * // With react-hook-form's Controller\n * <TimePicker label=\"Sync time\" id={field.name} name={field.name} value={field.value ?? null}\n * onChange={field.onChange} onBlur={field.onBlur}\n * isInvalid={!!fieldState.error} errorMessage={fieldState.error?.message} />\n */\nexport const TimePicker = forwardRef<HTMLButtonElement, TimePickerProps>(function TimePicker(\n {\n label,\n labelHidden,\n description,\n placeholder = \"Select a time\",\n value,\n defaultValue = null,\n onChange,\n minuteStep = 15,\n hourCycle = 12,\n size = \"md\",\n isDisabled,\n id,\n name,\n isInvalid,\n errorMessage,\n onBlur,\n className,\n },\n ref,\n) {\n const { locale } = useLocale();\n const [inner, setInner] = useState<TimeValue | null>(defaultValue);\n const current = value === undefined ? inner : value;\n const minutes = current ? minutesOf(current) : null;\n const key = minutes === null ? null : keyOf(minutes);\n\n const display = useMemo(() => {\n const format = new Intl.DateTimeFormat(locale, {\n hour: \"numeric\",\n minute: \"2-digit\",\n hourCycle: hourCycle === 24 ? \"h23\" : \"h12\",\n timeZone: \"UTC\",\n });\n return (total: number) =>\n format\n .format(new Date(Date.UTC(2000, 0, 1, Math.floor(total / 60), total % 60)))\n .replace(/\\u202f/g, \" \");\n }, [locale, hourCycle]);\n\n // The list: every step of the day, plus the current time if it falls between steps.\n const options = useMemo(() => {\n const all = Array.from({ length: Math.ceil(1440 / minuteStep) }, (_, i) => i * minuteStep);\n if (minutes !== null && !all.includes(minutes)) all.push(minutes);\n return all.sort((a, b) => a - b);\n }, [minuteStep, minutes]);\n\n // Which way the time moved, so the shown value rolls like a clock face.\n const [previous, setPrevious] = useState(minutes);\n const [dir, setDir] = useState(1);\n if (previous !== minutes) {\n setPrevious(minutes);\n setDir(minutes !== null && previous !== null && minutes < previous ? -1 : 1);\n }\n\n return (\n <AriaSelect\n data-slot=\"time-picker\"\n selectedKey={key}\n onSelectionChange={(next) => {\n if (next == null) return;\n const [hour = 0, minute = 0] = String(next).split(\":\").map(Number);\n const time = new Time(hour, minute);\n if (value === undefined) setInner(time);\n onChange?.(time);\n }}\n isDisabled={isDisabled}\n id={id}\n name={name}\n isInvalid={isInvalid}\n onBlur={onBlur}\n placeholder={placeholder}\n className={cn(selectVariants(), className)}\n >\n <AriaLabel\n data-slot=\"time-picker-label\"\n className={cnAll(labelVariants(), labelHidden && \"sr-only\")}\n >\n {label}\n </AriaLabel>\n <AriaButton\n ref={ref}\n data-slot=\"time-picker-trigger\"\n className={cnAll(selectTriggerVariants({ size }), timePickerInvalidFocusClass)}\n >\n <ClockGlyph />\n <RollingValue\n srValue={\n <AriaSelectValue data-slot=\"time-picker-value\" className=\"sr-only\">\n {({ isPlaceholder, selectedText }) => (isPlaceholder ? placeholder : selectedText)}\n </AriaSelectValue>\n }\n swapKey={key ?? \"placeholder\"}\n dir={key === null ? -1 : dir}\n >\n {minutes === null ? (\n <span data-slot=\"time-picker-placeholder\" className=\"text-muted-foreground\">\n {placeholder}\n </span>\n ) : (\n display(minutes)\n )}\n </RollingValue>\n <Chevron />\n </AriaButton>\n {description ? (\n <AriaText slot=\"description\" data-slot=\"time-picker-description\" className={descriptionClass}>\n {description}\n </AriaText>\n ) : null}\n {errorMessage == null ? null : (\n // Select's pattern: React Aria renders FieldError only while the field is invalid.\n <AriaFieldError data-slot=\"time-picker-error\" className={selectErrorClass}>\n {errorMessage}\n </AriaFieldError>\n )}\n <AriaPopover data-slot=\"time-picker-popover\" className={timePickerListVariants()}>\n <CenteredListBox>\n {options.map((total) => (\n <AriaListBoxItem\n key={keyOf(total)}\n id={keyOf(total)}\n textValue={display(total)}\n data-slot=\"time-picker-item\"\n className={timePickerItemVariants()}\n >\n {({ isSelected }) => (\n <>\n {display(total)}\n {isSelected ? (\n <span\n aria-hidden=\"true\"\n data-slot=\"time-picker-dot\"\n className=\"size-1.5 rounded-full bg-foreground\"\n />\n ) : null}\n </>\n )}\n </AriaListBoxItem>\n ))}\n </CenteredListBox>\n </AriaPopover>\n </AriaSelect>\n );\n});\n"],"mappings":"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"}
|
package/dist/toast/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { ToastContent, ToastRegion, ToastRegionProps, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants } from "./toast.js";
|
|
2
|
-
export { type ToastContent, ToastRegion, type ToastRegionProps, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants };
|
|
1
|
+
import { ToastContent, ToastRegion, ToastRegionProps, toast, toastCloseVariants, toastIconVariants, toastQueue, toastRegionVariants, toastVariants } from "./toast.js";
|
|
2
|
+
export { type ToastContent, ToastRegion, type ToastRegionProps, toast, toastCloseVariants, toastIconVariants, toastQueue, toastRegionVariants, toastVariants };
|
package/dist/toast/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { ToastRegion, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants } from "./toast.js";
|
|
2
|
-
export { ToastRegion, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants };
|
|
1
|
+
import { ToastRegion, toast, toastCloseVariants, toastIconVariants, toastQueue, toastRegionVariants, toastVariants } from "./toast.js";
|
|
2
|
+
export { ToastRegion, toast, toastCloseVariants, toastIconVariants, toastQueue, toastRegionVariants, toastVariants };
|
package/dist/toast/toast.d.ts
CHANGED
|
@@ -34,7 +34,11 @@ import { UNSTABLE_ToastQueue } from "react-aria-components";
|
|
|
34
34
|
/** Where the region sits. Fixed, above everything, out of the way of content. */
|
|
35
35
|
export declare const toastRegionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
36
36
|
export declare const toastVariants: (props?: ({
|
|
37
|
-
tone?: "
|
|
37
|
+
tone?: "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
|
|
38
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
39
|
+
/** The status icon's circle: the tone's light fill with its AA text colour. */
|
|
40
|
+
export declare const toastIconVariants: (props?: ({
|
|
41
|
+
tone?: "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
|
|
38
42
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
39
43
|
export declare const toastCloseVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
40
44
|
/** What a toast carries. `title` is required: an unnamed toast announces nothing. */
|
|
@@ -43,6 +47,8 @@ export interface ToastContent extends VariantProps<typeof toastVariants> {
|
|
|
43
47
|
title: ReactNode;
|
|
44
48
|
/** Secondary detail, beneath the title. */
|
|
45
49
|
description?: ReactNode;
|
|
50
|
+
/** Replaces the tone's status icon. Pass a 16px glyph; it is hidden from assistive tech. */
|
|
51
|
+
icon?: ReactNode;
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
48
54
|
* The queue every `toast()` call feeds.
|
package/dist/toast/toast.js
CHANGED
|
@@ -44,23 +44,42 @@ const toastRegionVariants = cva([
|
|
|
44
44
|
]);
|
|
45
45
|
const toastVariants = cva([
|
|
46
46
|
"flex items-start gap-3 box-border w-full",
|
|
47
|
-
"rounded-
|
|
48
|
-
"font-sans shadow-
|
|
47
|
+
"rounded-panel border-(length:--border-width) border-border bg-surface text-foreground",
|
|
48
|
+
"py-3.5 ps-4 pe-3.5 font-sans shadow-floating",
|
|
49
49
|
"motion-safe:animate-toast-in motion-reduce:animate-fade-in",
|
|
50
50
|
"motion-safe:data-[closing]:animate-toast-out motion-reduce:data-[closing]:animate-fade-out"
|
|
51
51
|
], {
|
|
52
52
|
variants: { tone: {
|
|
53
|
-
neutral: "
|
|
54
|
-
success: "
|
|
55
|
-
warning: "
|
|
56
|
-
danger: "
|
|
57
|
-
info: "
|
|
53
|
+
neutral: "",
|
|
54
|
+
success: "",
|
|
55
|
+
warning: "",
|
|
56
|
+
danger: "",
|
|
57
|
+
info: ""
|
|
58
58
|
} },
|
|
59
59
|
defaultVariants: { tone: "neutral" }
|
|
60
60
|
});
|
|
61
|
+
/** The status icon's circle: the tone's light fill with its AA text colour. */
|
|
62
|
+
const toastIconVariants = cva("mt-px grid size-7.5 shrink-0 place-items-center rounded-circle [&_svg]:size-4", {
|
|
63
|
+
variants: { tone: {
|
|
64
|
+
neutral: "bg-muted text-text-secondary",
|
|
65
|
+
success: "bg-success-subtle text-success-foreground",
|
|
66
|
+
warning: "bg-warning-subtle text-warning-foreground",
|
|
67
|
+
danger: "bg-destructive-subtle text-destructive-subtle-foreground",
|
|
68
|
+
info: "bg-info-subtle text-info-foreground"
|
|
69
|
+
} },
|
|
70
|
+
defaultVariants: { tone: "neutral" }
|
|
71
|
+
});
|
|
72
|
+
const TONE_ICON_PATHS = {
|
|
73
|
+
neutral: "M6 16v-5a6 6 0 1 1 12 0v5l1.5 2h-15zM10 20.5a2 2 0 0 0 4 0",
|
|
74
|
+
success: "M4 12.5l5 5L20 6.5",
|
|
75
|
+
warning: "M12 7v6.5M12 17.25h.01",
|
|
76
|
+
danger: "M7 7l10 10M17 7L7 17",
|
|
77
|
+
info: "M12 11v6M12 7.25h.01"
|
|
78
|
+
};
|
|
61
79
|
const toastCloseVariants = cva([
|
|
62
|
-
"ms-auto shrink-0 rounded-
|
|
63
|
-
"
|
|
80
|
+
"-mt-px -me-0.5 ms-auto grid size-8 shrink-0 place-items-center rounded-circle cursor-pointer",
|
|
81
|
+
"border-0 bg-transparent p-0 text-text-secondary",
|
|
82
|
+
"data-[hovered]:bg-muted data-[hovered]:text-foreground",
|
|
64
83
|
"toast-close",
|
|
65
84
|
"outline-none",
|
|
66
85
|
"data-[focus-visible]:outline-solid",
|
|
@@ -99,47 +118,70 @@ function toast(content, timeout = 5e3) {
|
|
|
99
118
|
*/
|
|
100
119
|
function ToastItem({ queued, closeLabel }) {
|
|
101
120
|
const [closing, setClosing] = useState(false);
|
|
121
|
+
const tone = queued.content.tone ?? "neutral";
|
|
102
122
|
const finish = useCallback(() => toastQueue.close(queued.key), [queued.key]);
|
|
103
123
|
useExitBackstop(closing, finish);
|
|
104
124
|
return /* @__PURE__ */ jsxs(UNSTABLE_Toast, {
|
|
105
125
|
toast: queued,
|
|
106
126
|
"data-slot": "toast",
|
|
107
|
-
"data-tone":
|
|
127
|
+
"data-tone": tone,
|
|
108
128
|
"data-closing": closing || void 0,
|
|
109
129
|
onAnimationEnd: (event) => {
|
|
110
130
|
if (closing && event.target === event.currentTarget) finish();
|
|
111
131
|
},
|
|
112
|
-
className: toastVariants({ tone
|
|
113
|
-
children: [
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
slot: "title",
|
|
118
|
-
"data-slot": "toast-title",
|
|
119
|
-
className: "text-sm leading-(--leading-snug) font-medium",
|
|
120
|
-
children: queued.content.title
|
|
121
|
-
}), queued.content.description == null ? null : /* @__PURE__ */ jsx(Text, {
|
|
122
|
-
slot: "description",
|
|
123
|
-
"data-slot": "toast-description",
|
|
124
|
-
className: "text-sm leading-(--leading-normal) opacity-90",
|
|
125
|
-
children: queued.content.description
|
|
126
|
-
})]
|
|
127
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
128
|
-
"aria-label": closeLabel,
|
|
129
|
-
"data-slot": "toast-close",
|
|
130
|
-
className: toastCloseVariants(),
|
|
131
|
-
onPress: () => setClosing(true),
|
|
132
|
-
children: /* @__PURE__ */ jsx("svg", {
|
|
132
|
+
className: toastVariants({ tone }),
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ jsx("span", {
|
|
135
|
+
"data-slot": "toast-icon",
|
|
136
|
+
"data-tone": tone,
|
|
133
137
|
"aria-hidden": "true",
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
className: toastIconVariants({ tone }),
|
|
139
|
+
children: queued.content.icon ?? /* @__PURE__ */ jsx("svg", {
|
|
140
|
+
viewBox: "0 0 24 24",
|
|
141
|
+
fill: "none",
|
|
142
|
+
stroke: "currentColor",
|
|
143
|
+
strokeWidth: "2.25",
|
|
144
|
+
strokeLinecap: "round",
|
|
145
|
+
strokeLinejoin: "round",
|
|
146
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
147
|
+
d: TONE_ICON_PATHS[tone],
|
|
148
|
+
pathLength: 1,
|
|
149
|
+
className: tone === "success" ? "toast-check" : void 0
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}),
|
|
153
|
+
/* @__PURE__ */ jsxs(UNSTABLE_ToastContent, {
|
|
154
|
+
"data-slot": "toast-content",
|
|
155
|
+
className: "flex min-w-0 flex-1 flex-col gap-0.5 pt-1.25",
|
|
156
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
157
|
+
slot: "title",
|
|
158
|
+
"data-slot": "toast-title",
|
|
159
|
+
className: "text-sm leading-(--leading-normal) font-medium text-foreground",
|
|
160
|
+
children: queued.content.title
|
|
161
|
+
}), queued.content.description == null ? null : /* @__PURE__ */ jsx(Text, {
|
|
162
|
+
slot: "description",
|
|
163
|
+
"data-slot": "toast-description",
|
|
164
|
+
className: "text-sm leading-(--leading-normal) text-text-secondary",
|
|
165
|
+
children: queued.content.description
|
|
166
|
+
})]
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ jsx(Button, {
|
|
169
|
+
"aria-label": closeLabel,
|
|
170
|
+
"data-slot": "toast-close",
|
|
171
|
+
className: toastCloseVariants(),
|
|
172
|
+
onPress: () => setClosing(true),
|
|
173
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
174
|
+
"aria-hidden": "true",
|
|
175
|
+
viewBox: "0 0 16 16",
|
|
176
|
+
fill: "none",
|
|
177
|
+
stroke: "currentColor",
|
|
178
|
+
strokeWidth: "1.75",
|
|
179
|
+
strokeLinecap: "round",
|
|
180
|
+
className: "size-(--icon-sm)",
|
|
181
|
+
children: /* @__PURE__ */ jsx("path", { d: "m4 4 8 8M12 4l-8 8" })
|
|
182
|
+
})
|
|
141
183
|
})
|
|
142
|
-
|
|
184
|
+
]
|
|
143
185
|
});
|
|
144
186
|
}
|
|
145
187
|
/**
|
|
@@ -162,6 +204,6 @@ const ToastRegion = forwardRef(function ToastRegion({ label, closeLabel = "Dismi
|
|
|
162
204
|
});
|
|
163
205
|
});
|
|
164
206
|
//#endregion
|
|
165
|
-
export { ToastRegion, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants };
|
|
207
|
+
export { ToastRegion, toast, toastCloseVariants, toastIconVariants, toastQueue, toastRegionVariants, toastVariants };
|
|
166
208
|
|
|
167
209
|
//# sourceMappingURL=toast.js.map
|
package/dist/toast/toast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":["AriaToastQueue","AriaToast","AriaToastContent","AriaText","AriaButton","AriaToastRegion"],"sources":["../../src/toast/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, useCallback, useState, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Text as AriaText,\n UNSTABLE_Toast as AriaToast,\n UNSTABLE_ToastContent as AriaToastContent,\n UNSTABLE_ToastQueue as AriaToastQueue,\n UNSTABLE_ToastRegion as AriaToastRegion,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useExitBackstop } from \"../lib/motion.js\";\n\n/**\n * Toast: a transient message, announced without stealing focus.\n *\n * Maturity: **experimental**, and the reason is upstream rather than here.\n * React Aria ships this API as `UNSTABLE_Toast*` in 1.21.1, so a RAC minor\n * bump can rename or reshape it. The alternative was hand-rolling the live\n * region, the queue, the timers and the focus behaviour, which is how every\n * toast implementation acquires its accessibility bugs -- so this takes the\n * upstream one and labels the risk instead of hiding it.\n *\n * THE PRODUCT HAS 30 FILES CALLING `useToast` AND 67 `toast(...)` CALLS, and\n * its implementation is the shadcn reducer with both of its known defects\n * still in place:\n *\n * TOAST_LIMIT = 1 -- a second toast REPLACES the first, so a\n * burst of results shows only the last one.\n * TOAST_REMOVE_DELAY = 1e6 -- sixteen minutes. Nothing auto-dismisses;\n * every toast waits to be closed by hand.\n *\n * Neither is ported. `maxVisibleToasts` defaults to 3 and `timeout` to 5s,\n * which is the WCAG 2.2.1 floor React Aria enforces -- it REFUSES a timeout\n * under 5000ms, because a message that vanishes faster cannot be read by\n * someone using a screen magnifier.\n *\n * The queue is a module singleton on purpose. The product's `toast()` is\n * importable from anywhere and called from API error handlers and effects\n * that have no component to hang a hook off, and a context-based queue would\n * have made those 67 call sites into 67 refactors.\n */\n\n/** Where the region sits. Fixed, above everything, out of the way of content. */\nexport const toastRegionVariants = cva([\n \"fixed z-50 flex flex-col gap-2 box-border\",\n // Logical insets so the stack follows the writing direction.\n \"bottom-(--density-bar-pad-y) end-(--density-bar-pad-x)\",\n \"w-[min(24rem,calc(100vw-2rem))]\",\n \"outline-none\",\n]);\n\nexport const toastVariants = cva(\n [\n \"flex items-start gap-3 box-border w-full\",\n \"rounded-md border-(length:--border-width) p-(--density-bar-pad-x)\",\n \"font-sans shadow-surface\",\n // Rises in when it mounts. When its own close control is pressed it sinks out\n // first (data-closing) and the queue is told when that ends. Reduced motion:\n // opacity only. Not colour alone: the region reflows too.\n \"motion-safe:animate-toast-in motion-reduce:animate-fade-in\",\n \"motion-safe:data-[closing]:animate-toast-out motion-reduce:data-[closing]:animate-fade-out\",\n ],\n {\n variants: {\n /*\n * The same vocabulary as Badge, deliberately. The product's toast had\n * `default` and `destructive` only, so 38 of its 67 calls pass a variant\n * that cannot say \"this worked\" -- and a save confirmation styled\n * identically to a failure is the most common thing a toast gets wrong.\n */\n tone: {\n neutral: \"bg-background border-separator text-foreground\",\n success: \"bg-success-subtle border-success-strong text-success-foreground\",\n warning: \"bg-warning-subtle border-warning-strong text-warning-foreground\",\n danger:\n \"bg-destructive-subtle border-destructive-strong text-destructive-subtle-foreground\",\n info: \"bg-info-subtle border-info-strong text-info-foreground\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\nexport const toastCloseVariants = cva([\n \"ms-auto shrink-0 rounded-sm cursor-pointer\",\n \"text-current opacity-70 data-[hovered]:opacity-100\",\n // Quick press, spring release (styles.css).\n \"toast-close\",\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]);\n\n/** What a toast carries. `title` is required: an unnamed toast announces nothing. */\nexport interface ToastContent extends VariantProps<typeof toastVariants> {\n /** The message. Required -- a toast with only a description announces as loose text. */\n title: ReactNode;\n /** Secondary detail, beneath the title. */\n description?: ReactNode;\n}\n\n/**\n * The queue every `toast()` call feeds.\n *\n * Exported so a consumer can read `visibleToasts` in a test, or dismiss them\n * on a route change -- there is no `clear()` upstream, so that is\n * `[...toastQueue.visibleToasts].forEach((t) => toastQueue.close(t.key))`.\n * Not something to touch in normal use.\n */\nexport const toastQueue = new AriaToastQueue<ToastContent>({\n /*\n * Three, against the product's one. Its TOAST_LIMIT of 1 means a second\n * toast REPLACES the first -- so a page that reports three failed uploads\n * shows one and silently drops the rest.\n */\n maxVisibleToasts: 3,\n});\n\n/**\n * Show a toast.\n *\n * @example\n * toast({ title: \"Report saved\" });\n * toast({ title: \"Upload failed\", description: err.message, tone: \"danger\" });\n *\n * @param timeout - ms before it dismisses itself. React Aria REFUSES anything\n * under 5000 and anything at all while the region is focused, because a\n * message that disappears before it can be read fails WCAG 2.2.1. Pass 0 to\n * require a manual dismiss, which is right for an error a user must act on.\n */\nexport function toast(content: ToastContent, timeout = 5000) {\n return toastQueue.add(content, timeout === 0 ? undefined : { timeout });\n}\n\ntype Queued = (typeof toastQueue.visibleToasts)[number];\n\n/**\n * One toast. Pressing its close control plays the exit first, then closes it in the\n * queue; React Aria unmounts a closed toast at once and has no exit of its own, so a\n * toast closed any other way (the timeout, `toastQueue.close`) leaves without one.\n */\nfunction ToastItem({ queued, closeLabel }: { queued: Queued; closeLabel: string }) {\n const [closing, setClosing] = useState(false);\n const finish = useCallback(() => toastQueue.close(queued.key), [queued.key]);\n // If the animation never reports (reduced motion, a hidden tab), close anyway.\n useExitBackstop(closing, finish);\n return (\n <AriaToast\n toast={queued}\n data-slot=\"toast\"\n data-tone={queued.content.tone ?? \"neutral\"}\n data-closing={closing || undefined}\n onAnimationEnd={(event) => {\n if (closing && event.target === event.currentTarget) finish();\n }}\n className={toastVariants({ tone: queued.content.tone })}\n >\n <AriaToastContent data-slot=\"toast-content\" className=\"flex min-w-0 flex-col gap-1\">\n {/*\n slot=\"title\" is what React Aria announces. A plain div here\n renders identically and leaves the toast unnamed.\n */}\n <AriaText\n slot=\"title\"\n data-slot=\"toast-title\"\n className=\"text-sm leading-(--leading-snug) font-medium\"\n >\n {queued.content.title}\n </AriaText>\n {queued.content.description == null ? null : (\n <AriaText\n slot=\"description\"\n data-slot=\"toast-description\"\n className=\"text-sm leading-(--leading-normal) opacity-90\"\n >\n {queued.content.description}\n </AriaText>\n )}\n </AriaToastContent>\n <AriaButton\n aria-label={closeLabel}\n data-slot=\"toast-close\"\n className={toastCloseVariants()}\n onPress={() => setClosing(true)}\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 className=\"size-(--icon-sm)\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </AriaToast>\n );\n}\n\nexport interface ToastRegionProps {\n /**\n * Names the region for assistive tech.\n *\n * **Required.** React Aria renders the region as a labelled landmark, and\n * an unnamed one is announced as \"region\" with no indication of what just\n * arrived. It is a prop rather than a default string because\n * ARCHITECTURE section 7 rule 9 routes user-visible copy through the\n * consumer until `useMessages()` exists.\n */\n label: string;\n /** Accessible name for each toast's dismiss control. */\n closeLabel?: string;\n /** Extra classes for the region. Merged last. */\n className?: string;\n}\n\n/**\n * Renders the queue. Mount ONCE, near the root.\n *\n * React Aria only renders the landmark while something is queued, so this\n * costs nothing on a quiet screen.\n */\nexport const ToastRegion = forwardRef<HTMLDivElement, ToastRegionProps>(function ToastRegion(\n { label, closeLabel = \"Dismiss\", className },\n ref,\n) {\n return (\n <AriaToastRegion\n ref={ref}\n queue={toastQueue}\n aria-label={label}\n data-slot=\"toast-region\"\n className={cn(toastRegionVariants(), className)}\n >\n {({ toast: queued }) => <ToastItem queued={queued} closeLabel={closeLabel} />}\n </AriaToastRegion>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,MAAa,sBAAsB,IAAI;CACrC;CAEA;CACA;CACA;AACF,CAAC;AAED,MAAa,gBAAgB,IAC3B;CACE;CACA;CACA;CAIA;CACA;AACF,GACA;CACE,UAAU,EAOR,MAAM;EACJ,SAAS;EACT,SAAS;EACT,SAAS;EACT,QACE;EACF,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CACF;AAEA,MAAa,qBAAqB,IAAI;CACpC;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;AAkBD,MAAa,aAAa,IAAIA,oBAA6B,EAMzD,kBAAkB,EACpB,CAAC;;;;;;;;;;;;;AAcD,SAAgB,MAAM,SAAuB,UAAU,KAAM;CAC3D,OAAO,WAAW,IAAI,SAAS,YAAY,IAAI,KAAA,IAAY,EAAE,QAAQ,CAAC;AACxE;;;;;;AASA,SAAS,UAAU,EAAE,QAAQ,cAAsD;CACjF,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,SAAS,kBAAkB,WAAW,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,GAAG,CAAC;CAE3E,gBAAgB,SAAS,MAAM;CAC/B,OACE,qBAACC,gBAAD;EACE,OAAO;EACP,aAAU;EACV,aAAW,OAAO,QAAQ,QAAQ;EAClC,gBAAc,WAAW,KAAA;EACzB,iBAAiB,UAAU;GACzB,IAAI,WAAW,MAAM,WAAW,MAAM,eAAe,OAAO;EAC9D;EACA,WAAW,cAAc,EAAE,MAAM,OAAO,QAAQ,KAAK,CAAC;EARxD,UAAA,CAUE,qBAACC,uBAAD;GAAkB,aAAU;GAAgB,WAAU;GAAtD,UAAA,CAKE,oBAACC,MAAD;IACE,MAAK;IACL,aAAU;IACV,WAAU;IAET,UAAA,OAAO,QAAQ;GACR,CAAA,GACT,OAAO,QAAQ,eAAe,OAAO,OACpC,oBAACA,MAAD;IACE,MAAK;IACL,aAAU;IACV,WAAU;IAET,UAAA,OAAO,QAAQ;GACR,CAAA,CAEI;EAClB,CAAA,GAAA,oBAACC,QAAD;GACE,cAAY;GACZ,aAAU;GACV,WAAW,mBAAmB;GAC9B,eAAe,WAAW,IAAI;GAE9B,UAAA,oBAAC,OAAD;IACE,eAAY;IACZ,SAAQ;IACR,MAAK;IACL,QAAO;IACP,aAAY;IACZ,eAAc;IACd,WAAU;IAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA;GAC3B,CAAA;EACK,CAAA,CACH;;AAEf;;;;;;;AAyBA,MAAa,cAAc,WAA6C,SAAS,YAC/E,EAAE,OAAO,aAAa,WAAW,aACjC,KACA;CACA,OACE,oBAACC,sBAAD;EACO;EACL,OAAO;EACP,cAAY;EACZ,aAAU;EACV,WAAW,GAAG,oBAAoB,GAAG,SAAS;EAE5C,WAAA,EAAE,OAAO,aAAa,oBAAC,WAAD;GAAmB;GAAoB;EAAa,CAAA;CAC7D,CAAA;AAErB,CAAC"}
|
|
1
|
+
{"version":3,"file":"toast.js","names":["AriaToastQueue","AriaToast","AriaToastContent","AriaText","AriaButton","AriaToastRegion"],"sources":["../../src/toast/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, useCallback, useState, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Text as AriaText,\n UNSTABLE_Toast as AriaToast,\n UNSTABLE_ToastContent as AriaToastContent,\n UNSTABLE_ToastQueue as AriaToastQueue,\n UNSTABLE_ToastRegion as AriaToastRegion,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useExitBackstop } from \"../lib/motion.js\";\n\n/**\n * Toast: a transient message, announced without stealing focus.\n *\n * Maturity: **experimental**, and the reason is upstream rather than here.\n * React Aria ships this API as `UNSTABLE_Toast*` in 1.21.1, so a RAC minor\n * bump can rename or reshape it. The alternative was hand-rolling the live\n * region, the queue, the timers and the focus behaviour, which is how every\n * toast implementation acquires its accessibility bugs -- so this takes the\n * upstream one and labels the risk instead of hiding it.\n *\n * THE PRODUCT HAS 30 FILES CALLING `useToast` AND 67 `toast(...)` CALLS, and\n * its implementation is the shadcn reducer with both of its known defects\n * still in place:\n *\n * TOAST_LIMIT = 1 -- a second toast REPLACES the first, so a\n * burst of results shows only the last one.\n * TOAST_REMOVE_DELAY = 1e6 -- sixteen minutes. Nothing auto-dismisses;\n * every toast waits to be closed by hand.\n *\n * Neither is ported. `maxVisibleToasts` defaults to 3 and `timeout` to 5s,\n * which is the WCAG 2.2.1 floor React Aria enforces -- it REFUSES a timeout\n * under 5000ms, because a message that vanishes faster cannot be read by\n * someone using a screen magnifier.\n *\n * The queue is a module singleton on purpose. The product's `toast()` is\n * importable from anywhere and called from API error handlers and effects\n * that have no component to hang a hook off, and a context-based queue would\n * have made those 67 call sites into 67 refactors.\n */\n\n/** Where the region sits. Fixed, above everything, out of the way of content. */\nexport const toastRegionVariants = cva([\n \"fixed z-50 flex flex-col gap-2 box-border\",\n // Logical insets so the stack follows the writing direction.\n \"bottom-(--density-bar-pad-y) end-(--density-bar-pad-x)\",\n \"w-[min(24rem,calc(100vw-2rem))]\",\n \"outline-none\",\n]);\n\nexport const toastVariants = cva(\n [\n \"flex items-start gap-3 box-border w-full\",\n // A white card on every tone: the status lives in the icon, not the fill.\n \"rounded-panel border-(length:--border-width) border-border bg-surface text-foreground\",\n \"py-3.5 ps-4 pe-3.5 font-sans shadow-floating\",\n // Rises in when it mounts. When its own close control is pressed it sinks out\n // first (data-closing) and the queue is told when that ends. Reduced motion:\n // opacity only. Not colour alone: the region reflows too.\n \"motion-safe:animate-toast-in motion-reduce:animate-fade-in\",\n \"motion-safe:data-[closing]:animate-toast-out motion-reduce:data-[closing]:animate-fade-out\",\n ],\n {\n variants: {\n /*\n * The same vocabulary as Badge, deliberately. The product's toast had\n * `default` and `destructive` only, so 38 of its 67 calls pass a variant\n * that cannot say \"this worked\" -- and a save confirmation styled\n * identically to a failure is the most common thing a toast gets wrong.\n *\n * The tone no longer tints the card (every tone is the same white card);\n * it picks the icon. The variant stays so `toastVariants({ tone })` still\n * type-checks for existing callers.\n */\n tone: {\n neutral: \"\",\n success: \"\",\n warning: \"\",\n danger: \"\",\n info: \"\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/** The status icon's circle: the tone's light fill with its AA text colour. */\nexport const toastIconVariants = cva(\n \"mt-px grid size-7.5 shrink-0 place-items-center rounded-circle [&_svg]:size-4\",\n {\n variants: {\n tone: {\n neutral: \"bg-muted text-text-secondary\",\n success: \"bg-success-subtle text-success-foreground\",\n warning: \"bg-warning-subtle text-warning-foreground\",\n danger: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n info: \"bg-info-subtle text-info-foreground\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n// One 24px glyph per tone. Success draws its tick on arrival (toast-check).\nconst TONE_ICON_PATHS = {\n neutral: \"M6 16v-5a6 6 0 1 1 12 0v5l1.5 2h-15zM10 20.5a2 2 0 0 0 4 0\",\n success: \"M4 12.5l5 5L20 6.5\",\n warning: \"M12 7v6.5M12 17.25h.01\",\n danger: \"M7 7l10 10M17 7L7 17\",\n info: \"M12 11v6M12 7.25h.01\",\n} as const;\n\nexport const toastCloseVariants = cva([\n // A plain icon: no fill, no border at rest. It hovers to the muted wash.\n \"-mt-px -me-0.5 ms-auto grid size-8 shrink-0 place-items-center rounded-circle cursor-pointer\",\n \"border-0 bg-transparent p-0 text-text-secondary\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n // Quick press, spring release (styles.css).\n \"toast-close\",\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]);\n\n/** What a toast carries. `title` is required: an unnamed toast announces nothing. */\nexport interface ToastContent extends VariantProps<typeof toastVariants> {\n /** The message. Required -- a toast with only a description announces as loose text. */\n title: ReactNode;\n /** Secondary detail, beneath the title. */\n description?: ReactNode;\n /** Replaces the tone's status icon. Pass a 16px glyph; it is hidden from assistive tech. */\n icon?: ReactNode;\n}\n\n/**\n * The queue every `toast()` call feeds.\n *\n * Exported so a consumer can read `visibleToasts` in a test, or dismiss them\n * on a route change -- there is no `clear()` upstream, so that is\n * `[...toastQueue.visibleToasts].forEach((t) => toastQueue.close(t.key))`.\n * Not something to touch in normal use.\n */\nexport const toastQueue = new AriaToastQueue<ToastContent>({\n /*\n * Three, against the product's one. Its TOAST_LIMIT of 1 means a second\n * toast REPLACES the first -- so a page that reports three failed uploads\n * shows one and silently drops the rest.\n */\n maxVisibleToasts: 3,\n});\n\n/**\n * Show a toast.\n *\n * @example\n * toast({ title: \"Report saved\" });\n * toast({ title: \"Upload failed\", description: err.message, tone: \"danger\" });\n *\n * @param timeout - ms before it dismisses itself. React Aria REFUSES anything\n * under 5000 and anything at all while the region is focused, because a\n * message that disappears before it can be read fails WCAG 2.2.1. Pass 0 to\n * require a manual dismiss, which is right for an error a user must act on.\n */\nexport function toast(content: ToastContent, timeout = 5000) {\n return toastQueue.add(content, timeout === 0 ? undefined : { timeout });\n}\n\ntype Queued = (typeof toastQueue.visibleToasts)[number];\n\n/**\n * One toast. Pressing its close control plays the exit first, then closes it in the\n * queue; React Aria unmounts a closed toast at once and has no exit of its own, so a\n * toast closed any other way (the timeout, `toastQueue.close`) leaves without one.\n */\nfunction ToastItem({ queued, closeLabel }: { queued: Queued; closeLabel: string }) {\n const [closing, setClosing] = useState(false);\n const tone = queued.content.tone ?? \"neutral\";\n const finish = useCallback(() => toastQueue.close(queued.key), [queued.key]);\n // If the animation never reports (reduced motion, a hidden tab), close anyway.\n useExitBackstop(closing, finish);\n return (\n <AriaToast\n toast={queued}\n data-slot=\"toast\"\n data-tone={tone}\n data-closing={closing || undefined}\n onAnimationEnd={(event) => {\n if (closing && event.target === event.currentTarget) finish();\n }}\n className={toastVariants({ tone })}\n >\n <span\n data-slot=\"toast-icon\"\n data-tone={tone}\n aria-hidden=\"true\"\n className={toastIconVariants({ tone })}\n >\n {queued.content.icon ?? (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.25\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path\n d={TONE_ICON_PATHS[tone]}\n pathLength={1}\n className={tone === \"success\" ? \"toast-check\" : undefined}\n />\n </svg>\n )}\n </span>\n <AriaToastContent\n data-slot=\"toast-content\"\n className=\"flex min-w-0 flex-1 flex-col gap-0.5 pt-1.25\"\n >\n {/*\n slot=\"title\" is what React Aria announces. A plain div here\n renders identically and leaves the toast unnamed.\n */}\n <AriaText\n slot=\"title\"\n data-slot=\"toast-title\"\n className=\"text-sm leading-(--leading-normal) font-medium text-foreground\"\n >\n {queued.content.title}\n </AriaText>\n {queued.content.description == null ? null : (\n <AriaText\n slot=\"description\"\n data-slot=\"toast-description\"\n className=\"text-sm leading-(--leading-normal) text-text-secondary\"\n >\n {queued.content.description}\n </AriaText>\n )}\n </AriaToastContent>\n <AriaButton\n aria-label={closeLabel}\n data-slot=\"toast-close\"\n className={toastCloseVariants()}\n onPress={() => setClosing(true)}\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 className=\"size-(--icon-sm)\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </AriaToast>\n );\n}\n\nexport interface ToastRegionProps {\n /**\n * Names the region for assistive tech.\n *\n * **Required.** React Aria renders the region as a labelled landmark, and\n * an unnamed one is announced as \"region\" with no indication of what just\n * arrived. It is a prop rather than a default string because\n * ARCHITECTURE section 7 rule 9 routes user-visible copy through the\n * consumer until `useMessages()` exists.\n */\n label: string;\n /** Accessible name for each toast's dismiss control. */\n closeLabel?: string;\n /** Extra classes for the region. Merged last. */\n className?: string;\n}\n\n/**\n * Renders the queue. Mount ONCE, near the root.\n *\n * React Aria only renders the landmark while something is queued, so this\n * costs nothing on a quiet screen.\n */\nexport const ToastRegion = forwardRef<HTMLDivElement, ToastRegionProps>(function ToastRegion(\n { label, closeLabel = \"Dismiss\", className },\n ref,\n) {\n return (\n <AriaToastRegion\n ref={ref}\n queue={toastQueue}\n aria-label={label}\n data-slot=\"toast-region\"\n className={cn(toastRegionVariants(), className)}\n >\n {({ toast: queued }) => <ToastItem queued={queued} closeLabel={closeLabel} />}\n </AriaToastRegion>\n );\n});\n"],"mappings":"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"}
|
|
@@ -39,12 +39,19 @@ interface TooltipProps$1 extends Omit<TooltipProps, "className" | "style" | "chi
|
|
|
39
39
|
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
40
40
|
className?: string;
|
|
41
41
|
/**
|
|
42
|
-
* Where it opens. The legacy `side` prop
|
|
43
|
-
*
|
|
42
|
+
* Where it opens: above the trigger by default. The legacy `side` prop
|
|
43
|
+
* (`"top"` 11 times, `"bottom"` once).
|
|
44
44
|
*
|
|
45
45
|
* @default "top"
|
|
46
46
|
*/
|
|
47
47
|
placement?: TooltipProps["placement"];
|
|
48
|
+
/**
|
|
49
|
+
* Draw a small arrow pointing at the trigger. Off by default: the bubble
|
|
50
|
+
* floats clear of the trigger with no arrow.
|
|
51
|
+
*
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
showArrow?: boolean;
|
|
48
55
|
}
|
|
49
56
|
declare const Tooltip$1: import("react").ForwardRefExoticComponent<TooltipProps$1 & import("react").RefAttributes<HTMLDivElement>>;
|
|
50
57
|
/**
|
package/dist/tooltip/tooltip.js
CHANGED
|
@@ -30,9 +30,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
30
30
|
* word -- so `delay` defaults to 0 here, and a story pins the default.
|
|
31
31
|
*/
|
|
32
32
|
const tooltipVariants = cva([
|
|
33
|
-
"w-fit rounded-
|
|
34
|
-
"bg-
|
|
35
|
-
"box-border font-sans text-
|
|
33
|
+
"w-fit max-w-60 rounded-control px-4 py-3",
|
|
34
|
+
"bg-foreground text-background shadow-raised",
|
|
35
|
+
"box-border font-sans text-sm leading-(--leading-normal) text-balance",
|
|
36
36
|
"outline-none",
|
|
37
37
|
"tooltip-motion"
|
|
38
38
|
]);
|
|
@@ -70,7 +70,7 @@ function WarmMark() {
|
|
|
70
70
|
hidden: true
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
|
-
const Tooltip$1 = forwardRef(function Tooltip$2({ children, className, placement = "top", ...props }, ref) {
|
|
73
|
+
const Tooltip$1 = forwardRef(function Tooltip$2({ children, className, placement = "top", showArrow = false, ...props }, ref) {
|
|
74
74
|
return /* @__PURE__ */ jsxs(Tooltip, {
|
|
75
75
|
ref,
|
|
76
76
|
...props,
|
|
@@ -79,16 +79,16 @@ const Tooltip$1 = forwardRef(function Tooltip$2({ children, className, placement
|
|
|
79
79
|
"data-slot": "tooltip",
|
|
80
80
|
className: cn(tooltipVariants(), className),
|
|
81
81
|
children: [
|
|
82
|
-
/* @__PURE__ */ jsx(OverlayArrow, {
|
|
82
|
+
showArrow ? /* @__PURE__ */ jsx(OverlayArrow, {
|
|
83
83
|
"data-slot": "tooltip-arrow",
|
|
84
84
|
children: /* @__PURE__ */ jsx("svg", {
|
|
85
85
|
width: "10",
|
|
86
86
|
height: "5",
|
|
87
87
|
viewBox: "0 0 10 5",
|
|
88
|
-
className: "fill-
|
|
88
|
+
className: "fill-foreground",
|
|
89
89
|
children: /* @__PURE__ */ jsx("path", { d: "M0 5 5 0l5 5z" })
|
|
90
90
|
})
|
|
91
|
-
}),
|
|
91
|
+
}) : null,
|
|
92
92
|
/* @__PURE__ */ jsx(WarmMark, {}),
|
|
93
93
|
children
|
|
94
94
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","names":["Tooltip","AriaTooltip","AriaOverlayArrow","TooltipTrigger","AriaTooltipTrigger"],"sources":["../../src/tooltip/tooltip.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useEffect, useRef, type ReactNode } from \"react\";\nimport {\n OverlayArrow as AriaOverlayArrow,\n Tooltip as AriaTooltip,\n TooltipTrigger as AriaTooltipTrigger,\n type TooltipProps as AriaTooltipProps,\n type TooltipTriggerComponentProps as AriaTooltipTriggerProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/motion.js\";\n\n/**\n * Tooltip, ported from the legacy shadcn/Radix component.\n *\n * Census: ten files import it but only SEVEN render anything -- three\n * import the parts and use none. Across those seven: `Tooltip` 34,\n * `TooltipTrigger` 34, `TooltipContent` 34, `TooltipProvider` 23. `side`\n * is passed 12 times: `\"top\"` 11, `\"bottom\"` once.\n *\n * `TooltipProvider` IS NOT PORTED, and that removes 23 call sites rather\n * than renaming them. The legacy `Tooltip` already wraps itself in a\n * provider, so every explicit `<TooltipProvider>` in app code nested a\n * second one inside the first -- harmless, but it also meant the\n * `skipDelayDuration` grouping the provider exists for never spanned two\n * tooltips. React Aria has no provider at all; that grouping is\n * module-global.\n *\n * THE DELAY IS THE ONE THING THAT WOULD HAVE CHANGED SILENTLY. The legacy\n * provider hardcodes `delayDuration = 0`, so all 34 tooltips appear\n * instantly on hover. React Aria's default is 1500ms. Porting without\n * noticing would have put a 1.5-second warm-up on every tooltip in the\n * app, and neither the type system nor the test suite would have said a\n * word -- so `delay` defaults to 0 here, and a story pins the default.\n */\nexport const tooltipVariants = cva([\n \"w-fit rounded-
|
|
1
|
+
{"version":3,"file":"tooltip.js","names":["Tooltip","AriaTooltip","AriaOverlayArrow","TooltipTrigger","AriaTooltipTrigger"],"sources":["../../src/tooltip/tooltip.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useEffect, useRef, type ReactNode } from \"react\";\nimport {\n OverlayArrow as AriaOverlayArrow,\n Tooltip as AriaTooltip,\n TooltipTrigger as AriaTooltipTrigger,\n type TooltipProps as AriaTooltipProps,\n type TooltipTriggerComponentProps as AriaTooltipTriggerProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/motion.js\";\n\n/**\n * Tooltip, ported from the legacy shadcn/Radix component.\n *\n * Census: ten files import it but only SEVEN render anything -- three\n * import the parts and use none. Across those seven: `Tooltip` 34,\n * `TooltipTrigger` 34, `TooltipContent` 34, `TooltipProvider` 23. `side`\n * is passed 12 times: `\"top\"` 11, `\"bottom\"` once.\n *\n * `TooltipProvider` IS NOT PORTED, and that removes 23 call sites rather\n * than renaming them. The legacy `Tooltip` already wraps itself in a\n * provider, so every explicit `<TooltipProvider>` in app code nested a\n * second one inside the first -- harmless, but it also meant the\n * `skipDelayDuration` grouping the provider exists for never spanned two\n * tooltips. React Aria has no provider at all; that grouping is\n * module-global.\n *\n * THE DELAY IS THE ONE THING THAT WOULD HAVE CHANGED SILENTLY. The legacy\n * provider hardcodes `delayDuration = 0`, so all 34 tooltips appear\n * instantly on hover. React Aria's default is 1500ms. Porting without\n * noticing would have put a 1.5-second warm-up on every tooltip in the\n * app, and neither the type system nor the test suite would have said a\n * word -- so `delay` defaults to 0 here, and a story pins the default.\n */\nexport const tooltipVariants = cva([\n // Design spec (Oct 2026): a solid ink bubble with a large corner and\n // comfortable padding, capped at 15rem so a long hint wraps onto a second line.\n \"w-fit max-w-60 rounded-control px-4 py-3\",\n // foreground/background, not a fixed black and white: the pair inverts with\n // the theme (light bubble in dark mode) and its ratio is the page-text pair\n // in the contrast contract.\n \"bg-foreground text-background shadow-raised\",\n \"box-border font-sans text-sm leading-(--leading-normal) text-balance\",\n \"outline-none\",\n // Rises from its trigger and fades; only fades in the warm window (styles.css).\n \"tooltip-motion\",\n]);\n\n/*\n * The warm window: while any tooltip is open, and for 300ms after the last one\n * closes, the next opens with no travel. TooltipTrigger writes it; the tooltip\n * that mounts next reads whether it was warm when its trigger opened it.\n */\nconst WARM_MS = 300;\nlet openCount = 0;\nlet coolTimer: ReturnType<typeof setTimeout> | undefined;\nlet warm = false;\nlet openedWarm = false;\n\nfunction noteOpen(open: boolean) {\n if (open) {\n openedWarm = warm;\n openCount += 1;\n clearTimeout(coolTimer);\n warm = true;\n return;\n }\n openCount = Math.max(0, openCount - 1);\n if (openCount) return;\n clearTimeout(coolTimer);\n coolTimer = setTimeout(() => {\n warm = false;\n }, WARM_MS);\n}\n\n/**\n * Mounts with the open tooltip (the Tooltip function itself runs while closed too) and\n * marks it `data-instant` if its trigger opened it inside the warm window, before paint.\n */\nfunction WarmMark() {\n const ref = useRef<HTMLSpanElement>(null);\n useIsomorphicLayoutEffect(() => {\n if (openedWarm) ref.current?.parentElement?.setAttribute(\"data-instant\", \"\");\n }, []);\n return <span ref={ref} hidden />;\n}\n\n/**\n * A hover and focus hint.\n *\n * @example\n * <TooltipTrigger>\n * <Button aria-label=\"Mute\"><MicOff /></Button>\n * <Tooltip>Mute</Tooltip>\n * </TooltipTrigger>\n */\nexport interface TooltipProps\n extends Omit<AriaTooltipProps, \"className\" | \"style\" | \"children\"> {\n children?: ReactNode;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Where it opens: above the trigger by default. The legacy `side` prop\n * (`\"top\"` 11 times, `\"bottom\"` once).\n *\n * @default \"top\"\n */\n placement?: AriaTooltipProps[\"placement\"];\n /**\n * Draw a small arrow pointing at the trigger. Off by default: the bubble\n * floats clear of the trigger with no arrow.\n *\n * @default false\n */\n showArrow?: boolean;\n}\n\nexport const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(function Tooltip(\n { children, className, placement = \"top\", showArrow = false, ...props },\n ref,\n) {\n return (\n <AriaTooltip\n // The ref goes on the tooltip itself, not the OverlayArrow child below\n // -- the arrow is a decorative sub-element, not the tooltip's own DOM\n // node.\n ref={ref}\n {...props}\n placement={placement}\n offset={8}\n data-slot=\"tooltip\"\n className={cn(tooltipVariants(), className)}\n >\n {/*\n Opt-in. An SVG arrow, not the legacy rotated square. That used\n `translate-y-[calc(-50%_-_2px)]` and `rounded-[2px]` -- two bracket\n values check:hardcoding rejects -- and a rotated box has to be\n re-placed for every side. React Aria rotates the whole OverlayArrow\n per placement, so one upward triangle serves all four.\n */}\n {showArrow ? (\n <AriaOverlayArrow data-slot=\"tooltip-arrow\">\n <svg width=\"10\" height=\"5\" viewBox=\"0 0 10 5\" className=\"fill-foreground\">\n <path d=\"M0 5 5 0l5 5z\" />\n </svg>\n </AriaOverlayArrow>\n ) : null}\n <WarmMark />\n {children}\n </AriaTooltip>\n );\n});\n\n/**\n * Pairs a trigger with a tooltip.\n *\n * React Aria's `TooltipTrigger`, EXCEPT that `delay` and `closeDelay`\n * default to 0 to match the legacy provider. React Aria ships 1500ms and\n * 500ms; taking those defaults would have changed all 34 call sites\n * without a word.\n *\n * Focus opens it immediately whatever `delay` says -- React Aria passes\n * `immediate` on focus -- which is the behaviour a keyboard user needs.\n */\nexport interface TooltipTriggerProps extends AriaTooltipTriggerProps {\n /** Hover warm-up in ms. @default 0 */\n delay?: number;\n /** Hover cool-down in ms. @default 0 */\n closeDelay?: number;\n}\n\nexport function TooltipTrigger({\n delay = 0,\n closeDelay = 0,\n onOpenChange,\n ...props\n}: TooltipTriggerProps) {\n const isOpen = useRef(false);\n // A trigger that unmounts while open never reports closing; count it out.\n useEffect(\n () => () => {\n if (isOpen.current) noteOpen(false);\n },\n [],\n );\n return (\n <AriaTooltipTrigger\n {...props}\n delay={delay}\n closeDelay={closeDelay}\n onOpenChange={(open) => {\n isOpen.current = open;\n noteOpen(open);\n onOpenChange?.(open);\n }}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAa,kBAAkB,IAAI;CAGjC;CAIA;CACA;CACA;CAEA;AACF,CAAC;AAOD,MAAM,UAAU;AAChB,IAAI,YAAY;AAChB,IAAI;AACJ,IAAI,OAAO;AACX,IAAI,aAAa;AAEjB,SAAS,SAAS,MAAe;CAC/B,IAAI,MAAM;EACR,aAAa;EACb,aAAa;EACb,aAAa,SAAS;EACtB,OAAO;EACP;CACF;CACA,YAAY,KAAK,IAAI,GAAG,YAAY,CAAC;CACrC,IAAI,WAAW;CACf,aAAa,SAAS;CACtB,YAAY,iBAAiB;EAC3B,OAAO;CACT,GAAG,OAAO;AACZ;;;;;AAMA,SAAS,WAAW;CAClB,MAAM,MAAM,OAAwB,IAAI;CACxC,gCAAgC;EAC9B,IAAI,YAAY,IAAI,SAAS,eAAe,aAAa,gBAAgB,EAAE;CAC7E,GAAG,CAAC,CAAC;CACL,OAAO,oBAAC,QAAD;EAAW;EAAK,QAAA;CAAQ,CAAA;AACjC;AAgCA,MAAaA,YAAU,WAAyC,SAASA,UACvE,EAAE,UAAU,WAAW,YAAY,OAAO,YAAY,OAAO,GAAG,SAChE,KACA;CACA,OACE,qBAACC,SAAD;EAIO;EACL,GAAI;EACO;EACX,QAAQ;EACR,aAAU;EACV,WAAW,GAAG,gBAAgB,GAAG,SAAS;EAT5C,UAAA;GAkBG,YACC,oBAACC,cAAD;IAAkB,aAAU;IAC1B,UAAA,oBAAC,OAAD;KAAK,OAAM;KAAK,QAAO;KAAI,SAAQ;KAAW,WAAU;KACtD,UAAA,oBAAC,QAAD,EAAM,GAAE,gBAAiB,CAAA;IACtB,CAAA;GACW,CAAA,IAChB;GACJ,oBAAC,UAAD,CAAW,CAAA;GACV;EACU;;AAEjB,CAAC;AAoBD,SAAgBC,iBAAe,EAC7B,QAAQ,GACR,aAAa,GACb,cACA,GAAG,SACmB;CACtB,MAAM,SAAS,OAAO,KAAK;CAE3B,sBACc;EACV,IAAI,OAAO,SAAS,SAAS,KAAK;CACpC,GACA,CAAC,CACH;CACA,OACE,oBAACC,gBAAD;EACE,GAAI;EACG;EACK;EACZ,eAAe,SAAS;GACtB,OAAO,UAAU;GACjB,SAAS,IAAI;GACb,eAAe,IAAI;EACrB;CACD,CAAA;AAEL"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/treemap/treemap.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Treemap: a hierarchy of parts, such as revenue by region, country and plan,
|
|
4
|
+
* when sizes matter more than exact comparisons. Tiles are laid out by the
|
|
5
|
+
* squarified algorithm (rows along the short side, each closed when another tile
|
|
6
|
+
* would make the worst aspect ratio worse), two levels at a time: the branches
|
|
7
|
+
* of the node in view, each holding its own children under a header.
|
|
8
|
+
*
|
|
9
|
+
* Tiles grow in a quick wave from the largest the first time the chart is seen.
|
|
10
|
+
* Pressing a branch (or Enter) zooms into it: the tile grows to fill the view and
|
|
11
|
+
* its children open inside it; Escape or a breadcrumb zooms back out, the view
|
|
12
|
+
* shrinking into its tile. New data moves every tile from where it is on screen.
|
|
13
|
+
* Hovering or the arrow keys read a tile in a tooltip, and the rest of its branch
|
|
14
|
+
* steps back. An optional second measure (growth, say) shades tiles in steps.
|
|
15
|
+
*
|
|
16
|
+
* Tiles are absolutely placed and sized each frame by one small spring tween, so
|
|
17
|
+
* their text never scales while they grow or shrink.
|
|
18
|
+
*/
|
|
19
|
+
export interface TreemapNode {
|
|
20
|
+
/** Stable identity, unique in the tree. A tile that survives a data change morphs to its new size. */
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
/** Leaf size. A branch's size is the sum of its children. */
|
|
24
|
+
value?: number;
|
|
25
|
+
/** Optional second measure for shading, such as growth. A branch uses the size weighted mean of its children. */
|
|
26
|
+
color?: number;
|
|
27
|
+
children?: TreemapNode[];
|
|
28
|
+
}
|
|
29
|
+
export interface TreemapProps {
|
|
30
|
+
/** The root. Its label names the whole in the first breadcrumb. */
|
|
31
|
+
data: TreemapNode;
|
|
32
|
+
/** What the whole is, such as "Revenue". Names the chart for assistive technology. */
|
|
33
|
+
label: string;
|
|
34
|
+
formatValue?: (value: number) => string;
|
|
35
|
+
/** Names the shading measure in the tooltip and scale, such as "Growth". Tiles take the hue of their top level branch and grow deeper with the measure. Leave it out to draw every tile at one depth. */
|
|
36
|
+
colorLabel?: string;
|
|
37
|
+
formatColor?: (value: number) => string;
|
|
38
|
+
/** Range of the colour measure. Defaults to the range of the leaves. */
|
|
39
|
+
colorDomain?: [number, number];
|
|
40
|
+
/** Controlled id of the node that fills the view. */
|
|
41
|
+
focus?: string;
|
|
42
|
+
defaultFocus?: string;
|
|
43
|
+
onFocusChange?: (id: string) => void;
|
|
44
|
+
/** Height of the tiles in pixels. @default 420 */
|
|
45
|
+
height?: number;
|
|
46
|
+
/** @default "No data yet" */
|
|
47
|
+
emptyLabel?: string;
|
|
48
|
+
/** Extra classes for the root. Merged last. */
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare const Treemap: import("react").ForwardRefExoticComponent<TreemapProps & import("react").RefAttributes<HTMLElement>>;
|
|
52
|
+
//#endregion
|
|
53
|
+
//# sourceMappingURL=treemap.d.ts.map
|