@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/card.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Card — Fluent 2-styled, shadcn-API card family.\n *\n * Structure matches shadcn/ui's current Card exactly: seven composable parts\n * (`Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`,\n * `CardContent`, `CardFooter`), each a plain `<div>` carrying its own\n * `data-slot`. `CardHeader` is a grid so an optional `CardAction` can sit in\n * its own column/row-span without extra markup (shadcn's `has-data-[slot=...]`\n * trick) — see https://ui.shadcn.com/docs/components/card.\n *\n * Fluent 2 aesthetics layered on top of that skeleton:\n * - `rounded-lg` + `border` + `shadow-4` (Fluent's resting elevation for a\n * filled card — https://storybooks.fluentui.dev/react/?path=/docs/components-card--docs)\n * instead of shadcn's default `rounded-xl` + `shadow-sm`.\n * - `bg-card text-card-foreground` (shadcn semantic tokens, re-pointed for\n * dark mode by tokens.css).\n * - `CardTitle` is `font-semibold` (Fluent card titles are weight 600).\n * - Spacing: Fluent's own medium card padding is 12px (`p-3`), noticeably\n * tighter than shadcn's 24px (`p-6`) default. We split the difference and\n * land on 16px (`gap-4` / `p-4` / `px-4`) — visibly tighter than shadcn so\n * the Fluent influence reads, while keeping enough breathing room that the\n * header/content/footer rhythm still feels like the familiar shadcn\n * composition (a straight 12px port felt cramped next to Button's 32px\n * controls). Document this if you change it.\n *\n * No `variant`/`size` props: shadcn's Card ships none, so there is no `cva`\n * table here (unlike Button) — every part is a plain function component.\n *\n * `interactive` (kit extension): Fluent cards have hover/pressed states when\n * clickable; passing `interactive` raises the resting `shadow-4` to `shadow-8`\n * on hover (Fluent's interactive-card elevation step) with a pointer cursor\n * and `data-interactive` hook. Visual-only — semantics/focus come from what\n * you compose it with (wrap in an `<a>`/`<button>` or add role/tabIndex).\n *\n * Server-safe: no `\"use client\"`, no hooks — the React import is type-only.\n */\n\nfunction Card({\n className,\n interactive = false,\n ...props\n}: ComponentProps<\"div\"> & {\n /** Fluent interactive-card elevation states (hover raises to shadow-8). */\n interactive?: boolean;\n}) {\n return (\n <div\n data-slot=\"card\"\n data-interactive={interactive || undefined}\n className={cn(\n \"flex flex-col gap-4 rounded-lg border bg-card py-4 text-card-foreground shadow-4\",\n interactive &&\n \"cursor-pointer transition-shadow duration-fast ease-ease hover:shadow-8 active:shadow-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction CardHeader({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-header\"\n className={cn(\n \"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction CardTitle({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-title\"\n className={cn(\"font-semibold leading-none\", className)}\n {...props}\n />\n );\n}\n\nfunction CardDescription({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nfunction CardAction({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-action\"\n className={cn(\n \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction CardContent({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-content\"\n className={cn(\"px-4\", className)}\n {...props}\n />\n );\n}\n\nfunction CardFooter({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-footer\"\n className={cn(\"flex items-center px-4 [.border-t]:pt-4\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardAction,\n CardDescription,\n CardContent,\n};\n"],"mappings":"AAkDI;AAhDJ,SAAS,UAAU;AAuCnB,SAAS,KAAK;AAAA,EACZ;AAAA,EACA,cAAc;AAAA,EACd,GAAG;AACL,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,oBAAkB,eAAe;AAAA,MACjC,WAAW;AAAA,QACT;AAAA,QACA,eACE;AAAA,QACF;AAAA,MACF;AAAA,MACC,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,UAAU,EAAE,WAAW,GAAG,MAAM,GAA0B;AACjE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,8BAA8B,SAAS;AAAA,MACpD,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,gBAAgB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACvE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,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,YAAY,EAAE,WAAW,GAAG,MAAM,GAA0B;AACnE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,QAAQ,SAAS;AAAA,MAC9B,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAA0B;AAClE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,101 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps } from 'react';
3
+ import { Checkbox as Checkbox$1 } from '@base-ui/react/checkbox';
4
+
5
+ /**
6
+ * Checkbox — Fluent 2-styled, shadcn-API checkbox.
7
+ *
8
+ * This is a Base UI case (conventions §9): the checked/unchecked/indeterminate
9
+ * state machine, the hidden native `<input type="checkbox">` for real form
10
+ * participation, and keyboard activation (Space/Enter) are genuine behavior a
11
+ * styled `<span>` can't express on its own, so this wraps
12
+ * `@base-ui/react/checkbox` (`Checkbox`, imported as a namespace exposing
13
+ * `Checkbox.Root` / `Checkbox.Indicator` — matches the actual
14
+ * `export * as Checkbox from "./index.parts.js"` shape in node_modules, the
15
+ * same pattern `avatar.tsx` and `radio-group.tsx` use for their own Base UI
16
+ * subpaths). `Checkbox.Root` renders a `<span role="checkbox">` plus a
17
+ * visually-hidden native `<input type="checkbox">` sibling for form
18
+ * submission; none of that is reimplemented here.
19
+ *
20
+ * `"use client"` is required here — integration-pass correction of the
21
+ * original reasoning below. `@base-ui/react/checkbox`'s `CheckboxRoot` and
22
+ * `CheckboxIndicator` do each carry their own `'use client'` directive, so
23
+ * *those* parts alone wouldn't force this file to be a client boundary
24
+ * (same reasoning as `avatar.tsx` / `radio-group.tsx`). But
25
+ * `CheckmarkFilled`/`SubtractRegular` from `@fluentui/react-icons` don't hold
26
+ * to that pattern: the package's shared icon-sizing module
27
+ * (`createFluentIcon.styles.js`) calls `@griffel/react`'s `__styles()` at
28
+ * module scope *without* its own `'use client'` directive, even though
29
+ * `__styles` itself is client-only. Rendering either icon from a Server
30
+ * Component pulls that module into the server's RSC graph, and `next build`
31
+ * (Turbopack) fails collecting page data with "Attempted to call __styles()
32
+ * from the server but __styles is on the client" — reproduced against
33
+ * `@fluentui/react-icons@2.0.333` / `@griffel/react@1.7.5`, and confirmed to
34
+ * affect every route that shares Turbopack's chunk for these icons, not just
35
+ * this one. `"use client"` here keeps the icon imports inside a client
36
+ * boundary so they're never evaluated on the server. `select.tsx` carries the
37
+ * same fix for the same reason.
38
+ *
39
+ * Box: Fluent 2's checkbox indicator is 16px square (`size-4`) with
40
+ * `borderRadius: tokens.borderRadiusSmall` (confirmed against Fluent UI v9's
41
+ * `useCheckboxStyles.styles.ts` — `indicatorSizeMedium = '16px'`,
42
+ * `borderRadius: tokens.borderRadiusSmall`, checkmark `fontSize: '12px'`).
43
+ * `borderRadiusSmall` is Fluent's 2px step, which is exactly this kit's
44
+ * `rounded-sm` (`--radius-sm: calc(var(--radius) - 4px)` = 6px − 4px = 2px in
45
+ * `tokens.css`) — no new radius token needed. `aspect-square` guards the box
46
+ * against flex-row squish when paired with a `Label` (mirrors
47
+ * `radio-group.tsx`'s `RadioGroupItem`).
48
+ *
49
+ * Borders + interaction ramp: the unchecked box outlines with Fluent's
50
+ * `NeutralStrokeAccessible` ramp — rest `#616161` (`border-stroke-accessible`),
51
+ * hover `#575757` (`border-stroke-accessible-hover`), pressed `#4d4d4d`
52
+ * (`border-stroke-accessible-pressed`) — the higher-contrast neutral Fluent
53
+ * specs for interactive control outlines (extracted from the Figma checkbox),
54
+ * NOT the lighter `--border`/`border-input` grey the box previously used. When
55
+ * checked/indeterminate the box fills brand, and its border + fill step
56
+ * through Fluent's `CompoundBrandBackground` on hover/press (`brand-70` /
57
+ * `brand-60`; dark hover `brand-80`), mirroring Button's documented per-theme
58
+ * brand ramp (conventions §4). The `data-[checked]:hover:`/`:active:`
59
+ * selectors outrank the neutral hover/active border by specificity, so a
60
+ * checked box stays brand (never the neutral grey) while hovered.
61
+ *
62
+ * Checked glyph: `CheckmarkFilled` from `@fluentui/react-icons` at `size-3`
63
+ * (12px, matching Fluent's checkmark `fontSize`) — a real glyph is
64
+ * appropriate here (unlike Radio's plain dot) because Fluent's checkbox
65
+ * indicator specifically shows a checkmark, not just a filled shape.
66
+ *
67
+ * Indeterminate: Base UI's `indeterminate` prop (forwarded to `Checkbox.Root`
68
+ * so it drives `aria-checked="mixed"` and the `data-indeterminate` attribute
69
+ * Base UI uses for its own state) renders a dash instead of a checkmark. The
70
+ * spec offered a choice between `SubtractRegular` and a hand-drawn 8×2 bar;
71
+ * `SubtractRegular` is used since `@fluentui/react-icons` ships that exact
72
+ * glyph and pulling in an icon dependency is already paid for by the checked
73
+ * state. Which icon to render is decided from this component's own
74
+ * `indeterminate` prop (a plain ternary), not a CSS toggle keyed off Base
75
+ * UI's data attributes — simpler and directly assertable in tests, and
76
+ * `Checkbox.Indicator` already only mounts while `checked || indeterminate`
77
+ * (see `CheckboxIndicator.mjs`'s `rendered` check), so no extra
78
+ * checked-conditional is needed beyond that ternary.
79
+ *
80
+ * States: `data-checked`/`data-unchecked`/`data-indeterminate` and
81
+ * `data-disabled` are Base UI's own presence attributes on `Checkbox.Root`'s
82
+ * rendered `<span>` (see `CheckboxRootDataAttributes` in node_modules —
83
+ * boolean presence, not a `data-state="checked"` value pair like Radix),
84
+ * matched here via Tailwind's `data-[checked]:` / `data-[indeterminate]:` /
85
+ * `data-[disabled]:` arbitrary-attribute variants — the same convention
86
+ * `radio-group.tsx` established. There is no native `:disabled` pseudo-class
87
+ * to reach for (confirmed against `useFocusableWhenDisabled.mjs`: a
88
+ * non-native-button Base UI root gets `aria-disabled`, not a real `disabled`
89
+ * DOM attribute, when disabled — and `:disabled` only ever matches elements
90
+ * that support the HTML `disabled` attribute natively), so the
91
+ * opacity/pointer-events disabled recipe from conventions §4 is re-expressed
92
+ * against `data-[disabled]:` instead of `disabled:`. Focus, per conventions
93
+ * §4, stays the generic offset-ring recipe (not the field bottom-accent
94
+ * variant — a checkbox is a selectable control, not a typed-into field).
95
+ * `aria-invalid` is a bare ARIA attribute Base UI passes through untouched
96
+ * (not in `CheckboxRoot`'s destructured prop list), so the standard
97
+ * `aria-invalid:` recipe applies as-is.
98
+ */
99
+ declare function Checkbox({ className, indeterminate, ...props }: ComponentProps<typeof Checkbox$1.Root>): react.JSX.Element;
100
+
101
+ export { Checkbox };
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
4
+ import { CheckmarkFilled, SubtractRegular } from "@fluentui/react-icons";
5
+ import { cn } from "../../lib/utils.js";
6
+ function Checkbox({
7
+ className,
8
+ indeterminate = false,
9
+ ...props
10
+ }) {
11
+ return /* @__PURE__ */ jsx(
12
+ CheckboxPrimitive.Root,
13
+ {
14
+ "data-slot": "checkbox",
15
+ indeterminate,
16
+ className: cn(
17
+ // layout — Fluent 16px box, small radius (Fluent borderRadiusSmall)
18
+ "aspect-square size-4 shrink-0 cursor-pointer rounded-sm border bg-background",
19
+ "inline-flex items-center justify-center",
20
+ // motion
21
+ "outline-none transition-colors duration-fast ease-ease",
22
+ // unchecked — Fluent NeutralStrokeAccessible rest/hover/pressed ramp
23
+ "border-stroke-accessible hover:border-stroke-accessible-hover active:border-stroke-accessible-pressed",
24
+ // checked / indeterminate — brand fill (Base UI presence attributes)
25
+ "data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground",
26
+ "data-[indeterminate]:border-primary data-[indeterminate]:bg-primary data-[indeterminate]:text-primary-foreground",
27
+ // checked interaction ramp — Fluent CompoundBrandBackground Hover/
28
+ // Pressed (border + fill together); dark hover brightens to brand-80
29
+ // per Button's per-theme ramp. Outranks the neutral hover/active
30
+ // border above by specificity so the checked box stays brand.
31
+ "data-[checked]:hover:border-brand-70 data-[checked]:hover:bg-brand-70 data-[checked]:active:border-brand-60 data-[checked]:active:bg-brand-60 dark:data-[checked]:hover:border-brand-80 dark:data-[checked]:hover:bg-brand-80",
32
+ // disabled — opacity-based (conventions §4), re-expressed against
33
+ // Base UI's data-disabled presence attribute (this renders a
34
+ // <span>, not a native disableable element — see doc comment)
35
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
36
+ // focus — generic offset-ring recipe (conventions §4)
37
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
38
+ // invalid — shadcn aria-invalid treatment (conventions §4)
39
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
40
+ className
41
+ ),
42
+ ...props,
43
+ children: /* @__PURE__ */ jsx(
44
+ CheckboxPrimitive.Indicator,
45
+ {
46
+ "data-slot": "checkbox-indicator",
47
+ className: "flex items-center justify-center",
48
+ children: indeterminate ? /* @__PURE__ */ jsx(SubtractRegular, { "aria-hidden": "true", className: "size-3" }) : /* @__PURE__ */ jsx(CheckmarkFilled, { "aria-hidden": "true", className: "size-3" })
49
+ }
50
+ )
51
+ }
52
+ );
53
+ }
54
+ export {
55
+ Checkbox
56
+ };
57
+ //# sourceMappingURL=checkbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/checkbox.tsx"],"sourcesContent":["\"use client\";\n\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\";\nimport { CheckmarkFilled, SubtractRegular } from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Checkbox — Fluent 2-styled, shadcn-API checkbox.\n *\n * This is a Base UI case (conventions §9): the checked/unchecked/indeterminate\n * state machine, the hidden native `<input type=\"checkbox\">` for real form\n * participation, and keyboard activation (Space/Enter) are genuine behavior a\n * styled `<span>` can't express on its own, so this wraps\n * `@base-ui/react/checkbox` (`Checkbox`, imported as a namespace exposing\n * `Checkbox.Root` / `Checkbox.Indicator` — matches the actual\n * `export * as Checkbox from \"./index.parts.js\"` shape in node_modules, the\n * same pattern `avatar.tsx` and `radio-group.tsx` use for their own Base UI\n * subpaths). `Checkbox.Root` renders a `<span role=\"checkbox\">` plus a\n * visually-hidden native `<input type=\"checkbox\">` sibling for form\n * submission; none of that is reimplemented here.\n *\n * `\"use client\"` is required here — integration-pass correction of the\n * original reasoning below. `@base-ui/react/checkbox`'s `CheckboxRoot` and\n * `CheckboxIndicator` do each carry their own `'use client'` directive, so\n * *those* parts alone wouldn't force this file to be a client boundary\n * (same reasoning as `avatar.tsx` / `radio-group.tsx`). But\n * `CheckmarkFilled`/`SubtractRegular` from `@fluentui/react-icons` don't hold\n * to that pattern: the package's shared icon-sizing module\n * (`createFluentIcon.styles.js`) calls `@griffel/react`'s `__styles()` at\n * module scope *without* its own `'use client'` directive, even though\n * `__styles` itself is client-only. Rendering either icon from a Server\n * Component pulls that module into the server's RSC graph, and `next build`\n * (Turbopack) fails collecting page data with \"Attempted to call __styles()\n * from the server but __styles is on the client\" — reproduced against\n * `@fluentui/react-icons@2.0.333` / `@griffel/react@1.7.5`, and confirmed to\n * affect every route that shares Turbopack's chunk for these icons, not just\n * this one. `\"use client\"` here keeps the icon imports inside a client\n * boundary so they're never evaluated on the server. `select.tsx` carries the\n * same fix for the same reason.\n *\n * Box: Fluent 2's checkbox indicator is 16px square (`size-4`) with\n * `borderRadius: tokens.borderRadiusSmall` (confirmed against Fluent UI v9's\n * `useCheckboxStyles.styles.ts` — `indicatorSizeMedium = '16px'`,\n * `borderRadius: tokens.borderRadiusSmall`, checkmark `fontSize: '12px'`).\n * `borderRadiusSmall` is Fluent's 2px step, which is exactly this kit's\n * `rounded-sm` (`--radius-sm: calc(var(--radius) - 4px)` = 6px − 4px = 2px in\n * `tokens.css`) — no new radius token needed. `aspect-square` guards the box\n * against flex-row squish when paired with a `Label` (mirrors\n * `radio-group.tsx`'s `RadioGroupItem`).\n *\n * Borders + interaction ramp: the unchecked box outlines with Fluent's\n * `NeutralStrokeAccessible` ramp — rest `#616161` (`border-stroke-accessible`),\n * hover `#575757` (`border-stroke-accessible-hover`), pressed `#4d4d4d`\n * (`border-stroke-accessible-pressed`) — the higher-contrast neutral Fluent\n * specs for interactive control outlines (extracted from the Figma checkbox),\n * NOT the lighter `--border`/`border-input` grey the box previously used. When\n * checked/indeterminate the box fills brand, and its border + fill step\n * through Fluent's `CompoundBrandBackground` on hover/press (`brand-70` /\n * `brand-60`; dark hover `brand-80`), mirroring Button's documented per-theme\n * brand ramp (conventions §4). The `data-[checked]:hover:`/`:active:`\n * selectors outrank the neutral hover/active border by specificity, so a\n * checked box stays brand (never the neutral grey) while hovered.\n *\n * Checked glyph: `CheckmarkFilled` from `@fluentui/react-icons` at `size-3`\n * (12px, matching Fluent's checkmark `fontSize`) — a real glyph is\n * appropriate here (unlike Radio's plain dot) because Fluent's checkbox\n * indicator specifically shows a checkmark, not just a filled shape.\n *\n * Indeterminate: Base UI's `indeterminate` prop (forwarded to `Checkbox.Root`\n * so it drives `aria-checked=\"mixed\"` and the `data-indeterminate` attribute\n * Base UI uses for its own state) renders a dash instead of a checkmark. The\n * spec offered a choice between `SubtractRegular` and a hand-drawn 8×2 bar;\n * `SubtractRegular` is used since `@fluentui/react-icons` ships that exact\n * glyph and pulling in an icon dependency is already paid for by the checked\n * state. Which icon to render is decided from this component's own\n * `indeterminate` prop (a plain ternary), not a CSS toggle keyed off Base\n * UI's data attributes — simpler and directly assertable in tests, and\n * `Checkbox.Indicator` already only mounts while `checked || indeterminate`\n * (see `CheckboxIndicator.mjs`'s `rendered` check), so no extra\n * checked-conditional is needed beyond that ternary.\n *\n * States: `data-checked`/`data-unchecked`/`data-indeterminate` and\n * `data-disabled` are Base UI's own presence attributes on `Checkbox.Root`'s\n * rendered `<span>` (see `CheckboxRootDataAttributes` in node_modules —\n * boolean presence, not a `data-state=\"checked\"` value pair like Radix),\n * matched here via Tailwind's `data-[checked]:` / `data-[indeterminate]:` /\n * `data-[disabled]:` arbitrary-attribute variants — the same convention\n * `radio-group.tsx` established. There is no native `:disabled` pseudo-class\n * to reach for (confirmed against `useFocusableWhenDisabled.mjs`: a\n * non-native-button Base UI root gets `aria-disabled`, not a real `disabled`\n * DOM attribute, when disabled — and `:disabled` only ever matches elements\n * that support the HTML `disabled` attribute natively), so the\n * opacity/pointer-events disabled recipe from conventions §4 is re-expressed\n * against `data-[disabled]:` instead of `disabled:`. Focus, per conventions\n * §4, stays the generic offset-ring recipe (not the field bottom-accent\n * variant — a checkbox is a selectable control, not a typed-into field).\n * `aria-invalid` is a bare ARIA attribute Base UI passes through untouched\n * (not in `CheckboxRoot`'s destructured prop list), so the standard\n * `aria-invalid:` recipe applies as-is.\n */\nfunction Checkbox({\n className,\n indeterminate = false,\n ...props\n}: ComponentProps<typeof CheckboxPrimitive.Root>) {\n return (\n <CheckboxPrimitive.Root\n data-slot=\"checkbox\"\n indeterminate={indeterminate}\n className={cn(\n // layout — Fluent 16px box, small radius (Fluent borderRadiusSmall)\n \"aspect-square size-4 shrink-0 cursor-pointer rounded-sm border bg-background\",\n \"inline-flex items-center justify-center\",\n // motion\n \"outline-none transition-colors duration-fast ease-ease\",\n // unchecked — Fluent NeutralStrokeAccessible rest/hover/pressed ramp\n \"border-stroke-accessible hover:border-stroke-accessible-hover active:border-stroke-accessible-pressed\",\n // checked / indeterminate — brand fill (Base UI presence attributes)\n \"data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground\",\n \"data-[indeterminate]:border-primary data-[indeterminate]:bg-primary data-[indeterminate]:text-primary-foreground\",\n // checked interaction ramp — Fluent CompoundBrandBackground Hover/\n // Pressed (border + fill together); dark hover brightens to brand-80\n // per Button's per-theme ramp. Outranks the neutral hover/active\n // border above by specificity so the checked box stays brand.\n \"data-[checked]:hover:border-brand-70 data-[checked]:hover:bg-brand-70 data-[checked]:active:border-brand-60 data-[checked]:active:bg-brand-60 dark:data-[checked]:hover:border-brand-80 dark:data-[checked]:hover:bg-brand-80\",\n // disabled — opacity-based (conventions §4), re-expressed against\n // Base UI's data-disabled presence attribute (this renders a\n // <span>, not a native disableable element — see doc comment)\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n // focus — generic offset-ring recipe (conventions §4)\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n // invalid — shadcn aria-invalid treatment (conventions §4)\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n data-slot=\"checkbox-indicator\"\n className=\"flex items-center justify-center\"\n >\n {indeterminate ? (\n <SubtractRegular aria-hidden=\"true\" className=\"size-3\" />\n ) : (\n <CheckmarkFilled aria-hidden=\"true\" className=\"size-3\" />\n )}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n );\n}\n\nexport { Checkbox };\n"],"mappings":";AAgJU;AA9IV,SAAS,YAAY,yBAAyB;AAC9C,SAAS,iBAAiB,uBAAuB;AAGjD,SAAS,UAAU;AAgGnB,SAAS,SAAS;AAAA,EAChB;AAAA,EACA,gBAAgB;AAAA,EAChB,GAAG;AACL,GAAkD;AAChD,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA,WAAW;AAAA;AAAA,QAET;AAAA,QACA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA,QACA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA;AAAA;AAAA;AAAA;AAAA,QAIA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,kBAAkB;AAAA,QAAlB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA,UAET,0BACC,oBAAC,mBAAgB,eAAY,QAAO,WAAU,UAAS,IAEvD,oBAAC,mBAAgB,eAAY,QAAO,WAAU,UAAS;AAAA;AAAA,MAE3D;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,212 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps } from 'react';
3
+ import { Combobox as Combobox$1 } from '@base-ui/react/combobox';
4
+
5
+ /**
6
+ * Combobox — Fluent 2-styled, composable filterable select built on Base UI.
7
+ *
8
+ * This is the REFERENCE for the kit's filter-list family: Multi Select and
9
+ * Command Menu are built by later agents reading THIS file the way the overlay
10
+ * batch read `select.tsx`. Read the divergence notes and extension-point notes
11
+ * below before extending it.
12
+ *
13
+ * It is the typed-into sibling of `Select`: the field is the kit's Input recipe
14
+ * (h-8, `border-input` + `border-b-stroke-accessible`, bottom brand-accent
15
+ * focus) instead of a button trigger, and the flyout is `SelectContent`'s
16
+ * validated popup recipe verbatim (`bg-popover`, `border`, `shadow-16`,
17
+ * `rounded-md`, `min-w-[var(--anchor-width)]`, scale+fade motion on Base UI's
18
+ * `data-starting-style`/`data-ending-style` hooks). Item rows are `SelectItem`'s
19
+ * 32px `data-[highlighted]` rows with the right-side check.
20
+ *
21
+ * ## Why `@base-ui/react/combobox` and NOT `@base-ui/react/autocomplete`
22
+ * Base UI ships BOTH. They share the same AriaCombobox engine but draw the line
23
+ * at *selection*:
24
+ * - **Combobox** has a selection model — `value`/`onValueChange` (the committed
25
+ * selected value, single or `multiple`) that is SEPARATE from
26
+ * `inputValue`/`onInputValueChange` (the transient filter text). Picking an
27
+ * item commits a discrete value; the input filters the list. This is exactly a
28
+ * "filterable select".
29
+ * - **Autocomplete** is `selectionMode: 'none'` — it deliberately omits
30
+ * `selectedValue`/`onValueChange`; the input value IS the only state. It's for
31
+ * free-text-with-suggestions (search boxes), where there is no discrete
32
+ * committed choice.
33
+ * The whole filter-list family (this, Multi Select, Command Menu) selects
34
+ * discrete items, so it wraps **combobox**. A future free-text search field is
35
+ * the one case that would wrap autocomplete instead.
36
+ *
37
+ * ## Base UI mapping (conventions §9)
38
+ * Namespace import of `@base-ui/react/combobox`, matching the export shape in
39
+ * node_modules (like `select.tsx`/`avatar.tsx`). shadcn-style part names mapped
40
+ * onto Base UI's model:
41
+ *
42
+ * | Exported (kit name) | Base UI primitive |
43
+ * | ------------------- | ---------------------------------------------------------- |
44
+ * | `Combobox` | `Combobox.Root` |
45
+ * | `ComboboxInput` | field wrapper + `Combobox.Input` + `Combobox.Clear` + `Combobox.Trigger` (+ `Combobox.Icon`) |
46
+ * | `ComboboxContent` | `Combobox.Portal` + `Combobox.Positioner` + `Combobox.Popup` |
47
+ * | `ComboboxList` | `Combobox.List` ← see divergence 5 |
48
+ * | `ComboboxEmpty` | `Combobox.Empty` |
49
+ * | `ComboboxItem` | `Combobox.Item` (+ `Combobox.ItemIndicator`) |
50
+ * | `ComboboxGroup` | `Combobox.Group` |
51
+ * | `ComboboxLabel` | `Combobox.GroupLabel` ← see divergence 1 |
52
+ * | `ComboboxSeparator` | `Combobox.Separator` |
53
+ * | `ComboboxValue` | `Combobox.Value` ← see divergence 4 |
54
+ *
55
+ * ## Divergences vs Base UI naming (all deliberate)
56
+ * 1. **`ComboboxLabel` maps to `Combobox.GroupLabel`, not `Combobox.Label`.**
57
+ * Base UI has two label parts: `Combobox.Label` labels the whole control (a
58
+ * field `<label>`), `Combobox.GroupLabel` is the heading inside a
59
+ * `Combobox.Group`. Like Select, the kit exposes the *group* heading as
60
+ * `ComboboxLabel` and does NOT re-export the control-level label — pair
61
+ * `ComboboxInput` with the kit's own `Label` component (a separate registry
62
+ * item; install it explicitly). Labeling recipe: give `Label` an `id` and set
63
+ * `aria-labelledby` on the `ComboboxInput` — but unlike Select's button
64
+ * trigger, the input IS a labelable form field, so a plain `htmlFor`/`id`
65
+ * pairing works too (the preview uses `htmlFor` + `id`).
66
+ * 2. **`ComboboxInput` is a composed field, not a bare `<input>`.** It renders a
67
+ * field-chrome wrapper (`data-slot="combobox-input-wrapper"`) around the real
68
+ * `Combobox.Input` (`data-slot="combobox-input"`), plus a `Combobox.Clear`
69
+ * reset button (Base UI auto-unmounts it when the field is empty) and a
70
+ * `Combobox.Trigger` chevron that toggles the popup. This is the sibling of
71
+ * `SelectTrigger`, which likewise bakes its chevron in. `className`, `ref` and
72
+ * all other props forward to the inner `<input>`; the wrapper is styled via
73
+ * `wrapperClassName`. The chevron/clear are `tabIndex={-1}` (not extra tab
74
+ * stops — the input already owns `role="combobox"` + keyboard control).
75
+ * 3. **No `Combobox.Arrow`, no scroll buttons.** The popup is a flyout beneath
76
+ * the field (not an anchored caret-overlap like a native `<select>`), so the
77
+ * positioner arrow and Select's ScrollUp/DownArrow parts are omitted; the
78
+ * `List` scrolls with `overflow-y-auto`.
79
+ * 4. **`ComboboxValue` renders the raw selected value unless `items` is given.**
80
+ * Same model as Select's `SelectValue`: `Combobox.Value` shows the raw
81
+ * `value` unless `<Combobox items={[{ value, label }]}>` is supplied or a
82
+ * render child is used. It is exported for callers who display the selection
83
+ * outside the input; the field itself shows the selection as input text (Base
84
+ * UI fills the input on select), so most single-select UIs never need it.
85
+ * 5. **`ComboboxList` is a separate part (Select auto-wraps its `List`).** Base
86
+ * UI does the filtering, so the natural API passes `items` to `<Combobox>` and
87
+ * renders the list with a render-function child:
88
+ * `<ComboboxList>{(item) => <ComboboxItem value={item}>{item.label}</ComboboxItem>}</ComboboxList>`.
89
+ * `ComboboxEmpty` is a sibling of `ComboboxList` INSIDE `ComboboxContent` (not
90
+ * a `List` child) — it mirrors Base UI's own structure and lets the no-match
91
+ * caption sit at the popup level. Static `ComboboxItem` children also work,
92
+ * but the built-in filter only runs against the `items` prop, so filtering UIs
93
+ * should pass `items` + the render-function form. Unlike `SelectContent`
94
+ * (which owns its `List`), keeping `List` explicit is what makes correct
95
+ * `Empty` placement and the render-function API possible.
96
+ *
97
+ * ## Divergences vs shadcn's Combobox recipe
98
+ * shadcn has NO Combobox component — its docs "Combobox" is a hand-rolled recipe
99
+ * composing `Popover` + `Command` (cmdk) with local `useState`, `open`, and a
100
+ * manually-wired `CommandInput`/`CommandItem`/`onSelect`. This kit instead ships
101
+ * a real `Combobox*` part family:
102
+ * - **Filtering is built in.** Base UI's `Combobox.Root` filters `List` items
103
+ * against the input value automatically (`mode="list"` default); the shadcn
104
+ * recipe leans on cmdk's `Command` to filter. No `cmdk` dependency here.
105
+ * - **One selection model, not two hooks.** shadcn juggles Popover `open` +
106
+ * Command `value` + an external `value` `useState`. Here `value`/`inputValue`
107
+ * are one model on the Root — controlled or uncontrolled — exposed honestly.
108
+ * - **Composable parts, not a monolith.** `Combobox`, `ComboboxInput`,
109
+ * `ComboboxContent`, `ComboboxItem`, … read as the sibling of `Select`, not as
110
+ * a bespoke Popover+Command assembly the consumer re-wires each time.
111
+ *
112
+ * ## Extension points for Multi Select (do NOT style chips here)
113
+ * `multiple` is passed through UNBLOCKED (`Combobox`'s generic second type param,
114
+ * exactly like `Select`). When `multiple` is set, Base UI:
115
+ * - makes `value`/`onValueChange` arrays;
116
+ * - keeps `Combobox.Clear` clearing to `[]`;
117
+ * - exposes `Combobox.Chips` / `Combobox.Chip` / `Combobox.ChipRemove` for a
118
+ * token field, and `Combobox.Row` for grid layouts.
119
+ * The Multi Select agent should build its OWN field part (e.g. `MultiSelectInput`
120
+ * / a chips field) that wraps `Combobox.Chips` around `Combobox.Input` inside the
121
+ * same field-chrome wrapper this file uses — it must NOT reuse `ComboboxInput`,
122
+ * which is the single-line surface. This file intentionally leaves chip styling
123
+ * unspecified; the `combobox-input-wrapper` class string is the shared field
124
+ * recipe to copy. Everything else (`ComboboxContent`, `ComboboxItem`,
125
+ * `ComboboxGroup`, `ComboboxLabel`, `ComboboxEmpty`) is reusable as-is.
126
+ *
127
+ * ## `"use client"` — required
128
+ * `@fluentui/react-icons` forces it: the package's shared icon-sizing module
129
+ * (`createFluentIcon.styles.js`) calls `@griffel/react`'s `__styles()` at module
130
+ * scope without its own `'use client'`, so importing an icon into a Server
131
+ * Component makes `next build` (Turbopack) fail collecting page data. Same fix
132
+ * and root cause as `select.tsx`/`checkbox.tsx` (conventions §9). Every Base UI
133
+ * Combobox part module already carries its own `'use client'`.
134
+ *
135
+ * ## data-slot note
136
+ * `Combobox` (Root) renders no DOM element and its Base UI props type is strict
137
+ * (no `data-*` passthrough), so it carries no `data-slot`. Every part that
138
+ * renders an element does (`combobox-input`, `combobox-content`, `combobox-item`,
139
+ * …) — the styling/testing hook consumers rely on.
140
+ */
141
+ declare function Combobox<Value, Multiple extends boolean | undefined = false>(props: Combobox$1.Root.Props<Value, Multiple>): react.JSX.Element;
142
+ /**
143
+ * Value — displays the selected value (Base UI `Combobox.Value`). Renders the
144
+ * raw value unless `items` is passed to `<Combobox>` or a render child is used
145
+ * (divergence 4). Exported for showing the selection outside the input; the
146
+ * field itself already reflects the selection as input text.
147
+ */
148
+ declare function ComboboxValue(props: ComponentProps<typeof Combobox$1.Value>): react.JSX.Element;
149
+ /**
150
+ * Input — the Fluent single-line combobox field (divergence 2). A field-chrome
151
+ * wrapper holds the real `<input>` (transparent, fills the field), a `Clear`
152
+ * reset button (auto-unmounts when empty) and a chevron `Trigger`. The wrapper
153
+ * carries the kit Input recipe: h-8, `rounded-md`, `border-input` +
154
+ * `border-b-stroke-accessible` at rest, and the Fluent bottom brand accent on
155
+ * focus via an inset box-shadow (no reflow) — keyed off `has-[input:focus-visible]`
156
+ * because focus lands on the inner input, not the wrapper. `aria-invalid` on the
157
+ * input swaps the field to destructive, declared after focus so it wins.
158
+ * `className`/`ref`/`...props` forward to the inner `<input>`; style the wrapper
159
+ * with `wrapperClassName`.
160
+ */
161
+ declare function ComboboxInput({ className, wrapperClassName, ...props }: ComponentProps<typeof Combobox$1.Input> & {
162
+ wrapperClassName?: string;
163
+ }): react.JSX.Element;
164
+ /**
165
+ * Content — the floating flyout: `Portal` → `Positioner` → `Popup`. Surface is
166
+ * `SelectContent`'s validated recipe verbatim: `bg-popover` + `border` +
167
+ * `shadow-16` (Fluent flyout elevation), `rounded-md`, min-width matched to the
168
+ * field via Base UI's `--anchor-width`, and the scale+fade open/close animation
169
+ * on `data-starting-style`/`data-ending-style` with token durations/easings.
170
+ * `transition-[opacity,scale]` (never transform-based — conventions §3.5).
171
+ * Children are placed directly in the popup: a `ComboboxEmpty` (no-match
172
+ * caption) and a `ComboboxList` (the scroll container). See divergence 5.
173
+ */
174
+ declare function ComboboxContent({ className, children, sideOffset, align, side, ...props }: ComponentProps<typeof Combobox$1.Popup> & Pick<ComponentProps<typeof Combobox$1.Positioner>, "side" | "align" | "sideOffset">): react.JSX.Element;
175
+ /**
176
+ * List — the scrollable options container (Base UI `Combobox.List`, divergence
177
+ * 5). `children` may be a render function `(item, index) => …` (the filtering
178
+ * API: pass `items` to `<Combobox>` and map each) or static `ComboboxItem`
179
+ * children. Scrolls at `overflow-y-auto`; capped at `--available-height` or 20rem.
180
+ */
181
+ declare function ComboboxList({ className, ...props }: ComponentProps<typeof Combobox$1.List>): react.JSX.Element;
182
+ /**
183
+ * Empty — the no-match state (Base UI `Combobox.Empty`). Base UI renders its
184
+ * children ONLY when the filtered list is empty, and its root element must stay
185
+ * mounted to announce (it carries `role="presentation"` + a live region), so
186
+ * always place a `<ComboboxEmpty>` in the content rather than conditionally
187
+ * rendering it. Muted, centered caption.
188
+ */
189
+ declare function ComboboxEmpty({ className, ...props }: ComponentProps<typeof Combobox$1.Empty>): react.JSX.Element;
190
+ /**
191
+ * Item — one option (Base UI `Combobox.Item`, renders a `<div>` with
192
+ * `role="option"`). `value` selects it; the selected-state check
193
+ * (`CheckmarkRegular`) is absolutely positioned on the right inside
194
+ * `ItemIndicator`, which Base UI only mounts for the selected item. 32px row,
195
+ * 4px list radius, highlight (keyboard focus or hover) via `data-highlighted` →
196
+ * `bg-accent`, disabled items muted + inert — the `SelectItem` recipe.
197
+ */
198
+ declare function ComboboxItem({ className, children, ...props }: ComponentProps<typeof Combobox$1.Item>): react.JSX.Element;
199
+ /**
200
+ * Group — wraps a set of items under a `ComboboxLabel` heading (Base UI
201
+ * `Combobox.Group`). With Base UI's built-in filtering, pass grouped `items` to
202
+ * `<Combobox>` so empty groups drop out automatically; see the preview.
203
+ */
204
+ declare function ComboboxGroup({ className, ...props }: ComponentProps<typeof Combobox$1.Group>): react.JSX.Element;
205
+ /**
206
+ * Label — a heading for a `ComboboxGroup` (Base UI `Combobox.GroupLabel`), NOT a
207
+ * control-level field label (divergence 1). Small muted caption, Fluent style.
208
+ */
209
+ declare function ComboboxLabel({ className, ...props }: ComponentProps<typeof Combobox$1.GroupLabel>): react.JSX.Element;
210
+ declare function ComboboxSeparator({ className, ...props }: ComponentProps<typeof Combobox$1.Separator>): react.JSX.Element;
211
+
212
+ export { Combobox, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxList, ComboboxSeparator, ComboboxValue };
@@ -0,0 +1,236 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox";
4
+ import {
5
+ CheckmarkRegular,
6
+ ChevronDownRegular,
7
+ DismissRegular
8
+ } from "@fluentui/react-icons";
9
+ import { cn } from "../../lib/utils.js";
10
+ function Combobox(props) {
11
+ return /* @__PURE__ */ jsx(ComboboxPrimitive.Root, { ...props });
12
+ }
13
+ function ComboboxValue(props) {
14
+ return /* @__PURE__ */ jsx(ComboboxPrimitive.Value, { "data-slot": "combobox-value", ...props });
15
+ }
16
+ function ComboboxInput({
17
+ className,
18
+ wrapperClassName,
19
+ ...props
20
+ }) {
21
+ return /* @__PURE__ */ jsxs(
22
+ "div",
23
+ {
24
+ "data-slot": "combobox-input-wrapper",
25
+ className: cn(
26
+ // layout — Fluent medium field, 32px, matches Input. Resting bottom edge
27
+ // uses the darker NeutralStrokeAccessible (#616161) accent; the other
28
+ // sides stay border-input. Focus (border-primary + inset underline) and
29
+ // aria-invalid (border-destructive) both override it.
30
+ "flex h-8 w-full items-center rounded-md border border-input border-b-stroke-accessible bg-background text-sm",
31
+ // motion — color + box-shadow so the focus accent animates (§4)
32
+ "transition-[color,box-shadow] duration-fast ease-ease",
33
+ // focus — Fluent bottom brand accent via inset box-shadow, no reflow (§4).
34
+ // has-[input:focus-visible] because focus is on the inner input; text
35
+ // inputs match :focus-visible on pointer focus too, so this covers both.
36
+ "has-[input:focus-visible]:border-primary has-[input:focus-visible]:shadow-[inset_0_-2px_0_0_var(--brand-80)] dark:has-[input:focus-visible]:shadow-[inset_0_-2px_0_0_var(--brand-100)]",
37
+ // invalid — shadcn aria-invalid treatment; after focus so it wins the border
38
+ "has-[input[aria-invalid='true']]:border-destructive has-[input[aria-invalid='true']]:ring-destructive/20 dark:has-[input[aria-invalid='true']]:ring-destructive/40",
39
+ "has-[input[aria-invalid='true']:focus-visible]:shadow-[inset_0_-2px_0_0_var(--destructive)]",
40
+ // disabled — opacity read when the whole control is disabled
41
+ "has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50",
42
+ wrapperClassName
43
+ ),
44
+ children: [
45
+ /* @__PURE__ */ jsx(
46
+ ComboboxPrimitive.Input,
47
+ {
48
+ "data-slot": "combobox-input",
49
+ className: cn(
50
+ "h-full min-w-0 flex-1 bg-transparent px-3 py-1 outline-none",
51
+ "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
52
+ className
53
+ ),
54
+ ...props
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx(
58
+ ComboboxPrimitive.Clear,
59
+ {
60
+ "data-slot": "combobox-clear",
61
+ "aria-label": "Clear",
62
+ tabIndex: -1,
63
+ className: cn(
64
+ "mr-0.5 flex size-6 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none",
65
+ "transition-colors duration-fast ease-ease hover:text-foreground",
66
+ "focus-visible:ring-2 focus-visible:ring-ring",
67
+ "[&_svg]:pointer-events-none [&_svg]:size-4"
68
+ ),
69
+ children: /* @__PURE__ */ jsx(DismissRegular, {})
70
+ }
71
+ ),
72
+ /* @__PURE__ */ jsx(
73
+ ComboboxPrimitive.Trigger,
74
+ {
75
+ "data-slot": "combobox-trigger",
76
+ "aria-label": "Show options",
77
+ tabIndex: -1,
78
+ className: cn(
79
+ "mr-2 flex shrink-0 items-center text-muted-foreground outline-none",
80
+ "[&_svg]:pointer-events-none [&_svg]:size-4"
81
+ ),
82
+ children: /* @__PURE__ */ jsx(ComboboxPrimitive.Icon, { "data-slot": "combobox-icon", children: /* @__PURE__ */ jsx(ChevronDownRegular, {}) })
83
+ }
84
+ )
85
+ ]
86
+ }
87
+ );
88
+ }
89
+ function ComboboxContent({
90
+ className,
91
+ children,
92
+ sideOffset = 4,
93
+ align = "start",
94
+ side = "bottom",
95
+ ...props
96
+ }) {
97
+ return /* @__PURE__ */ jsx(ComboboxPrimitive.Portal, { children: /* @__PURE__ */ jsx(
98
+ ComboboxPrimitive.Positioner,
99
+ {
100
+ "data-slot": "combobox-positioner",
101
+ sideOffset,
102
+ align,
103
+ side,
104
+ className: "z-50 outline-none",
105
+ children: /* @__PURE__ */ jsx(
106
+ ComboboxPrimitive.Popup,
107
+ {
108
+ "data-slot": "combobox-content",
109
+ className: cn(
110
+ "relative min-w-[var(--anchor-width)] origin-[var(--transform-origin)] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-16 outline-none",
111
+ // motion — subtle scale + fade on open (enter) / close (exit)
112
+ "transition-[opacity,scale] duration-fast ease-decelerate-mid",
113
+ "data-starting-style:scale-95 data-starting-style:opacity-0",
114
+ "data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid",
115
+ className
116
+ ),
117
+ ...props,
118
+ children
119
+ }
120
+ )
121
+ }
122
+ ) });
123
+ }
124
+ function ComboboxList({
125
+ className,
126
+ ...props
127
+ }) {
128
+ return /* @__PURE__ */ jsx(
129
+ ComboboxPrimitive.List,
130
+ {
131
+ "data-slot": "combobox-list",
132
+ className: cn(
133
+ "max-h-[min(var(--available-height),20rem)] overflow-y-auto overscroll-contain p-1",
134
+ className
135
+ ),
136
+ ...props
137
+ }
138
+ );
139
+ }
140
+ function ComboboxEmpty({
141
+ className,
142
+ ...props
143
+ }) {
144
+ return /* @__PURE__ */ jsx(
145
+ ComboboxPrimitive.Empty,
146
+ {
147
+ "data-slot": "combobox-empty",
148
+ className: cn("py-6 text-center text-sm text-muted-foreground", className),
149
+ ...props
150
+ }
151
+ );
152
+ }
153
+ function ComboboxItem({
154
+ className,
155
+ children,
156
+ ...props
157
+ }) {
158
+ return /* @__PURE__ */ jsxs(
159
+ ComboboxPrimitive.Item,
160
+ {
161
+ "data-slot": "combobox-item",
162
+ className: cn(
163
+ // layout — Fluent 32px row, 4px list radius, room on the right for check
164
+ "relative flex h-8 w-full cursor-default items-center rounded-md pr-8 pl-2 text-sm outline-none select-none",
165
+ // rest text is NeutralForeground2, darkening to accent-foreground on
166
+ // highlight (Figma validation: Fluent list rows rest at #424242)
167
+ "text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
168
+ // disabled item — muted + non-interactive
169
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
170
+ // icons a consumer puts inside the item text
171
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
172
+ className
173
+ ),
174
+ ...props,
175
+ children: [
176
+ /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ jsx(ComboboxPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckmarkRegular, { className: "size-4" }) }) }),
177
+ children
178
+ ]
179
+ }
180
+ );
181
+ }
182
+ function ComboboxGroup({
183
+ className,
184
+ ...props
185
+ }) {
186
+ return /* @__PURE__ */ jsx(
187
+ ComboboxPrimitive.Group,
188
+ {
189
+ "data-slot": "combobox-group",
190
+ className: cn(className),
191
+ ...props
192
+ }
193
+ );
194
+ }
195
+ function ComboboxLabel({
196
+ className,
197
+ ...props
198
+ }) {
199
+ return /* @__PURE__ */ jsx(
200
+ ComboboxPrimitive.GroupLabel,
201
+ {
202
+ "data-slot": "combobox-label",
203
+ className: cn(
204
+ "px-2 py-1.5 text-xs font-medium text-muted-foreground select-none",
205
+ className
206
+ ),
207
+ ...props
208
+ }
209
+ );
210
+ }
211
+ function ComboboxSeparator({
212
+ className,
213
+ ...props
214
+ }) {
215
+ return /* @__PURE__ */ jsx(
216
+ ComboboxPrimitive.Separator,
217
+ {
218
+ "data-slot": "combobox-separator",
219
+ className: cn("pointer-events-none -mx-1 my-1 h-px bg-border", className),
220
+ ...props
221
+ }
222
+ );
223
+ }
224
+ export {
225
+ Combobox,
226
+ ComboboxContent,
227
+ ComboboxEmpty,
228
+ ComboboxGroup,
229
+ ComboboxInput,
230
+ ComboboxItem,
231
+ ComboboxLabel,
232
+ ComboboxList,
233
+ ComboboxSeparator,
234
+ ComboboxValue
235
+ };
236
+ //# sourceMappingURL=combobox.js.map