@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
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":[]}
|