@charcuterie/ui 0.1.0 → 0.2.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.
Files changed (106) hide show
  1. package/README.md +9 -0
  2. package/dist/Accordion/Accordion.d.ts +82 -0
  3. package/dist/Accordion/Accordion.d.ts.map +1 -0
  4. package/dist/Accordion/Accordion.js +108 -0
  5. package/dist/Accordion/Accordion.js.map +1 -0
  6. package/dist/Accordion/AccordionSection.d.ts +28 -0
  7. package/dist/Accordion/AccordionSection.d.ts.map +1 -0
  8. package/dist/Accordion/AccordionSection.js +53 -0
  9. package/dist/Accordion/AccordionSection.js.map +1 -0
  10. package/dist/Field/Field.d.ts +69 -0
  11. package/dist/Field/Field.d.ts.map +1 -0
  12. package/dist/Field/Field.js +73 -0
  13. package/dist/Field/Field.js.map +1 -0
  14. package/dist/FileDropZone/FileDropZone.d.ts +63 -0
  15. package/dist/FileDropZone/FileDropZone.d.ts.map +1 -0
  16. package/dist/FileDropZone/FileDropZone.js +122 -0
  17. package/dist/FileDropZone/FileDropZone.js.map +1 -0
  18. package/dist/LogViewer/LogViewer.d.ts +88 -0
  19. package/dist/LogViewer/LogViewer.d.ts.map +1 -0
  20. package/dist/LogViewer/LogViewer.js +104 -0
  21. package/dist/LogViewer/LogViewer.js.map +1 -0
  22. package/dist/Menu/Menu.d.ts +81 -0
  23. package/dist/Menu/Menu.d.ts.map +1 -0
  24. package/dist/Menu/Menu.js +199 -0
  25. package/dist/Menu/Menu.js.map +1 -0
  26. package/dist/Menu/MenuAction.d.ts +24 -0
  27. package/dist/Menu/MenuAction.d.ts.map +1 -0
  28. package/dist/Menu/MenuAction.js +37 -0
  29. package/dist/Menu/MenuAction.js.map +1 -0
  30. package/dist/Select/Select.d.ts +87 -0
  31. package/dist/Select/Select.d.ts.map +1 -0
  32. package/dist/Select/Select.js +51 -0
  33. package/dist/Select/Select.js.map +1 -0
  34. package/dist/SortableTableHeader/SortableTableHeader.d.ts +61 -0
  35. package/dist/SortableTableHeader/SortableTableHeader.d.ts.map +1 -0
  36. package/dist/SortableTableHeader/SortableTableHeader.js +56 -0
  37. package/dist/SortableTableHeader/SortableTableHeader.js.map +1 -0
  38. package/dist/Toast/Toast.d.ts +54 -0
  39. package/dist/Toast/Toast.d.ts.map +1 -0
  40. package/dist/Toast/Toast.js +121 -0
  41. package/dist/Toast/Toast.js.map +1 -0
  42. package/dist/Toast/ToastRegion.d.ts +57 -0
  43. package/dist/Toast/ToastRegion.d.ts.map +1 -0
  44. package/dist/Toast/ToastRegion.js +41 -0
  45. package/dist/Toast/ToastRegion.js.map +1 -0
  46. package/dist/Toast/toastLifecycle.d.ts +34 -0
  47. package/dist/Toast/toastLifecycle.d.ts.map +1 -0
  48. package/dist/Toast/toastLifecycle.js +25 -0
  49. package/dist/Toast/toastLifecycle.js.map +1 -0
  50. package/dist/Tooltip/Tooltip.d.ts +68 -0
  51. package/dist/Tooltip/Tooltip.d.ts.map +1 -0
  52. package/dist/Tooltip/Tooltip.js +104 -0
  53. package/dist/Tooltip/Tooltip.js.map +1 -0
  54. package/dist/index.d.ts +22 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +11 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/mountStory.testHelpers.d.ts +0 -11
  59. package/dist/mountStory.testHelpers.d.ts.map +1 -1
  60. package/dist/mountStory.testHelpers.js +27 -0
  61. package/dist/mountStory.testHelpers.js.map +1 -1
  62. package/package.json +3 -3
  63. package/src/Accordion/Accordion.mdx +83 -0
  64. package/src/Accordion/Accordion.stories.tsx +146 -0
  65. package/src/Accordion/Accordion.test.tsx +163 -0
  66. package/src/Accordion/Accordion.tsx +182 -0
  67. package/src/Accordion/AccordionSection.tsx +163 -0
  68. package/src/Field/Field.mdx +74 -0
  69. package/src/Field/Field.stories.tsx +144 -0
  70. package/src/Field/Field.test.tsx +142 -0
  71. package/src/Field/Field.tsx +163 -0
  72. package/src/FileDropZone/FileDropZone.mdx +78 -0
  73. package/src/FileDropZone/FileDropZone.stories.tsx +136 -0
  74. package/src/FileDropZone/FileDropZone.test.tsx +230 -0
  75. package/src/FileDropZone/FileDropZone.tsx +234 -0
  76. package/src/LogViewer/LogViewer.mdx +88 -0
  77. package/src/LogViewer/LogViewer.stories.tsx +135 -0
  78. package/src/LogViewer/LogViewer.test.tsx +173 -0
  79. package/src/LogViewer/LogViewer.tsx +195 -0
  80. package/src/Menu/Menu.mdx +103 -0
  81. package/src/Menu/Menu.stories.tsx +161 -0
  82. package/src/Menu/Menu.test.tsx +202 -0
  83. package/src/Menu/Menu.tsx +300 -0
  84. package/src/Menu/MenuAction.tsx +95 -0
  85. package/src/Select/Select.mdx +74 -0
  86. package/src/Select/Select.stories.tsx +126 -0
  87. package/src/Select/Select.test.tsx +129 -0
  88. package/src/Select/Select.tsx +200 -0
  89. package/src/SortableTableHeader/SortableTableHeader.mdx +67 -0
  90. package/src/SortableTableHeader/SortableTableHeader.stories.tsx +207 -0
  91. package/src/SortableTableHeader/SortableTableHeader.test.tsx +180 -0
  92. package/src/SortableTableHeader/SortableTableHeader.tsx +131 -0
  93. package/src/Toast/Toast.mdx +84 -0
  94. package/src/Toast/Toast.stories.tsx +192 -0
  95. package/src/Toast/Toast.test.tsx +217 -0
  96. package/src/Toast/Toast.tsx +224 -0
  97. package/src/Toast/ToastRegion.tsx +98 -0
  98. package/src/Toast/toastLifecycle.ts +45 -0
  99. package/src/Tooltip/Tooltip.mdx +69 -0
  100. package/src/Tooltip/Tooltip.stories.tsx +104 -0
  101. package/src/Tooltip/Tooltip.test.tsx +138 -0
  102. package/src/Tooltip/Tooltip.tsx +169 -0
  103. package/src/index.ts +43 -0
  104. package/src/mountStory.testHelpers.ts +30 -0
  105. package/src/sourceRules.test.ts +221 -30
  106. package/CHANGELOG.md +0 -13
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuAction.d.ts","sourceRoot":"","sources":["../../src/Menu/MenuAction.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,kEAAkE;IAClE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,CACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,iBAAiB,GAAG,IAAI,KAC9B,IAAI,CAAA;CACV,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GAAI,wDAMxB,eAAe,KAAG,SAyDpB,CAAA"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import { FOCUS_RING_CLASS } from "../intentStyles.js";
4
+ import { toClassName } from "../toClassName.js";
5
+ /**
6
+ * Its own file because **registration is an effect**, and an effect
7
+ * cannot run in a loop — the same reason `TabTrigger` and
8
+ * `AccordionSection` are separate. Any member of a registering group
9
+ * ends up shaped like this.
10
+ *
11
+ * A disabled item is simply not registered, so the arrow keys skip
12
+ * it without any command in `RovingFocus` knowing what "disabled"
13
+ * means. It stays in the DOM and stays announced, which is the
14
+ * difference between "you cannot do this right now" and "this does
15
+ * not exist".
16
+ */
17
+ export const MenuAction = ({ item, onDismiss, register, tabIndex, trackElement, }) => {
18
+ const { icon, isDisabled = false, key, label, onSelect, } = item;
19
+ useEffect(() => {
20
+ if (isDisabled) {
21
+ return;
22
+ }
23
+ return register(key);
24
+ }, [isDisabled, key, register]);
25
+ return (_jsxs("button", { className: toClassName("flex w-full cursor-pointer items-center gap-2 rounded-sm bg-transparent px-2 py-1.5 text-start text-content-primary text-sm transition-colors duration-(--duration-fast) ease-standard", "hover:bg-intent-neutral-surface", isDisabled &&
26
+ "cursor-not-allowed text-content-disabled hover:bg-transparent", FOCUS_RING_CLASS), disabled: isDisabled, onClick: () => {
27
+ onSelect();
28
+ // Choosing dismisses. A menu that stays open after an action
29
+ // has fired is the one interaction users read as "it didn't
30
+ // work", and every one of the fleet's menus closes here
31
+ // today by unmounting its whole parent.
32
+ onDismiss();
33
+ }, ref: (element) => {
34
+ trackElement(key, element);
35
+ }, role: "menuitem", tabIndex: tabIndex, type: "button", children: [icon === undefined ? null : (_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-content-secondary", children: icon })), label] }));
36
+ };
37
+ //# sourceMappingURL=MenuAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuAction.js","sourceRoot":"","sources":["../../src/Menu/MenuAction.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAe/C;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,YAAY,GACI,EAAa,EAAE;IAC/B,MAAM,EACJ,IAAI,EACJ,UAAU,GAAG,KAAK,EAClB,GAAG,EACH,KAAK,EACL,QAAQ,GACT,GAAG,IAAI,CAAA;IAER,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,EAAE,CAAC;YACf,OAAM;QACR,CAAC;QAED,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAA;IACtB,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE/B,OAAO,CACL,kBACE,SAAS,EAAE,WAAW,CACpB,wLAAwL,EACxL,iCAAiC,EACjC,UAAU;YACR,+DAA+D,EACjE,gBAAgB,CACjB,EACD,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,GAAG,EAAE;YACZ,QAAQ,EAAE,CAAA;YAEV,6DAA6D;YAC7D,4DAA4D;YAC5D,wDAAwD;YACxD,wCAAwC;YACxC,SAAS,EAAE,CAAA;QACb,CAAC,EACD,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE;YACf,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAC5B,CAAC,EACD,IAAI,EAAC,UAAU,EACf,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAC,QAAQ,aAEZ,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC3B,8BAGc,MAAM,EAClB,SAAS,EAAC,iCAAiC,YAE1C,IAAI,GACA,CACR,EAEA,KAAK,IACC,CACV,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,87 @@
1
+ import type { ControlSize } from "@charcuterie/tokens";
2
+ import type { ComponentPropsWithRef, ReactNode } from "react";
3
+ export type SelectOption = {
4
+ isDisabled?: boolean;
5
+ label: string;
6
+ value: string;
7
+ };
8
+ export type SelectOptionGroup = {
9
+ label: string;
10
+ options: SelectOption[];
11
+ };
12
+ export type SelectItem = SelectOption | SelectOptionGroup;
13
+ /**
14
+ * The HTML attributes are passed through, and that is not
15
+ * convenience — it is what makes the `Field` slot work at all.
16
+ *
17
+ * `Field` clones `id`, `aria-describedby`, `aria-invalid` and
18
+ * `required` onto whatever control it is given. A component with a
19
+ * closed prop list **silently drops all four**: the field renders,
20
+ * the label still points at nothing, and every assertion about the
21
+ * control's own props passes because they never arrived. Found by
22
+ * the one test that asked the control what it had been handed.
23
+ *
24
+ * Same arrangement as `Button`, for the same reason.
25
+ */
26
+ export type SelectProps = Omit<ComponentPropsWithRef<"select">, "disabled" | "onChange" | "size" | "value"> & {
27
+ className?: string;
28
+ isDisabled?: boolean;
29
+ /**
30
+ * The accessible name, when this is **not** inside a `Field`. A
31
+ * `<select>` with neither a `<label for>` nor an `aria-label` is
32
+ * announced as "combobox" and is unfindable by
33
+ * `getByRole("combobox", { name })` — twelve of the fleet's
34
+ * fourteen native selects are in exactly that state.
35
+ */
36
+ label?: string;
37
+ onChange?: (value: string) => void;
38
+ options: SelectItem[];
39
+ /**
40
+ * A disabled first entry, not a real option. Reads as a prompt to
41
+ * a sighted user and cannot be submitted, which is what a
42
+ * placeholder should mean — unlike an empty-valued option, which
43
+ * is selectable and silently posts "".
44
+ */
45
+ placeholder?: string;
46
+ size?: ControlSize;
47
+ /** **Initial** only, like every other value prop in this library. */
48
+ value?: string;
49
+ };
50
+ /**
51
+ * A styled native `<select>`, and the fleet's fourteen hand-rolled
52
+ * ones collapse onto it.
53
+ *
54
+ * ### There is no state kind here, and that is the finding
55
+ *
56
+ * Every other interactive component in this package owns its state
57
+ * through `@charcuterie/logic`. This one owns none, because the
58
+ * platform already does: a `<select>` holds the chosen option in the
59
+ * DOM, and putting a `useSinglePicker` beside it would create the
60
+ * exact conflict the state layer exists to prevent — two things
61
+ * believing they hold `value`, which is the Radix argument from
62
+ * `Popover` pointed at ourselves. So `value` seeds `defaultValue`
63
+ * and changes are reported outward.
64
+ *
65
+ * The consequence worth knowing: **`Select` is uncontrolled and
66
+ * `Tabs` is not**, and a reader should be able to tell why without
67
+ * asking. A tab bar's selection has no DOM representation, so
68
+ * something has to hold it; a select's does.
69
+ *
70
+ * ### Native, and what that rules out
71
+ *
72
+ * A `<select>` cannot render a rich option — an icon, two lines, a
73
+ * badge — and cannot filter. mux-magic has six controls that need
74
+ * one of those (`CommandPicker`, `PathPicker`, `LinkPicker`,
75
+ * `EnumPicker`, `AssFieldPicker`, `RenameTargetPicker`), and every
76
+ * one of them is a **`Combobox`**: a text input that filters a
77
+ * listbox. That is a different pattern with a different ARIA
78
+ * contract, it is P2 in the plan, and building a bare `Listbox`
79
+ * here that none of the six could adopt would have shipped a
80
+ * component with no caller.
81
+ *
82
+ * What native buys, and what a hand-rolled listbox has to rebuild
83
+ * from nothing: type-ahead, Home/End, PageUp/PageDown, the mobile
84
+ * wheel picker, form submission, `:invalid`, and autofill.
85
+ */
86
+ export declare const Select: ({ className, isDisabled, label, onChange, options, placeholder, size, value, ...selectProps }: SelectProps) => ReactNode;
87
+ //# sourceMappingURL=Select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/Select/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAEV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAYd,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,iBAAiB,CAAA;AAEzD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAC3C,GAAG;IACF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,MAAM,GAAI,+FAUpB,WAAW,KAAG,SA8EhB,CAAA"}
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CONTROL_SIZE_CLASS, MIN_TOUCH_TARGET_CLASS, } from "../controlStyles.js";
3
+ import { DISABLED_CLASS, FOCUS_RING_CLASS, } from "../intentStyles.js";
4
+ import { toClassName } from "../toClassName.js";
5
+ const getIsGroup = (item) => "options" in item;
6
+ /**
7
+ * A styled native `<select>`, and the fleet's fourteen hand-rolled
8
+ * ones collapse onto it.
9
+ *
10
+ * ### There is no state kind here, and that is the finding
11
+ *
12
+ * Every other interactive component in this package owns its state
13
+ * through `@charcuterie/logic`. This one owns none, because the
14
+ * platform already does: a `<select>` holds the chosen option in the
15
+ * DOM, and putting a `useSinglePicker` beside it would create the
16
+ * exact conflict the state layer exists to prevent — two things
17
+ * believing they hold `value`, which is the Radix argument from
18
+ * `Popover` pointed at ourselves. So `value` seeds `defaultValue`
19
+ * and changes are reported outward.
20
+ *
21
+ * The consequence worth knowing: **`Select` is uncontrolled and
22
+ * `Tabs` is not**, and a reader should be able to tell why without
23
+ * asking. A tab bar's selection has no DOM representation, so
24
+ * something has to hold it; a select's does.
25
+ *
26
+ * ### Native, and what that rules out
27
+ *
28
+ * A `<select>` cannot render a rich option — an icon, two lines, a
29
+ * badge — and cannot filter. mux-magic has six controls that need
30
+ * one of those (`CommandPicker`, `PathPicker`, `LinkPicker`,
31
+ * `EnumPicker`, `AssFieldPicker`, `RenameTargetPicker`), and every
32
+ * one of them is a **`Combobox`**: a text input that filters a
33
+ * listbox. That is a different pattern with a different ARIA
34
+ * contract, it is P2 in the plan, and building a bare `Listbox`
35
+ * here that none of the six could adopt would have shipped a
36
+ * component with no caller.
37
+ *
38
+ * What native buys, and what a hand-rolled listbox has to rebuild
39
+ * from nothing: type-ahead, Home/End, PageUp/PageDown, the mobile
40
+ * wheel picker, form submission, `:invalid`, and autofill.
41
+ */
42
+ export const Select = ({ className, isDisabled = false, label, onChange, options, placeholder, size = "md", value, ...selectProps }) => (_jsxs("div", { className: "relative inline-grid w-full items-center", children: [_jsxs("select", { ...selectProps, "aria-label": label, className: toClassName("w-full cursor-pointer appearance-none rounded-md border border-border-default bg-surface-raised text-content-primary transition-colors duration-(--duration-fast) ease-standard",
43
+ // Room for the chevron below, which sits in the same grid
44
+ // cell. A `<select>`'s own arrow is what `appearance-none`
45
+ // removes, and forgetting to put one back leaves a control
46
+ // that looks exactly like a text input.
47
+ "pe-9", CONTROL_SIZE_CLASS[size], MIN_TOUCH_TARGET_CLASS, "hover:border-border-strong", FOCUS_RING_CLASS, DISABLED_CLASS, className), defaultValue: value ??
48
+ (placeholder === undefined ? undefined : ""), disabled: isDisabled, onChange: (changeEvent) => {
49
+ onChange?.(changeEvent.currentTarget.value);
50
+ }, children: [placeholder === undefined ? null : (_jsx("option", { disabled: true, value: "", children: placeholder })), options.map((item) => getIsGroup(item) ? (_jsx("optgroup", { label: item.label, children: item.options.map((option) => (_jsx("option", { disabled: option.isDisabled, value: option.value, children: option.label }, option.value))) }, item.label)) : (_jsx("option", { disabled: item.isDisabled, value: item.value, children: item.label }, item.value)))] }), _jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute end-3 size-4 text-content-secondary", fill: "none", focusable: false, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.75, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "m6 9 6 6 6-6" }) })] }));
51
+ //# sourceMappingURL=Select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Select.js","sourceRoot":"","sources":["../../src/Select/Select.tsx"],"names":[],"mappings":";AAOA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,cAAc,EACd,gBAAgB,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAwD/C,MAAM,UAAU,GAAG,CACjB,IAAgB,EACW,EAAE,CAAC,SAAS,IAAI,IAAI,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,SAAS,EACT,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,QAAQ,EACR,OAAO,EACP,WAAW,EACX,IAAI,GAAG,IAAI,EACX,KAAK,EACL,GAAG,WAAW,EACF,EAAa,EAAE,CAAC,CAC5B,eAAK,SAAS,EAAC,0CAA0C,aACvD,qBACM,WAAW,gBACH,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,iLAAiL;YACjL,0DAA0D;YAC1D,2DAA2D;YAC3D,2DAA2D;YAC3D,wCAAwC;YACxC,MAAM,EACN,kBAAkB,CAAC,IAAI,CAAC,EACxB,sBAAsB,EACtB,4BAA4B,EAC5B,gBAAgB,EAChB,cAAc,EACd,SAAS,CACV,EACD,YAAY,EACV,KAAK;gBACL,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAE9C,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,CACR,WAA2C,EAC3C,EAAE;gBACF,QAAQ,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;YAC7C,CAAC,aAEA,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAClC,iBAAQ,QAAQ,QAAC,KAAK,EAAC,EAAE,YACtB,WAAW,GACL,CACV,EAEA,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACpB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CACjB,mBAA2B,KAAK,EAAE,IAAI,CAAC,KAAK,YACzC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC5B,iBACE,QAAQ,EAAE,MAAM,CAAC,UAAU,EAE3B,KAAK,EAAE,MAAM,CAAC,KAAK,YAElB,MAAM,CAAC,KAAK,IAHR,MAAM,CAAC,KAAK,CAIV,CACV,CAAC,IATW,IAAI,CAAC,KAAK,CAUd,CACZ,CAAC,CAAC,CAAC,CACF,iBACE,QAAQ,EAAE,IAAI,CAAC,UAAU,EAEzB,KAAK,EAAE,IAAI,CAAC,KAAK,YAEhB,IAAI,CAAC,KAAK,IAHN,IAAI,CAAC,KAAK,CAIR,CACV,CACF,IACM,EAET,6BAGc,MAAM,EAClB,SAAS,EAAC,kEAAkE,EAC5E,IAAI,EAAC,MAAM,EACX,SAAS,EAAE,KAAK,EAChB,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAE,IAAI,EACjB,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,4BAA4B,YAElC,eAAM,CAAC,EAAC,cAAc,GAAG,GACrB,IACF,CACP,CAAA"}
@@ -0,0 +1,61 @@
1
+ import type { ReactNode } from "react";
2
+ export type SortDirection = "ascending" | "descending";
3
+ export type SortableTableHeaderProps = {
4
+ children: ReactNode;
5
+ className?: string;
6
+ /**
7
+ * The direction **this column** is sorted in, or `undefined` when
8
+ * it is not the sorted one. Undefined is not "ascending by
9
+ * default": a table has exactly one sorted column and every other
10
+ * header has to say `aria-sort="none"`, which is what tells a
11
+ * screen reader which one is live.
12
+ */
13
+ direction?: SortDirection;
14
+ /** Called with the direction the header is asking to move to. */
15
+ onSort: (direction: SortDirection) => void;
16
+ };
17
+ /**
18
+ * **The header cell, and nothing else.** No `DataTable`, no rows, no
19
+ * comparator — the plan is explicit about that, and the reason is
20
+ * that sorting *data* is app logic that differs per column type,
21
+ * while announcing *that* a column is sorted is markup every table
22
+ * gets wrong the same way.
23
+ *
24
+ * ### `aria-sort` does not exist anywhere in the fleet
25
+ *
26
+ * Not in mux-magic, not in rip-deck, not in castkit. mux-magic's
27
+ * `FileExplorerModal` — the one sortable table there is — renders
28
+ * the state as a character:
29
+ *
30
+ * ```tsx
31
+ * {sortDirection === "asc" ? "▲" : "▼"}
32
+ * ```
33
+ *
34
+ * A screen reader announces that as "black up-pointing triangle", if
35
+ * the font has it at all, and the sandbox's headless Chromium does
36
+ * not have it — so the same glyph measures blank in a screenshot.
37
+ * Sighted users see a direction, everyone else gets nothing, and no
38
+ * automated check anywhere reports it: axe has no rule for a missing
39
+ * `aria-sort`, because a table with none is a table that is simply
40
+ * not sorted as far as the accessibility tree knows.
41
+ *
42
+ * This is therefore the one component in M6 whose payoff is not
43
+ * duplication — there is a single site — but a class of failure
44
+ * that is invisible to every gate the fleet has.
45
+ *
46
+ * ### The cell announces, the button acts
47
+ *
48
+ * `aria-sort` belongs on the `<th>`; the click target is a `<button>`
49
+ * inside it. Putting the role on the button instead is the common
50
+ * mistake and it breaks the grid semantics — a `<th>` that is a
51
+ * button is no longer a column header to anything reading the table
52
+ * structure.
53
+ *
54
+ * The name stays the column's name. It is tempting to append the
55
+ * state — "Name, sorted ascending" — and that duplicates what
56
+ * `aria-sort` already says, so the column is announced twice and
57
+ * `getByRole("columnheader", { name: "Name" })` stops matching the
58
+ * moment somebody sorts it.
59
+ */
60
+ export declare const SortableTableHeader: ({ children, className, direction, onSort, }: SortableTableHeaderProps) => ReactNode;
61
+ //# sourceMappingURL=SortableTableHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SortableTableHeader.d.ts","sourceRoot":"","sources":["../../src/SortableTableHeader/SortableTableHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,YAAY,CAAA;AAEtD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,iEAAiE;IACjE,MAAM,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAA;CAC3C,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,mBAAmB,GAAI,6CAKjC,wBAAwB,KAAG,SAsD7B,CAAA"}
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FOCUS_RING_CLASS } from "../intentStyles.js";
3
+ import { toClassName } from "../toClassName.js";
4
+ const NEXT_DIRECTION = {
5
+ ascending: "descending",
6
+ descending: "ascending",
7
+ };
8
+ /**
9
+ * **The header cell, and nothing else.** No `DataTable`, no rows, no
10
+ * comparator — the plan is explicit about that, and the reason is
11
+ * that sorting *data* is app logic that differs per column type,
12
+ * while announcing *that* a column is sorted is markup every table
13
+ * gets wrong the same way.
14
+ *
15
+ * ### `aria-sort` does not exist anywhere in the fleet
16
+ *
17
+ * Not in mux-magic, not in rip-deck, not in castkit. mux-magic's
18
+ * `FileExplorerModal` — the one sortable table there is — renders
19
+ * the state as a character:
20
+ *
21
+ * ```tsx
22
+ * {sortDirection === "asc" ? "▲" : "▼"}
23
+ * ```
24
+ *
25
+ * A screen reader announces that as "black up-pointing triangle", if
26
+ * the font has it at all, and the sandbox's headless Chromium does
27
+ * not have it — so the same glyph measures blank in a screenshot.
28
+ * Sighted users see a direction, everyone else gets nothing, and no
29
+ * automated check anywhere reports it: axe has no rule for a missing
30
+ * `aria-sort`, because a table with none is a table that is simply
31
+ * not sorted as far as the accessibility tree knows.
32
+ *
33
+ * This is therefore the one component in M6 whose payoff is not
34
+ * duplication — there is a single site — but a class of failure
35
+ * that is invisible to every gate the fleet has.
36
+ *
37
+ * ### The cell announces, the button acts
38
+ *
39
+ * `aria-sort` belongs on the `<th>`; the click target is a `<button>`
40
+ * inside it. Putting the role on the button instead is the common
41
+ * mistake and it breaks the grid semantics — a `<th>` that is a
42
+ * button is no longer a column header to anything reading the table
43
+ * structure.
44
+ *
45
+ * The name stays the column's name. It is tempting to append the
46
+ * state — "Name, sorted ascending" — and that duplicates what
47
+ * `aria-sort` already says, so the column is announced twice and
48
+ * `getByRole("columnheader", { name: "Name" })` stops matching the
49
+ * moment somebody sorts it.
50
+ */
51
+ export const SortableTableHeader = ({ children, className, direction, onSort, }) => (_jsx("th", { "aria-sort": direction ?? "none", className: toClassName("border-border-subtle border-b p-0 text-start", className), scope: "col", children: _jsxs("button", { className: toClassName("flex w-full cursor-pointer items-center gap-1.5 bg-transparent px-3 py-2 text-start font-medium text-content-secondary text-xs uppercase tracking-wide transition-colors duration-(--duration-fast) ease-standard", "hover:text-content-primary", FOCUS_RING_CLASS), onClick: () => {
52
+ onSort(direction === undefined
53
+ ? "ascending"
54
+ : NEXT_DIRECTION[direction]);
55
+ }, type: "button", children: [children, _jsx("svg", { "aria-hidden": "true", className: toClassName("size-3.5 shrink-0 transition-[transform,opacity] duration-(--duration-fast) ease-standard", direction === undefined && "opacity-0", direction === "descending" && "rotate-180"), fill: "none", focusable: false, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "m6 15 6-6 6 6" }) })] }) }));
56
+ //# sourceMappingURL=SortableTableHeader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SortableTableHeader.js","sourceRoot":"","sources":["../../src/SortableTableHeader/SortableTableHeader.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAmB/C,MAAM,cAAc,GAClB;IACE,SAAS,EAAE,YAAY;IACvB,UAAU,EAAE,WAAW;CACxB,CAAA;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAClC,QAAQ,EACR,SAAS,EACT,SAAS,EACT,MAAM,GACmB,EAAa,EAAE,CAAC,CACzC,0BAMa,SAAS,IAAI,MAAM,EAC9B,SAAS,EAAE,WAAW,CACpB,8CAA8C,EAC9C,SAAS,CACV,EACD,KAAK,EAAC,KAAK,YAEX,kBACE,SAAS,EAAE,WAAW,CACpB,mNAAmN,EACnN,4BAA4B,EAC5B,gBAAgB,CACjB,EACD,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,CACJ,SAAS,KAAK,SAAS;gBACrB,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAC9B,CAAA;QACH,CAAC,EACD,IAAI,EAAC,QAAQ,aAEZ,QAAQ,EAET,6BAIc,MAAM,EAClB,SAAS,EAAE,WAAW,CACpB,2FAA2F,EAC3F,SAAS,KAAK,SAAS,IAAI,WAAW,EACtC,SAAS,KAAK,YAAY,IAAI,YAAY,CAC3C,EACD,IAAI,EAAC,MAAM,EACX,SAAS,EAAE,KAAK,EAChB,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAE,CAAC,EACd,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,4BAA4B,YAElC,eAAM,CAAC,EAAC,eAAe,GAAG,GACtB,IACC,GACN,CACN,CAAA"}
@@ -0,0 +1,54 @@
1
+ import type { IntentName } from "@charcuterie/tokens";
2
+ import type { ReactNode } from "react";
3
+ export type ToastProps = {
4
+ /** Detail under the title. Optional — many toasts are one line. */
5
+ children?: ReactNode;
6
+ className?: string;
7
+ /**
8
+ * Milliseconds at `visible` before it starts leaving. `0` pins it
9
+ * open, which is what a toast carrying an action has to do — WCAG
10
+ * 2.2.1 gives a user the right to finish reading.
11
+ */
12
+ duration?: number;
13
+ /** How long `exiting` lasts, matched to the transition below. */
14
+ exitDuration?: number;
15
+ intent?: IntentName;
16
+ /** Called once, when the machine reaches its terminal state. */
17
+ onRemove: () => void;
18
+ /**
19
+ * A string, not a `ReactNode`. It is what the region announces and
20
+ * what names this toast's own dismiss button — a stack of three
21
+ * toasts all offering "Dismiss" is three matches for one query,
22
+ * which `expectAgentDrivable` treats as a failure.
23
+ */
24
+ title: string;
25
+ };
26
+ /**
27
+ * One toast, and its whole job is the four-state machine the plan
28
+ * specified and M2 left unbuilt.
29
+ *
30
+ * ### Why a `Status`, when a boolean and a timeout would do
31
+ *
32
+ * Because a boolean cannot express *why* the node is still mounted.
33
+ * A toast that is leaving is still painted, still occupies its slot,
34
+ * and must not be announced again — and `isVisible === false` says
35
+ * none of that. The states are what a reader needs to know the
36
+ * difference between "arriving", "leaving", and "gone but not yet
37
+ * unmounted", and `createStatus` makes the illegal ones throw:
38
+ * `visible → removed` skips the exit and is rejected, and `removed`
39
+ * is terminal so a late timer cannot remove the same toast twice.
40
+ *
41
+ * That last one is not hypothetical. Hover-to-pause plus a dismiss
42
+ * button means two independent timers can be in flight, and the
43
+ * hand-rolled version of this component reconciles them with a
44
+ * `clearTimeout` and a ref. Here the second one throws.
45
+ *
46
+ * ### It does not announce itself
47
+ *
48
+ * No `role="status"` and no `aria-live` on this element — the
49
+ * `ToastRegion` around it owns that, and a live region inside a live
50
+ * region is a message announced twice. See that component for the
51
+ * reason there is only ever one, and it is polite.
52
+ */
53
+ export declare const Toast: ({ children, className, duration, exitDuration, intent, onRemove, title, }: ToastProps) => ReactNode;
54
+ //# sourceMappingURL=Toast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../src/Toast/Toast.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAYtC,MAAM,MAAM,UAAU,GAAG;IACvB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AASD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,KAAK,GAAI,2EAQnB,UAAU,KAAG,SA+If,CAAA"}
@@ -0,0 +1,121 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useStatus } from "@charcuterie/logic";
3
+ import { useEffect, useRef } from "react";
4
+ import { IconButton } from "../IconButton/IconButton.js";
5
+ import { INTENT_APPEARANCE_CLASS, INTENT_CONTENT_CLASS, } from "../intentStyles.js";
6
+ import { toClassName } from "../toClassName.js";
7
+ import { toastTransitions } from "./toastLifecycle.js";
8
+ const ENTER_CLASS = {
9
+ entering: "translate-y-2 opacity-0",
10
+ exiting: "translate-y-1 opacity-0",
11
+ removed: "opacity-0",
12
+ visible: "translate-y-0 opacity-100",
13
+ };
14
+ /**
15
+ * One toast, and its whole job is the four-state machine the plan
16
+ * specified and M2 left unbuilt.
17
+ *
18
+ * ### Why a `Status`, when a boolean and a timeout would do
19
+ *
20
+ * Because a boolean cannot express *why* the node is still mounted.
21
+ * A toast that is leaving is still painted, still occupies its slot,
22
+ * and must not be announced again — and `isVisible === false` says
23
+ * none of that. The states are what a reader needs to know the
24
+ * difference between "arriving", "leaving", and "gone but not yet
25
+ * unmounted", and `createStatus` makes the illegal ones throw:
26
+ * `visible → removed` skips the exit and is rejected, and `removed`
27
+ * is terminal so a late timer cannot remove the same toast twice.
28
+ *
29
+ * That last one is not hypothetical. Hover-to-pause plus a dismiss
30
+ * button means two independent timers can be in flight, and the
31
+ * hand-rolled version of this component reconciles them with a
32
+ * `clearTimeout` and a ref. Here the second one throws.
33
+ *
34
+ * ### It does not announce itself
35
+ *
36
+ * No `role="status"` and no `aria-live` on this element — the
37
+ * `ToastRegion` around it owns that, and a live region inside a live
38
+ * region is a message announced twice. See that component for the
39
+ * reason there is only ever one, and it is polite.
40
+ */
41
+ export const Toast = ({ children, className, duration = 5000, exitDuration = 200, intent = "neutral", onRemove, title, }) => {
42
+ const { is, status, transitionTo } = useStatus({
43
+ initialState: "entering",
44
+ transitions: toastTransitions,
45
+ });
46
+ const onRemoveRef = useRef(onRemove);
47
+ onRemoveRef.current = onRemove;
48
+ useEffect(() => {
49
+ if (!is("entering")) {
50
+ return;
51
+ }
52
+ // A frame, not zero. The element has to be painted in its
53
+ // `entering` position before the class changes, or the browser
54
+ // coalesces both into one style recalculation and there is no
55
+ // transition to see — the single most common reason a CSS
56
+ // enter-animation "does nothing".
57
+ const frame = globalThis.requestAnimationFrame(() => {
58
+ // Guarded, because `is` and `transitionTo` are stable — this
59
+ // effect runs once, so the frame is *not* cancelled when the
60
+ // status changes underneath it. A dismiss during the enter
61
+ // frame moves the toast to `exiting`, and `exiting → visible`
62
+ // is a legal transition (it is how hover-to-pause works), so
63
+ // an unguarded write here brings a dismissed toast straight
64
+ // back and restarts its duration timer.
65
+ if (is("entering")) {
66
+ transitionTo("visible");
67
+ }
68
+ });
69
+ return () => {
70
+ globalThis.cancelAnimationFrame(frame);
71
+ };
72
+ }, [is, transitionTo]);
73
+ useEffect(() => {
74
+ if (status !== "visible" || duration === 0) {
75
+ return;
76
+ }
77
+ const timer = globalThis.setTimeout(() => {
78
+ transitionTo("exiting");
79
+ }, duration);
80
+ return () => {
81
+ globalThis.clearTimeout(timer);
82
+ };
83
+ }, [duration, status, transitionTo]);
84
+ useEffect(() => {
85
+ if (status !== "exiting") {
86
+ return;
87
+ }
88
+ const timer = globalThis.setTimeout(() => {
89
+ transitionTo("removed");
90
+ }, exitDuration);
91
+ return () => {
92
+ globalThis.clearTimeout(timer);
93
+ };
94
+ }, [exitDuration, status, transitionTo]);
95
+ useEffect(() => {
96
+ if (status === "removed") {
97
+ onRemoveRef.current();
98
+ }
99
+ }, [status]);
100
+ return (_jsxs("li", { className: toClassName("pointer-events-auto flex items-start gap-3 rounded-md border p-3 shadow-medium transition-[opacity,transform] duration-(--duration-normal) ease-standard", INTENT_APPEARANCE_CLASS[intent].soft, ENTER_CLASS[status], className),
101
+ // Pausing on hover is WCAG 2.2.1's "pause" for a moving
102
+ // deadline, and it is the reason `exiting → visible` is a
103
+ // legal transition rather than an oddity in the table.
104
+ onMouseEnter: () => {
105
+ if (status === "exiting") {
106
+ transitionTo("visible");
107
+ }
108
+ }, children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("span", { className: toClassName("font-medium text-sm", INTENT_CONTENT_CLASS[intent]), children: title }), children === undefined ? null : (_jsx("span", { className: "text-content-secondary text-xs", children: children }))] }), _jsx(IconButton, { appearance: "ghost", className: "ms-auto shrink-0",
109
+ // The name has to be distinct per toast, or a stack of three
110
+ // gives `getByRole("button", { name: "Dismiss" })` three
111
+ // matches and `expectAgentDrivable` fails the story — which
112
+ // is the point of that helper: an agent picking one of three
113
+ // at random is worse than finding none.
114
+ label: `Dismiss ${title}`, onClick: () => {
115
+ if (status !== "exiting" &&
116
+ status !== "removed") {
117
+ transitionTo("exiting");
118
+ }
119
+ }, size: "sm", children: _jsx("svg", { "aria-hidden": "true", className: "size-[1.15em] shrink-0", fill: "none", focusable: false, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.75, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) }) })] }));
120
+ };
121
+ //# sourceMappingURL=Toast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toast.js","sourceRoot":"","sources":["../../src/Toast/Toast.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAG9C,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA8B,CAAA;AACzD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AA0BtD,MAAM,WAAW,GAAgC;IAC/C,QAAQ,EAAE,yBAAyB;IACnC,OAAO,EAAE,yBAAyB;IAClC,OAAO,EAAE,WAAW;IACpB,OAAO,EAAE,2BAA2B;CACrC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,QAAQ,GAAG,IAAI,EACf,YAAY,GAAG,GAAG,EAClB,MAAM,GAAG,SAAS,EAClB,QAAQ,EACR,KAAK,GACM,EAAa,EAAE;IAC1B,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,GAChC,SAAS,CAAc;QACrB,YAAY,EAAE,UAAU;QACxB,WAAW,EAAE,gBAAgB;KAC9B,CAAC,CAAA;IAEJ,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IAEpC,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAA;IAE9B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;YACpB,OAAM;QACR,CAAC;QAED,0DAA0D;QAC1D,+DAA+D;QAC/D,8DAA8D;QAC9D,0DAA0D;QAC1D,kCAAkC;QAClC,MAAM,KAAK,GAAG,UAAU,CAAC,qBAAqB,CAAC,GAAG,EAAE;YAClD,6DAA6D;YAC7D,6DAA6D;YAC7D,2DAA2D;YAC3D,8DAA8D;YAC9D,6DAA6D;YAC7D,4DAA4D;YAC5D,wCAAwC;YACxC,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;gBACnB,YAAY,CAAC,SAAS,CAAC,CAAA;YACzB,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,OAAO,GAAG,EAAE;YACV,UAAU,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAA;QACxC,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAA;IAEtB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,SAAS,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC3C,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE;YACvC,YAAY,CAAC,SAAS,CAAC,CAAA;QACzB,CAAC,EAAE,QAAQ,CAAC,CAAA;QAEZ,OAAO,GAAG,EAAE;YACV,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;IAEpC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE;YACvC,YAAY,CAAC,SAAS,CAAC,CAAA;QACzB,CAAC,EAAE,YAAY,CAAC,CAAA;QAEhB,OAAO,GAAG,EAAE;YACV,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;IAExC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,WAAW,CAAC,OAAO,EAAE,CAAA;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,OAAO,CACL,cACE,SAAS,EAAE,WAAW,CACpB,0JAA0J,EAC1J,uBAAuB,CAAC,MAAM,CAAC,CAAC,IAAI,EACpC,WAAW,CAAC,MAAM,CAAC,EACnB,SAAS,CACV;QACD,wDAAwD;QACxD,0DAA0D;QAC1D,uDAAuD;QACvD,YAAY,EAAE,GAAG,EAAE;YACjB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,YAAY,CAAC,SAAS,CAAC,CAAA;YACzB,CAAC;QACH,CAAC,aAED,eAAK,SAAS,EAAC,+BAA+B,aAC5C,eACE,SAAS,EAAE,WAAW,CACpB,qBAAqB,EACrB,oBAAoB,CAAC,MAAM,CAAC,CAC7B,YAEA,KAAK,GACD,EAEN,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC/B,eAAM,SAAS,EAAC,gCAAgC,YAC7C,QAAQ,GACJ,CACR,IACG,EAEN,KAAC,UAAU,IACT,UAAU,EAAC,OAAO,EAClB,SAAS,EAAC,kBAAkB;gBAC5B,6DAA6D;gBAC7D,yDAAyD;gBACzD,4DAA4D;gBAC5D,6DAA6D;gBAC7D,wCAAwC;gBACxC,KAAK,EAAE,WAAW,KAAK,EAAE,EACzB,OAAO,EAAE,GAAG,EAAE;oBACZ,IACE,MAAM,KAAK,SAAS;wBACpB,MAAM,KAAK,SAAS,EACpB,CAAC;wBACD,YAAY,CAAC,SAAS,CAAC,CAAA;oBACzB,CAAC;gBACH,CAAC,EACD,IAAI,EAAC,IAAI,YAET,6BACc,MAAM,EAClB,SAAS,EAAC,wBAAwB,EAClC,IAAI,EAAC,MAAM,EACX,SAAS,EAAE,KAAK,EAChB,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAE,IAAI,EACjB,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,4BAA4B,YAElC,eAAM,CAAC,EAAC,sBAAsB,GAAG,GAC7B,GACK,IACV,CACN,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,57 @@
1
+ import type { IntentName } from "@charcuterie/tokens";
2
+ import type { ReactNode } from "react";
3
+ export type ToastRecord = {
4
+ description?: ReactNode;
5
+ /** `0` pins this one open. See `Toast`'s `duration`. */
6
+ duration?: number;
7
+ intent?: IntentName;
8
+ key: string;
9
+ title: string;
10
+ };
11
+ export type ToastRegionProps = {
12
+ className?: string;
13
+ /**
14
+ * The region's accessible name. `role="status"` takes **none from
15
+ * its content** — the trap `Spinner` walked into in M3 and
16
+ * `Popover` in M4.
17
+ */
18
+ label?: string;
19
+ onDismiss: (key: string) => void;
20
+ toasts: ToastRecord[];
21
+ };
22
+ /**
23
+ * The live region, and the list is the caller's.
24
+ *
25
+ * ### One region, and it is polite
26
+ *
27
+ * Not two. The usual arrangement — a polite region and an assertive
28
+ * one, so a danger toast interrupts — was rejected because the
29
+ * interrupting case does not belong in a toast at all. A message
30
+ * that must be heard *now* is one the user has to be able to go back
31
+ * and read, and a toast is gone in five seconds; if it matters that
32
+ * much it is an `Alert` on the page, which this library already
33
+ * ships and which stays put.
34
+ *
35
+ * That is the same line `Field` draws for its error text, and it is
36
+ * worth keeping the two consistent: **this library announces once,
37
+ * in the place the user can act on it.**
38
+ *
39
+ * ### The stack does not own the toasts
40
+ *
41
+ * `toasts` comes in and `onDismiss` goes out — there is no
42
+ * `toast()` function, no imperative queue, and no context provider.
43
+ * An app already has a store (mux-magic has Jotai, rip-deck has its
44
+ * own), and a component library that ships a second one is asking
45
+ * every consumer to keep two lists of the same notifications in
46
+ * sync. The lifecycle *inside* one toast is state a store cannot
47
+ * see, which is why `Toast` owns that and nothing more.
48
+ *
49
+ * ### `pointer-events-none` on the container, `auto` on each toast
50
+ *
51
+ * A fixed overlay across the bottom of the viewport swallows every
52
+ * click in that band, including on the app behind it, and it does so
53
+ * invisibly. The container passes clicks through and each toast
54
+ * takes them back.
55
+ */
56
+ export declare const ToastRegion: ({ className, label, onDismiss, toasts, }: ToastRegionProps) => ReactNode;
57
+ //# sourceMappingURL=ToastRegion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToastRegion.d.ts","sourceRoot":"","sources":["../../src/Toast/ToastRegion.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,WAAW,GAAI,0CAKzB,gBAAgB,KAAG,SA+BrB,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { toClassName } from "../toClassName.js";
3
+ import { Toast } from "./Toast.js";
4
+ /**
5
+ * The live region, and the list is the caller's.
6
+ *
7
+ * ### One region, and it is polite
8
+ *
9
+ * Not two. The usual arrangement — a polite region and an assertive
10
+ * one, so a danger toast interrupts — was rejected because the
11
+ * interrupting case does not belong in a toast at all. A message
12
+ * that must be heard *now* is one the user has to be able to go back
13
+ * and read, and a toast is gone in five seconds; if it matters that
14
+ * much it is an `Alert` on the page, which this library already
15
+ * ships and which stays put.
16
+ *
17
+ * That is the same line `Field` draws for its error text, and it is
18
+ * worth keeping the two consistent: **this library announces once,
19
+ * in the place the user can act on it.**
20
+ *
21
+ * ### The stack does not own the toasts
22
+ *
23
+ * `toasts` comes in and `onDismiss` goes out — there is no
24
+ * `toast()` function, no imperative queue, and no context provider.
25
+ * An app already has a store (mux-magic has Jotai, rip-deck has its
26
+ * own), and a component library that ships a second one is asking
27
+ * every consumer to keep two lists of the same notifications in
28
+ * sync. The lifecycle *inside* one toast is state a store cannot
29
+ * see, which is why `Toast` owns that and nothing more.
30
+ *
31
+ * ### `pointer-events-none` on the container, `auto` on each toast
32
+ *
33
+ * A fixed overlay across the bottom of the viewport swallows every
34
+ * click in that band, including on the app behind it, and it does so
35
+ * invisibly. The container passes clicks through and each toast
36
+ * takes them back.
37
+ */
38
+ export const ToastRegion = ({ className, label = "Notifications", onDismiss, toasts, }) => (_jsx("div", { "aria-label": label, "aria-atomic": "false", className: toClassName("pointer-events-none fixed inset-x-0 bottom-0 z-50 flex flex-col items-center gap-2 p-4", className), role: "status", children: _jsx("ul", { className: "pointer-events-none flex w-full max-w-sm list-none flex-col gap-2 p-0", children: toasts.map((toast) => (_jsx(Toast, { duration: toast.duration, intent: toast.intent, onRemove: () => {
39
+ onDismiss(toast.key);
40
+ }, title: toast.title, children: toast.description }, toast.key))) }) }));
41
+ //# sourceMappingURL=ToastRegion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToastRegion.js","sourceRoot":"","sources":["../../src/Toast/ToastRegion.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAa,CAAA;AAuBnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAC1B,SAAS,EACT,KAAK,GAAG,eAAe,EACvB,SAAS,EACT,MAAM,GACW,EAAa,EAAE,CAAC,CACjC,4BACc,KAAK,iBAML,OAAO,EACnB,SAAS,EAAE,WAAW,CACpB,wFAAwF,EACxF,SAAS,CACV,EACD,IAAI,EAAC,QAAQ,YAEb,aAAI,SAAS,EAAC,uEAAuE,YAClF,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACrB,KAAC,KAAK,IACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ,EACxB,MAAM,EAAE,KAAK,CAAC,MAAM,EAEpB,QAAQ,EAAE,GAAG,EAAE;gBACb,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACtB,CAAC,EACD,KAAK,EAAE,KAAK,CAAC,KAAK,YAEjB,KAAK,CAAC,WAAW,IANb,KAAK,CAAC,GAAG,CAOR,CACT,CAAC,GACC,GACD,CACP,CAAA"}