@seekalfred/primitives 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +975 -13
- package/dist/accordion/accordion.d.ts +107 -0
- package/dist/accordion/accordion.js +84 -0
- package/dist/accordion/accordion.js.map +1 -0
- package/dist/accordion/index.d.ts +2 -0
- package/dist/accordion/index.js +2 -0
- package/dist/alert/alert.d.ts +68 -0
- package/dist/alert/alert.js +67 -0
- package/dist/alert/alert.js.map +1 -0
- package/dist/alert/index.d.ts +2 -0
- package/dist/alert/index.js +2 -0
- package/dist/alert-dialog/alert-dialog.d.ts +53 -0
- package/dist/alert-dialog/alert-dialog.js +16 -0
- package/dist/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/alert-dialog/index.d.ts +2 -0
- package/dist/alert-dialog/index.js +2 -0
- package/dist/avatar/avatar.d.ts +50 -0
- package/dist/avatar/avatar.js +91 -0
- package/dist/avatar/avatar.js.map +1 -0
- package/dist/avatar/index.d.ts +2 -0
- package/dist/avatar/index.js +2 -0
- package/dist/badge/badge.d.ts +47 -0
- package/dist/badge/badge.js +189 -0
- package/dist/badge/badge.js.map +1 -0
- package/dist/badge/index.d.ts +2 -0
- package/dist/badge/index.js +2 -0
- package/dist/breadcrumb/breadcrumb.d.ts +98 -0
- package/dist/breadcrumb/breadcrumb.js +93 -0
- package/dist/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/breadcrumb/index.d.ts +2 -0
- package/dist/breadcrumb/index.js +2 -0
- package/dist/button/button.d.ts +1 -1
- package/dist/button/button.js +7 -0
- package/dist/button/button.js.map +1 -1
- package/dist/calendar/calendar.d.ts +154 -0
- package/dist/calendar/calendar.js +282 -0
- package/dist/calendar/calendar.js.map +1 -0
- package/dist/calendar/index.d.ts +2 -0
- package/dist/calendar/index.js +2 -0
- package/dist/card/card.d.ts +93 -0
- package/dist/card/card.js +105 -0
- package/dist/card/card.js.map +1 -0
- package/dist/card/index.d.ts +2 -0
- package/dist/card/index.js +2 -0
- package/dist/checkbox/checkbox.d.ts +63 -0
- package/dist/checkbox/checkbox.js +94 -0
- package/dist/checkbox/checkbox.js.map +1 -0
- package/dist/checkbox/index.d.ts +2 -0
- package/dist/checkbox/index.js +2 -0
- package/dist/dialog/dialog.d.ts +140 -0
- package/dist/dialog/dialog.js +127 -0
- package/dist/dialog/dialog.js.map +1 -0
- package/dist/dialog/index.d.ts +2 -0
- package/dist/dialog/index.js +2 -0
- package/dist/index.d.ts +30 -1
- package/dist/index.js +30 -1
- package/dist/input/index.d.ts +2 -0
- package/dist/input/index.js +2 -0
- package/dist/input/input.d.ts +80 -0
- package/dist/input/input.js +195 -0
- package/dist/input/input.js.map +1 -0
- package/dist/label/index.d.ts +2 -0
- package/dist/label/index.js +2 -0
- package/dist/label/label.d.ts +41 -0
- package/dist/label/label.js +44 -0
- package/dist/label/label.js.map +1 -0
- package/dist/lib/field-surface.js +40 -0
- package/dist/lib/field-surface.js.map +1 -0
- package/dist/lib/selection-control.js +57 -0
- package/dist/lib/selection-control.js.map +1 -0
- package/dist/menu/index.d.ts +2 -0
- package/dist/menu/index.js +2 -0
- package/dist/menu/menu.d.ts +98 -0
- package/dist/menu/menu.js +99 -0
- package/dist/menu/menu.js.map +1 -0
- package/dist/popover/index.d.ts +2 -0
- package/dist/popover/index.js +2 -0
- package/dist/popover/popover.d.ts +50 -0
- package/dist/popover/popover.js +46 -0
- package/dist/popover/popover.js.map +1 -0
- package/dist/progress/index.d.ts +2 -0
- package/dist/progress/index.js +2 -0
- package/dist/progress/progress.d.ts +82 -0
- package/dist/progress/progress.js +82 -0
- package/dist/progress/progress.js.map +1 -0
- package/dist/radio-group/index.d.ts +2 -0
- package/dist/radio-group/index.js +2 -0
- package/dist/radio-group/radio-group.d.ts +53 -0
- package/dist/radio-group/radio-group.js +65 -0
- package/dist/radio-group/radio-group.js.map +1 -0
- package/dist/scroll-area/index.d.ts +2 -0
- package/dist/scroll-area/index.js +2 -0
- package/dist/scroll-area/scroll-area.d.ts +74 -0
- package/dist/scroll-area/scroll-area.js +63 -0
- package/dist/scroll-area/scroll-area.js.map +1 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +2 -0
- package/dist/select/select.d.ts +105 -0
- package/dist/select/select.js +149 -0
- package/dist/select/select.js.map +1 -0
- package/dist/separator/index.d.ts +2 -0
- package/dist/separator/index.js +2 -0
- package/dist/separator/separator.d.ts +42 -0
- package/dist/separator/separator.js +49 -0
- package/dist/separator/separator.js.map +1 -0
- package/dist/sheet/index.d.ts +2 -0
- package/dist/sheet/index.js +2 -0
- package/dist/sheet/sheet.d.ts +96 -0
- package/dist/sheet/sheet.js +98 -0
- package/dist/sheet/sheet.js.map +1 -0
- package/dist/skeleton/index.d.ts +2 -0
- package/dist/skeleton/index.js +2 -0
- package/dist/skeleton/skeleton.d.ts +33 -0
- package/dist/skeleton/skeleton.js +42 -0
- package/dist/skeleton/skeleton.js.map +1 -0
- package/dist/slider/index.d.ts +2 -0
- package/dist/slider/index.js +2 -0
- package/dist/slider/slider.d.ts +70 -0
- package/dist/slider/slider.js +92 -0
- package/dist/slider/slider.js.map +1 -0
- package/dist/spinner/index.d.ts +2 -0
- package/dist/spinner/index.js +2 -0
- package/dist/spinner/spinner.d.ts +75 -0
- package/dist/spinner/spinner.js +68 -0
- package/dist/spinner/spinner.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/switch/index.d.ts +2 -0
- package/dist/switch/index.js +2 -0
- package/dist/switch/switch.d.ts +50 -0
- package/dist/switch/switch.js +66 -0
- package/dist/switch/switch.js.map +1 -0
- package/dist/table/index.d.ts +2 -0
- package/dist/table/index.js +2 -0
- package/dist/table/table.d.ts +288 -0
- package/dist/table/table.js +513 -0
- package/dist/table/table.js.map +1 -0
- package/dist/tabs/index.d.ts +2 -0
- package/dist/tabs/index.js +2 -0
- package/dist/tabs/tabs.d.ts +79 -0
- package/dist/tabs/tabs.js +103 -0
- package/dist/tabs/tabs.js.map +1 -0
- package/dist/textarea/index.d.ts +2 -0
- package/dist/textarea/index.js +2 -0
- package/dist/textarea/textarea.d.ts +27 -0
- package/dist/textarea/textarea.js +37 -0
- package/dist/textarea/textarea.js.map +1 -0
- package/dist/toast/index.d.ts +2 -0
- package/dist/toast/index.js +2 -0
- package/dist/toast/toast.d.ts +93 -0
- package/dist/toast/toast.js +145 -0
- package/dist/toast/toast.js.map +1 -0
- package/dist/tooltip/index.d.ts +2 -0
- package/dist/tooltip/index.js +2 -0
- package/dist/tooltip/tooltip.d.ts +70 -0
- package/dist/tooltip/tooltip.js +65 -0
- package/dist/tooltip/tooltip.js.map +1 -0
- package/package.json +125 -7
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { TabListProps, TabPanelProps, TabProps, TabsProps } from "react-aria-components";
|
|
2
|
+
//#region src/tabs/tabs.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Tabs, ported from the legacy shadcn/Radix component.
|
|
5
|
+
*
|
|
6
|
+
* The largest legacy surface in this migration: 18 files, `TabsTrigger`
|
|
7
|
+
* 51, `TabsContent` 31, `Tabs` 17, `TabsList` 17. Props: `value` 88,
|
|
8
|
+
* `className` 76, `defaultValue` 12, `onValueChange` 10, `disabled` 6.
|
|
9
|
+
*
|
|
10
|
+
* The rename is mechanical but it touches every one of those:
|
|
11
|
+
* `value` -> `selectedKey`, `defaultValue` -> `defaultSelectedKey`,
|
|
12
|
+
* `onValueChange` -> `onSelectionChange`, and the `value` on each trigger
|
|
13
|
+
* and panel becomes `id`. React Aria pairs a Tab to its TabPanel by that
|
|
14
|
+
* id, exactly as Radix paired them by value.
|
|
15
|
+
*
|
|
16
|
+
* THE SELECTED TAB GAINS A BORDER, and that is a contrast fix rather than
|
|
17
|
+
* a decoration. The legacy marks the selected tab with `bg-background` on
|
|
18
|
+
* a `bg-muted` list -- 1.13:1, which is not a boundary anybody can see --
|
|
19
|
+
* and leans on `shadow-sm` to carry the rest. Under WCAG 1.4.11 the thing
|
|
20
|
+
* telling you which tab is active has to clear 3:1. `border-border` on
|
|
21
|
+
* `muted` is 4.65:1 and is already in the contrast contract.
|
|
22
|
+
*
|
|
23
|
+
* Two bracket values are gone because check:hardcoding rejects them:
|
|
24
|
+
* `p-[3px]` on the list and `h-[calc(100%-1px)]` on the trigger. The list
|
|
25
|
+
* is `p-1` and the trigger sizes to the list rather than to a value one
|
|
26
|
+
* pixel short of it.
|
|
27
|
+
*
|
|
28
|
+
* ONE BEHAVIOUR DIFFERS AND IS WORTH KNOWING. Radix keeps every mounted
|
|
29
|
+
* TabsContent in the DOM and hides the inactive ones; React Aria renders
|
|
30
|
+
* ONLY the selected panel. A panel that holds uncommitted form state, a
|
|
31
|
+
* running video or a scroll position will lose it on tab change. React
|
|
32
|
+
* Aria's `shouldForceMount` on TabPanel restores the old behaviour where
|
|
33
|
+
* a call site needs it.
|
|
34
|
+
*/
|
|
35
|
+
export declare const tabsVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
36
|
+
export declare const tabListVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
37
|
+
export declare const tabVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
38
|
+
export declare const tabPanelVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
39
|
+
/**
|
|
40
|
+
* A set of panels, one visible at a time.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* <Tabs defaultSelectedKey="overview">
|
|
44
|
+
* <TabList aria-label="Sections">
|
|
45
|
+
* <Tab id="overview">Overview</Tab>
|
|
46
|
+
* <Tab id="activity">Activity</Tab>
|
|
47
|
+
* </TabList>
|
|
48
|
+
* <TabPanel id="overview">...</TabPanel>
|
|
49
|
+
* <TabPanel id="activity">...</TabPanel>
|
|
50
|
+
* </Tabs>
|
|
51
|
+
*/
|
|
52
|
+
interface TabsProps$1 extends Omit<TabsProps, "className" | "style"> {
|
|
53
|
+
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
54
|
+
className?: string;
|
|
55
|
+
}
|
|
56
|
+
declare function Tabs$1({ className, ...props }: TabsProps$1): import("react").JSX.Element;
|
|
57
|
+
/**
|
|
58
|
+
* The row of tabs.
|
|
59
|
+
*
|
|
60
|
+
* `aria-label` is worth passing: a tablist is a landmark-ish grouping and
|
|
61
|
+
* a page with two of them is ambiguous without one.
|
|
62
|
+
*/
|
|
63
|
+
interface TabListProps$1 extends Omit<TabListProps<object>, "className" | "style"> {
|
|
64
|
+
className?: string;
|
|
65
|
+
}
|
|
66
|
+
declare function TabList$1({ className, ...props }: TabListProps$1): import("react").JSX.Element;
|
|
67
|
+
/** One tab. Its `id` pairs it with the panel of the same `id`. */
|
|
68
|
+
interface TabProps$1 extends Omit<TabProps, "className" | "style"> {
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
declare function Tab$1({ className, ...props }: TabProps$1): import("react").JSX.Element;
|
|
72
|
+
/** One panel. Its `id` pairs it with the tab of the same `id`. */
|
|
73
|
+
interface TabPanelProps$1 extends Omit<TabPanelProps, "className" | "style"> {
|
|
74
|
+
className?: string;
|
|
75
|
+
}
|
|
76
|
+
declare function TabPanel$1({ className, ...props }: TabPanelProps$1): import("react").JSX.Element;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { Tab$1 as Tab, TabList$1 as TabList, TabListProps$1 as TabListProps, TabPanel$1 as TabPanel, TabPanelProps$1 as TabPanelProps, TabProps$1 as TabProps, Tabs$1 as Tabs, TabsProps$1 as TabsProps };
|
|
79
|
+
//# sourceMappingURL=tabs.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { Tab, TabList, TabPanel, Tabs } from "react-aria-components";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/tabs/tabs.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Tabs, ported from the legacy shadcn/Radix component.
|
|
9
|
+
*
|
|
10
|
+
* The largest legacy surface in this migration: 18 files, `TabsTrigger`
|
|
11
|
+
* 51, `TabsContent` 31, `Tabs` 17, `TabsList` 17. Props: `value` 88,
|
|
12
|
+
* `className` 76, `defaultValue` 12, `onValueChange` 10, `disabled` 6.
|
|
13
|
+
*
|
|
14
|
+
* The rename is mechanical but it touches every one of those:
|
|
15
|
+
* `value` -> `selectedKey`, `defaultValue` -> `defaultSelectedKey`,
|
|
16
|
+
* `onValueChange` -> `onSelectionChange`, and the `value` on each trigger
|
|
17
|
+
* and panel becomes `id`. React Aria pairs a Tab to its TabPanel by that
|
|
18
|
+
* id, exactly as Radix paired them by value.
|
|
19
|
+
*
|
|
20
|
+
* THE SELECTED TAB GAINS A BORDER, and that is a contrast fix rather than
|
|
21
|
+
* a decoration. The legacy marks the selected tab with `bg-background` on
|
|
22
|
+
* a `bg-muted` list -- 1.13:1, which is not a boundary anybody can see --
|
|
23
|
+
* and leans on `shadow-sm` to carry the rest. Under WCAG 1.4.11 the thing
|
|
24
|
+
* telling you which tab is active has to clear 3:1. `border-border` on
|
|
25
|
+
* `muted` is 4.65:1 and is already in the contrast contract.
|
|
26
|
+
*
|
|
27
|
+
* Two bracket values are gone because check:hardcoding rejects them:
|
|
28
|
+
* `p-[3px]` on the list and `h-[calc(100%-1px)]` on the trigger. The list
|
|
29
|
+
* is `p-1` and the trigger sizes to the list rather than to a value one
|
|
30
|
+
* pixel short of it.
|
|
31
|
+
*
|
|
32
|
+
* ONE BEHAVIOUR DIFFERS AND IS WORTH KNOWING. Radix keeps every mounted
|
|
33
|
+
* TabsContent in the DOM and hides the inactive ones; React Aria renders
|
|
34
|
+
* ONLY the selected panel. A panel that holds uncommitted form state, a
|
|
35
|
+
* running video or a scroll position will lose it on tab change. React
|
|
36
|
+
* Aria's `shouldForceMount` on TabPanel restores the old behaviour where
|
|
37
|
+
* a call site needs it.
|
|
38
|
+
*/
|
|
39
|
+
const tabsVariants = cva(["flex flex-col gap-2 box-border font-sans"]);
|
|
40
|
+
const tabListVariants = cva([
|
|
41
|
+
"inline-flex w-fit items-center justify-center gap-1",
|
|
42
|
+
"rounded-lg p-1",
|
|
43
|
+
"bg-muted text-muted-foreground",
|
|
44
|
+
"box-border"
|
|
45
|
+
]);
|
|
46
|
+
const tabVariants = cva([
|
|
47
|
+
"inline-flex flex-1 items-center justify-center gap-1.5 whitespace-nowrap",
|
|
48
|
+
"rounded-md px-2 py-1",
|
|
49
|
+
"text-sm leading-(--leading-normal) font-medium text-foreground",
|
|
50
|
+
"cursor-pointer select-none box-border",
|
|
51
|
+
"border-(length:--border-width) border-transparent",
|
|
52
|
+
"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard",
|
|
53
|
+
"data-[selected]:bg-background data-[selected]:border-border",
|
|
54
|
+
"data-[selected]:shadow-surface",
|
|
55
|
+
"outline-none",
|
|
56
|
+
"data-[focus-visible]:outline-solid",
|
|
57
|
+
"data-[focus-visible]:outline-(length:--focus-ring-width)",
|
|
58
|
+
"data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
59
|
+
"data-[focus-visible]:outline-(--color-ring)",
|
|
60
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)",
|
|
61
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
62
|
+
"[&_svg:not([class*='size-'])]:size-(--icon-sm)"
|
|
63
|
+
]);
|
|
64
|
+
const tabPanelVariants = cva([
|
|
65
|
+
"flex-1 box-border font-sans text-foreground",
|
|
66
|
+
"outline-none",
|
|
67
|
+
"data-[focus-visible]:outline-solid",
|
|
68
|
+
"data-[focus-visible]:outline-(length:--focus-ring-width)",
|
|
69
|
+
"data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
70
|
+
"data-[focus-visible]:outline-(--color-ring)"
|
|
71
|
+
]);
|
|
72
|
+
function Tabs$1({ className, ...props }) {
|
|
73
|
+
return /* @__PURE__ */ jsx(Tabs, {
|
|
74
|
+
...props,
|
|
75
|
+
"data-slot": "tabs",
|
|
76
|
+
className: cn(tabsVariants(), className)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function TabList$1({ className, ...props }) {
|
|
80
|
+
return /* @__PURE__ */ jsx(TabList, {
|
|
81
|
+
...props,
|
|
82
|
+
"data-slot": "tab-list",
|
|
83
|
+
className: cn(tabListVariants(), className)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function Tab$1({ className, ...props }) {
|
|
87
|
+
return /* @__PURE__ */ jsx(Tab, {
|
|
88
|
+
...props,
|
|
89
|
+
"data-slot": "tab",
|
|
90
|
+
className: cn(tabVariants(), className)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function TabPanel$1({ className, ...props }) {
|
|
94
|
+
return /* @__PURE__ */ jsx(TabPanel, {
|
|
95
|
+
...props,
|
|
96
|
+
"data-slot": "tab-panel",
|
|
97
|
+
className: cn(tabPanelVariants(), className)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { Tab$1 as Tab, TabList$1 as TabList, TabPanel$1 as TabPanel, Tabs$1 as Tabs, tabListVariants, tabPanelVariants, tabVariants, tabsVariants };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.js","names":["Tabs","AriaTabs","TabList","AriaTabList","Tab","AriaTab","TabPanel","AriaTabPanel"],"sources":["../../src/tabs/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n Tab as AriaTab,\n TabList as AriaTabList,\n TabPanel as AriaTabPanel,\n Tabs as AriaTabs,\n type TabListProps as AriaTabListProps,\n type TabPanelProps as AriaTabPanelProps,\n type TabProps as AriaTabProps,\n type TabsProps as AriaTabsProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Tabs, ported from the legacy shadcn/Radix component.\n *\n * The largest legacy surface in this migration: 18 files, `TabsTrigger`\n * 51, `TabsContent` 31, `Tabs` 17, `TabsList` 17. Props: `value` 88,\n * `className` 76, `defaultValue` 12, `onValueChange` 10, `disabled` 6.\n *\n * The rename is mechanical but it touches every one of those:\n * `value` -> `selectedKey`, `defaultValue` -> `defaultSelectedKey`,\n * `onValueChange` -> `onSelectionChange`, and the `value` on each trigger\n * and panel becomes `id`. React Aria pairs a Tab to its TabPanel by that\n * id, exactly as Radix paired them by value.\n *\n * THE SELECTED TAB GAINS A BORDER, and that is a contrast fix rather than\n * a decoration. The legacy marks the selected tab with `bg-background` on\n * a `bg-muted` list -- 1.13:1, which is not a boundary anybody can see --\n * and leans on `shadow-sm` to carry the rest. Under WCAG 1.4.11 the thing\n * telling you which tab is active has to clear 3:1. `border-border` on\n * `muted` is 4.65:1 and is already in the contrast contract.\n *\n * Two bracket values are gone because check:hardcoding rejects them:\n * `p-[3px]` on the list and `h-[calc(100%-1px)]` on the trigger. The list\n * is `p-1` and the trigger sizes to the list rather than to a value one\n * pixel short of it.\n *\n * ONE BEHAVIOUR DIFFERS AND IS WORTH KNOWING. Radix keeps every mounted\n * TabsContent in the DOM and hides the inactive ones; React Aria renders\n * ONLY the selected panel. A panel that holds uncommitted form state, a\n * running video or a scroll position will lose it on tab change. React\n * Aria's `shouldForceMount` on TabPanel restores the old behaviour where\n * a call site needs it.\n */\nexport const tabsVariants = cva([\"flex flex-col gap-2 box-border font-sans\"]);\n\nexport const tabListVariants = cva([\n \"inline-flex w-fit items-center justify-center gap-1\",\n // p-1, not the legacy p-[3px]: a bracket value check:hardcoding rejects.\n \"rounded-lg p-1\",\n \"bg-muted text-muted-foreground\",\n \"box-border\",\n]);\n\nexport const tabVariants = cva([\n \"inline-flex flex-1 items-center justify-center gap-1.5 whitespace-nowrap\",\n \"rounded-md px-2 py-1\",\n \"text-sm leading-(--leading-normal) font-medium text-foreground\",\n \"cursor-pointer select-none box-border\",\n // A transparent border at rest so selecting one does not move the text.\n \"border-(length:--border-width) border-transparent\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n\n // The selected tab. bg-background on a muted list is 1.13:1 -- invisible\n // as a boundary -- so the border is what satisfies WCAG 1.4.11 at\n // 4.65:1. The fill and the shadow are the polish on top of it.\n \"data-[selected]:bg-background data-[selected]:border-border\",\n \"data-[selected]:shadow-surface\",\n\n // outline-solid is load-bearing: outline-none sets --tw-outline-style to\n // none and the width utility resolves its style from that same variable,\n // so a focus rule can apply a width and a colour and paint nothing.\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 \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n]);\n\nexport const tabPanelVariants = cva([\n \"flex-1 box-border font-sans text-foreground\",\n // React Aria gives the panel tabindex=0 when it holds no focusable\n // child, so it can take a ring it does not want at rest.\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/**\n * A set of panels, one visible at a time.\n *\n * @example\n * <Tabs defaultSelectedKey=\"overview\">\n * <TabList aria-label=\"Sections\">\n * <Tab id=\"overview\">Overview</Tab>\n * <Tab id=\"activity\">Activity</Tab>\n * </TabList>\n * <TabPanel id=\"overview\">...</TabPanel>\n * <TabPanel id=\"activity\">...</TabPanel>\n * </Tabs>\n */\nexport interface TabsProps extends Omit<AriaTabsProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Tabs({ className, ...props }: TabsProps) {\n return <AriaTabs {...props} data-slot=\"tabs\" className={cn(tabsVariants(), className)} />;\n}\n\n/**\n * The row of tabs.\n *\n * `aria-label` is worth passing: a tablist is a landmark-ish grouping and\n * a page with two of them is ambiguous without one.\n */\nexport interface TabListProps extends Omit<AriaTabListProps<object>, \"className\" | \"style\"> {\n className?: string;\n}\n\nexport function TabList({ className, ...props }: TabListProps) {\n return (\n <AriaTabList {...props} data-slot=\"tab-list\" className={cn(tabListVariants(), className)} />\n );\n}\n\n/** One tab. Its `id` pairs it with the panel of the same `id`. */\nexport interface TabProps extends Omit<AriaTabProps, \"className\" | \"style\"> {\n className?: string;\n}\n\nexport function Tab({ className, ...props }: TabProps) {\n return <AriaTab {...props} data-slot=\"tab\" className={cn(tabVariants(), className)} />;\n}\n\n/** One panel. Its `id` pairs it with the tab of the same `id`. */\nexport interface TabPanelProps extends Omit<AriaTabPanelProps, \"className\" | \"style\"> {\n className?: string;\n}\n\nexport function TabPanel({ className, ...props }: TabPanelProps) {\n return (\n <AriaTabPanel {...props} data-slot=\"tab-panel\" className={cn(tabPanelVariants(), className)} />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,MAAa,eAAe,IAAI,CAAC,0CAA0C,CAAC;AAE5E,MAAa,kBAAkB,IAAI;CACjC;CAEA;CACA;CACA;AACF,CAAC;AAED,MAAa,cAAc,IAAI;CAC7B;CACA;CACA;CACA;CAEA;CACA;CAKA;CACA;CAKA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;AACF,CAAC;AAED,MAAa,mBAAmB,IAAI;CAClC;CAGA;CACA;CACA;CACA;CACA;AACF,CAAC;AAoBD,SAAgBA,OAAK,EAAE,WAAW,GAAG,SAAoB;CACvD,OAAO,oBAACC,MAAD;EAAU,GAAI;EAAO,aAAU;EAAO,WAAW,GAAG,aAAa,GAAG,SAAS;CAAI,CAAA;AAC1F;AAYA,SAAgBC,UAAQ,EAAE,WAAW,GAAG,SAAuB;CAC7D,OACE,oBAACC,SAAD;EAAa,GAAI;EAAO,aAAU;EAAW,WAAW,GAAG,gBAAgB,GAAG,SAAS;CAAI,CAAA;AAE/F;AAOA,SAAgBC,MAAI,EAAE,WAAW,GAAG,SAAmB;CACrD,OAAO,oBAACC,KAAD;EAAS,GAAI;EAAO,aAAU;EAAM,WAAW,GAAG,YAAY,GAAG,SAAS;CAAI,CAAA;AACvF;AAOA,SAAgBC,WAAS,EAAE,WAAW,GAAG,SAAwB;CAC/D,OACE,oBAACC,UAAD;EAAc,GAAI;EAAO,aAAU;EAAY,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAAI,CAAA;AAElG"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TextAreaProps } from "react-aria-components";
|
|
2
|
+
//#region src/textarea/textarea.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Textarea, ported from the legacy component onto React Aria's TextArea.
|
|
5
|
+
*
|
|
6
|
+
* Census: 3 files, 3 uses -- `value`, `onChange`, `placeholder`, `id`,
|
|
7
|
+
* `ref`. All native props, all still native here.
|
|
8
|
+
*
|
|
9
|
+
* The surface comes from `fieldSurfaceVariants`, shared with Input. The two
|
|
10
|
+
* legacy files styled it separately and had already drifted -- different
|
|
11
|
+
* background, different focus ring, invalid styling on one and not the
|
|
12
|
+
* other -- and none of those differences was a decision anyone made.
|
|
13
|
+
*/
|
|
14
|
+
export declare const textareaVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
15
|
+
/**
|
|
16
|
+
* A multi-line text field.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <Textarea placeholder="Tell us more" value={v} onChange={e => set(e.target.value)} />
|
|
20
|
+
*/
|
|
21
|
+
export interface TextareaProps extends Omit<TextAreaProps, "className" | "style"> {
|
|
22
|
+
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function Textarea({ className, ...props }: TextareaProps): import("react").JSX.Element;
|
|
26
|
+
//#endregion
|
|
27
|
+
//# sourceMappingURL=textarea.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { TextArea } from "react-aria-components";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/textarea/textarea.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Textarea, ported from the legacy component onto React Aria's TextArea.
|
|
10
|
+
*
|
|
11
|
+
* Census: 3 files, 3 uses -- `value`, `onChange`, `placeholder`, `id`,
|
|
12
|
+
* `ref`. All native props, all still native here.
|
|
13
|
+
*
|
|
14
|
+
* The surface comes from `fieldSurfaceVariants`, shared with Input. The two
|
|
15
|
+
* legacy files styled it separately and had already drifted -- different
|
|
16
|
+
* background, different focus ring, invalid styling on one and not the
|
|
17
|
+
* other -- and none of those differences was a decision anyone made.
|
|
18
|
+
*/
|
|
19
|
+
const textareaVariants = cva([
|
|
20
|
+
fieldSurfaceVariants(),
|
|
21
|
+
"min-h-20",
|
|
22
|
+
"px-(--density-control-pad-md) py-(--density-control-pad-sm)",
|
|
23
|
+
"text-base",
|
|
24
|
+
"resize-y",
|
|
25
|
+
"leading-(--leading-normal)"
|
|
26
|
+
]);
|
|
27
|
+
function Textarea({ className, ...props }) {
|
|
28
|
+
return /* @__PURE__ */ jsx(TextArea, {
|
|
29
|
+
...props,
|
|
30
|
+
"data-slot": "textarea",
|
|
31
|
+
className: cn(textareaVariants(), className)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { Textarea, textareaVariants };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=textarea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.js","names":["AriaTextArea"],"sources":["../../src/textarea/textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n TextArea as AriaTextArea,\n type TextAreaProps as AriaTextAreaProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Textarea, ported from the legacy component onto React Aria's TextArea.\n *\n * Census: 3 files, 3 uses -- `value`, `onChange`, `placeholder`, `id`,\n * `ref`. All native props, all still native here.\n *\n * The surface comes from `fieldSurfaceVariants`, shared with Input. The two\n * legacy files styled it separately and had already drifted -- different\n * background, different focus ring, invalid styling on one and not the\n * other -- and none of those differences was a decision anyone made.\n */\nexport const textareaVariants = cva([\n fieldSurfaceVariants(),\n\n // min-h-20 is 80px, the legacy min-h-[80px] -- but as a spacing-scale step\n // rather than a bracket value, which check:hardcoding rejects.\n \"min-h-20\",\n \"px-(--density-control-pad-md) py-(--density-control-pad-sm)\",\n \"text-base\",\n\n // A textarea is resizable by the user agent, and horizontal resize breaks\n // any layout it sits in. Vertical only.\n \"resize-y\",\n // Height is content, not a control slot, so the line-height has to be set\n // explicitly -- unset it computes to `normal`, which varies with the\n // font's own metrics.\n \"leading-(--leading-normal)\",\n]);\n\n/**\n * A multi-line text field.\n *\n * @example\n * <Textarea placeholder=\"Tell us more\" value={v} onChange={e => set(e.target.value)} />\n */\nexport interface TextareaProps extends Omit<AriaTextAreaProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Textarea({ className, ...props }: TextareaProps) {\n return (\n <AriaTextArea {...props} data-slot=\"textarea\" className={cn(textareaVariants(), className)} />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsBA,MAAa,mBAAmB,IAAI;CAClC,qBAAqB;CAIrB;CACA;CACA;CAIA;CAIA;AACF,CAAC;AAaD,SAAgB,SAAS,EAAE,WAAW,GAAG,SAAwB;CAC/D,OACE,oBAACA,UAAD;EAAc,GAAI;EAAO,aAAU;EAAW,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAAI,CAAA;AAEjG"}
|
|
@@ -0,0 +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 };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { VariantProps } from "class-variance-authority";
|
|
2
|
+
import { UNSTABLE_ToastQueue } from "react-aria-components";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
//#region src/toast/toast.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Toast: a transient message, announced without stealing focus.
|
|
7
|
+
*
|
|
8
|
+
* Maturity: **experimental**, and the reason is upstream rather than here.
|
|
9
|
+
* React Aria ships this API as `UNSTABLE_Toast*` in 1.21.1, so a RAC minor
|
|
10
|
+
* bump can rename or reshape it. The alternative was hand-rolling the live
|
|
11
|
+
* region, the queue, the timers and the focus behaviour, which is how every
|
|
12
|
+
* toast implementation acquires its accessibility bugs -- so this takes the
|
|
13
|
+
* upstream one and labels the risk instead of hiding it.
|
|
14
|
+
*
|
|
15
|
+
* THE PRODUCT HAS 30 FILES CALLING `useToast` AND 67 `toast(...)` CALLS, and
|
|
16
|
+
* its implementation is the shadcn reducer with both of its known defects
|
|
17
|
+
* still in place:
|
|
18
|
+
*
|
|
19
|
+
* TOAST_LIMIT = 1 -- a second toast REPLACES the first, so a
|
|
20
|
+
* burst of results shows only the last one.
|
|
21
|
+
* TOAST_REMOVE_DELAY = 1e6 -- sixteen minutes. Nothing auto-dismisses;
|
|
22
|
+
* every toast waits to be closed by hand.
|
|
23
|
+
*
|
|
24
|
+
* Neither is ported. `maxVisibleToasts` defaults to 3 and `timeout` to 5s,
|
|
25
|
+
* which is the WCAG 2.2.1 floor React Aria enforces -- it REFUSES a timeout
|
|
26
|
+
* under 5000ms, because a message that vanishes faster cannot be read by
|
|
27
|
+
* someone using a screen magnifier.
|
|
28
|
+
*
|
|
29
|
+
* The queue is a module singleton on purpose. The product's `toast()` is
|
|
30
|
+
* importable from anywhere and called from API error handlers and effects
|
|
31
|
+
* that have no component to hang a hook off, and a context-based queue would
|
|
32
|
+
* have made those 67 call sites into 67 refactors.
|
|
33
|
+
*/
|
|
34
|
+
/** Where the region sits. Fixed, above everything, out of the way of content. */
|
|
35
|
+
export declare const toastRegionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
36
|
+
export declare const toastVariants: (props?: ({
|
|
37
|
+
tone?: "neutral" | "success" | "warning" | "danger" | "info" | null | undefined;
|
|
38
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
39
|
+
export declare const toastCloseVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
40
|
+
/** What a toast carries. `title` is required: an unnamed toast announces nothing. */
|
|
41
|
+
export interface ToastContent extends VariantProps<typeof toastVariants> {
|
|
42
|
+
/** The message. Required -- a toast with only a description announces as loose text. */
|
|
43
|
+
title: ReactNode;
|
|
44
|
+
/** Secondary detail, beneath the title. */
|
|
45
|
+
description?: ReactNode;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The queue every `toast()` call feeds.
|
|
49
|
+
*
|
|
50
|
+
* Exported so a consumer can read `visibleToasts` in a test, or dismiss them
|
|
51
|
+
* on a route change -- there is no `clear()` upstream, so that is
|
|
52
|
+
* `[...toastQueue.visibleToasts].forEach((t) => toastQueue.close(t.key))`.
|
|
53
|
+
* Not something to touch in normal use.
|
|
54
|
+
*/
|
|
55
|
+
export declare const toastQueue: UNSTABLE_ToastQueue<ToastContent>;
|
|
56
|
+
/**
|
|
57
|
+
* Show a toast.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* toast({ title: "Report saved" });
|
|
61
|
+
* toast({ title: "Upload failed", description: err.message, tone: "danger" });
|
|
62
|
+
*
|
|
63
|
+
* @param timeout - ms before it dismisses itself. React Aria REFUSES anything
|
|
64
|
+
* under 5000 and anything at all while the region is focused, because a
|
|
65
|
+
* message that disappears before it can be read fails WCAG 2.2.1. Pass 0 to
|
|
66
|
+
* require a manual dismiss, which is right for an error a user must act on.
|
|
67
|
+
*/
|
|
68
|
+
export declare function toast(content: ToastContent, timeout?: number): string;
|
|
69
|
+
export interface ToastRegionProps {
|
|
70
|
+
/**
|
|
71
|
+
* Names the region for assistive tech.
|
|
72
|
+
*
|
|
73
|
+
* **Required.** React Aria renders the region as a labelled landmark, and
|
|
74
|
+
* an unnamed one is announced as "region" with no indication of what just
|
|
75
|
+
* arrived. It is a prop rather than a default string because
|
|
76
|
+
* ARCHITECTURE section 7 rule 9 routes user-visible copy through the
|
|
77
|
+
* consumer until `useMessages()` exists.
|
|
78
|
+
*/
|
|
79
|
+
label: string;
|
|
80
|
+
/** Accessible name for each toast's dismiss control. */
|
|
81
|
+
closeLabel?: string;
|
|
82
|
+
/** Extra classes for the region. Merged last. */
|
|
83
|
+
className?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Renders the queue. Mount ONCE, near the root.
|
|
87
|
+
*
|
|
88
|
+
* React Aria only renders the landmark while something is queued, so this
|
|
89
|
+
* costs nothing on a quiet screen.
|
|
90
|
+
*/
|
|
91
|
+
export declare function ToastRegion({ label, closeLabel, className }: ToastRegionProps): import("react").JSX.Element;
|
|
92
|
+
//#endregion
|
|
93
|
+
//# sourceMappingURL=toast.d.ts.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { Button, Text, UNSTABLE_Toast, UNSTABLE_ToastContent, UNSTABLE_ToastQueue, UNSTABLE_ToastRegion } from "react-aria-components";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/toast/toast.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Toast: a transient message, announced without stealing focus.
|
|
9
|
+
*
|
|
10
|
+
* Maturity: **experimental**, and the reason is upstream rather than here.
|
|
11
|
+
* React Aria ships this API as `UNSTABLE_Toast*` in 1.21.1, so a RAC minor
|
|
12
|
+
* bump can rename or reshape it. The alternative was hand-rolling the live
|
|
13
|
+
* region, the queue, the timers and the focus behaviour, which is how every
|
|
14
|
+
* toast implementation acquires its accessibility bugs -- so this takes the
|
|
15
|
+
* upstream one and labels the risk instead of hiding it.
|
|
16
|
+
*
|
|
17
|
+
* THE PRODUCT HAS 30 FILES CALLING `useToast` AND 67 `toast(...)` CALLS, and
|
|
18
|
+
* its implementation is the shadcn reducer with both of its known defects
|
|
19
|
+
* still in place:
|
|
20
|
+
*
|
|
21
|
+
* TOAST_LIMIT = 1 -- a second toast REPLACES the first, so a
|
|
22
|
+
* burst of results shows only the last one.
|
|
23
|
+
* TOAST_REMOVE_DELAY = 1e6 -- sixteen minutes. Nothing auto-dismisses;
|
|
24
|
+
* every toast waits to be closed by hand.
|
|
25
|
+
*
|
|
26
|
+
* Neither is ported. `maxVisibleToasts` defaults to 3 and `timeout` to 5s,
|
|
27
|
+
* which is the WCAG 2.2.1 floor React Aria enforces -- it REFUSES a timeout
|
|
28
|
+
* under 5000ms, because a message that vanishes faster cannot be read by
|
|
29
|
+
* someone using a screen magnifier.
|
|
30
|
+
*
|
|
31
|
+
* The queue is a module singleton on purpose. The product's `toast()` is
|
|
32
|
+
* importable from anywhere and called from API error handlers and effects
|
|
33
|
+
* that have no component to hang a hook off, and a context-based queue would
|
|
34
|
+
* have made those 67 call sites into 67 refactors.
|
|
35
|
+
*/
|
|
36
|
+
/** Where the region sits. Fixed, above everything, out of the way of content. */
|
|
37
|
+
const toastRegionVariants = cva([
|
|
38
|
+
"fixed z-50 flex flex-col gap-2 box-border",
|
|
39
|
+
"bottom-(--density-bar-pad-y) end-(--density-bar-pad-x)",
|
|
40
|
+
"w-[min(24rem,calc(100vw-2rem))]",
|
|
41
|
+
"outline-none"
|
|
42
|
+
]);
|
|
43
|
+
const toastVariants = cva([
|
|
44
|
+
"flex items-start gap-3 box-border w-full",
|
|
45
|
+
"rounded-md border-(length:--border-width) p-(--density-bar-pad-x)",
|
|
46
|
+
"font-sans shadow-surface",
|
|
47
|
+
"motion-safe:transition-opacity motion-safe:duration-(--duration-fast) motion-safe:ease-standard",
|
|
48
|
+
"data-[animation=exiting]:opacity-0"
|
|
49
|
+
], {
|
|
50
|
+
variants: { tone: {
|
|
51
|
+
neutral: "bg-background border-separator text-foreground",
|
|
52
|
+
success: "bg-success-subtle border-success-strong text-success-foreground",
|
|
53
|
+
warning: "bg-warning-subtle border-warning-strong text-warning-foreground",
|
|
54
|
+
danger: "bg-destructive-subtle border-destructive-strong text-destructive-subtle-foreground",
|
|
55
|
+
info: "bg-info-subtle border-info-strong text-info-foreground"
|
|
56
|
+
} },
|
|
57
|
+
defaultVariants: { tone: "neutral" }
|
|
58
|
+
});
|
|
59
|
+
const toastCloseVariants = cva([
|
|
60
|
+
"ms-auto shrink-0 rounded-sm cursor-pointer",
|
|
61
|
+
"text-current opacity-70 data-[hovered]:opacity-100",
|
|
62
|
+
"outline-none",
|
|
63
|
+
"data-[focus-visible]:outline-solid",
|
|
64
|
+
"data-[focus-visible]:outline-(length:--focus-ring-width)",
|
|
65
|
+
"data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
66
|
+
"data-[focus-visible]:outline-(--color-ring)"
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* The queue every `toast()` call feeds.
|
|
70
|
+
*
|
|
71
|
+
* Exported so a consumer can read `visibleToasts` in a test, or dismiss them
|
|
72
|
+
* on a route change -- there is no `clear()` upstream, so that is
|
|
73
|
+
* `[...toastQueue.visibleToasts].forEach((t) => toastQueue.close(t.key))`.
|
|
74
|
+
* Not something to touch in normal use.
|
|
75
|
+
*/
|
|
76
|
+
const toastQueue = new UNSTABLE_ToastQueue({ maxVisibleToasts: 3 });
|
|
77
|
+
/**
|
|
78
|
+
* Show a toast.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* toast({ title: "Report saved" });
|
|
82
|
+
* toast({ title: "Upload failed", description: err.message, tone: "danger" });
|
|
83
|
+
*
|
|
84
|
+
* @param timeout - ms before it dismisses itself. React Aria REFUSES anything
|
|
85
|
+
* under 5000 and anything at all while the region is focused, because a
|
|
86
|
+
* message that disappears before it can be read fails WCAG 2.2.1. Pass 0 to
|
|
87
|
+
* require a manual dismiss, which is right for an error a user must act on.
|
|
88
|
+
*/
|
|
89
|
+
function toast(content, timeout = 5e3) {
|
|
90
|
+
return toastQueue.add(content, timeout === 0 ? void 0 : { timeout });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Renders the queue. Mount ONCE, near the root.
|
|
94
|
+
*
|
|
95
|
+
* React Aria only renders the landmark while something is queued, so this
|
|
96
|
+
* costs nothing on a quiet screen.
|
|
97
|
+
*/
|
|
98
|
+
function ToastRegion({ label, closeLabel = "Dismiss", className }) {
|
|
99
|
+
return /* @__PURE__ */ jsx(UNSTABLE_ToastRegion, {
|
|
100
|
+
queue: toastQueue,
|
|
101
|
+
"aria-label": label,
|
|
102
|
+
"data-slot": "toast-region",
|
|
103
|
+
className: cn(toastRegionVariants(), className),
|
|
104
|
+
children: ({ toast: queued }) => /* @__PURE__ */ jsxs(UNSTABLE_Toast, {
|
|
105
|
+
toast: queued,
|
|
106
|
+
"data-slot": "toast",
|
|
107
|
+
"data-tone": queued.content.tone ?? "neutral",
|
|
108
|
+
className: toastVariants({ tone: queued.content.tone }),
|
|
109
|
+
children: [/* @__PURE__ */ jsxs(UNSTABLE_ToastContent, {
|
|
110
|
+
"data-slot": "toast-content",
|
|
111
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
112
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
113
|
+
slot: "title",
|
|
114
|
+
"data-slot": "toast-title",
|
|
115
|
+
className: "text-sm leading-(--leading-snug) font-medium",
|
|
116
|
+
children: queued.content.title
|
|
117
|
+
}), queued.content.description == null ? null : /* @__PURE__ */ jsx(Text, {
|
|
118
|
+
slot: "description",
|
|
119
|
+
"data-slot": "toast-description",
|
|
120
|
+
className: "text-sm leading-(--leading-normal) opacity-90",
|
|
121
|
+
children: queued.content.description
|
|
122
|
+
})]
|
|
123
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
124
|
+
slot: "close",
|
|
125
|
+
"aria-label": closeLabel,
|
|
126
|
+
"data-slot": "toast-close",
|
|
127
|
+
className: toastCloseVariants(),
|
|
128
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
129
|
+
"aria-hidden": "true",
|
|
130
|
+
viewBox: "0 0 16 16",
|
|
131
|
+
fill: "none",
|
|
132
|
+
stroke: "currentColor",
|
|
133
|
+
strokeWidth: "1.75",
|
|
134
|
+
strokeLinecap: "round",
|
|
135
|
+
className: "size-(--icon-sm)",
|
|
136
|
+
children: /* @__PURE__ */ jsx("path", { d: "m4 4 8 8M12 4l-8 8" })
|
|
137
|
+
})
|
|
138
|
+
})]
|
|
139
|
+
})
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { ToastRegion, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants };
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=toast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast.js","names":["AriaToastQueue","AriaToastRegion","AriaToast","AriaToastContent","AriaText","AriaButton"],"sources":["../../src/toast/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport 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\";\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 \"motion-safe:transition-opacity motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n // Not colour alone: an exiting toast fades AND the region reflows.\n \"data-[animation=exiting]:opacity-0\",\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 \"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\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 function ToastRegion({ label, closeLabel = \"Dismiss\", className }: ToastRegionProps) {\n return (\n <AriaToastRegion\n queue={toastQueue}\n aria-label={label}\n data-slot=\"toast-region\"\n className={cn(toastRegionVariants(), className)}\n >\n {({ toast: queued }) => (\n <AriaToast\n toast={queued}\n data-slot=\"toast\"\n data-tone={queued.content.tone ?? \"neutral\"}\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 slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"toast-close\"\n className={toastCloseVariants()}\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 </AriaToastRegion>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,sBAAsB,IAAI;CACrC;CAEA;CACA;CACA;AACF,CAAC;AAED,MAAa,gBAAgB,IAC3B;CACE;CACA;CACA;CACA;CAEA;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;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;;;;;;;AAyBA,SAAgB,YAAY,EAAE,OAAO,aAAa,WAAW,aAA+B;CAC1F,OACE,oBAACC,sBAAD;EACE,OAAO;EACP,cAAY;EACZ,aAAU;EACV,WAAW,GAAG,oBAAoB,GAAG,SAAS;EAE5C,WAAA,EAAE,OAAO,aACT,qBAACC,gBAAD;GACE,OAAO;GACP,aAAU;GACV,aAAW,OAAO,QAAQ,QAAQ;GAClC,WAAW,cAAc,EAAE,MAAM,OAAO,QAAQ,KAAK,CAAC;GAJxD,UAAA,CAME,qBAACC,uBAAD;IAAkB,aAAU;IAAgB,WAAU;IAAtD,UAAA,CAKE,oBAACC,MAAD;KACE,MAAK;KACL,aAAU;KACV,WAAU;KAET,UAAA,OAAO,QAAQ;IACR,CAAA,GACT,OAAO,QAAQ,eAAe,OAAO,OACpC,oBAACA,MAAD;KACE,MAAK;KACL,aAAU;KACV,WAAU;KAET,UAAA,OAAO,QAAQ;IACR,CAAA,CAEI;GAClB,CAAA,GAAA,oBAACC,QAAD;IACE,MAAK;IACL,cAAY;IACZ,aAAU;IACV,WAAW,mBAAmB;IAE9B,UAAA,oBAAC,OAAD;KACE,eAAY;KACZ,SAAQ;KACR,MAAK;KACL,QAAO;KACP,aAAY;KACZ,eAAc;KACd,WAAU;KAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA;IAC3B,CAAA;GACK,CAAA,CACH;;CAEE,CAAA;AAErB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { TooltipProps, TooltipTriggerComponentProps } from "react-aria-components";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/tooltip/tooltip.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Tooltip, ported from the legacy shadcn/Radix component.
|
|
6
|
+
*
|
|
7
|
+
* Census: ten files import it but only SEVEN render anything -- three
|
|
8
|
+
* import the parts and use none. Across those seven: `Tooltip` 34,
|
|
9
|
+
* `TooltipTrigger` 34, `TooltipContent` 34, `TooltipProvider` 23. `side`
|
|
10
|
+
* is passed 12 times: `"top"` 11, `"bottom"` once.
|
|
11
|
+
*
|
|
12
|
+
* `TooltipProvider` IS NOT PORTED, and that removes 23 call sites rather
|
|
13
|
+
* than renaming them. The legacy `Tooltip` already wraps itself in a
|
|
14
|
+
* provider, so every explicit `<TooltipProvider>` in app code nested a
|
|
15
|
+
* second one inside the first -- harmless, but it also meant the
|
|
16
|
+
* `skipDelayDuration` grouping the provider exists for never spanned two
|
|
17
|
+
* tooltips. React Aria has no provider at all; that grouping is
|
|
18
|
+
* module-global.
|
|
19
|
+
*
|
|
20
|
+
* THE DELAY IS THE ONE THING THAT WOULD HAVE CHANGED SILENTLY. The legacy
|
|
21
|
+
* provider hardcodes `delayDuration = 0`, so all 34 tooltips appear
|
|
22
|
+
* instantly on hover. React Aria's default is 1500ms. Porting without
|
|
23
|
+
* noticing would have put a 1.5-second warm-up on every tooltip in the
|
|
24
|
+
* app, and neither the type system nor the test suite would have said a
|
|
25
|
+
* word -- so `delay` defaults to 0 here, and a story pins the default.
|
|
26
|
+
*/
|
|
27
|
+
export declare const tooltipVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
28
|
+
/**
|
|
29
|
+
* A hover and focus hint.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* <TooltipTrigger>
|
|
33
|
+
* <Button aria-label="Mute"><MicOff /></Button>
|
|
34
|
+
* <Tooltip>Mute</Tooltip>
|
|
35
|
+
* </TooltipTrigger>
|
|
36
|
+
*/
|
|
37
|
+
interface TooltipProps$1 extends Omit<TooltipProps, "className" | "style" | "children"> {
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
40
|
+
className?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Where it opens. The legacy `side` prop: `"top"` 11 times, `"bottom"`
|
|
43
|
+
* once, and the default for the other 22.
|
|
44
|
+
*
|
|
45
|
+
* @default "top"
|
|
46
|
+
*/
|
|
47
|
+
placement?: TooltipProps["placement"];
|
|
48
|
+
}
|
|
49
|
+
declare function Tooltip$1({ children, className, placement, ...props }: TooltipProps$1): import("react").JSX.Element;
|
|
50
|
+
/**
|
|
51
|
+
* Pairs a trigger with a tooltip.
|
|
52
|
+
*
|
|
53
|
+
* React Aria's `TooltipTrigger`, EXCEPT that `delay` and `closeDelay`
|
|
54
|
+
* default to 0 to match the legacy provider. React Aria ships 1500ms and
|
|
55
|
+
* 500ms; taking those defaults would have changed all 34 call sites
|
|
56
|
+
* without a word.
|
|
57
|
+
*
|
|
58
|
+
* Focus opens it immediately whatever `delay` says -- React Aria passes
|
|
59
|
+
* `immediate` on focus -- which is the behaviour a keyboard user needs.
|
|
60
|
+
*/
|
|
61
|
+
export interface TooltipTriggerProps extends TooltipTriggerComponentProps {
|
|
62
|
+
/** Hover warm-up in ms. @default 0 */
|
|
63
|
+
delay?: number;
|
|
64
|
+
/** Hover cool-down in ms. @default 0 */
|
|
65
|
+
closeDelay?: number;
|
|
66
|
+
}
|
|
67
|
+
declare function TooltipTrigger$1({ delay, closeDelay, ...props }: TooltipTriggerProps): import("react").JSX.Element;
|
|
68
|
+
//#endregion
|
|
69
|
+
export { Tooltip$1 as Tooltip, TooltipProps$1 as TooltipProps, TooltipTrigger$1 as TooltipTrigger };
|
|
70
|
+
//# sourceMappingURL=tooltip.d.ts.map
|