@graundtech/fluent2-react-kit 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/components/ui/accordion.d.ts +133 -0
- package/dist/components/ui/accordion.js +80 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/alert.d.ts +106 -0
- package/dist/components/ui/alert.js +73 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +42 -0
- package/dist/components/ui/avatar.js +55 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +36 -0
- package/dist/components/ui/badge.js +81 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +53 -0
- package/dist/components/ui/breadcrumb.js +149 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +24 -0
- package/dist/components/ui/button.js +86 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/card.d.ts +51 -0
- package/dist/components/ui/card.js +97 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +101 -0
- package/dist/components/ui/checkbox.js +57 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +212 -0
- package/dist/components/ui/combobox.js +236 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/command.d.ts +187 -0
- package/dist/components/ui/command.js +225 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +117 -0
- package/dist/components/ui/dialog.js +165 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +155 -0
- package/dist/components/ui/dropdown-menu.js +295 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/input.d.ts +44 -0
- package/dist/components/ui/input.js +43 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +39 -0
- package/dist/components/ui/label.js +30 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/link.d.ts +62 -0
- package/dist/components/ui/link.js +61 -0
- package/dist/components/ui/link.js.map +1 -0
- package/dist/components/ui/multi-select.d.ts +115 -0
- package/dist/components/ui/multi-select.js +133 -0
- package/dist/components/ui/multi-select.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +46 -0
- package/dist/components/ui/pagination.js +164 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +117 -0
- package/dist/components/ui/popover.js +62 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +98 -0
- package/dist/components/ui/progress.js +67 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +94 -0
- package/dist/components/ui/radio-group.js +65 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/select.d.ts +145 -0
- package/dist/components/ui/select.js +240 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +43 -0
- package/dist/components/ui/separator.js +27 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +81 -0
- package/dist/components/ui/skeleton.js +24 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +56 -0
- package/dist/components/ui/spinner.js +83 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/switch.d.ts +60 -0
- package/dist/components/ui/switch.js +63 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +107 -0
- package/dist/components/ui/tabs.js +102 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +28 -0
- package/dist/components/ui/textarea.js +36 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/toast.d.ts +185 -0
- package/dist/components/ui/toast.js +181 -0
- package/dist/components/ui/toast.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +115 -0
- package/dist/components/ui/tooltip.js +72 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +284 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +9 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +77 -0
- package/src/styles/tokens.css +596 -0
- package/src/styles/tokens.test.ts +216 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
function Tabs({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}) {
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
TabsPrimitive.Root,
|
|
11
|
+
{
|
|
12
|
+
"data-slot": "tabs",
|
|
13
|
+
className: cn("flex flex-col gap-2", className),
|
|
14
|
+
...props
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function TabsList({
|
|
19
|
+
className,
|
|
20
|
+
children,
|
|
21
|
+
...props
|
|
22
|
+
}) {
|
|
23
|
+
return /* @__PURE__ */ jsxs(
|
|
24
|
+
TabsPrimitive.List,
|
|
25
|
+
{
|
|
26
|
+
"data-slot": "tabs-list",
|
|
27
|
+
className: cn(
|
|
28
|
+
"relative flex items-center gap-1 border-b border-border",
|
|
29
|
+
className
|
|
30
|
+
),
|
|
31
|
+
...props,
|
|
32
|
+
children: [
|
|
33
|
+
children,
|
|
34
|
+
/* @__PURE__ */ jsx(
|
|
35
|
+
TabsPrimitive.Indicator,
|
|
36
|
+
{
|
|
37
|
+
"data-slot": "tabs-indicator",
|
|
38
|
+
className: "absolute bottom-0 left-[calc(var(--active-tab-left)+12px)] h-[3px] w-[calc(var(--active-tab-width)-24px)] rounded-full bg-primary transition-[left,width] duration-normal ease-ease"
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function TabsTrigger({
|
|
46
|
+
className,
|
|
47
|
+
...props
|
|
48
|
+
}) {
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
TabsPrimitive.Tab,
|
|
51
|
+
{
|
|
52
|
+
"data-slot": "tabs-trigger",
|
|
53
|
+
className: cn(
|
|
54
|
+
// layout
|
|
55
|
+
// py-3 + 20px line-height = Fluent's 44px Medium tab height (Figma
|
|
56
|
+
// validation pass 2, node 9116:18471).
|
|
57
|
+
"cursor-pointer px-3 py-3 text-sm font-medium whitespace-nowrap select-none",
|
|
58
|
+
// motion
|
|
59
|
+
"outline-none transition-colors duration-fast ease-ease",
|
|
60
|
+
// rest / hover — muted until active
|
|
61
|
+
"text-muted-foreground hover:text-foreground",
|
|
62
|
+
// active — Fluent foreground + semibold (divergence 2)
|
|
63
|
+
"data-[active]:text-foreground data-[active]:font-semibold",
|
|
64
|
+
// focus — generic offset-ring recipe (conventions §4)
|
|
65
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
66
|
+
// disabled — opacity-based (conventions §4). Unlike `SelectTrigger`,
|
|
67
|
+
// Base UI's `Tabs.Tab` never sets the native `disabled` attribute —
|
|
68
|
+
// it stays a focusable `<button>` per the WAI-ARIA "disabled but
|
|
69
|
+
// reachable" tab pattern and only exposes `aria-disabled`/
|
|
70
|
+
// `data-disabled` (see `TabsTab.js`), so this is `data-[disabled]:`
|
|
71
|
+
// only; a plain `disabled:` selector would never match.
|
|
72
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
73
|
+
className
|
|
74
|
+
),
|
|
75
|
+
...props
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
function TabsContent({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}) {
|
|
83
|
+
return /* @__PURE__ */ jsx(
|
|
84
|
+
TabsPrimitive.Panel,
|
|
85
|
+
{
|
|
86
|
+
"data-slot": "tabs-content",
|
|
87
|
+
className: cn(
|
|
88
|
+
"pt-4 outline-none",
|
|
89
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
90
|
+
className
|
|
91
|
+
),
|
|
92
|
+
...props
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
Tabs,
|
|
98
|
+
TabsContent,
|
|
99
|
+
TabsList,
|
|
100
|
+
TabsTrigger
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Tabs — Fluent 2-styled, shadcn-API tabs.\n *\n * ## Base UI mapping (conventions §9)\n * Roving-tabindex focus, arrow-key navigation, and (per divergence 3 below)\n * the sliding active-tab indicator are genuine behavior/layout-measurement\n * that plain markup can't express, so the four parts wrap `@base-ui/react/tabs`\n * (namespace import as `TabsPrimitive`, matching the actual\n * `export * as Tabs from \"./index.parts.js\"` shape in node_modules — the same\n * pattern `select.tsx`/`radio-group.tsx` use):\n *\n * | Exported (shadcn name) | Base UI primitive |\n * | ----------------------- | --------------------- |\n * | `Tabs` | `Tabs.Root` |\n * | `TabsList` | `Tabs.List` (+ `Tabs.Indicator` appended) |\n * | `TabsTrigger` | `Tabs.Tab` |\n * | `TabsContent` | `Tabs.Panel` |\n *\n * `\"use client\"` is required here — unlike `radio-group.tsx`, this wrapper\n * itself needs it, not just the primitive parts. `Tabs.Indicator` measures the\n * active tab's DOM layout (`getBoundingClientRect`/`offsetLeft`) inside a\n * `React.useEffect` to drive the sliding-underline CSS vars (divergence 3), so\n * the component genuinely participates in client-side behavior beyond just\n * rendering a client-boundary child — matching the reasoning that already\n * governs `select.tsx`/`checkbox.tsx` for a different trigger (icons there;\n * layout measurement here).\n *\n * ## Divergences from the shadcn/Radix Tabs API (all deliberate)\n * 1. **`TabsList` is a transparent underline bar, not a muted pill.** shadcn's\n * reference `TabsList` is a segmented control: `bg-muted` rounded box with\n * each `TabsTrigger` becoming a filled `bg-background` pill when active.\n * Fluent 2's `TabList` has no pill — it's a transparent row of labels over a\n * hairline `border-b`, with a 2px brand bar that slides under the active\n * label. This implementation follows Fluent, not shadcn's segmented look:\n * `TabsList` carries `border-b border-border` (the resting hairline) and no\n * background; `TabsTrigger` is unstyled at rest beyond text color (muted →\n * foreground on hover, foreground + semibold when active via\n * `data-[active]:`), never a filled pill.\n * 2. **Active state uses Base UI's `data-active`, not shadcn's `data-state`\n * value pair.** Radix's `Tabs.Trigger` exposes `data-state=\"active\"|\n * \"inactive\"`; Base UI's `Tabs.Tab` instead exposes `data-active` as a plain\n * presence attribute (see `TabsTabDataAttributes` in node_modules — boolean\n * presence, not a value pair, the same model `radio-group.tsx`'s\n * `data-checked` uses). Styled here via the bracketed\n * `data-[active]:` form per conventions §4.\n * 3. **The sliding indicator uses `Tabs.Indicator`'s CSS-var position API**\n * (`--active-tab-left`/`--active-tab-width`, from `TabsIndicatorCssVars` in\n * node_modules), not a custom `ref`-measured span. `Tabs.Indicator` renders\n * a `<span>` inside `TabsList` with those vars set as inline custom\n * properties (re-measured via a `ResizeObserver`-backed listener whenever\n * the active tab or list layout changes) and `hidden` until a non-zero size\n * is available. This wrapper positions the span with\n * `left-[var(--active-tab-left)] w-[var(--active-tab-width)]` and animates\n * moves with `transition-[left,width] duration-normal ease-ease` — the\n * Fluent sliding-underline motion, confirmed working. **jsdom caveat:**\n * jsdom has no layout engine, so `offsetWidth`/`getBoundingClientRect` are\n * always `0`; `Tabs.Indicator` treats that as \"not yet measured\" and stays\n * `hidden`, so the indicator element exists in the test DOM\n * (`data-slot=\"tabs-indicator\"`) but the sliding motion itself is only\n * observable in a real browser (see the Select/Dialog popup-motion note in\n * conventions §3.5 for the same class of jsdom limitation applied to\n * motion). The component test asserts the indicator renders and tracks\n * `data-activation-direction`, not the pixel position.\n * 4. **Default activation is manual (Enter/Space), not automatic-on-arrow.**\n * Radix/shadcn Tabs activates on arrow-key focus move (`automatic`). Base\n * UI's `Tabs.List` defaults `activateOnFocus` to `false` — the\n * WAI-ARIA-recommended \"manual activation\" pattern: arrow keys move focus\n * among tabs, and the focused tab is only selected on `Enter`/`Space` (or\n * click). This wrapper does not override that default, so the kit ships\n * Base UI's more accessible default rather than papering over it to match\n * Radix bit-for-bit; callers who want automatic activation can pass\n * `activateOnFocus` to `TabsList` themselves (it's forwarded straight\n * through as an ordinary prop).\n *\n * ## data-slot note\n * Every part renders a real DOM element, so every part gets a `data-slot`:\n * `tabs`, `tabs-list`, `tabs-indicator`, `tabs-trigger`, `tabs-content`.\n */\n\nfunction Tabs({\n className,\n ...props\n}: ComponentProps<typeof TabsPrimitive.Root>) {\n return (\n <TabsPrimitive.Root\n data-slot=\"tabs\"\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n />\n );\n}\n\n/**\n * List — transparent row of tab labels over a hairline bottom border, with\n * the brand sliding indicator (divergence 3) appended as the last child so it\n * paints above the hairline.\n */\nfunction TabsList({\n className,\n children,\n ...props\n}: ComponentProps<typeof TabsPrimitive.List>) {\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n className={cn(\n \"relative flex items-center gap-1 border-b border-border\",\n className\n )}\n {...props}\n >\n {children}\n <TabsPrimitive.Indicator\n data-slot=\"tabs-indicator\"\n // Fluent's Medium indicator is a 3px rounded pill inset 12px from\n // each edge of the active tab — not an edge-to-edge bar (Figma\n // validation pass 2, node 9116:18476).\n className=\"absolute bottom-0 left-[calc(var(--active-tab-left)+12px)] h-[3px] w-[calc(var(--active-tab-width)-24px)] rounded-full bg-primary transition-[left,width] duration-normal ease-ease\"\n />\n </TabsPrimitive.List>\n );\n}\n\n/**\n * Trigger — muted label at rest, brand-adjacent foreground + semibold when\n * active (divergence 2: `data-[active]:`, not shadcn's filled pill). Focus\n * uses the generic offset-ring recipe (conventions §4) — a tab is a\n * selectable control, not a typed-into field, so it doesn't get the bottom\n * brand-accent treatment `Input`/`Select` use.\n */\nfunction TabsTrigger({\n className,\n ...props\n}: ComponentProps<typeof TabsPrimitive.Tab>) {\n return (\n <TabsPrimitive.Tab\n data-slot=\"tabs-trigger\"\n className={cn(\n // layout\n // py-3 + 20px line-height = Fluent's 44px Medium tab height (Figma\n // validation pass 2, node 9116:18471).\n \"cursor-pointer px-3 py-3 text-sm font-medium whitespace-nowrap select-none\",\n // motion\n \"outline-none transition-colors duration-fast ease-ease\",\n // rest / hover — muted until active\n \"text-muted-foreground hover:text-foreground\",\n // active — Fluent foreground + semibold (divergence 2)\n \"data-[active]:text-foreground data-[active]:font-semibold\",\n // focus — generic offset-ring recipe (conventions §4)\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n // disabled — opacity-based (conventions §4). Unlike `SelectTrigger`,\n // Base UI's `Tabs.Tab` never sets the native `disabled` attribute —\n // it stays a focusable `<button>` per the WAI-ARIA \"disabled but\n // reachable\" tab pattern and only exposes `aria-disabled`/\n // `data-disabled` (see `TabsTab.js`), so this is `data-[disabled]:`\n // only; a plain `disabled:` selector would never match.\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className\n )}\n {...props}\n />\n );\n}\n\n/**\n * Content — the panel shown for the active tab. Base UI's `Tabs.Panel`\n * unmounts inactive panels from the accessibility tree (`hidden`) and only\n * the active one is focusable/rendered visible, so this wrapper is just\n * spacing + a focus ring for when a panel itself receives focus (e.g. no\n * focusable content inside it).\n */\nfunction TabsContent({\n className,\n ...props\n}: ComponentProps<typeof TabsPrimitive.Panel>) {\n return (\n <TabsPrimitive.Panel\n data-slot=\"tabs-content\"\n className={cn(\n \"pt-4 outline-none\",\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\n"],"mappings":";AA2FI,cAmBA,YAnBA;AAzFJ,SAAS,QAAQ,qBAAqB;AAGtC,SAAS,UAAU;AAiFnB,SAAS,KAAK;AAAA,EACZ;AAAA,EACA,GAAG;AACL,GAA8C;AAC5C,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,uBAAuB,SAAS;AAAA,MAC7C,GAAG;AAAA;AAAA,EACN;AAEJ;AAOA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA8C;AAC5C,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACD;AAAA,UAAC,cAAc;AAAA,UAAd;AAAA,YACC,aAAU;AAAA,YAIV,WAAU;AAAA;AAAA,QACZ;AAAA;AAAA;AAAA,EACF;AAEJ;AASA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA,GAAG;AACL,GAA6C;AAC3C,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA;AAAA;AAAA;AAAA,QAIT;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AASA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA,GAAG;AACL,GAA+C;AAC7C,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Textarea — Fluent 2-styled, shadcn-API multi-line text field.
|
|
6
|
+
*
|
|
7
|
+
* Aesthetics follow Fluent 2 (flat at rest, `rounded-md` = 4px); the prop
|
|
8
|
+
* surface is a bare `ComponentProps<"textarea">` — shadcn parity, no size
|
|
9
|
+
* variants. No `cva`/variants export: there is only one visual treatment,
|
|
10
|
+
* same reasoning as `Input`/`Separator`.
|
|
11
|
+
*
|
|
12
|
+
* Server-safe: no `"use client"`, no hooks — the React import is type-only,
|
|
13
|
+
* so the file can be dropped straight into an RSC tree.
|
|
14
|
+
*
|
|
15
|
+
* `field-sizing-content` (current shadcn/ui baseline) lets the field grow
|
|
16
|
+
* with its content up to `min-h-16`, instead of being permanently boxed to a
|
|
17
|
+
* fixed row count; the browser's native corner-drag resize handle still
|
|
18
|
+
* applies on top of that since no `resize-*` utility overrides it.
|
|
19
|
+
*
|
|
20
|
+
* Focus/invalid: identical Fluent bottom-accent recipe as `Input` (see that
|
|
21
|
+
* file for the full rationale) — an inset box-shadow underline plus
|
|
22
|
+
* `border-primary` on focus, swapping to `--destructive` when the field is
|
|
23
|
+
* both invalid and focused. Copied verbatim from `input.tsx` per
|
|
24
|
+
* conventions §4 so every field-like control in the kit behaves identically.
|
|
25
|
+
*/
|
|
26
|
+
declare function Textarea({ className, ...props }: ComponentProps<"textarea">): react.JSX.Element;
|
|
27
|
+
|
|
28
|
+
export { Textarea };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
function Textarea({ className, ...props }) {
|
|
4
|
+
return /* @__PURE__ */ jsx(
|
|
5
|
+
"textarea",
|
|
6
|
+
{
|
|
7
|
+
"data-slot": "textarea",
|
|
8
|
+
className: cn(
|
|
9
|
+
// layout — grows with content (field-sizing-content), Fluent flat field
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full min-w-0 rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
11
|
+
// placeholder / text selection
|
|
12
|
+
"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
|
|
13
|
+
// motion — color transitions plus the focus box-shadow accent below
|
|
14
|
+
"outline-none transition-[color,box-shadow] duration-fast ease-ease",
|
|
15
|
+
// disabled — opacity-based, matches conventions §4 exactly
|
|
16
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
17
|
+
// focus — Fluent bottom brand accent via inset box-shadow, no reflow.
|
|
18
|
+
// Replaces the generic ring recipe for this component (documented
|
|
19
|
+
// deviation, conventions §4).
|
|
20
|
+
"focus-visible:border-primary focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-80)] dark:focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-100)]",
|
|
21
|
+
// invalid — shadcn aria-invalid treatment, conventions §4. Declared
|
|
22
|
+
// after focus-visible so it wins the border tie when both apply; the
|
|
23
|
+
// compound aria-invalid+focus-visible rule swaps the underline accent
|
|
24
|
+
// to destructive too, so an invalid+focused field reads consistently.
|
|
25
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
26
|
+
"aria-invalid:focus-visible:shadow-[inset_0_-2px_0_0_var(--destructive)]",
|
|
27
|
+
className
|
|
28
|
+
),
|
|
29
|
+
...props
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
Textarea
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=textarea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/textarea.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Textarea — Fluent 2-styled, shadcn-API multi-line text field.\n *\n * Aesthetics follow Fluent 2 (flat at rest, `rounded-md` = 4px); the prop\n * surface is a bare `ComponentProps<\"textarea\">` — shadcn parity, no size\n * variants. No `cva`/variants export: there is only one visual treatment,\n * same reasoning as `Input`/`Separator`.\n *\n * Server-safe: no `\"use client\"`, no hooks — the React import is type-only,\n * so the file can be dropped straight into an RSC tree.\n *\n * `field-sizing-content` (current shadcn/ui baseline) lets the field grow\n * with its content up to `min-h-16`, instead of being permanently boxed to a\n * fixed row count; the browser's native corner-drag resize handle still\n * applies on top of that since no `resize-*` utility overrides it.\n *\n * Focus/invalid: identical Fluent bottom-accent recipe as `Input` (see that\n * file for the full rationale) — an inset box-shadow underline plus\n * `border-primary` on focus, swapping to `--destructive` when the field is\n * both invalid and focused. Copied verbatim from `input.tsx` per\n * conventions §4 so every field-like control in the kit behaves identically.\n */\nfunction Textarea({ className, ...props }: ComponentProps<\"textarea\">) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n // layout — grows with content (field-sizing-content), Fluent flat field\n \"flex field-sizing-content min-h-16 w-full min-w-0 rounded-md border border-input bg-background px-3 py-2 text-sm\",\n // placeholder / text selection\n \"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground\",\n // motion — color transitions plus the focus box-shadow accent below\n \"outline-none transition-[color,box-shadow] duration-fast ease-ease\",\n // disabled — opacity-based, matches conventions §4 exactly\n \"disabled:pointer-events-none disabled:opacity-50\",\n // focus — Fluent bottom brand accent via inset box-shadow, no reflow.\n // Replaces the generic ring recipe for this component (documented\n // deviation, conventions §4).\n \"focus-visible:border-primary focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-80)] dark:focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-100)]\",\n // invalid — shadcn aria-invalid treatment, conventions §4. Declared\n // after focus-visible so it wins the border tie when both apply; the\n // compound aria-invalid+focus-visible rule swaps the underline accent\n // to destructive too, so an invalid+focused field reads consistently.\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n \"aria-invalid:focus-visible:shadow-[inset_0_-2px_0_0_var(--destructive)]\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n"],"mappings":"AA4BI;AA1BJ,SAAS,UAAU;AAwBnB,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAA+B;AACrE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA;AAAA,QAET;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA;AAAA;AAAA,QAIA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ComponentProps } from 'react';
|
|
4
|
+
import { Toast as Toast$1 } from '@base-ui/react/toast';
|
|
5
|
+
import { VariantProps } from 'class-variance-authority';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Toast — Fluent 2-styled, shadcn-API toast notifications.
|
|
9
|
+
*
|
|
10
|
+
* Follows the overlay patterns set by `select.tsx`/`dialog.tsx` (portal + a
|
|
11
|
+
* floating surface, Base UI `data-starting-style`/`data-ending-style` transition
|
|
12
|
+
* hooks) and is the designated next consumer of the status-extension tokens the
|
|
13
|
+
* `alert.tsx` variants introduced: the `variant` cva mirrors Alert's treatment
|
|
14
|
+
* (`*-subtle` fills, `*-border` borders, `*-text`/status accents on the title),
|
|
15
|
+
* so a toast reads as the transient sibling of the inline Alert.
|
|
16
|
+
*
|
|
17
|
+
* ## Base UI mapping (conventions §9)
|
|
18
|
+
* Behavior — the toast queue/manager, auto-dismiss timers, priority-based
|
|
19
|
+
* live-region announcement, F6 viewport landmark, portalling, swipe-to-dismiss,
|
|
20
|
+
* and the stacking/offset CSS variables — genuinely needs a primitive, so the
|
|
21
|
+
* parts wrap `@base-ui/react/toast` (namespace import, matching the actual
|
|
22
|
+
* export shape in node_modules, exactly like `select.tsx`/`dialog.tsx`). shadcn
|
|
23
|
+
* part names are mapped onto Base UI's model:
|
|
24
|
+
*
|
|
25
|
+
* | Exported (shadcn name) | Base UI primitive |
|
|
26
|
+
* | ---------------------- | ---------------------------------------------------- |
|
|
27
|
+
* | `ToastProvider` | `Toast.Provider` |
|
|
28
|
+
* | `ToastViewport` | `Toast.Portal` + `Toast.Viewport` ← see divergence 1 |
|
|
29
|
+
* | `Toast` | `Toast.Root` + `Toast.Content` ← see divergence 2 |
|
|
30
|
+
* | `ToastTitle` | `Toast.Title` (renders `<h2>`) |
|
|
31
|
+
* | `ToastDescription` | `Toast.Description` (renders `<p>`) |
|
|
32
|
+
* | `ToastAction` | `Toast.Action` (renders `<button>`) |
|
|
33
|
+
* | `ToastClose` | `Toast.Close` (renders `<button>`, ✕ via `DismissRegular`) |
|
|
34
|
+
* | `useToast` | `Toast.useToastManager` ← see divergence 3 |
|
|
35
|
+
*
|
|
36
|
+
* ## Firing a toast — how the wrapper maps to Base UI's manager (the API)
|
|
37
|
+
* Unlike shadcn's sonner-based `toast()` global, Base UI has no
|
|
38
|
+
* render-anywhere singleton: toasts live in a manager scoped to the nearest
|
|
39
|
+
* `ToastProvider`, and each visible toast is rendered by a caller-owned map over
|
|
40
|
+
* `useToast().toasts`. The kit keeps that model honest (same call as `select.tsx`
|
|
41
|
+
* did with `SelectValue`) rather than papering a fake global over it:
|
|
42
|
+
*
|
|
43
|
+
* ```tsx
|
|
44
|
+
* function ToastList() {
|
|
45
|
+
* const { toasts } = useToast();
|
|
46
|
+
* return toasts.map((toast) => (
|
|
47
|
+
* <Toast key={toast.id} toast={toast} variant={toast.data?.variant}>
|
|
48
|
+
* <ToastTitle />
|
|
49
|
+
* <ToastDescription />
|
|
50
|
+
* <ToastClose />
|
|
51
|
+
* </Toast>
|
|
52
|
+
* ));
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* function Fire() {
|
|
56
|
+
* const { add } = useToast();
|
|
57
|
+
* return (
|
|
58
|
+
* <button onClick={() => add({
|
|
59
|
+
* title: "Saved",
|
|
60
|
+
* description: "Your changes were saved.",
|
|
61
|
+
* data: { variant: "success" },
|
|
62
|
+
* })}>Save</button>
|
|
63
|
+
* );
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* `useToast()` returns Base UI's `{ toasts, add, close, update, promise }`.
|
|
68
|
+
* `add(options)` enqueues a toast and returns its id; `options.timeout` (ms,
|
|
69
|
+
* default 5000 via the Provider, `0` disables) drives auto-dismiss;
|
|
70
|
+
* `options.priority` (`'low'` polite / `'high'` assertive) drives the live-region
|
|
71
|
+
* urgency; `options.data` carries arbitrary payload — the kit's convention is to
|
|
72
|
+
* stash the visual `variant` there and read it back in the map (`toast.data?.
|
|
73
|
+
* variant`). Empty `<ToastTitle />`/`<ToastDescription />` fall back to the
|
|
74
|
+
* toast's `title`/`description` strings automatically (Base UI behavior), so the
|
|
75
|
+
* list rarely needs children. `Toast.createToastManager()` (re-exported as
|
|
76
|
+
* `createToastManager`) builds a manager usable outside React — pass it to
|
|
77
|
+
* `<ToastProvider toastManager={…}>` to fire toasts from non-component code.
|
|
78
|
+
*
|
|
79
|
+
* ## Divergences from the shadcn/Radix Toast API (all deliberate)
|
|
80
|
+
* 1. **`ToastViewport` folds `Toast.Portal` + `Toast.Viewport`.** shadcn's
|
|
81
|
+
* viewport is a bare positioned list; Base UI portals the viewport to
|
|
82
|
+
* `document.body` (so it escapes overflow/transform ancestors) and exposes it
|
|
83
|
+
* as a separate `Portal` part. The kit composes both, matching how
|
|
84
|
+
* `SelectContent` folds `Portal` + `Positioner` + `Popup`. Render it once,
|
|
85
|
+
* inside `ToastProvider`, with your `ToastList` map as its children.
|
|
86
|
+
* 2. **`Toast` composes `Toast.Root` + `Toast.Content`; children render into the
|
|
87
|
+
* Content.** Base UI splits an individual toast into `Root` (the stacked,
|
|
88
|
+
* swipeable, height-animated box that carries the `toast` prop) and `Content`
|
|
89
|
+
* (the inner layout whose `data-behind`/`data-expanded` opacity fades the
|
|
90
|
+
* stacked-behind toasts back). Folding them keeps the shadcn-flat inner API —
|
|
91
|
+
* `<Toast><ToastTitle/><ToastDescription/><ToastClose/></Toast>` — while
|
|
92
|
+
* preserving the fade. `ToastClose` is absolutely positioned (shadcn parity),
|
|
93
|
+
* so it can be a direct child. The **required** `toast` prop is Base UI's per-
|
|
94
|
+
* toast object from the `useToast().toasts` map.
|
|
95
|
+
* 3. **`useToast` is Base UI's `useToastManager`, not shadcn's `useToast()`.**
|
|
96
|
+
* shadcn's hook returns `{ toast, dismiss, toasts }` where `toast()` is a
|
|
97
|
+
* global fire-and-forget; Base UI's returns `{ toasts, add, close, update,
|
|
98
|
+
* promise }` scoped to the Provider. The kit re-exports it under the familiar
|
|
99
|
+
* `useToast` name but keeps Base UI's shape — call `add()` (not `toast()`) to
|
|
100
|
+
* fire, and render the `toasts` array yourself (divergence 2). See the firing
|
|
101
|
+
* recipe above.
|
|
102
|
+
*
|
|
103
|
+
* ## Motion note — the toast stack animates `transform`, NOT the popup `scale`
|
|
104
|
+
* The kit's other overlays (`select`/`dialog`) forbid `transition-[transform,
|
|
105
|
+
* opacity]` and require `transition-[opacity,scale]` (conventions §3.5): their
|
|
106
|
+
* zoom rides Tailwind's `scale-*`/`translate-*` utilities, which v4 compiles to
|
|
107
|
+
* the independent `scale:`/`translate:` CSS properties, so a `transform`-based
|
|
108
|
+
* transition list would leave the zoom snapping. Toast is the **sanctioned
|
|
109
|
+
* exception**: Base UI drives the entire stack — index offset, peek, scale-back,
|
|
110
|
+
* swipe displacement, and enter/exit slide — through the `transform` *shorthand*
|
|
111
|
+
* built from its `--toast-*` CSS variables (`--toast-index`, `--toast-offset-y`,
|
|
112
|
+
* `--toast-height`, `--toast-swipe-movement-x/y`), so the recipe here is an
|
|
113
|
+
* explicit `[transform:…]` arbitrary value with a matching `[transition:transform
|
|
114
|
+
* …]` — Base UI's documented styling recipe, adapted to kit tokens. This is not
|
|
115
|
+
* a §3.5 violation: no Tailwind `scale-*`/`translate-*` utility is used, so there
|
|
116
|
+
* is no independent-property split to break. Opacity/height animate alongside in
|
|
117
|
+
* the same explicit transition list because Base UI needs three coordinated
|
|
118
|
+
* durations the token utilities can't express.
|
|
119
|
+
*
|
|
120
|
+
* ## `"use client"` — required
|
|
121
|
+
* Two reasons, same as `dialog.tsx`. The Base UI Toast parts manage client
|
|
122
|
+
* state (the manager context, timers, swipe listeners — each part module carries
|
|
123
|
+
* its own `'use client'`), and — decisively — `DismissRegular` from
|
|
124
|
+
* `@fluentui/react-icons` must stay inside a client boundary: the package's
|
|
125
|
+
* shared icon-sizing module calls `@griffel/react`'s client-only `__styles()` at
|
|
126
|
+
* module scope without its own directive, so importing an icon from a Server
|
|
127
|
+
* Component breaks `next build`. See conventions §9; `select.tsx`/`checkbox.tsx`
|
|
128
|
+
* are the precedents.
|
|
129
|
+
*
|
|
130
|
+
* ## data-slot note
|
|
131
|
+
* `ToastProvider` renders no DOM element of its own (context only), so it
|
|
132
|
+
* carries no `data-slot`. Every part that renders an element does —
|
|
133
|
+
* `toast-viewport`, `toast` (Root), `toast-content`, `toast-title`, etc.
|
|
134
|
+
*/
|
|
135
|
+
declare function ToastProvider(props: ComponentProps<typeof Toast$1.Provider>): react.JSX.Element;
|
|
136
|
+
/**
|
|
137
|
+
* Viewport — the fixed bottom-right stack region, portalled to `document.body`.
|
|
138
|
+
* `w-[22.5rem]` (matching Base UI's demo) on a comfortable viewport, collapsing
|
|
139
|
+
* to `calc(100vw-2rem)` on narrow screens; `z-50` over app content. The stacked
|
|
140
|
+
* toasts inside position themselves absolutely against this box (see `Toast`).
|
|
141
|
+
*/
|
|
142
|
+
declare function ToastViewport({ className, ...props }: ComponentProps<typeof Toast$1.Viewport>): react.JSX.Element;
|
|
143
|
+
/**
|
|
144
|
+
* Toast surface variants — mirror `alert.tsx`'s token usage exactly. `default`
|
|
145
|
+
* is the neutral floating popover surface (`bg-popover`); the four status
|
|
146
|
+
* variants reuse the status-extension tokens (`*-subtle` fill, `*-border`
|
|
147
|
+
* border) with the intent color carried by the *title* (`ToastTitle`) and the
|
|
148
|
+
* body left as `text-muted-foreground`, so status is never conveyed by
|
|
149
|
+
* body-text color alone (conventions §5). See `alert.tsx`'s doc comment for the
|
|
150
|
+
* per-variant contrast math against these same token pairs. The title accent is
|
|
151
|
+
* targeted with a descendant selector (`[&_[data-slot=toast-title]]`) because
|
|
152
|
+
* the title sits inside the folded `Content`, not as a direct Root child.
|
|
153
|
+
*/
|
|
154
|
+
declare const toastVariants: (props?: ({
|
|
155
|
+
variant?: "default" | "destructive" | "success" | "warning" | "info" | null | undefined;
|
|
156
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
157
|
+
/**
|
|
158
|
+
* Toast — one notification. Wraps `Toast.Root` (the stacked/swipeable/height-
|
|
159
|
+
* animated box carrying the required `toast` prop and the `variant` surface) and
|
|
160
|
+
* folds a `Toast.Content` inside (the inner flex layout whose
|
|
161
|
+
* `data-behind`/`data-expanded` opacity fades stacked-behind toasts). `children`
|
|
162
|
+
* (ToastTitle/Description/Action/Close) render into the Content; `ToastClose` is
|
|
163
|
+
* absolutely positioned, so `pr-10` reserves room for it.
|
|
164
|
+
*/
|
|
165
|
+
declare function Toast({ className, variant, children, ...props }: ComponentProps<typeof Toast$1.Root> & VariantProps<typeof toastVariants>): react.JSX.Element;
|
|
166
|
+
declare function ToastTitle({ className, ...props }: ComponentProps<typeof Toast$1.Title>): react.JSX.Element;
|
|
167
|
+
declare function ToastDescription({ className, ...props }: ComponentProps<typeof Toast$1.Description>): react.JSX.Element;
|
|
168
|
+
/**
|
|
169
|
+
* Action — an optional in-toast button (e.g. "Undo"). Styled as the kit's
|
|
170
|
+
* neutral outline button in miniature (conventions §4 neutral ramp), sitting
|
|
171
|
+
* left-aligned under the message. Base UI closes the toast after the action
|
|
172
|
+
* fires unless the handler prevents it.
|
|
173
|
+
*/
|
|
174
|
+
declare function ToastAction({ className, ...props }: ComponentProps<typeof Toast$1.Action>): react.JSX.Element;
|
|
175
|
+
/**
|
|
176
|
+
* Close — the icon-only ✕ dismiss button, absolutely positioned top-right (like
|
|
177
|
+
* `dialog.tsx`'s built-in close). Accessible name comes from the `sr-only` span;
|
|
178
|
+
* ✕ glyph is `DismissRegular` (which is what forces `"use client"`, §9). Base UI
|
|
179
|
+
* marks it non-swipe-triggering automatically.
|
|
180
|
+
*/
|
|
181
|
+
declare function ToastClose({ className, ...props }: ComponentProps<typeof Toast$1.Close>): react.JSX.Element;
|
|
182
|
+
declare const useToast: typeof Toast$1.useToastManager;
|
|
183
|
+
declare const createToastManager: typeof Toast$1.createToastManager;
|
|
184
|
+
|
|
185
|
+
export { Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, createToastManager, toastVariants, useToast };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Toast as ToastPrimitive } from "@base-ui/react/toast";
|
|
4
|
+
import { DismissRegular } from "@fluentui/react-icons";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
function ToastProvider(props) {
|
|
8
|
+
return /* @__PURE__ */ jsx(ToastPrimitive.Provider, { ...props });
|
|
9
|
+
}
|
|
10
|
+
function ToastViewport({
|
|
11
|
+
className,
|
|
12
|
+
...props
|
|
13
|
+
}) {
|
|
14
|
+
return /* @__PURE__ */ jsx(ToastPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
15
|
+
ToastPrimitive.Viewport,
|
|
16
|
+
{
|
|
17
|
+
"data-slot": "toast-viewport",
|
|
18
|
+
className: cn(
|
|
19
|
+
"fixed right-4 bottom-4 z-50 mx-auto flex w-[calc(100vw-2rem)] sm:right-6 sm:bottom-6 sm:w-[22.5rem]",
|
|
20
|
+
className
|
|
21
|
+
),
|
|
22
|
+
...props
|
|
23
|
+
}
|
|
24
|
+
) });
|
|
25
|
+
}
|
|
26
|
+
const toastVariants = cva(
|
|
27
|
+
[
|
|
28
|
+
// --- Base UI stacking / swipe / enter-exit recipe (adapted from the Base UI
|
|
29
|
+
// Toast docs' bottom-right demo; the --toast-* vars are Base UI's contract).
|
|
30
|
+
// See the "Motion note" above for why this uses the `transform` shorthand.
|
|
31
|
+
"[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))]",
|
|
32
|
+
"absolute right-0 bottom-0 left-auto z-[calc(1000-var(--toast-index))] mr-0 w-full origin-bottom",
|
|
33
|
+
"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))]",
|
|
34
|
+
// an invisible bridge below the toast so the pointer can move between stacked
|
|
35
|
+
// toasts without leaving the hover (Base UI recipe)
|
|
36
|
+
"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
|
|
37
|
+
"h-[var(--height)] data-expanded:h-[var(--toast-height)]",
|
|
38
|
+
"data-expanded:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))]",
|
|
39
|
+
// enter (starting) / exit (ending) slide + fade, plus the swipe-out offsets
|
|
40
|
+
"data-starting-style:[transform:translateY(150%)]",
|
|
41
|
+
"data-ending-style:opacity-0 data-limited:opacity-0",
|
|
42
|
+
"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]",
|
|
43
|
+
"data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-expanded:data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]",
|
|
44
|
+
"data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-expanded:data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
|
|
45
|
+
"data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-expanded:data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]",
|
|
46
|
+
"data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-expanded:data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
|
|
47
|
+
"[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]",
|
|
48
|
+
// --- Fluent surface: flat floating card, medium radius, flyout elevation.
|
|
49
|
+
"rounded-md border shadow-16 select-none",
|
|
50
|
+
// F6-navigable; keyboard focus ring (conventions §4)
|
|
51
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
52
|
+
],
|
|
53
|
+
{
|
|
54
|
+
variants: {
|
|
55
|
+
variant: {
|
|
56
|
+
default: "border-border bg-popover text-popover-foreground",
|
|
57
|
+
destructive: "border-destructive-border bg-destructive-subtle text-foreground [&_[data-slot=toast-title]]:text-destructive-text",
|
|
58
|
+
success: "border-success-border bg-success-subtle text-foreground [&_[data-slot=toast-title]]:text-success",
|
|
59
|
+
warning: "border-warning-border bg-warning-subtle text-foreground [&_[data-slot=toast-title]]:text-warning-text",
|
|
60
|
+
info: "border-brand-140 bg-brand-160 text-foreground [&_[data-slot=toast-title]]:text-brand-80 dark:border-brand-70 dark:bg-brand-30 dark:[&_[data-slot=toast-title]]:text-brand-100"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
defaultVariants: {
|
|
64
|
+
variant: "default"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
function Toast({
|
|
69
|
+
className,
|
|
70
|
+
variant = "default",
|
|
71
|
+
children,
|
|
72
|
+
...props
|
|
73
|
+
}) {
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
ToastPrimitive.Root,
|
|
76
|
+
{
|
|
77
|
+
"data-slot": "toast",
|
|
78
|
+
"data-variant": variant,
|
|
79
|
+
className: cn(toastVariants({ variant, className })),
|
|
80
|
+
...props,
|
|
81
|
+
children: /* @__PURE__ */ jsx(
|
|
82
|
+
ToastPrimitive.Content,
|
|
83
|
+
{
|
|
84
|
+
"data-slot": "toast-content",
|
|
85
|
+
className: cn(
|
|
86
|
+
"relative flex h-full flex-col gap-1 overflow-hidden p-4 pr-10",
|
|
87
|
+
// fade the stacked-behind content back in only when the viewport expands
|
|
88
|
+
"transition-opacity duration-normal ease-decelerate-mid data-behind:opacity-0 data-expanded:opacity-100"
|
|
89
|
+
),
|
|
90
|
+
children
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function ToastTitle({
|
|
97
|
+
className,
|
|
98
|
+
...props
|
|
99
|
+
}) {
|
|
100
|
+
return /* @__PURE__ */ jsx(
|
|
101
|
+
ToastPrimitive.Title,
|
|
102
|
+
{
|
|
103
|
+
"data-slot": "toast-title",
|
|
104
|
+
className: cn("text-sm font-semibold", className),
|
|
105
|
+
...props
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
function ToastDescription({
|
|
110
|
+
className,
|
|
111
|
+
...props
|
|
112
|
+
}) {
|
|
113
|
+
return /* @__PURE__ */ jsx(
|
|
114
|
+
ToastPrimitive.Description,
|
|
115
|
+
{
|
|
116
|
+
"data-slot": "toast-description",
|
|
117
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
118
|
+
...props
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
function ToastAction({
|
|
123
|
+
className,
|
|
124
|
+
...props
|
|
125
|
+
}) {
|
|
126
|
+
return /* @__PURE__ */ jsx(
|
|
127
|
+
ToastPrimitive.Action,
|
|
128
|
+
{
|
|
129
|
+
"data-slot": "toast-action",
|
|
130
|
+
className: cn(
|
|
131
|
+
"mt-1 inline-flex h-8 w-fit shrink-0 items-center justify-center rounded-md border border-input bg-transparent px-3 text-sm font-medium",
|
|
132
|
+
"cursor-pointer outline-none transition-colors duration-fast ease-ease",
|
|
133
|
+
"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:bg-input/30 dark:hover:bg-input/50 dark:active:bg-input/70",
|
|
134
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
135
|
+
"disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
136
|
+
className
|
|
137
|
+
),
|
|
138
|
+
...props
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function ToastClose({
|
|
143
|
+
className,
|
|
144
|
+
...props
|
|
145
|
+
}) {
|
|
146
|
+
return /* @__PURE__ */ jsxs(
|
|
147
|
+
ToastPrimitive.Close,
|
|
148
|
+
{
|
|
149
|
+
"data-slot": "toast-close",
|
|
150
|
+
className: cn(
|
|
151
|
+
"absolute top-3 right-3 flex size-7 items-center justify-center rounded-md text-muted-foreground",
|
|
152
|
+
"cursor-pointer outline-none transition-colors duration-fast ease-ease",
|
|
153
|
+
"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-accent/50 dark:active:bg-accent/70",
|
|
154
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
155
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
156
|
+
"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
157
|
+
className
|
|
158
|
+
),
|
|
159
|
+
...props,
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsx(DismissRegular, {}),
|
|
162
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const useToast = ToastPrimitive.useToastManager;
|
|
168
|
+
const createToastManager = ToastPrimitive.createToastManager;
|
|
169
|
+
export {
|
|
170
|
+
Toast,
|
|
171
|
+
ToastAction,
|
|
172
|
+
ToastClose,
|
|
173
|
+
ToastDescription,
|
|
174
|
+
ToastProvider,
|
|
175
|
+
ToastTitle,
|
|
176
|
+
ToastViewport,
|
|
177
|
+
createToastManager,
|
|
178
|
+
toastVariants,
|
|
179
|
+
useToast
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=toast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { Toast as ToastPrimitive } from \"@base-ui/react/toast\";\nimport { DismissRegular } from \"@fluentui/react-icons\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Toast — Fluent 2-styled, shadcn-API toast notifications.\n *\n * Follows the overlay patterns set by `select.tsx`/`dialog.tsx` (portal + a\n * floating surface, Base UI `data-starting-style`/`data-ending-style` transition\n * hooks) and is the designated next consumer of the status-extension tokens the\n * `alert.tsx` variants introduced: the `variant` cva mirrors Alert's treatment\n * (`*-subtle` fills, `*-border` borders, `*-text`/status accents on the title),\n * so a toast reads as the transient sibling of the inline Alert.\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — the toast queue/manager, auto-dismiss timers, priority-based\n * live-region announcement, F6 viewport landmark, portalling, swipe-to-dismiss,\n * and the stacking/offset CSS variables — genuinely needs a primitive, so the\n * parts wrap `@base-ui/react/toast` (namespace import, matching the actual\n * export shape in node_modules, exactly like `select.tsx`/`dialog.tsx`). shadcn\n * part names are mapped onto Base UI's model:\n *\n * | Exported (shadcn name) | Base UI primitive |\n * | ---------------------- | ---------------------------------------------------- |\n * | `ToastProvider` | `Toast.Provider` |\n * | `ToastViewport` | `Toast.Portal` + `Toast.Viewport` ← see divergence 1 |\n * | `Toast` | `Toast.Root` + `Toast.Content` ← see divergence 2 |\n * | `ToastTitle` | `Toast.Title` (renders `<h2>`) |\n * | `ToastDescription` | `Toast.Description` (renders `<p>`) |\n * | `ToastAction` | `Toast.Action` (renders `<button>`) |\n * | `ToastClose` | `Toast.Close` (renders `<button>`, ✕ via `DismissRegular`) |\n * | `useToast` | `Toast.useToastManager` ← see divergence 3 |\n *\n * ## Firing a toast — how the wrapper maps to Base UI's manager (the API)\n * Unlike shadcn's sonner-based `toast()` global, Base UI has no\n * render-anywhere singleton: toasts live in a manager scoped to the nearest\n * `ToastProvider`, and each visible toast is rendered by a caller-owned map over\n * `useToast().toasts`. The kit keeps that model honest (same call as `select.tsx`\n * did with `SelectValue`) rather than papering a fake global over it:\n *\n * ```tsx\n * function ToastList() {\n * const { toasts } = useToast();\n * return toasts.map((toast) => (\n * <Toast key={toast.id} toast={toast} variant={toast.data?.variant}>\n * <ToastTitle />\n * <ToastDescription />\n * <ToastClose />\n * </Toast>\n * ));\n * }\n *\n * function Fire() {\n * const { add } = useToast();\n * return (\n * <button onClick={() => add({\n * title: \"Saved\",\n * description: \"Your changes were saved.\",\n * data: { variant: \"success\" },\n * })}>Save</button>\n * );\n * }\n * ```\n *\n * `useToast()` returns Base UI's `{ toasts, add, close, update, promise }`.\n * `add(options)` enqueues a toast and returns its id; `options.timeout` (ms,\n * default 5000 via the Provider, `0` disables) drives auto-dismiss;\n * `options.priority` (`'low'` polite / `'high'` assertive) drives the live-region\n * urgency; `options.data` carries arbitrary payload — the kit's convention is to\n * stash the visual `variant` there and read it back in the map (`toast.data?.\n * variant`). Empty `<ToastTitle />`/`<ToastDescription />` fall back to the\n * toast's `title`/`description` strings automatically (Base UI behavior), so the\n * list rarely needs children. `Toast.createToastManager()` (re-exported as\n * `createToastManager`) builds a manager usable outside React — pass it to\n * `<ToastProvider toastManager={…}>` to fire toasts from non-component code.\n *\n * ## Divergences from the shadcn/Radix Toast API (all deliberate)\n * 1. **`ToastViewport` folds `Toast.Portal` + `Toast.Viewport`.** shadcn's\n * viewport is a bare positioned list; Base UI portals the viewport to\n * `document.body` (so it escapes overflow/transform ancestors) and exposes it\n * as a separate `Portal` part. The kit composes both, matching how\n * `SelectContent` folds `Portal` + `Positioner` + `Popup`. Render it once,\n * inside `ToastProvider`, with your `ToastList` map as its children.\n * 2. **`Toast` composes `Toast.Root` + `Toast.Content`; children render into the\n * Content.** Base UI splits an individual toast into `Root` (the stacked,\n * swipeable, height-animated box that carries the `toast` prop) and `Content`\n * (the inner layout whose `data-behind`/`data-expanded` opacity fades the\n * stacked-behind toasts back). Folding them keeps the shadcn-flat inner API —\n * `<Toast><ToastTitle/><ToastDescription/><ToastClose/></Toast>` — while\n * preserving the fade. `ToastClose` is absolutely positioned (shadcn parity),\n * so it can be a direct child. The **required** `toast` prop is Base UI's per-\n * toast object from the `useToast().toasts` map.\n * 3. **`useToast` is Base UI's `useToastManager`, not shadcn's `useToast()`.**\n * shadcn's hook returns `{ toast, dismiss, toasts }` where `toast()` is a\n * global fire-and-forget; Base UI's returns `{ toasts, add, close, update,\n * promise }` scoped to the Provider. The kit re-exports it under the familiar\n * `useToast` name but keeps Base UI's shape — call `add()` (not `toast()`) to\n * fire, and render the `toasts` array yourself (divergence 2). See the firing\n * recipe above.\n *\n * ## Motion note — the toast stack animates `transform`, NOT the popup `scale`\n * The kit's other overlays (`select`/`dialog`) forbid `transition-[transform,\n * opacity]` and require `transition-[opacity,scale]` (conventions §3.5): their\n * zoom rides Tailwind's `scale-*`/`translate-*` utilities, which v4 compiles to\n * the independent `scale:`/`translate:` CSS properties, so a `transform`-based\n * transition list would leave the zoom snapping. Toast is the **sanctioned\n * exception**: Base UI drives the entire stack — index offset, peek, scale-back,\n * swipe displacement, and enter/exit slide — through the `transform` *shorthand*\n * built from its `--toast-*` CSS variables (`--toast-index`, `--toast-offset-y`,\n * `--toast-height`, `--toast-swipe-movement-x/y`), so the recipe here is an\n * explicit `[transform:…]` arbitrary value with a matching `[transition:transform\n * …]` — Base UI's documented styling recipe, adapted to kit tokens. This is not\n * a §3.5 violation: no Tailwind `scale-*`/`translate-*` utility is used, so there\n * is no independent-property split to break. Opacity/height animate alongside in\n * the same explicit transition list because Base UI needs three coordinated\n * durations the token utilities can't express.\n *\n * ## `\"use client\"` — required\n * Two reasons, same as `dialog.tsx`. The Base UI Toast parts manage client\n * state (the manager context, timers, swipe listeners — each part module carries\n * its own `'use client'`), and — decisively — `DismissRegular` from\n * `@fluentui/react-icons` must stay inside a client boundary: the package's\n * shared icon-sizing module calls `@griffel/react`'s client-only `__styles()` at\n * module scope without its own directive, so importing an icon from a Server\n * Component breaks `next build`. See conventions §9; `select.tsx`/`checkbox.tsx`\n * are the precedents.\n *\n * ## data-slot note\n * `ToastProvider` renders no DOM element of its own (context only), so it\n * carries no `data-slot`. Every part that renders an element does —\n * `toast-viewport`, `toast` (Root), `toast-content`, `toast-title`, etc.\n */\n\nfunction ToastProvider(props: ComponentProps<typeof ToastPrimitive.Provider>) {\n return <ToastPrimitive.Provider {...props} />;\n}\n\n/**\n * Viewport — the fixed bottom-right stack region, portalled to `document.body`.\n * `w-[22.5rem]` (matching Base UI's demo) on a comfortable viewport, collapsing\n * to `calc(100vw-2rem)` on narrow screens; `z-50` over app content. The stacked\n * toasts inside position themselves absolutely against this box (see `Toast`).\n */\nfunction ToastViewport({\n className,\n ...props\n}: ComponentProps<typeof ToastPrimitive.Viewport>) {\n return (\n <ToastPrimitive.Portal>\n <ToastPrimitive.Viewport\n data-slot=\"toast-viewport\"\n className={cn(\n \"fixed right-4 bottom-4 z-50 mx-auto flex w-[calc(100vw-2rem)] sm:right-6 sm:bottom-6 sm:w-[22.5rem]\",\n className\n )}\n {...props}\n />\n </ToastPrimitive.Portal>\n );\n}\n\n/**\n * Toast surface variants — mirror `alert.tsx`'s token usage exactly. `default`\n * is the neutral floating popover surface (`bg-popover`); the four status\n * variants reuse the status-extension tokens (`*-subtle` fill, `*-border`\n * border) with the intent color carried by the *title* (`ToastTitle`) and the\n * body left as `text-muted-foreground`, so status is never conveyed by\n * body-text color alone (conventions §5). See `alert.tsx`'s doc comment for the\n * per-variant contrast math against these same token pairs. The title accent is\n * targeted with a descendant selector (`[&_[data-slot=toast-title]]`) because\n * the title sits inside the folded `Content`, not as a direct Root child.\n */\nconst toastVariants = cva(\n [\n // --- Base UI stacking / swipe / enter-exit recipe (adapted from the Base UI\n // Toast docs' bottom-right demo; the --toast-* vars are Base UI's contract).\n // See the \"Motion note\" above for why this uses the `transform` shorthand.\n \"[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))]\",\n \"absolute right-0 bottom-0 left-auto z-[calc(1000-var(--toast-index))] mr-0 w-full origin-bottom\",\n \"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))]\",\n // an invisible bridge below the toast so the pointer can move between stacked\n // toasts without leaving the hover (Base UI recipe)\n \"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']\",\n \"h-[var(--height)] data-expanded:h-[var(--toast-height)]\",\n \"data-expanded:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))]\",\n // enter (starting) / exit (ending) slide + fade, plus the swipe-out offsets\n \"data-starting-style:[transform:translateY(150%)]\",\n \"data-ending-style:opacity-0 data-limited:opacity-0\",\n \"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]\",\n \"data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-expanded:data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]\",\n \"data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-expanded:data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]\",\n \"data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-expanded:data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]\",\n \"data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-expanded:data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]\",\n \"[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]\",\n // --- Fluent surface: flat floating card, medium radius, flyout elevation.\n \"rounded-md border shadow-16 select-none\",\n // F6-navigable; keyboard focus ring (conventions §4)\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n ],\n {\n variants: {\n variant: {\n default: \"border-border bg-popover text-popover-foreground\",\n destructive:\n \"border-destructive-border bg-destructive-subtle text-foreground [&_[data-slot=toast-title]]:text-destructive-text\",\n success:\n \"border-success-border bg-success-subtle text-foreground [&_[data-slot=toast-title]]:text-success\",\n warning:\n \"border-warning-border bg-warning-subtle text-foreground [&_[data-slot=toast-title]]:text-warning-text\",\n info: \"border-brand-140 bg-brand-160 text-foreground [&_[data-slot=toast-title]]:text-brand-80 dark:border-brand-70 dark:bg-brand-30 dark:[&_[data-slot=toast-title]]:text-brand-100\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\n/**\n * Toast — one notification. Wraps `Toast.Root` (the stacked/swipeable/height-\n * animated box carrying the required `toast` prop and the `variant` surface) and\n * folds a `Toast.Content` inside (the inner flex layout whose\n * `data-behind`/`data-expanded` opacity fades stacked-behind toasts). `children`\n * (ToastTitle/Description/Action/Close) render into the Content; `ToastClose` is\n * absolutely positioned, so `pr-10` reserves room for it.\n */\nfunction Toast({\n className,\n variant = \"default\",\n children,\n ...props\n}: ComponentProps<typeof ToastPrimitive.Root> &\n VariantProps<typeof toastVariants>) {\n return (\n <ToastPrimitive.Root\n data-slot=\"toast\"\n data-variant={variant}\n className={cn(toastVariants({ variant, className }))}\n {...props}\n >\n <ToastPrimitive.Content\n data-slot=\"toast-content\"\n className={cn(\n \"relative flex h-full flex-col gap-1 overflow-hidden p-4 pr-10\",\n // fade the stacked-behind content back in only when the viewport expands\n \"transition-opacity duration-normal ease-decelerate-mid data-behind:opacity-0 data-expanded:opacity-100\"\n )}\n >\n {children}\n </ToastPrimitive.Content>\n </ToastPrimitive.Root>\n );\n}\n\nfunction ToastTitle({\n className,\n ...props\n}: ComponentProps<typeof ToastPrimitive.Title>) {\n return (\n <ToastPrimitive.Title\n data-slot=\"toast-title\"\n className={cn(\"text-sm font-semibold\", className)}\n {...props}\n />\n );\n}\n\nfunction ToastDescription({\n className,\n ...props\n}: ComponentProps<typeof ToastPrimitive.Description>) {\n return (\n <ToastPrimitive.Description\n data-slot=\"toast-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Action — an optional in-toast button (e.g. \"Undo\"). Styled as the kit's\n * neutral outline button in miniature (conventions §4 neutral ramp), sitting\n * left-aligned under the message. Base UI closes the toast after the action\n * fires unless the handler prevents it.\n */\nfunction ToastAction({\n className,\n ...props\n}: ComponentProps<typeof ToastPrimitive.Action>) {\n return (\n <ToastPrimitive.Action\n data-slot=\"toast-action\"\n className={cn(\n \"mt-1 inline-flex h-8 w-fit shrink-0 items-center justify-center rounded-md border border-input bg-transparent px-3 text-sm font-medium\",\n \"cursor-pointer outline-none transition-colors duration-fast ease-ease\",\n \"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:bg-input/30 dark:hover:bg-input/50 dark:active:bg-input/70\",\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50\",\n className\n )}\n {...props}\n />\n );\n}\n\n/**\n * Close — the icon-only ✕ dismiss button, absolutely positioned top-right (like\n * `dialog.tsx`'s built-in close). Accessible name comes from the `sr-only` span;\n * ✕ glyph is `DismissRegular` (which is what forces `\"use client\"`, §9). Base UI\n * marks it non-swipe-triggering automatically.\n */\nfunction ToastClose({\n className,\n ...props\n}: ComponentProps<typeof ToastPrimitive.Close>) {\n return (\n <ToastPrimitive.Close\n data-slot=\"toast-close\"\n className={cn(\n \"absolute top-3 right-3 flex size-7 items-center justify-center rounded-md text-muted-foreground\",\n \"cursor-pointer outline-none transition-colors duration-fast ease-ease\",\n \"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-accent/50 dark:active:bg-accent/70\",\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <DismissRegular />\n <span className=\"sr-only\">Close</span>\n </ToastPrimitive.Close>\n );\n}\n\nconst useToast = ToastPrimitive.useToastManager;\nconst createToastManager = ToastPrimitive.createToastManager;\n\nexport {\n Toast,\n ToastProvider,\n ToastViewport,\n ToastTitle,\n ToastDescription,\n ToastAction,\n ToastClose,\n useToast,\n createToastManager,\n toastVariants,\n};\n"],"mappings":";AA2IS,cAuLL,YAvLK;AAzIT,SAAS,SAAS,sBAAsB;AACxC,SAAS,sBAAsB;AAC/B,SAAS,WAA8B;AAGvC,SAAS,UAAU;AAmInB,SAAS,cAAc,OAAuD;AAC5E,SAAO,oBAAC,eAAe,UAAf,EAAyB,GAAG,OAAO;AAC7C;AAQA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA,GAAG;AACL,GAAmD;AACjD,SACE,oBAAC,eAAe,QAAf,EACC;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAaA,MAAM,gBAAgB;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,IAIE;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,QACF,SACE;AAAA,QACF,SACE;AAAA,QACF,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAUA,SAAS,MAAM;AAAA,EACb;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GACsC;AACpC,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,cAAc,EAAE,SAAS,UAAU,CAAC,CAAC;AAAA,MAClD,GAAG;AAAA,MAEJ;AAAA,QAAC,eAAe;AAAA,QAAf;AAAA,UACC,aAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA;AAAA,YAEA;AAAA,UACF;AAAA,UAEC;AAAA;AAAA,MACH;AAAA;AAAA,EACF;AAEJ;AAEA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,yBAAyB,SAAS;AAAA,MAC/C,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,iBAAiB;AAAA,EACxB;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;AAQA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAQA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,kBAAe;AAAA,QAChB,oBAAC,UAAK,WAAU,WAAU,mBAAK;AAAA;AAAA;AAAA,EACjC;AAEJ;AAEA,MAAM,WAAW,eAAe;AAChC,MAAM,qBAAqB,eAAe;","names":[]}
|