@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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/components/ui/accordion.d.ts +133 -0
  4. package/dist/components/ui/accordion.js +80 -0
  5. package/dist/components/ui/accordion.js.map +1 -0
  6. package/dist/components/ui/alert.d.ts +106 -0
  7. package/dist/components/ui/alert.js +73 -0
  8. package/dist/components/ui/alert.js.map +1 -0
  9. package/dist/components/ui/avatar.d.ts +42 -0
  10. package/dist/components/ui/avatar.js +55 -0
  11. package/dist/components/ui/avatar.js.map +1 -0
  12. package/dist/components/ui/badge.d.ts +36 -0
  13. package/dist/components/ui/badge.js +81 -0
  14. package/dist/components/ui/badge.js.map +1 -0
  15. package/dist/components/ui/breadcrumb.d.ts +53 -0
  16. package/dist/components/ui/breadcrumb.js +149 -0
  17. package/dist/components/ui/breadcrumb.js.map +1 -0
  18. package/dist/components/ui/button.d.ts +24 -0
  19. package/dist/components/ui/button.js +86 -0
  20. package/dist/components/ui/button.js.map +1 -0
  21. package/dist/components/ui/card.d.ts +51 -0
  22. package/dist/components/ui/card.js +97 -0
  23. package/dist/components/ui/card.js.map +1 -0
  24. package/dist/components/ui/checkbox.d.ts +101 -0
  25. package/dist/components/ui/checkbox.js +57 -0
  26. package/dist/components/ui/checkbox.js.map +1 -0
  27. package/dist/components/ui/combobox.d.ts +212 -0
  28. package/dist/components/ui/combobox.js +236 -0
  29. package/dist/components/ui/combobox.js.map +1 -0
  30. package/dist/components/ui/command.d.ts +187 -0
  31. package/dist/components/ui/command.js +225 -0
  32. package/dist/components/ui/command.js.map +1 -0
  33. package/dist/components/ui/dialog.d.ts +117 -0
  34. package/dist/components/ui/dialog.js +165 -0
  35. package/dist/components/ui/dialog.js.map +1 -0
  36. package/dist/components/ui/dropdown-menu.d.ts +155 -0
  37. package/dist/components/ui/dropdown-menu.js +295 -0
  38. package/dist/components/ui/dropdown-menu.js.map +1 -0
  39. package/dist/components/ui/input.d.ts +44 -0
  40. package/dist/components/ui/input.js +43 -0
  41. package/dist/components/ui/input.js.map +1 -0
  42. package/dist/components/ui/label.d.ts +39 -0
  43. package/dist/components/ui/label.js +30 -0
  44. package/dist/components/ui/label.js.map +1 -0
  45. package/dist/components/ui/link.d.ts +62 -0
  46. package/dist/components/ui/link.js +61 -0
  47. package/dist/components/ui/link.js.map +1 -0
  48. package/dist/components/ui/multi-select.d.ts +115 -0
  49. package/dist/components/ui/multi-select.js +133 -0
  50. package/dist/components/ui/multi-select.js.map +1 -0
  51. package/dist/components/ui/pagination.d.ts +46 -0
  52. package/dist/components/ui/pagination.js +164 -0
  53. package/dist/components/ui/pagination.js.map +1 -0
  54. package/dist/components/ui/popover.d.ts +117 -0
  55. package/dist/components/ui/popover.js +62 -0
  56. package/dist/components/ui/popover.js.map +1 -0
  57. package/dist/components/ui/progress.d.ts +98 -0
  58. package/dist/components/ui/progress.js +67 -0
  59. package/dist/components/ui/progress.js.map +1 -0
  60. package/dist/components/ui/radio-group.d.ts +94 -0
  61. package/dist/components/ui/radio-group.js +65 -0
  62. package/dist/components/ui/radio-group.js.map +1 -0
  63. package/dist/components/ui/select.d.ts +145 -0
  64. package/dist/components/ui/select.js +240 -0
  65. package/dist/components/ui/select.js.map +1 -0
  66. package/dist/components/ui/separator.d.ts +43 -0
  67. package/dist/components/ui/separator.js +27 -0
  68. package/dist/components/ui/separator.js.map +1 -0
  69. package/dist/components/ui/skeleton.d.ts +81 -0
  70. package/dist/components/ui/skeleton.js +24 -0
  71. package/dist/components/ui/skeleton.js.map +1 -0
  72. package/dist/components/ui/spinner.d.ts +56 -0
  73. package/dist/components/ui/spinner.js +83 -0
  74. package/dist/components/ui/spinner.js.map +1 -0
  75. package/dist/components/ui/switch.d.ts +60 -0
  76. package/dist/components/ui/switch.js +63 -0
  77. package/dist/components/ui/switch.js.map +1 -0
  78. package/dist/components/ui/tabs.d.ts +107 -0
  79. package/dist/components/ui/tabs.js +102 -0
  80. package/dist/components/ui/tabs.js.map +1 -0
  81. package/dist/components/ui/textarea.d.ts +28 -0
  82. package/dist/components/ui/textarea.js +36 -0
  83. package/dist/components/ui/textarea.js.map +1 -0
  84. package/dist/components/ui/toast.d.ts +185 -0
  85. package/dist/components/ui/toast.js +181 -0
  86. package/dist/components/ui/toast.js.map +1 -0
  87. package/dist/components/ui/tooltip.d.ts +115 -0
  88. package/dist/components/ui/tooltip.js +72 -0
  89. package/dist/components/ui/tooltip.js.map +1 -0
  90. package/dist/index.d.ts +55 -0
  91. package/dist/index.js +284 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/lib/utils.d.ts +9 -0
  94. package/dist/lib/utils.js +9 -0
  95. package/dist/lib/utils.js.map +1 -0
  96. package/package.json +77 -0
  97. package/src/styles/tokens.css +596 -0
  98. package/src/styles/tokens.test.ts +216 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Graund Tech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @graundtech/fluent2-react-kit
2
+
3
+ Fluent 2-inspired React components with shadcn/ui-style APIs — plain function components, `cva` variants, `asChild` polymorphism, the prop names (`variant`, `size`, …) shadcn/ui users already know. Built on [Base UI](https://base-ui.com) primitives, Tailwind CSS v4, and CSS variables.
4
+
5
+ - **Demo & docs:** <https://fluent2-react-kit.graund.io>
6
+ - **Source & registry:** <https://github.com/graundtech/fluent2-react-kit>
7
+
8
+ > **Prefer owning the source?** Every component is also a [shadcn registry](https://ui.shadcn.com/docs/registry) item — `npx shadcn add` copies the code straight into your project, no runtime dependency. See the [repo README](https://github.com/graundtech/fluent2-react-kit#readme) for the registry workflow. This package is the traditional alternative: a versioned dependency with managed updates.
9
+
10
+ ## Requirements
11
+
12
+ - React ≥ 18
13
+ - Tailwind CSS v4 (the components are styled with Tailwind utilities driven by the kit's design tokens)
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @graundtech/fluent2-react-kit
19
+ ```
20
+
21
+ ## Set up the styles
22
+
23
+ Add the token stylesheet and point Tailwind at the package in your global CSS — all three lines matter:
24
+
25
+ ```css
26
+ /* globals.css */
27
+ @import "tailwindcss";
28
+ @import "@graundtech/fluent2-react-kit/tokens.css";
29
+ @source "../node_modules/@graundtech/fluent2-react-kit/dist";
30
+ ```
31
+
32
+ - `tokens.css` carries the full Fluent 2 token system (light, dark, and high-contrast themes) expressed in the shadcn/ui CSS-variable contract, plus the Tailwind v4 `@theme` bridge. It must come **after** `@import "tailwindcss"`.
33
+ - `@source` is required because Tailwind v4 doesn't scan `node_modules` — without it the components render unstyled. The path is relative to your CSS file; adjust the `../` depth to match where it lives (e.g. `../../node_modules/...` from `src/app/globals.css`).
34
+
35
+ Dark mode follows the shadcn convention: add `.dark` to any ancestor (usually `<html>`).
36
+
37
+ ## Use
38
+
39
+ ```tsx
40
+ import { Button, Card, CardContent, CardHeader, CardTitle } from "@graundtech/fluent2-react-kit";
41
+
42
+ export function Example() {
43
+ return (
44
+ <Card>
45
+ <CardHeader>
46
+ <CardTitle>Hello Fluent</CardTitle>
47
+ </CardHeader>
48
+ <CardContent>
49
+ <Button>Get started</Button>
50
+ </CardContent>
51
+ </Card>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ## Server Components
57
+
58
+ The package ships per-module `"use client"` directives, mirroring how the components are authored:
59
+
60
+ - Interactive components (`Dialog`, `Combobox`, `DropdownMenu`, `Select`, `Tabs`, `Toast`, …) are Client Components and just work in a Next.js App Router tree.
61
+ - Presentational components (`Button`, `Card`, `Badge`, `Breadcrumb`, `Alert`, …) carry no directive and stay Server Component-safe — including calling their `cva` variant helpers (`buttonVariants(...)`, `badgeVariants(...)`) from server code.
62
+
63
+ ## What's inside
64
+
65
+ 29 components (`Accordion`, `Alert`, `Avatar`, `Badge`, `Breadcrumb`, `Button`, `Card`, `Checkbox`, `Combobox`, `Command`, `Dialog`, `DropdownMenu`, `Input`, `Label`, `Link`, `MultiSelect`, `Pagination`, `Popover`, `Progress`, `RadioGroup`, `Select`, `Separator`, `Skeleton`, `Spinner`, `Switch`, `Tabs`, `Textarea`, `Toast`, `Tooltip`), the `cn()` helper, and the token stylesheet. The full component matrix with per-component notes lives in the [repo README](https://github.com/graundtech/fluent2-react-kit#component-status).
66
+
67
+ ## License
68
+
69
+ [MIT](https://github.com/graundtech/fluent2-react-kit/blob/main/LICENSE)
@@ -0,0 +1,133 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps } from 'react';
3
+ import { Accordion as Accordion$1 } from '@base-ui/react/accordion';
4
+
5
+ /**
6
+ * Accordion — Fluent 2-styled, shadcn-API accordion (expand/collapse groups).
7
+ *
8
+ * ## Base UI mapping (conventions §9)
9
+ * Behavior — open/close state, focus-safe disabled triggers, and a
10
+ * height-animated collapsible panel — genuinely needs a primitive, so the
11
+ * parts wrap `@base-ui/react/accordion` (namespace import, matching the
12
+ * actual `export * as Accordion from "./index.parts.js"` shape in
13
+ * node_modules, same pattern as `select.tsx`/`checkbox.tsx`). shadcn part
14
+ * names map onto Base UI's model:
15
+ *
16
+ * | Exported (shadcn name) | Base UI primitive |
17
+ * | ----------------------- | ------------------------ |
18
+ * | `Accordion` | `Accordion.Root` |
19
+ * | `AccordionItem` | `Accordion.Item` |
20
+ * | `AccordionTrigger` | `Accordion.Header` (h3) + `Accordion.Trigger` (button), composed like shadcn |
21
+ * | `AccordionContent` | `Accordion.Panel` |
22
+ *
23
+ * ## Divergences from the shadcn/Radix Accordion API (all deliberate)
24
+ * 1. **No `type="single" | "multiple"` prop.** Radix's Accordion takes a
25
+ * `type` discriminant that also changes the shape of `value`
26
+ * (`string | undefined` for `"single"`, `string[]` for `"multiple"`).
27
+ * Base UI instead has a single `multiple?: boolean` prop (default
28
+ * `false`) and *always* represents the open set as an array
29
+ * (`AccordionValue<Value> = Value[]`), even in single mode (an array of
30
+ * zero or one items). This wrapper does not paper over that with a fake
31
+ * `type` shim — `<Accordion>` passes `AccordionPrimitive.Root.Props`
32
+ * straight through, so callers use `multiple` and read/write `value`/
33
+ * `defaultValue`/`onValueChange` as arrays. `multiple=false` (the
34
+ * default) still enforces "opening one closes the others" the same way
35
+ * Radix's `type="single"` does — Base UI's root closes any previously
36
+ * open item when a new one opens and `multiple` is false.
37
+ * 2. **`AccordionItem` takes `value` directly** (not wrapped/renamed) — same
38
+ * prop name as Base UI, and it auto-generates a stable id if omitted, so
39
+ * `value` is optional here (unlike Radix, where an uncontrolled
40
+ * `type="single"` item still needs an explicit `value` to open by
41
+ * default).
42
+ *
43
+ * ## Chevron rotation
44
+ * `AccordionTrigger` carries Base UI's own `data-panel-open` presence
45
+ * attribute (from `AccordionTriggerDataAttributes.panelOpen`, verified
46
+ * against the compiled source — *not* `data-open`, which lives on
47
+ * `Accordion.Item`/`Accordion.Panel` instead) when its panel is open. The
48
+ * chevron is a child of the trigger button, so the trigger carries `group`
49
+ * and the chevron targets `group-data-[panel-open]:rotate-180` (conventions
50
+ * §4's bracketed data-attribute form) — the bracket form works on ancestor
51
+ * *and* self, `group-` just relays the parent's attribute to a descendant
52
+ * selector.
53
+ *
54
+ * ## Height animation
55
+ * `Accordion.Panel` measures its own content and exposes it as two CSS
56
+ * custom properties set as inline styles on the panel element itself:
57
+ * `--accordion-panel-height` and `--accordion-panel-width` (verified against
58
+ * `AccordionPanelCssVars` in the compiled source — during open/close these
59
+ * update live as Base UI's `ResizeObserver` re-measures). `AccordionContent`
60
+ * reads `--accordion-panel-height` back via `h-[var(--accordion-panel-height)]`
61
+ * on the outer, `overflow-hidden` panel and transitions `height` (never
62
+ * `transition-transform`/`scale`/`translate` — height is the property that
63
+ * actually changes here, conventions §3.5's rule generalized) with token
64
+ * durations/easings: `duration-normal ease-decelerate-mid` entering,
65
+ * `ease-accelerate-mid` (via `data-ending-style:ease-accelerate-mid`, which
66
+ * wins by source order) exiting. `data-starting-style`/`data-ending-style`
67
+ * (Base UI's enter/exit hooks, already used by `select.tsx`'s popup) pin the
68
+ * height to `0` at both animation boundaries, so the transition always
69
+ * animates *from* `0` *to* the measured height (open) or the reverse
70
+ * (close) — this is Base UI's own documented pattern for this component,
71
+ * reproduced with the kit's tokens instead of raw CSS. Padding lives on an
72
+ * *inner* `div` (not the animated panel itself) so the padding doesn't
73
+ * distort the measured/animated height — the same inner-wrapper split
74
+ * shadcn's own Radix-based `AccordionContent` uses. `AccordionPanel`
75
+ * unmounts while closed by default (`keepMounted` is not forced on); Base
76
+ * UI's own transition-status tracking (mirrors Radix `Presence`) keeps it
77
+ * mounted for the *closing* animation without any extra prop here.
78
+ *
79
+ * ## `"use client"` — required (conventions §9)
80
+ * Every Base UI Accordion part module carries its own `'use client'`
81
+ * directive, so on that basis alone this wrapper could stay
82
+ * server-renderable (same reasoning as `avatar.tsx`). But
83
+ * `ChevronDownRegular` from `@fluentui/react-icons` breaks that: the
84
+ * package's shared icon-sizing module (`createFluentIcon.styles.js`) calls
85
+ * `@griffel/react`'s `__styles()` at module scope *without* its own
86
+ * `'use client'` directive, even though `__styles` itself is client-only.
87
+ * Rendering the icon from a Server Component pulls that module into the
88
+ * server's RSC graph, and `next build` (Turbopack) fails collecting page
89
+ * data with "Attempted to call __styles() from the server but __styles is
90
+ * on the client" — reproduced against `@fluentui/react-icons@2.0.333` /
91
+ * `@griffel/react@1.7.5`, and confirmed to affect every route sharing
92
+ * Turbopack's chunk for this icon, not just this one. `"use client"` here
93
+ * keeps the icon import inside a client boundary so it's never evaluated on
94
+ * the server. `select.tsx` and `checkbox.tsx` carry the same fix for the
95
+ * same reason.
96
+ *
97
+ * ## Keyboard behavior note
98
+ * Base UI's Accordion no longer implements roving-tabindex arrow-key
99
+ * navigation between triggers — `AccordionRoot.Props.orientation` is
100
+ * explicitly documented (and typed) as `@deprecated`, "following the APG
101
+ * guidance update to remove the roving focus pattern" for accordions, and
102
+ * the compiled `AccordionTrigger` has no `onKeyDown` handler beyond the
103
+ * generic focusable-when-disabled Tab guard — only `onClick`. So each
104
+ * trigger is a normal Tab stop (Enter/Space activate it, matching any
105
+ * `<button>`); ArrowUp/ArrowDown do **not** move focus between triggers in
106
+ * this version, and the test file asserts that actual (non-roving)
107
+ * behavior rather than the older Radix pattern.
108
+ */
109
+ declare function Accordion<Value = unknown>(props: Accordion$1.Root.Props<Value>): react.JSX.Element;
110
+ /**
111
+ * Item — groups one trigger with its panel. `border-b` per shadcn (every
112
+ * item but the last is visually separated by the next item's top-less
113
+ * border — the whole accordion typically sits inside a container that adds
114
+ * the top border, matching shadcn's own layout).
115
+ */
116
+ declare function AccordionItem({ className, ...props }: ComponentProps<typeof Accordion$1.Item>): react.JSX.Element;
117
+ /**
118
+ * Trigger — `Accordion.Header` (renders `<h3>`, the heading level shadcn
119
+ * uses) wrapping `Accordion.Trigger` (the actual `<button>`), exactly like
120
+ * shadcn's Radix-based composition. The chevron rotates via
121
+ * `group-data-[panel-open]:rotate-180` — see the component doc comment for
122
+ * why the attribute lives on the trigger rather than the icon itself.
123
+ */
124
+ declare function AccordionTrigger({ className, children, ...props }: ComponentProps<typeof Accordion$1.Trigger>): react.JSX.Element;
125
+ /**
126
+ * Content — the collapsible panel. See the component doc comment's "Height
127
+ * animation" section for the exact mechanism. `role="region"` and
128
+ * `aria-labelledby` (pointing at the trigger) are set by Base UI itself, not
129
+ * reimplemented here.
130
+ */
131
+ declare function AccordionContent({ className, children, ...props }: ComponentProps<typeof Accordion$1.Panel>): react.JSX.Element;
132
+
133
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
4
+ import { ChevronDownRegular } from "@fluentui/react-icons";
5
+ import { cn } from "../../lib/utils.js";
6
+ function Accordion(props) {
7
+ return /* @__PURE__ */ jsx(AccordionPrimitive.Root, { "data-slot": "accordion", ...props });
8
+ }
9
+ function AccordionItem({
10
+ className,
11
+ ...props
12
+ }) {
13
+ return /* @__PURE__ */ jsx(
14
+ AccordionPrimitive.Item,
15
+ {
16
+ "data-slot": "accordion-item",
17
+ className: cn("border-b border-border last:border-b-0", className),
18
+ ...props
19
+ }
20
+ );
21
+ }
22
+ function AccordionTrigger({
23
+ className,
24
+ children,
25
+ ...props
26
+ }) {
27
+ return /* @__PURE__ */ jsx(AccordionPrimitive.Header, { "data-slot": "accordion-header", className: "flex", children: /* @__PURE__ */ jsxs(
28
+ AccordionPrimitive.Trigger,
29
+ {
30
+ "data-slot": "accordion-trigger",
31
+ className: cn(
32
+ // py-3 + 20px line-height = Fluent's 44px Medium header; title is
33
+ // Body 1 Regular (400), not Medium — same fix class as Label (M6).
34
+ // (Figma validation pass 2, nodes 9074:915/9074:921.)
35
+ "group flex flex-1 items-center justify-between gap-2 py-3 text-left text-sm font-normal outline-none",
36
+ "transition-colors duration-fast ease-ease hover:underline",
37
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
38
+ "disabled:pointer-events-none disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
39
+ className
40
+ ),
41
+ ...props,
42
+ children: [
43
+ children,
44
+ /* @__PURE__ */ jsx(
45
+ ChevronDownRegular,
46
+ {
47
+ "aria-hidden": "true",
48
+ className: "size-4 shrink-0 text-muted-foreground transition-transform duration-normal ease-ease group-data-[panel-open]:rotate-180"
49
+ }
50
+ )
51
+ ]
52
+ }
53
+ ) });
54
+ }
55
+ function AccordionContent({
56
+ className,
57
+ children,
58
+ ...props
59
+ }) {
60
+ return /* @__PURE__ */ jsx(
61
+ AccordionPrimitive.Panel,
62
+ {
63
+ "data-slot": "accordion-content",
64
+ className: cn(
65
+ "h-[var(--accordion-panel-height)] overflow-hidden text-sm",
66
+ "transition-[height] duration-normal ease-decelerate-mid",
67
+ "data-starting-style:h-0 data-ending-style:h-0 data-ending-style:ease-accelerate-mid"
68
+ ),
69
+ ...props,
70
+ children: /* @__PURE__ */ jsx("div", { className: cn("pb-4 text-muted-foreground", className), children })
71
+ }
72
+ );
73
+ }
74
+ export {
75
+ Accordion,
76
+ AccordionContent,
77
+ AccordionItem,
78
+ AccordionTrigger
79
+ };
80
+ //# sourceMappingURL=accordion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/accordion.tsx"],"sourcesContent":["\"use client\";\n\nimport { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\";\nimport { ChevronDownRegular } from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Accordion — Fluent 2-styled, shadcn-API accordion (expand/collapse groups).\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — open/close state, focus-safe disabled triggers, and a\n * height-animated collapsible panel — genuinely needs a primitive, so the\n * parts wrap `@base-ui/react/accordion` (namespace import, matching the\n * actual `export * as Accordion from \"./index.parts.js\"` shape in\n * node_modules, same pattern as `select.tsx`/`checkbox.tsx`). shadcn part\n * names map onto Base UI's model:\n *\n * | Exported (shadcn name) | Base UI primitive |\n * | ----------------------- | ------------------------ |\n * | `Accordion` | `Accordion.Root` |\n * | `AccordionItem` | `Accordion.Item` |\n * | `AccordionTrigger` | `Accordion.Header` (h3) + `Accordion.Trigger` (button), composed like shadcn |\n * | `AccordionContent` | `Accordion.Panel` |\n *\n * ## Divergences from the shadcn/Radix Accordion API (all deliberate)\n * 1. **No `type=\"single\" | \"multiple\"` prop.** Radix's Accordion takes a\n * `type` discriminant that also changes the shape of `value`\n * (`string | undefined` for `\"single\"`, `string[]` for `\"multiple\"`).\n * Base UI instead has a single `multiple?: boolean` prop (default\n * `false`) and *always* represents the open set as an array\n * (`AccordionValue<Value> = Value[]`), even in single mode (an array of\n * zero or one items). This wrapper does not paper over that with a fake\n * `type` shim — `<Accordion>` passes `AccordionPrimitive.Root.Props`\n * straight through, so callers use `multiple` and read/write `value`/\n * `defaultValue`/`onValueChange` as arrays. `multiple=false` (the\n * default) still enforces \"opening one closes the others\" the same way\n * Radix's `type=\"single\"` does — Base UI's root closes any previously\n * open item when a new one opens and `multiple` is false.\n * 2. **`AccordionItem` takes `value` directly** (not wrapped/renamed) — same\n * prop name as Base UI, and it auto-generates a stable id if omitted, so\n * `value` is optional here (unlike Radix, where an uncontrolled\n * `type=\"single\"` item still needs an explicit `value` to open by\n * default).\n *\n * ## Chevron rotation\n * `AccordionTrigger` carries Base UI's own `data-panel-open` presence\n * attribute (from `AccordionTriggerDataAttributes.panelOpen`, verified\n * against the compiled source — *not* `data-open`, which lives on\n * `Accordion.Item`/`Accordion.Panel` instead) when its panel is open. The\n * chevron is a child of the trigger button, so the trigger carries `group`\n * and the chevron targets `group-data-[panel-open]:rotate-180` (conventions\n * §4's bracketed data-attribute form) — the bracket form works on ancestor\n * *and* self, `group-` just relays the parent's attribute to a descendant\n * selector.\n *\n * ## Height animation\n * `Accordion.Panel` measures its own content and exposes it as two CSS\n * custom properties set as inline styles on the panel element itself:\n * `--accordion-panel-height` and `--accordion-panel-width` (verified against\n * `AccordionPanelCssVars` in the compiled source — during open/close these\n * update live as Base UI's `ResizeObserver` re-measures). `AccordionContent`\n * reads `--accordion-panel-height` back via `h-[var(--accordion-panel-height)]`\n * on the outer, `overflow-hidden` panel and transitions `height` (never\n * `transition-transform`/`scale`/`translate` — height is the property that\n * actually changes here, conventions §3.5's rule generalized) with token\n * durations/easings: `duration-normal ease-decelerate-mid` entering,\n * `ease-accelerate-mid` (via `data-ending-style:ease-accelerate-mid`, which\n * wins by source order) exiting. `data-starting-style`/`data-ending-style`\n * (Base UI's enter/exit hooks, already used by `select.tsx`'s popup) pin the\n * height to `0` at both animation boundaries, so the transition always\n * animates *from* `0` *to* the measured height (open) or the reverse\n * (close) — this is Base UI's own documented pattern for this component,\n * reproduced with the kit's tokens instead of raw CSS. Padding lives on an\n * *inner* `div` (not the animated panel itself) so the padding doesn't\n * distort the measured/animated height — the same inner-wrapper split\n * shadcn's own Radix-based `AccordionContent` uses. `AccordionPanel`\n * unmounts while closed by default (`keepMounted` is not forced on); Base\n * UI's own transition-status tracking (mirrors Radix `Presence`) keeps it\n * mounted for the *closing* animation without any extra prop here.\n *\n * ## `\"use client\"` — required (conventions §9)\n * Every Base UI Accordion part module carries its own `'use client'`\n * directive, so on that basis alone this wrapper could stay\n * server-renderable (same reasoning as `avatar.tsx`). But\n * `ChevronDownRegular` from `@fluentui/react-icons` breaks that: the\n * package's shared icon-sizing module (`createFluentIcon.styles.js`) calls\n * `@griffel/react`'s `__styles()` at module scope *without* its own\n * `'use client'` directive, even though `__styles` itself is client-only.\n * Rendering the icon from a Server Component pulls that module into the\n * server's RSC graph, and `next build` (Turbopack) fails collecting page\n * data with \"Attempted to call __styles() from the server but __styles is\n * on the client\" — reproduced against `@fluentui/react-icons@2.0.333` /\n * `@griffel/react@1.7.5`, and confirmed to affect every route sharing\n * Turbopack's chunk for this icon, not just this one. `\"use client\"` here\n * keeps the icon import inside a client boundary so it's never evaluated on\n * the server. `select.tsx` and `checkbox.tsx` carry the same fix for the\n * same reason.\n *\n * ## Keyboard behavior note\n * Base UI's Accordion no longer implements roving-tabindex arrow-key\n * navigation between triggers — `AccordionRoot.Props.orientation` is\n * explicitly documented (and typed) as `@deprecated`, \"following the APG\n * guidance update to remove the roving focus pattern\" for accordions, and\n * the compiled `AccordionTrigger` has no `onKeyDown` handler beyond the\n * generic focusable-when-disabled Tab guard — only `onClick`. So each\n * trigger is a normal Tab stop (Enter/Space activate it, matching any\n * `<button>`); ArrowUp/ArrowDown do **not** move focus between triggers in\n * this version, and the test file asserts that actual (non-roving)\n * behavior rather than the older Radix pattern.\n */\nfunction Accordion<Value = unknown>(\n props: AccordionPrimitive.Root.Props<Value>\n) {\n return <AccordionPrimitive.Root data-slot=\"accordion\" {...props} />;\n}\n\n/**\n * Item — groups one trigger with its panel. `border-b` per shadcn (every\n * item but the last is visually separated by the next item's top-less\n * border — the whole accordion typically sits inside a container that adds\n * the top border, matching shadcn's own layout).\n */\nfunction AccordionItem({\n className,\n ...props\n}: ComponentProps<typeof AccordionPrimitive.Item>) {\n return (\n <AccordionPrimitive.Item\n data-slot=\"accordion-item\"\n className={cn(\"border-b border-border last:border-b-0\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Trigger — `Accordion.Header` (renders `<h3>`, the heading level shadcn\n * uses) wrapping `Accordion.Trigger` (the actual `<button>`), exactly like\n * shadcn's Radix-based composition. The chevron rotates via\n * `group-data-[panel-open]:rotate-180` — see the component doc comment for\n * why the attribute lives on the trigger rather than the icon itself.\n */\nfunction AccordionTrigger({\n className,\n children,\n ...props\n}: ComponentProps<typeof AccordionPrimitive.Trigger>) {\n return (\n <AccordionPrimitive.Header data-slot=\"accordion-header\" className=\"flex\">\n <AccordionPrimitive.Trigger\n data-slot=\"accordion-trigger\"\n className={cn(\n // py-3 + 20px line-height = Fluent's 44px Medium header; title is\n // Body 1 Regular (400), not Medium — same fix class as Label (M6).\n // (Figma validation pass 2, nodes 9074:915/9074:921.)\n \"group flex flex-1 items-center justify-between gap-2 py-3 text-left text-sm font-normal outline-none\",\n \"transition-colors duration-fast ease-ease hover:underline\",\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 {children}\n <ChevronDownRegular\n aria-hidden=\"true\"\n className=\"size-4 shrink-0 text-muted-foreground transition-transform duration-normal ease-ease group-data-[panel-open]:rotate-180\"\n />\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n );\n}\n\n/**\n * Content — the collapsible panel. See the component doc comment's \"Height\n * animation\" section for the exact mechanism. `role=\"region\"` and\n * `aria-labelledby` (pointing at the trigger) are set by Base UI itself, not\n * reimplemented here.\n */\nfunction AccordionContent({\n className,\n children,\n ...props\n}: ComponentProps<typeof AccordionPrimitive.Panel>) {\n return (\n <AccordionPrimitive.Panel\n data-slot=\"accordion-content\"\n className={cn(\n \"h-[var(--accordion-panel-height)] overflow-hidden text-sm\",\n \"transition-[height] duration-normal ease-decelerate-mid\",\n \"data-starting-style:h-0 data-ending-style:h-0 data-ending-style:ease-accelerate-mid\"\n )}\n {...props}\n >\n <div className={cn(\"pb-4 text-muted-foreground\", className)}>\n {children}\n </div>\n </AccordionPrimitive.Panel>\n );\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"],"mappings":";AAmHS,cAoCH,YApCG;AAjHT,SAAS,aAAa,0BAA0B;AAChD,SAAS,0BAA0B;AAGnC,SAAS,UAAU;AA0GnB,SAAS,UACP,OACA;AACA,SAAO,oBAAC,mBAAmB,MAAnB,EAAwB,aAAU,aAAa,GAAG,OAAO;AACnE;AAQA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA,GAAG;AACL,GAAmD;AACjD,SACE;AAAA,IAAC,mBAAmB;AAAA,IAAnB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,0CAA0C,SAAS;AAAA,MAChE,GAAG;AAAA;AAAA,EACN;AAEJ;AASA,SAAS,iBAAiB;AAAA,EACxB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE,oBAAC,mBAAmB,QAAnB,EAA0B,aAAU,oBAAmB,WAAU,QAChE;AAAA,IAAC,mBAAmB;AAAA,IAAnB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA;AAAA;AAAA;AAAA,QAIT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACD;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAU;AAAA;AAAA,QACZ;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;AAQA,SAAS,iBAAiB;AAAA,EACxB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoD;AAClD,SACE;AAAA,IAAC,mBAAmB;AAAA,IAAnB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,SAAI,WAAW,GAAG,8BAA8B,SAAS,GACvD,UACH;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,106 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps } from 'react';
3
+ import * as class_variance_authority_types from 'class-variance-authority/types';
4
+ import { VariantProps } from 'class-variance-authority';
5
+
6
+ /**
7
+ * Alert — Fluent 2-styled (MessageBar look), shadcn-API alert family.
8
+ *
9
+ * Structure matches shadcn/ui's current Alert (three parts: `Alert`,
10
+ * `AlertTitle`, `AlertDescription` — see
11
+ * https://ui.shadcn.com/docs/components/alert): a two-column CSS grid
12
+ * (`grid-cols-[0_1fr]`, collapsing to a real icon column via
13
+ * `has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr]` only when an `<svg>`
14
+ * child is present) so title/description always sit in column 2 and an
15
+ * optional leading icon occupies column 1 without extra markup. No `asChild`
16
+ * — current shadcn doesn't offer one for Alert either.
17
+ *
18
+ * Fluent 2 visual reference: MessageBar
19
+ * (https://storybooks.fluentui.dev/react/?path=/docs/components-messagebar--docs)
20
+ * — a subtle intent-tinted background + intent-colored border + intent-colored
21
+ * icon, with the running body copy staying regular foreground (not tinted).
22
+ * That's a deliberate split from vanilla shadcn's `destructive` variant (which
23
+ * tints the *entire* alert text `text-destructive` and only softens the
24
+ * description). Here only the icon and `AlertTitle` pick up the intent color
25
+ * (via `[&>svg]:text-*` and `*:data-[slot=alert-title]:text-*`);
26
+ * `AlertDescription` always stays `text-muted-foreground` so status is
27
+ * conveyed by icon + border + title color together, never by body-text color
28
+ * alone (accessibility checklist §5).
29
+ *
30
+ * Deviations from current shadcn, both deliberate:
31
+ * - `rounded-md` (Fluent "medium", 4px) instead of shadcn's `rounded-lg`.
32
+ * Per docs/component-conventions.md §3.4, `rounded-md` is this kit's
33
+ * default *control* radius (buttons, inputs, badges) and `rounded-lg` is
34
+ * reserved for card/dialog-scale surfaces; an inline notification bar reads
35
+ * as the former.
36
+ * - `AlertTitle` is `font-semibold` instead of shadcn's `font-medium`, for
37
+ * consistency with `CardTitle` (also `font-semibold`) elsewhere in this kit.
38
+ *
39
+ * Token usage:
40
+ * - `default`: neutral/Informative — `bg-secondary` (`#f5f5f5` light /
41
+ * `#333333` dark) with a muted icon, reproducing Fluent's grey-filled
42
+ * "Informative" MessageBar surface (an exact hex match to Figma's
43
+ * `Status background color`). This is NOT `bg-card`: in light mode `--card`
44
+ * is `#ffffff`, identical to the page background, so a `bg-card` alert reads
45
+ * as a bare 1px-bordered box rather than the tinted surface every other
46
+ * intent (and Fluent's Informative) shows. The border keeps `--border`
47
+ * (`#d1d1d1`), matching Figma's Informative stroke.
48
+ * - `destructive` / `success` / `warning`: the status-extension tokens
49
+ * (`*-subtle` background, `*-border` border) added to tokens.css for this
50
+ * component. `destructive` and `warning` both intentionally accent with a
51
+ * dedicated `*-text` token instead of the raw status color —
52
+ * `--destructive`/`--warning` are the *fill* colors (meant for
53
+ * `Badge`-style filled surfaces with a matching `*-foreground` on top);
54
+ * as running/title text on a subtle surface neither clears AA in both
55
+ * themes on its own. `--destructive-text`/`--warning-text` exist
56
+ * specifically to fix that. Manual sRGB contrast checks against the token
57
+ * hex values in tokens.css (this environment can't run real axe
58
+ * color-contrast checks — see the component's test file for why):
59
+ * - destructive icon/title (`--destructive-text`) vs
60
+ * `--destructive-subtle`: light `#b10e1c` on `#fdf6f6` ~6.68:1, dark
61
+ * `#ff9a90` on `#3b1212` ~8.02:1 — both pass AA comfortably. (Plain
62
+ * `--destructive`, `#d13438` in both themes, was ~4.6:1 light /
63
+ * ~3.3:1 dark — the dark case failed the 4.5:1 AA body-text minimum,
64
+ * which is why `--destructive-text` was added at the token layer
65
+ * rather than re-tuning `--destructive` itself, mirroring how
66
+ * `--warning-text` sits alongside `--warning`.)
67
+ * - success icon/title vs `--success-subtle`: light ~5.0:1, dark ~6.5:1
68
+ * — both pass AA comfortably.
69
+ * - warning icon/title (`--warning-text`) vs `--warning-subtle`: light
70
+ * ~5.1:1, dark ~10.6:1 — both pass AA comfortably.
71
+ * - `info`: brand-tinted, using the brand ramp directly since it has no
72
+ * dedicated status-extension tokens. Light picks `bg-brand-160`
73
+ * (`#ebf3fc`, the same "selected nav" tint `--sidebar-accent` already
74
+ * uses) with `text-brand-80` (`#0f6cbd`, `= --primary` in light) for the
75
+ * icon/title — ~4.8:1 against the tint. The brand ramp is global (same hex
76
+ * in both themes per spec §2), so dark needs explicit overrides:
77
+ * `dark:bg-brand-30` keeps the tint dark instead of blinding-light, and
78
+ * `dark:text-brand-100` (not `--primary`'s dark value, `brand-70`, which
79
+ * only clears ~2.1:1 against `brand-30`) is picked for ~5.0:1 contrast —
80
+ * `brand-100` is also already `--ring`'s dark-mode color, so it reads as
81
+ * "the" visible accent blue in dark surfaces elsewhere in this kit.
82
+ * `border-brand-140` / `dark:border-brand-70` sit as a midtone between
83
+ * each theme's subtle background and accent stop, mirroring how
84
+ * `*-border` sits between `*-subtle` and the icon/title accent for the
85
+ * status-extension trio above.
86
+ *
87
+ * Role: `role="alert"` is applied as a *default* — it sits before the
88
+ * `{...props}` spread, so a consumer can override it and a passed
89
+ * `role="status"` wins. Choose deliberately: `role="alert"` is an *assertive*
90
+ * live region (it interrupts the screen reader) and is only *announced* when
91
+ * the alert is mounted dynamically after first paint — a statically-rendered
92
+ * alert present at load isn't announced under either role. Use `role="status"`
93
+ * (polite — queued, not interrupting) for success/info or other non-urgent
94
+ * updates, and keep the assertive default for genuinely urgent/destructive
95
+ * messages. The default stays `role="alert"` (shadcn parity).
96
+ *
97
+ * Server-safe: no `"use client"`, no hooks — the React import is type-only.
98
+ */
99
+ declare const alertVariants: (props?: ({
100
+ variant?: "default" | "destructive" | "success" | "warning" | "info" | null | undefined;
101
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
102
+ declare function Alert({ className, variant, ...props }: ComponentProps<"div"> & VariantProps<typeof alertVariants>): react.JSX.Element;
103
+ declare function AlertTitle({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
104
+ declare function AlertDescription({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
105
+
106
+ export { Alert, AlertDescription, AlertTitle, alertVariants };
@@ -0,0 +1,73 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { cva } from "class-variance-authority";
3
+ import { cn } from "../../lib/utils.js";
4
+ const alertVariants = cva(
5
+ [
6
+ "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-md border px-4 py-3 text-sm",
7
+ "has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3",
8
+ "[&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current"
9
+ ],
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-secondary text-foreground [&>svg]:text-muted-foreground",
14
+ destructive: "bg-destructive-subtle border-destructive-border text-foreground [&>svg]:text-destructive-text *:data-[slot=alert-title]:text-destructive-text",
15
+ success: "bg-success-subtle border-success-border text-foreground [&>svg]:text-success *:data-[slot=alert-title]:text-success",
16
+ warning: "bg-warning-subtle border-warning-border text-foreground [&>svg]:text-warning-text *:data-[slot=alert-title]:text-warning-text",
17
+ info: "bg-brand-160 border-brand-140 text-foreground [&>svg]:text-brand-80 *:data-[slot=alert-title]:text-brand-80 dark:bg-brand-30 dark:border-brand-70 dark:[&>svg]:text-brand-100 dark:*:data-[slot=alert-title]:text-brand-100"
18
+ }
19
+ },
20
+ defaultVariants: {
21
+ variant: "default"
22
+ }
23
+ }
24
+ );
25
+ function Alert({
26
+ className,
27
+ variant = "default",
28
+ ...props
29
+ }) {
30
+ return /* @__PURE__ */ jsx(
31
+ "div",
32
+ {
33
+ "data-slot": "alert",
34
+ "data-variant": variant,
35
+ role: "alert",
36
+ className: cn(alertVariants({ variant, className })),
37
+ ...props
38
+ }
39
+ );
40
+ }
41
+ function AlertTitle({ className, ...props }) {
42
+ return /* @__PURE__ */ jsx(
43
+ "div",
44
+ {
45
+ "data-slot": "alert-title",
46
+ className: cn(
47
+ "col-start-2 line-clamp-1 min-h-4 font-semibold tracking-tight",
48
+ className
49
+ ),
50
+ ...props
51
+ }
52
+ );
53
+ }
54
+ function AlertDescription({ className, ...props }) {
55
+ return /* @__PURE__ */ jsx(
56
+ "div",
57
+ {
58
+ "data-slot": "alert-description",
59
+ className: cn(
60
+ "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
61
+ className
62
+ ),
63
+ ...props
64
+ }
65
+ );
66
+ }
67
+ export {
68
+ Alert,
69
+ AlertDescription,
70
+ AlertTitle,
71
+ alertVariants
72
+ };
73
+ //# sourceMappingURL=alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/alert.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Alert — Fluent 2-styled (MessageBar look), shadcn-API alert family.\n *\n * Structure matches shadcn/ui's current Alert (three parts: `Alert`,\n * `AlertTitle`, `AlertDescription` — see\n * https://ui.shadcn.com/docs/components/alert): a two-column CSS grid\n * (`grid-cols-[0_1fr]`, collapsing to a real icon column via\n * `has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr]` only when an `<svg>`\n * child is present) so title/description always sit in column 2 and an\n * optional leading icon occupies column 1 without extra markup. No `asChild`\n * — current shadcn doesn't offer one for Alert either.\n *\n * Fluent 2 visual reference: MessageBar\n * (https://storybooks.fluentui.dev/react/?path=/docs/components-messagebar--docs)\n * — a subtle intent-tinted background + intent-colored border + intent-colored\n * icon, with the running body copy staying regular foreground (not tinted).\n * That's a deliberate split from vanilla shadcn's `destructive` variant (which\n * tints the *entire* alert text `text-destructive` and only softens the\n * description). Here only the icon and `AlertTitle` pick up the intent color\n * (via `[&>svg]:text-*` and `*:data-[slot=alert-title]:text-*`);\n * `AlertDescription` always stays `text-muted-foreground` so status is\n * conveyed by icon + border + title color together, never by body-text color\n * alone (accessibility checklist §5).\n *\n * Deviations from current shadcn, both deliberate:\n * - `rounded-md` (Fluent \"medium\", 4px) instead of shadcn's `rounded-lg`.\n * Per docs/component-conventions.md §3.4, `rounded-md` is this kit's\n * default *control* radius (buttons, inputs, badges) and `rounded-lg` is\n * reserved for card/dialog-scale surfaces; an inline notification bar reads\n * as the former.\n * - `AlertTitle` is `font-semibold` instead of shadcn's `font-medium`, for\n * consistency with `CardTitle` (also `font-semibold`) elsewhere in this kit.\n *\n * Token usage:\n * - `default`: neutral/Informative — `bg-secondary` (`#f5f5f5` light /\n * `#333333` dark) with a muted icon, reproducing Fluent's grey-filled\n * \"Informative\" MessageBar surface (an exact hex match to Figma's\n * `Status background color`). This is NOT `bg-card`: in light mode `--card`\n * is `#ffffff`, identical to the page background, so a `bg-card` alert reads\n * as a bare 1px-bordered box rather than the tinted surface every other\n * intent (and Fluent's Informative) shows. The border keeps `--border`\n * (`#d1d1d1`), matching Figma's Informative stroke.\n * - `destructive` / `success` / `warning`: the status-extension tokens\n * (`*-subtle` background, `*-border` border) added to tokens.css for this\n * component. `destructive` and `warning` both intentionally accent with a\n * dedicated `*-text` token instead of the raw status color —\n * `--destructive`/`--warning` are the *fill* colors (meant for\n * `Badge`-style filled surfaces with a matching `*-foreground` on top);\n * as running/title text on a subtle surface neither clears AA in both\n * themes on its own. `--destructive-text`/`--warning-text` exist\n * specifically to fix that. Manual sRGB contrast checks against the token\n * hex values in tokens.css (this environment can't run real axe\n * color-contrast checks — see the component's test file for why):\n * - destructive icon/title (`--destructive-text`) vs\n * `--destructive-subtle`: light `#b10e1c` on `#fdf6f6` ~6.68:1, dark\n * `#ff9a90` on `#3b1212` ~8.02:1 — both pass AA comfortably. (Plain\n * `--destructive`, `#d13438` in both themes, was ~4.6:1 light /\n * ~3.3:1 dark — the dark case failed the 4.5:1 AA body-text minimum,\n * which is why `--destructive-text` was added at the token layer\n * rather than re-tuning `--destructive` itself, mirroring how\n * `--warning-text` sits alongside `--warning`.)\n * - success icon/title vs `--success-subtle`: light ~5.0:1, dark ~6.5:1\n * — both pass AA comfortably.\n * - warning icon/title (`--warning-text`) vs `--warning-subtle`: light\n * ~5.1:1, dark ~10.6:1 — both pass AA comfortably.\n * - `info`: brand-tinted, using the brand ramp directly since it has no\n * dedicated status-extension tokens. Light picks `bg-brand-160`\n * (`#ebf3fc`, the same \"selected nav\" tint `--sidebar-accent` already\n * uses) with `text-brand-80` (`#0f6cbd`, `= --primary` in light) for the\n * icon/title — ~4.8:1 against the tint. The brand ramp is global (same hex\n * in both themes per spec §2), so dark needs explicit overrides:\n * `dark:bg-brand-30` keeps the tint dark instead of blinding-light, and\n * `dark:text-brand-100` (not `--primary`'s dark value, `brand-70`, which\n * only clears ~2.1:1 against `brand-30`) is picked for ~5.0:1 contrast —\n * `brand-100` is also already `--ring`'s dark-mode color, so it reads as\n * \"the\" visible accent blue in dark surfaces elsewhere in this kit.\n * `border-brand-140` / `dark:border-brand-70` sit as a midtone between\n * each theme's subtle background and accent stop, mirroring how\n * `*-border` sits between `*-subtle` and the icon/title accent for the\n * status-extension trio above.\n *\n * Role: `role=\"alert\"` is applied as a *default* — it sits before the\n * `{...props}` spread, so a consumer can override it and a passed\n * `role=\"status\"` wins. Choose deliberately: `role=\"alert\"` is an *assertive*\n * live region (it interrupts the screen reader) and is only *announced* when\n * the alert is mounted dynamically after first paint — a statically-rendered\n * alert present at load isn't announced under either role. Use `role=\"status\"`\n * (polite — queued, not interrupting) for success/info or other non-urgent\n * updates, and keep the assertive default for genuinely urgent/destructive\n * messages. The default stays `role=\"alert\"` (shadcn parity).\n *\n * Server-safe: no `\"use client\"`, no hooks — the React import is type-only.\n */\nconst alertVariants = cva(\n [\n \"relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-md border px-4 py-3 text-sm\",\n \"has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3\",\n \"[&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-secondary text-foreground [&>svg]:text-muted-foreground\",\n destructive:\n \"bg-destructive-subtle border-destructive-border text-foreground [&>svg]:text-destructive-text *:data-[slot=alert-title]:text-destructive-text\",\n success:\n \"bg-success-subtle border-success-border text-foreground [&>svg]:text-success *:data-[slot=alert-title]:text-success\",\n warning:\n \"bg-warning-subtle border-warning-border text-foreground [&>svg]:text-warning-text *:data-[slot=alert-title]:text-warning-text\",\n info: \"bg-brand-160 border-brand-140 text-foreground [&>svg]:text-brand-80 *:data-[slot=alert-title]:text-brand-80 dark:bg-brand-30 dark:border-brand-70 dark:[&>svg]:text-brand-100 dark:*:data-[slot=alert-title]:text-brand-100\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\nfunction Alert({\n className,\n variant = \"default\",\n ...props\n}: ComponentProps<\"div\"> & VariantProps<typeof alertVariants>) {\n return (\n <div\n data-slot=\"alert\"\n data-variant={variant}\n role=\"alert\"\n className={cn(alertVariants({ variant, className }))}\n {...props}\n />\n );\n}\n\nfunction AlertTitle({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn(\n \"col-start-2 line-clamp-1 min-h-4 font-semibold tracking-tight\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDescription({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n \"col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Alert, AlertTitle, AlertDescription, alertVariants };\n"],"mappings":"AAiII;AAjIJ,SAAS,WAA8B;AAGvC,SAAS,UAAU;AA+FnB,MAAM,gBAAgB;AAAA,EACpB;AAAA,IACE;AAAA,IACA;AAAA,IACA;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;AAEA,SAAS,MAAM;AAAA,EACb;AAAA,EACA,UAAU;AAAA,EACV,GAAG;AACL,GAA+D;AAC7D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,MAAK;AAAA,MACL,WAAW,GAAG,cAAc,EAAE,SAAS,UAAU,CAAC,CAAC;AAAA,MAClD,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAA0B;AAClE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,iBAAiB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACxE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,42 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps } from 'react';
3
+ import { Avatar as Avatar$1 } from '@base-ui/react/avatar';
4
+
5
+ /**
6
+ * Avatar — Fluent 2-styled, shadcn-API avatar.
7
+ *
8
+ * This is the batch's Base UI case (conventions §9): an image-with-fallback
9
+ * loading state is genuine behavior, not something plain markup can express,
10
+ * so the three parts wrap `@base-ui/react/avatar` (`Avatar.Root`,
11
+ * `Avatar.Image`, `Avatar.Fallback` — imported as a namespace from the
12
+ * `@base-ui/react/avatar` subpath, matching the actual export shape in
13
+ * node_modules) instead of a raw `<span>`/`<img>`.
14
+ *
15
+ * `"use client"` is intentionally omitted here. Base UI's Root/Image/Fallback
16
+ * modules each carry their own `'use client'` directive at the source level
17
+ * (verified in the compiled package output), so they are already client
18
+ * boundaries on their own. This wrapper file contains no hooks/handlers of
19
+ * its own — it only forwards props into those parts — so it can stay a plain
20
+ * (server-renderable) module per conventions §2: a Server Component tree can
21
+ * render a Client Component child without the parent re-declaring the
22
+ * directive. Confirmed via `pnpm typecheck` + the test suite; add the
23
+ * directive here only if that stops holding true.
24
+ *
25
+ * Aesthetics follow Fluent 2 (32px medium avatar, circular, brand-tinted
26
+ * initials fallback); the prop surface (`Avatar` / `AvatarImage` /
27
+ * `AvatarFallback`, no `size` prop — consumers resize via `className`, e.g.
28
+ * `size-10`) follows the classic shadcn/ui avatar API. Fluent's
29
+ * sized/shape/presence-badge variants are out of scope for this pass — see
30
+ * backlog notes returned alongside this component.
31
+ */
32
+ declare function Avatar({ className, ...props }: ComponentProps<typeof Avatar$1.Root>): react.JSX.Element;
33
+ declare function AvatarImage({ className, ...props }: ComponentProps<typeof Avatar$1.Image>): react.JSX.Element;
34
+ /**
35
+ * Fluent-inspired brand-tinted initials fallback. Contrast checked against
36
+ * WCAG AA (4.5:1, since 14px/600 does not clear the "large text" bold
37
+ * threshold): light `brand-160`/`brand-70` ≈ 5.95:1; dark `brand-40`/`brand-140`
38
+ * ≈ 7.73:1. Both comfortably pass.
39
+ */
40
+ declare function AvatarFallback({ className, ...props }: ComponentProps<typeof Avatar$1.Fallback>): react.JSX.Element;
41
+
42
+ export { Avatar, AvatarFallback, AvatarImage };
@@ -0,0 +1,55 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
3
+ import { cn } from "../../lib/utils.js";
4
+ function Avatar({
5
+ className,
6
+ ...props
7
+ }) {
8
+ return /* @__PURE__ */ jsx(
9
+ AvatarPrimitive.Root,
10
+ {
11
+ "data-slot": "avatar",
12
+ className: cn(
13
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
14
+ className
15
+ ),
16
+ ...props
17
+ }
18
+ );
19
+ }
20
+ function AvatarImage({
21
+ className,
22
+ ...props
23
+ }) {
24
+ return /* @__PURE__ */ jsx(
25
+ AvatarPrimitive.Image,
26
+ {
27
+ "data-slot": "avatar-image",
28
+ className: cn("aspect-square size-full object-cover", className),
29
+ ...props
30
+ }
31
+ );
32
+ }
33
+ function AvatarFallback({
34
+ className,
35
+ ...props
36
+ }) {
37
+ return /* @__PURE__ */ jsx(
38
+ AvatarPrimitive.Fallback,
39
+ {
40
+ "data-slot": "avatar-fallback",
41
+ className: cn(
42
+ "flex size-full items-center justify-center rounded-full bg-brand-160 text-sm font-semibold text-brand-70 select-none",
43
+ "dark:bg-brand-40 dark:text-brand-140",
44
+ className
45
+ ),
46
+ ...props
47
+ }
48
+ );
49
+ }
50
+ export {
51
+ Avatar,
52
+ AvatarFallback,
53
+ AvatarImage
54
+ };
55
+ //# sourceMappingURL=avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/avatar.tsx"],"sourcesContent":["import { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Avatar — Fluent 2-styled, shadcn-API avatar.\n *\n * This is the batch's Base UI case (conventions §9): an image-with-fallback\n * loading state is genuine behavior, not something plain markup can express,\n * so the three parts wrap `@base-ui/react/avatar` (`Avatar.Root`,\n * `Avatar.Image`, `Avatar.Fallback` — imported as a namespace from the\n * `@base-ui/react/avatar` subpath, matching the actual export shape in\n * node_modules) instead of a raw `<span>`/`<img>`.\n *\n * `\"use client\"` is intentionally omitted here. Base UI's Root/Image/Fallback\n * modules each carry their own `'use client'` directive at the source level\n * (verified in the compiled package output), so they are already client\n * boundaries on their own. This wrapper file contains no hooks/handlers of\n * its own — it only forwards props into those parts — so it can stay a plain\n * (server-renderable) module per conventions §2: a Server Component tree can\n * render a Client Component child without the parent re-declaring the\n * directive. Confirmed via `pnpm typecheck` + the test suite; add the\n * directive here only if that stops holding true.\n *\n * Aesthetics follow Fluent 2 (32px medium avatar, circular, brand-tinted\n * initials fallback); the prop surface (`Avatar` / `AvatarImage` /\n * `AvatarFallback`, no `size` prop — consumers resize via `className`, e.g.\n * `size-10`) follows the classic shadcn/ui avatar API. Fluent's\n * sized/shape/presence-badge variants are out of scope for this pass — see\n * backlog notes returned alongside this component.\n */\nfunction Avatar({\n className,\n ...props\n}: ComponentProps<typeof AvatarPrimitive.Root>) {\n return (\n <AvatarPrimitive.Root\n data-slot=\"avatar\"\n className={cn(\n \"relative flex size-8 shrink-0 overflow-hidden rounded-full\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction AvatarImage({\n className,\n ...props\n}: ComponentProps<typeof AvatarPrimitive.Image>) {\n return (\n <AvatarPrimitive.Image\n data-slot=\"avatar-image\"\n className={cn(\"aspect-square size-full object-cover\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Fluent-inspired brand-tinted initials fallback. Contrast checked against\n * WCAG AA (4.5:1, since 14px/600 does not clear the \"large text\" bold\n * threshold): light `brand-160`/`brand-70` ≈ 5.95:1; dark `brand-40`/`brand-140`\n * ≈ 7.73:1. Both comfortably pass.\n */\nfunction AvatarFallback({\n className,\n ...props\n}: ComponentProps<typeof AvatarPrimitive.Fallback>) {\n return (\n <AvatarPrimitive.Fallback\n data-slot=\"avatar-fallback\"\n className={cn(\n \"flex size-full items-center justify-center rounded-full bg-brand-160 text-sm font-semibold text-brand-70 select-none\",\n \"dark:bg-brand-40 dark:text-brand-140\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Avatar, AvatarImage, AvatarFallback };\n"],"mappings":"AAqCI;AArCJ,SAAS,UAAU,uBAAuB;AAG1C,SAAS,UAAU;AA6BnB,SAAS,OAAO;AAAA,EACd;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wCAAwC,SAAS;AAAA,MAC9D,GAAG;AAAA;AAAA,EACN;AAEJ;AAQA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA,GAAG;AACL,GAAoD;AAClD,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}