@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/dropdown-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\";\nimport { CheckmarkRegular, ChevronRightRegular } from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * DropdownMenu — Fluent 2-styled, shadcn-API dropdown menu.\n *\n * Second overlay component in the kit, so it deliberately reuses every pattern\n * `select.tsx` established (portal + positioner + popup surface, `shadow-16`\n * flyout elevation, 32px `data-highlighted` item rows, scale+fade motion via\n * `data-starting-style`/`data-ending-style`). Its popup surface and item rows\n * are visually identical to Select's — same tokens, same radii.\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — focus management, portalling, open/close state, roving tabindex,\n * type-ahead, collision-aware positioning, submenu orchestration — genuinely\n * needs a primitive, so the parts wrap `@base-ui/react/menu` (namespace import,\n * matching the actual export shape in node_modules, exactly like `select.tsx`).\n * shadcn part names are mapped onto Base UI's model:\n *\n * | Exported (shadcn name) | Base UI primitive |\n * | -------------------------- | ------------------------------------------------------ |\n * | `DropdownMenu` | `Menu.Root` |\n * | `DropdownMenuTrigger` | `Menu.Trigger` |\n * | `DropdownMenuContent` | `Menu.Portal` + `Menu.Positioner` + `Menu.Popup` |\n * | `DropdownMenuGroup` | `Menu.Group` |\n * | `DropdownMenuLabel` | `Menu.GroupLabel` ← see divergence 1 |\n * | `DropdownMenuItem` | `Menu.Item` |\n * | `DropdownMenuCheckboxItem` | `Menu.CheckboxItem` (+ `Menu.CheckboxItemIndicator`) |\n * | `DropdownMenuRadioGroup` | `Menu.RadioGroup` |\n * | `DropdownMenuRadioItem` | `Menu.RadioItem` (+ `Menu.RadioItemIndicator`) |\n * | `DropdownMenuSeparator` | `Menu.Separator` |\n * | `DropdownMenuShortcut` | plain `<span>` (no Base UI part) ← see divergence 4 |\n * | `DropdownMenuSub` | `Menu.SubmenuRoot` ← see divergence 2 |\n * | `DropdownMenuSubTrigger` | `Menu.SubmenuTrigger` (+ `ChevronRightRegular`) |\n * | `DropdownMenuSubContent` | `Menu.Portal` + `Menu.Positioner` + `Menu.Popup` |\n *\n * ## Divergences from the shadcn/Radix DropdownMenu API (all deliberate)\n * 1. **`DropdownMenuLabel` maps to Base UI `Menu.GroupLabel`.** Like `select.tsx`,\n * the label part is the heading *inside* a `DropdownMenuGroup` (Base UI\n * associates it with the group for screen readers). It is styled to match\n * `SelectLabel` (muted `text-xs` caption) rather than shadcn's darker\n * `text-sm`, keeping every group heading in the kit visually consistent. It\n * still honours shadcn's `inset` prop (`data-[inset]:pl-8`).\n * 2. **`DropdownMenuSub` is Base UI `Menu.SubmenuRoot`, not a Radix `Sub`.** Base\n * UI models a submenu as its own nested root; `DropdownMenuSubContent`\n * therefore re-composes `Portal` → `Positioner` → `Popup` (Radix's\n * `SubContent` is a single part). Submenus open to the side and Base UI\n * positions them collision-aware; the trigger stays highlighted while its\n * submenu is open via `data-[popup-open]`.\n * 3. **Composition uses Base UI's `render` prop, not `asChild`.** shadcn's\n * `<DropdownMenuTrigger asChild>` has no analogue here — Base UI parts take a\n * `render` prop for polymorphism. To use the kit `Button` as the trigger,\n * pass `render={<Button>…</Button>}`. The wrapper forwards `render` straight\n * through, so the honest Base UI model is preserved (same reasoning as\n * `select.tsx` keeping `SelectValue`'s render-function child).\n * 4. **`DropdownMenuShortcut` is a plain `<span>`** (shadcn parity — it is not a\n * Base UI part, just a right-aligned muted caption for a keyboard hint).\n * 5. **`DropdownMenuItem` keeps shadcn's `inset` + `variant` props.** `variant`\n * is surfaced as `data-variant` (mirrors `Button`'s `data-variant` hook); the\n * `\"destructive\"` value tints the row and its icons with the `destructive`\n * token (never a hardcoded red), and its highlight uses `destructive/10`\n * (`/20` in dark) so the destructive intent survives the hover state.\n *\n * ## `\"use client\"` — required (same root cause as `select.tsx`)\n * Every Base UI Menu part module carries its own `'use client'`, so on that\n * basis this wrapper could stay server-renderable. But `CheckmarkRegular` /\n * `ChevronRightRegular` from `@fluentui/react-icons` force the directive: 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'`, even though `__styles` is client-only. Rendering any of these\n * icons from a Server Component pulls that module into the server's RSC graph,\n * and `next build` (Turbopack) fails collecting page data with \"Attempted to\n * call __styles() from the server but __styles is on the client\" — reproduced\n * against `@fluentui/react-icons@2.0.333` / `@griffel/react@1.7.5`, and it\n * poisons every route sharing Turbopack's chunk for those icons. `\"use client\"`\n * here keeps the icon imports inside a client boundary. `select.tsx` and\n * `checkbox.tsx` carry the same fix (their doc comments explain it inline).\n *\n * ## data-slot note\n * `DropdownMenu` (Root) and `DropdownMenuSub` (SubmenuRoot) render no DOM element\n * of their own, so they carry no `data-slot`. Every part that renders an element\n * does — `dropdown-menu-trigger`, `dropdown-menu-content`, `dropdown-menu-item`,\n * etc. — which is what tests/consumers hook onto.\n */\n\nfunction DropdownMenu<Payload>(props: MenuPrimitive.Root.Props<Payload>) {\n return <MenuPrimitive.Root {...props} />;\n}\n\nfunction DropdownMenuTrigger({\n className,\n ...props\n}: ComponentProps<typeof MenuPrimitive.Trigger>) {\n return (\n <MenuPrimitive.Trigger\n data-slot=\"dropdown-menu-trigger\"\n className={cn(className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuGroup({\n className,\n ...props\n}: ComponentProps<typeof MenuPrimitive.Group>) {\n return (\n <MenuPrimitive.Group\n data-slot=\"dropdown-menu-group\"\n className={cn(className)}\n {...props}\n />\n );\n}\n\n/**\n * Content — the floating flyout: `Portal` → `Positioner` → `Popup`. Surface is\n * `bg-popover` + `border` + `shadow-16` (Fluent flyout elevation, conventions\n * §3.6), `rounded-md`, `min-w-[8rem]` (menus size to content, unlike Select\n * which matches the anchor width). A subtle scale+fade open/close animation\n * rides Base UI's `data-starting-style`/`data-ending-style` hooks with token\n * durations/easings — the exact lines from `SelectContent`.\n */\nfunction DropdownMenuContent({\n className,\n sideOffset = 4,\n align = \"start\",\n side = \"bottom\",\n ...props\n}: ComponentProps<typeof MenuPrimitive.Popup> &\n Pick<\n ComponentProps<typeof MenuPrimitive.Positioner>,\n \"side\" | \"align\" | \"sideOffset\"\n >) {\n return (\n <MenuPrimitive.Portal>\n <MenuPrimitive.Positioner\n data-slot=\"dropdown-menu-positioner\"\n sideOffset={sideOffset}\n align={align}\n side={side}\n className=\"z-50 outline-none\"\n >\n <MenuPrimitive.Popup\n data-slot=\"dropdown-menu-content\"\n className={cn(\n \"min-w-[8rem] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground shadow-16 outline-none\",\n \"max-h-[var(--available-height)]\",\n // motion — subtle scale + fade on open (enter) / close (exit)\n \"transition-[opacity,scale] duration-fast ease-decelerate-mid\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n \"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n className\n )}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n );\n}\n\n/**\n * Label — a heading for a `DropdownMenuGroup` (Base UI `Menu.GroupLabel`, see\n * divergence 1). Small muted caption, matched to `SelectLabel`. `inset` shifts\n * it to line up with items that carry a leading indicator.\n */\nfunction DropdownMenuLabel({\n className,\n inset,\n ...props\n}: ComponentProps<typeof MenuPrimitive.GroupLabel> & { inset?: boolean }) {\n return (\n <MenuPrimitive.GroupLabel\n data-slot=\"dropdown-menu-label\"\n data-inset={inset || undefined}\n className={cn(\n // Fluent Menu section header — Caption 1 Stronger (12px bold) in\n // NeutralForeground2, per the Figma \"Menu section\" symbol; deliberately\n // heavier than SelectLabel's muted caption (Figma validation pass 2).\n \"px-2 py-1.5 text-xs font-bold text-foreground-2 select-none\",\n \"data-[inset]:pl-8\",\n className\n )}\n {...props}\n />\n );\n}\n\n/**\n * Item — one command row. 32px tall (`h-8`) with the Fluent 4px list radius,\n * matching `SelectItem`. Highlight (keyboard focus or hover) rides Base UI's\n * `data-highlighted` → `bg-accent`/`text-accent-foreground`. `inset` adds the\n * leading gutter used by checkbox/radio rows; `variant=\"destructive\"` tints the\n * text/icons with the `destructive` token and gives the highlight a destructive\n * wash. Icons follow `SelectItem`'s size-only rules (no muting).\n */\nfunction DropdownMenuItem({\n className,\n inset,\n variant = \"default\",\n ...props\n}: ComponentProps<typeof MenuPrimitive.Item> & {\n inset?: boolean;\n variant?: \"default\" | \"destructive\";\n}) {\n return (\n <MenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset || undefined}\n data-variant={variant}\n className={cn(\n // layout — Fluent 32px row, 4px list radius\n \"relative flex h-8 cursor-default items-center gap-2 rounded-md px-2 text-sm outline-none select-none\",\n // rest text is NeutralForeground2, darkening to accent-foreground on\n // highlight (Figma validation: Fluent list rows rest at #424242)\n \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n // disabled item — muted + non-interactive\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n // inset — leading gutter to align with indicator rows\n \"data-[inset]:pl-8\",\n // destructive variant — token-tinted text, icons, and highlight wash\n \"data-[variant=destructive]:text-destructive\",\n \"data-[variant=destructive]:data-[highlighted]:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:text-destructive dark:data-[variant=destructive]:data-[highlighted]:bg-destructive/20\",\n \"data-[variant=destructive]:[&_svg:not([class*='text-'])]:text-destructive\",\n // icons — size only (matches SelectItem)\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\n/**\n * CheckboxItem — a toggleable row. The check (`CheckmarkRegular`) sits in a\n * left gutter (`pl-8`), inside `Menu.CheckboxItemIndicator`, which Base UI only\n * mounts while checked. Base UI keeps the menu open on toggle (its\n * `closeOnClick` defaults to `false` for checkbox items).\n */\nfunction DropdownMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: ComponentProps<typeof MenuPrimitive.CheckboxItem>) {\n return (\n <MenuPrimitive.CheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n checked={checked}\n className={cn(\n \"relative flex h-8 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none select-none\",\n \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex size-4 items-center justify-center\">\n <MenuPrimitive.CheckboxItemIndicator>\n <CheckmarkRegular className=\"size-4\" />\n </MenuPrimitive.CheckboxItemIndicator>\n </span>\n {children}\n </MenuPrimitive.CheckboxItem>\n );\n}\n\nfunction DropdownMenuRadioGroup({\n className,\n ...props\n}: ComponentProps<typeof MenuPrimitive.RadioGroup>) {\n return (\n <MenuPrimitive.RadioGroup\n data-slot=\"dropdown-menu-radio-group\"\n className={cn(className)}\n {...props}\n />\n );\n}\n\n/**\n * RadioItem — one option in a `DropdownMenuRadioGroup`. The selected marker is a\n * small filled circle (an inline `<svg>` using `fill-current`, so it inherits\n * the row's text color and needs no extra icon import — same rationale as the\n * kit `RadioGroup` dot). `Menu.RadioItemIndicator` only mounts while selected.\n */\nfunction DropdownMenuRadioItem({\n className,\n children,\n ...props\n}: ComponentProps<typeof MenuPrimitive.RadioItem>) {\n return (\n <MenuPrimitive.RadioItem\n data-slot=\"dropdown-menu-radio-item\"\n className={cn(\n \"relative flex h-8 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none select-none\",\n \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex size-4 items-center justify-center\">\n <MenuPrimitive.RadioItemIndicator>\n <svg viewBox=\"0 0 8 8\" className=\"size-2 fill-current\" aria-hidden>\n <circle cx=\"4\" cy=\"4\" r=\"4\" />\n </svg>\n </MenuPrimitive.RadioItemIndicator>\n </span>\n {children}\n </MenuPrimitive.RadioItem>\n );\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: ComponentProps<typeof MenuPrimitive.Separator>) {\n return (\n <MenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Shortcut — a right-aligned keyboard hint caption. Plain `<span>` (divergence\n * 4), shadcn parity: `ml-auto text-xs tracking-widest text-muted-foreground`.\n */\nfunction DropdownMenuShortcut({\n className,\n ...props\n}: ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn(\n \"ml-auto text-xs tracking-widest text-muted-foreground\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSub(props: MenuPrimitive.SubmenuRoot.Props) {\n return <MenuPrimitive.SubmenuRoot {...props} />;\n}\n\n/**\n * SubTrigger — an item that opens a submenu, with a trailing `ChevronRightRegular`.\n * Same row treatment as `DropdownMenuItem`; stays highlighted while its submenu\n * is open via Base UI's `data-[popup-open]`. Honours `inset`.\n */\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: ComponentProps<typeof MenuPrimitive.SubmenuTrigger> & { inset?: boolean }) {\n return (\n <MenuPrimitive.SubmenuTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset || undefined}\n className={cn(\n \"relative flex h-8 cursor-default items-center gap-2 rounded-md px-2 text-sm outline-none select-none\",\n \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n \"data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n \"data-[inset]:pl-8\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronRightRegular className=\"ml-auto size-4 text-muted-foreground\" />\n </MenuPrimitive.SubmenuTrigger>\n );\n}\n\n/**\n * SubContent — a submenu's floating surface. Same `Portal` → `Positioner` →\n * `Popup` composition and surface as `DropdownMenuContent`, but defaults to\n * opening on the inline-end side (Base UI still flips it collision-aware).\n */\nfunction DropdownMenuSubContent({\n className,\n sideOffset = 4,\n align = \"start\",\n ...props\n}: ComponentProps<typeof MenuPrimitive.Popup> &\n Pick<\n ComponentProps<typeof MenuPrimitive.Positioner>,\n \"side\" | \"align\" | \"sideOffset\"\n >) {\n return (\n <MenuPrimitive.Portal>\n <MenuPrimitive.Positioner\n data-slot=\"dropdown-menu-sub-positioner\"\n sideOffset={sideOffset}\n align={align}\n className=\"z-50 outline-none\"\n >\n <MenuPrimitive.Popup\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(\n \"min-w-[8rem] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground shadow-16 outline-none\",\n \"max-h-[var(--available-height)]\",\n \"transition-[opacity,scale] duration-fast ease-decelerate-mid\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n \"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n className\n )}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n );\n}\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuLabel,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubTrigger,\n DropdownMenuSubContent,\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,44 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps } from 'react';
3
+
4
+ /**
5
+ * Input — Fluent 2-styled, shadcn-API text input.
6
+ *
7
+ * Aesthetics follow Fluent 2 (32px medium field, `rounded-md` = 4px, flat at
8
+ * rest); the prop surface is a bare `ComponentProps<"input">` — shadcn parity,
9
+ * no size variants (Fluent's small/large field sizes are backlog, not shipped
10
+ * here). No `cva`/variants export: there is only one visual treatment, same
11
+ * reasoning as `Separator`.
12
+ *
13
+ * Server-safe: no `"use client"`, no hooks — the React import is type-only,
14
+ * so the file can be dropped straight into an RSC tree.
15
+ *
16
+ * Focus — the signature Fluent detail is a 2px brand underline on the bottom
17
+ * edge while the rest of the border stays put, with **no layout shift**. The
18
+ * generic kit focus ring (conventions §4: `ring-2 ring-ring ring-offset-2`)
19
+ * is a poor fit here — an offset ring around a form field reads as floating
20
+ * outside the field rather than "this field is active," which is the Fluent
21
+ * signature. So this component deliberately swaps it for an **inset
22
+ * box-shadow** (`shadow-[inset_0_-2px_0_0_var(--brand-80)]`), documented here
23
+ * as the allowed per-component deviation conventions §4 anticipates:
24
+ * - It paints *inside* the border box, so nothing reflows — no offset ring,
25
+ * no extra border width.
26
+ * - `border` also switches to `border-primary` on focus so the whole field
27
+ * reads as active, not just the underline.
28
+ * - `--brand-80`/`--brand-100` are read directly via `var()` (not a Tailwind
29
+ * `bg-brand-*` utility) because the ramp is global — same hex in light and
30
+ * dark (tokens.css §brand ramp comment) — so the light/dark split is a
31
+ * `dark:` override on which stop is used, exactly as the spec prescribes.
32
+ *
33
+ * Invalid — the shadcn `aria-invalid:` treatment from conventions §4
34
+ * (destructive border + destructive ring tint) is declared *after* the focus
35
+ * classes so it wins the border tie-break when a field is both invalid and
36
+ * focused (same declaration order Button already relies on for its own
37
+ * focus/invalid overlap). The bottom-edge accent itself also swaps to
38
+ * `--destructive` in the invalid+focused compound state, so a required field
39
+ * left invalid doesn't show a reassuring brand-blue underline while the rest
40
+ * of the field is screaming destructive-red.
41
+ */
42
+ declare function Input({ className, ...props }: ComponentProps<"input">): react.JSX.Element;
43
+
44
+ export { Input };
@@ -0,0 +1,43 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/utils.js";
3
+ function Input({ className, ...props }) {
4
+ return /* @__PURE__ */ jsx(
5
+ "input",
6
+ {
7
+ "data-slot": "input",
8
+ className: cn(
9
+ // layout — Fluent medium field, 32px height. The resting bottom edge
10
+ // is the darker NeutralStrokeAccessible (#616161) while the other three
11
+ // sides stay border-input (#d1d1d1) — Fluent's signature field accent.
12
+ // On focus, border-primary paints all sides (and the inset underline
13
+ // covers this); aria-invalid's border-destructive likewise wins by
14
+ // specificity, so the darker bottom only shows at rest.
15
+ "flex h-8 w-full min-w-0 rounded-md border border-input border-b-stroke-accessible bg-background px-3 py-1 text-sm",
16
+ // placeholder / text selection
17
+ "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
18
+ // file input button (shadcn parity)
19
+ "file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground",
20
+ // motion — color transitions plus the focus box-shadow accent below
21
+ "outline-none transition-[color,box-shadow] duration-fast ease-ease",
22
+ // disabled — opacity-based, matches conventions §4 exactly
23
+ "disabled:pointer-events-none disabled:opacity-50",
24
+ // focus — Fluent bottom brand accent via inset box-shadow, no reflow.
25
+ // Replaces the generic ring recipe for this component (documented
26
+ // deviation, conventions §4).
27
+ "focus-visible:border-primary focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-80)] dark:focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-100)]",
28
+ // invalid — shadcn aria-invalid treatment, conventions §4. Declared
29
+ // after focus-visible so it wins the border tie when both apply; the
30
+ // compound aria-invalid+focus-visible rule swaps the underline accent
31
+ // to destructive too, so an invalid+focused field reads consistently.
32
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
33
+ "aria-invalid:focus-visible:shadow-[inset_0_-2px_0_0_var(--destructive)]",
34
+ className
35
+ ),
36
+ ...props
37
+ }
38
+ );
39
+ }
40
+ export {
41
+ Input
42
+ };
43
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/input.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Input — Fluent 2-styled, shadcn-API text input.\n *\n * Aesthetics follow Fluent 2 (32px medium field, `rounded-md` = 4px, flat at\n * rest); the prop surface is a bare `ComponentProps<\"input\">` — shadcn parity,\n * no size variants (Fluent's small/large field sizes are backlog, not shipped\n * here). No `cva`/variants export: there is only one visual treatment, same\n * reasoning as `Separator`.\n *\n * Server-safe: no `\"use client\"`, no hooks — the React import is type-only,\n * so the file can be dropped straight into an RSC tree.\n *\n * Focus — the signature Fluent detail is a 2px brand underline on the bottom\n * edge while the rest of the border stays put, with **no layout shift**. The\n * generic kit focus ring (conventions §4: `ring-2 ring-ring ring-offset-2`)\n * is a poor fit here — an offset ring around a form field reads as floating\n * outside the field rather than \"this field is active,\" which is the Fluent\n * signature. So this component deliberately swaps it for an **inset\n * box-shadow** (`shadow-[inset_0_-2px_0_0_var(--brand-80)]`), documented here\n * as the allowed per-component deviation conventions §4 anticipates:\n * - It paints *inside* the border box, so nothing reflows — no offset ring,\n * no extra border width.\n * - `border` also switches to `border-primary` on focus so the whole field\n * reads as active, not just the underline.\n * - `--brand-80`/`--brand-100` are read directly via `var()` (not a Tailwind\n * `bg-brand-*` utility) because the ramp is global — same hex in light and\n * dark (tokens.css §brand ramp comment) — so the light/dark split is a\n * `dark:` override on which stop is used, exactly as the spec prescribes.\n *\n * Invalid — the shadcn `aria-invalid:` treatment from conventions §4\n * (destructive border + destructive ring tint) is declared *after* the focus\n * classes so it wins the border tie-break when a field is both invalid and\n * focused (same declaration order Button already relies on for its own\n * focus/invalid overlap). The bottom-edge accent itself also swaps to\n * `--destructive` in the invalid+focused compound state, so a required field\n * left invalid doesn't show a reassuring brand-blue underline while the rest\n * of the field is screaming destructive-red.\n */\nfunction Input({ className, ...props }: ComponentProps<\"input\">) {\n return (\n <input\n data-slot=\"input\"\n className={cn(\n // layout — Fluent medium field, 32px height. The resting bottom edge\n // is the darker NeutralStrokeAccessible (#616161) while the other three\n // sides stay border-input (#d1d1d1) — Fluent's signature field accent.\n // On focus, border-primary paints all sides (and the inset underline\n // covers this); aria-invalid's border-destructive likewise wins by\n // specificity, so the darker bottom only shows at rest.\n \"flex h-8 w-full min-w-0 rounded-md border border-input border-b-stroke-accessible bg-background px-3 py-1 text-sm\",\n // placeholder / text selection\n \"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground\",\n // file input button (shadcn parity)\n \"file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground\",\n // motion — color transitions plus the focus box-shadow accent below\n \"outline-none transition-[color,box-shadow] duration-fast ease-ease\",\n // disabled — opacity-based, matches conventions §4 exactly\n \"disabled:pointer-events-none disabled:opacity-50\",\n // focus — Fluent bottom brand accent via inset box-shadow, no reflow.\n // Replaces the generic ring recipe for this component (documented\n // deviation, conventions §4).\n \"focus-visible:border-primary focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-80)] dark:focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-100)]\",\n // invalid — shadcn aria-invalid treatment, conventions §4. Declared\n // after focus-visible so it wins the border tie when both apply; the\n // compound aria-invalid+focus-visible rule swaps the underline accent\n // to destructive too, so an invalid+focused field reads consistently.\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n \"aria-invalid:focus-visible:shadow-[inset_0_-2px_0_0_var(--destructive)]\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Input };\n"],"mappings":"AA4CI;AA1CJ,SAAS,UAAU;AAwCnB,SAAS,MAAM,EAAE,WAAW,GAAG,MAAM,GAA4B;AAC/D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOT;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA,QAEA;AAAA;AAAA;AAAA;AAAA,QAIA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,39 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps } from 'react';
3
+
4
+ /**
5
+ * Label — Fluent 2-styled, shadcn-API form label.
6
+ *
7
+ * A plain native `<label>`. Unlike the current shadcn/ui implementation (which
8
+ * wraps a Radix/Base UI `Label` primitive), no JS primitive is needed here: the
9
+ * native element already provides the `htmlFor`/`id` association and
10
+ * click-to-focus behavior a "Label" primitive exists to simulate. Server-safe:
11
+ * no `"use client"`, no hooks — the React import is type-only.
12
+ *
13
+ * Base classes match current shadcn/ui (`flex items-center text-sm leading-none
14
+ * select-none`, plus the `peer-disabled`/`group-data-[disabled=true]`
15
+ * ergonomics). Weight is `font-normal` (Regular 400): Fluent 2's Label defaults
16
+ * to Regular, with Semibold (600) offered as an explicit `type="Semibold"`
17
+ * opt-in — confirmed against the Figma Label component set, whose default
18
+ * variant is literally `Type=Regular (Default)`. (An earlier version of this
19
+ * file claimed "Fluent 2's Label type ramp is 14px/600 (semibold)" and hard-set
20
+ * `font-semibold`; that premise was wrong — it made every label render heavier
21
+ * than the Fluent default.) Consumers wanting the Semibold emphasis pass
22
+ * `className="font-semibold"`, which wins through `cn`'s tailwind-merge.
23
+ *
24
+ * Fluent 2 extension: a `required` prop renders a `text-destructive` asterisk
25
+ * after the label's children, separated by `gap-1` (4px = Fluent
26
+ * `spacingHorizontalXS`). The asterisk inherits the label's Regular weight
27
+ * (matching Fluent, which keeps the asterisk Regular even on Semibold labels)
28
+ * and stays `text-destructive` per the kit's single-red policy
29
+ * (tokens-research.md §12.9). It is purely visual — wrapped in
30
+ * `aria-hidden="true"` so it never pollutes the computed accessible name.
31
+ * Screen readers announce "required" from the associated form control instead,
32
+ * so pair `required` here with the real `required`/`aria-required="true"`
33
+ * attribute on the input/select/textarea this label is for.
34
+ */
35
+ declare function Label({ className, required, children, ...props }: ComponentProps<"label"> & {
36
+ required?: boolean;
37
+ }): react.JSX.Element;
38
+
39
+ export { Label };
@@ -0,0 +1,30 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/utils.js";
3
+ function Label({
4
+ className,
5
+ required = false,
6
+ children,
7
+ ...props
8
+ }) {
9
+ return /* @__PURE__ */ jsxs(
10
+ "label",
11
+ {
12
+ "data-slot": "label",
13
+ className: cn(
14
+ "flex items-center gap-1 text-sm leading-none font-normal select-none",
15
+ "group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
16
+ "peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
17
+ className
18
+ ),
19
+ ...props,
20
+ children: [
21
+ children,
22
+ required ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-destructive", children: "*" }) : null
23
+ ]
24
+ }
25
+ );
26
+ }
27
+ export {
28
+ Label
29
+ };
30
+ //# sourceMappingURL=label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/label.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Label — Fluent 2-styled, shadcn-API form label.\n *\n * A plain native `<label>`. Unlike the current shadcn/ui implementation (which\n * wraps a Radix/Base UI `Label` primitive), no JS primitive is needed here: the\n * native element already provides the `htmlFor`/`id` association and\n * click-to-focus behavior a \"Label\" primitive exists to simulate. Server-safe:\n * no `\"use client\"`, no hooks — the React import is type-only.\n *\n * Base classes match current shadcn/ui (`flex items-center text-sm leading-none\n * select-none`, plus the `peer-disabled`/`group-data-[disabled=true]`\n * ergonomics). Weight is `font-normal` (Regular 400): Fluent 2's Label defaults\n * to Regular, with Semibold (600) offered as an explicit `type=\"Semibold\"`\n * opt-in — confirmed against the Figma Label component set, whose default\n * variant is literally `Type=Regular (Default)`. (An earlier version of this\n * file claimed \"Fluent 2's Label type ramp is 14px/600 (semibold)\" and hard-set\n * `font-semibold`; that premise was wrong — it made every label render heavier\n * than the Fluent default.) Consumers wanting the Semibold emphasis pass\n * `className=\"font-semibold\"`, which wins through `cn`'s tailwind-merge.\n *\n * Fluent 2 extension: a `required` prop renders a `text-destructive` asterisk\n * after the label's children, separated by `gap-1` (4px = Fluent\n * `spacingHorizontalXS`). The asterisk inherits the label's Regular weight\n * (matching Fluent, which keeps the asterisk Regular even on Semibold labels)\n * and stays `text-destructive` per the kit's single-red policy\n * (tokens-research.md §12.9). It is purely visual — wrapped in\n * `aria-hidden=\"true\"` so it never pollutes the computed accessible name.\n * Screen readers announce \"required\" from the associated form control instead,\n * so pair `required` here with the real `required`/`aria-required=\"true\"`\n * attribute on the input/select/textarea this label is for.\n */\nfunction Label({\n className,\n required = false,\n children,\n ...props\n}: ComponentProps<\"label\"> & { required?: boolean }) {\n return (\n <label\n data-slot=\"label\"\n className={cn(\n \"flex items-center gap-1 text-sm leading-none font-normal select-none\",\n \"group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50\",\n \"peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\n className\n )}\n {...props}\n >\n {children}\n {required ? (\n <span aria-hidden=\"true\" className=\"text-destructive\">\n *\n </span>\n ) : null}\n </label>\n );\n}\n\nexport { Label };\n"],"mappings":"AA0CI,SAYI,KAZJ;AAxCJ,SAAS,UAAU;AAiCnB,SAAS,MAAM;AAAA,EACb;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,GAAqD;AACnD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,WACC,oBAAC,UAAK,eAAY,QAAO,WAAU,oBAAmB,eAEtD,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,62 @@
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
+ * Link — Fluent 2-styled, shadcn-API link.
8
+ *
9
+ * shadcn has no Link primitive; this is a Fluent-driven addition that follows
10
+ * the same authoring pattern as `Button` (cva + `asChild` + `data-slot`) so it
11
+ * reads as part of the same system. The color ramp matches Fluent's
12
+ * `BrandForegroundLink` states exactly — one brand-ramp step darker than an
13
+ * earlier (off-by-one-light) version: rest `brand-70` (`#115ea3`), hover
14
+ * `brand-60` (`#0f548c`), pressed `brand-50` (`#0c3b5e`) in light; rest
15
+ * `brand-100`, hover `brand-110`, pressed `brand-120` in dark. Weight is
16
+ * `font-normal` (Fluent Link is Body 1 / Regular 400, not the medium 500 an
17
+ * earlier version used). Button's own `link` variant carries the same color
18
+ * ramp (kept in sync) but keeps the button's `font-semibold` weight.
19
+ *
20
+ * Two variants, mapping directly to the two Fluent 2 link usages
21
+ * (https://storybooks.fluentui.dev/react/?path=/docs/components-link--docs):
22
+ * - `default` — a standalone link (nav, button-like, card action). No
23
+ * underline at rest; underline appears on hover only, matching Button's
24
+ * `link` variant so the two read identically.
25
+ * - `inline` — a link inside a sentence of body text. Fluent requires a
26
+ * persistent underline here so the link is distinguishable from surrounding
27
+ * text by shape, not color alone (this also keeps it usable for anyone who
28
+ * can't perceive the brand-color contrast against body text).
29
+ *
30
+ * `asChild` (Radix Slot) exists specifically so this component can wrap a
31
+ * framework router's own link and still carry Fluent's visual treatment:
32
+ *
33
+ * <Link asChild>
34
+ * <NextLink href="/dashboard">Dashboard</NextLink>
35
+ * </Link>
36
+ *
37
+ * Disabled: anchors have no native `disabled` attribute, so this component
38
+ * does not invent one (that would break the plain DOM-attribute contract
39
+ * `ComponentProps<"a">` gives consumers). Instead it styles `aria-disabled`.
40
+ * Two things consumers must still do themselves, because neither can be
41
+ * enforced from inside the component:
42
+ * - Omit `href` on a disabled link where possible — an anchor without `href`
43
+ * is not in the tab order and is not exposed as a link by assistive tech,
44
+ * which is the most robust "disabled" for a link.
45
+ * - If `href` must stay (e.g. to preserve layout/routing), pair
46
+ * `aria-disabled="true"` with these classes: `pointer-events-none` blocks
47
+ * pointer activation and `no-underline`/`opacity-50` give a visible cue.
48
+ * This does **not** remove the link from the tab order or stop Enter-key
49
+ * activation on its own — add `tabIndex={-1}` too if full keyboard removal
50
+ * is required.
51
+ *
52
+ * Server-safe: no `"use client"`, no hooks/handlers — the React import is
53
+ * type-only, so the file can be dropped straight into an RSC tree.
54
+ */
55
+ declare const linkVariants: (props?: ({
56
+ variant?: "inline" | "default" | null | undefined;
57
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
58
+ declare function Link({ className, variant, asChild, ...props }: ComponentProps<"a"> & VariantProps<typeof linkVariants> & {
59
+ asChild?: boolean;
60
+ }): react.JSX.Element;
61
+
62
+ export { Link, linkVariants };
@@ -0,0 +1,61 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../../lib/utils.js";
5
+ const linkVariants = cva(
6
+ [
7
+ // shape + type — Fluent Link is Body 1 / Regular 400
8
+ "rounded-xs font-normal text-brand-70 underline-offset-4 dark:text-brand-100",
9
+ // motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion
10
+ // automatically (the duration tokens collapse to 0.01ms in tokens.css).
11
+ "outline-none transition-colors duration-fast ease-ease",
12
+ // interactive state ramp — Fluent BrandForegroundLink, one brand step
13
+ // darker than the color set above, kept in sync with Button's `link`
14
+ // variant. rest brand-70 → hover brand-60 → pressed brand-50 (light);
15
+ // rest brand-100 → hover brand-110 → pressed brand-120 (dark). The brand
16
+ // ramp is global, so the dark steps are declared explicitly (conventions §4).
17
+ "hover:text-brand-60 hover:underline active:text-brand-50 dark:hover:text-brand-110 dark:active:text-brand-120",
18
+ // focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap
19
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
20
+ // disabled — anchors have no native `disabled`; styled off `aria-disabled` instead.
21
+ // Opacity-based to match the rest of the kit's disabled treatment (see conventions
22
+ // doc §4); `no-underline` also cancels the `inline` variant's persistent underline.
23
+ "aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:no-underline"
24
+ ],
25
+ {
26
+ variants: {
27
+ variant: {
28
+ // Standalone link — no underline at rest, only on hover.
29
+ default: "",
30
+ // Inline-in-prose link — Fluent requires an always-on underline so the
31
+ // link reads as a link independent of its brand-color contrast.
32
+ inline: "underline"
33
+ }
34
+ },
35
+ defaultVariants: {
36
+ variant: "default"
37
+ }
38
+ }
39
+ );
40
+ function Link({
41
+ className,
42
+ variant = "default",
43
+ asChild = false,
44
+ ...props
45
+ }) {
46
+ const Comp = asChild ? Slot : "a";
47
+ return /* @__PURE__ */ jsx(
48
+ Comp,
49
+ {
50
+ "data-slot": "link",
51
+ "data-variant": variant,
52
+ className: cn(linkVariants({ variant, className })),
53
+ ...props
54
+ }
55
+ );
56
+ }
57
+ export {
58
+ Link,
59
+ linkVariants
60
+ };
61
+ //# sourceMappingURL=link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/link.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Link — Fluent 2-styled, shadcn-API link.\n *\n * shadcn has no Link primitive; this is a Fluent-driven addition that follows\n * the same authoring pattern as `Button` (cva + `asChild` + `data-slot`) so it\n * reads as part of the same system. The color ramp matches Fluent's\n * `BrandForegroundLink` states exactly — one brand-ramp step darker than an\n * earlier (off-by-one-light) version: rest `brand-70` (`#115ea3`), hover\n * `brand-60` (`#0f548c`), pressed `brand-50` (`#0c3b5e`) in light; rest\n * `brand-100`, hover `brand-110`, pressed `brand-120` in dark. Weight is\n * `font-normal` (Fluent Link is Body 1 / Regular 400, not the medium 500 an\n * earlier version used). Button's own `link` variant carries the same color\n * ramp (kept in sync) but keeps the button's `font-semibold` weight.\n *\n * Two variants, mapping directly to the two Fluent 2 link usages\n * (https://storybooks.fluentui.dev/react/?path=/docs/components-link--docs):\n * - `default` — a standalone link (nav, button-like, card action). No\n * underline at rest; underline appears on hover only, matching Button's\n * `link` variant so the two read identically.\n * - `inline` — a link inside a sentence of body text. Fluent requires a\n * persistent underline here so the link is distinguishable from surrounding\n * text by shape, not color alone (this also keeps it usable for anyone who\n * can't perceive the brand-color contrast against body text).\n *\n * `asChild` (Radix Slot) exists specifically so this component can wrap a\n * framework router's own link and still carry Fluent's visual treatment:\n *\n * <Link asChild>\n * <NextLink href=\"/dashboard\">Dashboard</NextLink>\n * </Link>\n *\n * Disabled: anchors have no native `disabled` attribute, so this component\n * does not invent one (that would break the plain DOM-attribute contract\n * `ComponentProps<\"a\">` gives consumers). Instead it styles `aria-disabled`.\n * Two things consumers must still do themselves, because neither can be\n * enforced from inside the component:\n * - Omit `href` on a disabled link where possible — an anchor without `href`\n * is not in the tab order and is not exposed as a link by assistive tech,\n * which is the most robust \"disabled\" for a link.\n * - If `href` must stay (e.g. to preserve layout/routing), pair\n * `aria-disabled=\"true\"` with these classes: `pointer-events-none` blocks\n * pointer activation and `no-underline`/`opacity-50` give a visible cue.\n * This does **not** remove the link from the tab order or stop Enter-key\n * activation on its own — add `tabIndex={-1}` too if full keyboard removal\n * is required.\n *\n * Server-safe: no `\"use client\"`, no hooks/handlers — the React import is\n * type-only, so the file can be dropped straight into an RSC tree.\n */\nconst linkVariants = cva(\n [\n // shape + type — Fluent Link is Body 1 / Regular 400\n \"rounded-xs font-normal text-brand-70 underline-offset-4 dark:text-brand-100\",\n // motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion\n // automatically (the duration tokens collapse to 0.01ms in tokens.css).\n \"outline-none transition-colors duration-fast ease-ease\",\n // interactive state ramp — Fluent BrandForegroundLink, one brand step\n // darker than the color set above, kept in sync with Button's `link`\n // variant. rest brand-70 → hover brand-60 → pressed brand-50 (light);\n // rest brand-100 → hover brand-110 → pressed brand-120 (dark). The brand\n // ramp is global, so the dark steps are declared explicitly (conventions §4).\n \"hover:text-brand-60 hover:underline active:text-brand-50 dark:hover:text-brand-110 dark:active:text-brand-120\",\n // focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n // disabled — anchors have no native `disabled`; styled off `aria-disabled` instead.\n // Opacity-based to match the rest of the kit's disabled treatment (see conventions\n // doc §4); `no-underline` also cancels the `inline` variant's persistent underline.\n \"aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:no-underline\",\n ],\n {\n variants: {\n variant: {\n // Standalone link — no underline at rest, only on hover.\n default: \"\",\n // Inline-in-prose link — Fluent requires an always-on underline so the\n // link reads as a link independent of its brand-color contrast.\n inline: \"underline\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\nfunction Link({\n className,\n variant = \"default\",\n asChild = false,\n ...props\n}: ComponentProps<\"a\"> &\n VariantProps<typeof linkVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n data-slot=\"link\"\n data-variant={variant}\n className={cn(linkVariants({ variant, className }))}\n {...props}\n />\n );\n}\n\nexport { Link, linkVariants };\n"],"mappings":"AAuGI;AAvGJ,SAAS,YAAY;AACrB,SAAS,WAA8B;AAGvC,SAAS,UAAU;AAmDnB,MAAM,eAAe;AAAA,EACnB;AAAA;AAAA,IAEE;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA;AAAA,QAEP,SAAS;AAAA;AAAA;AAAA,QAGT,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEA,SAAS,KAAK;AAAA,EACZ;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,GAAG;AACL,GAGK;AACH,QAAM,OAAO,UAAU,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,aAAa,EAAE,SAAS,UAAU,CAAC,CAAC;AAAA,MACjD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,115 @@
1
+ import * as react from 'react';
2
+ import { ComponentProps, ReactNode } from 'react';
3
+ import { Combobox } from '@base-ui/react/combobox';
4
+ import { ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxItem, ComboboxLabel, ComboboxList, ComboboxSeparator } from './combobox.js';
5
+
6
+ /**
7
+ * Multi Select — Fluent 2-styled multiple-selection combobox with a chips field.
8
+ *
9
+ * Built DIRECTLY on the kit's `Combobox` reference (`./combobox`). Base UI's
10
+ * `Combobox.Root` already carries the multiple-selection engine — passing
11
+ * `multiple` turns `value`/`onValueChange` into arrays and unlocks the chips
12
+ * parts (`Combobox.Chips`/`Chip`/`ChipRemove`). This file's only NEW surface is
13
+ * the **chips field** (`MultiSelectInput` + `MultiSelectChip`); everything on the
14
+ * popup side is re-exported from `combobox.tsx` unchanged.
15
+ *
16
+ * ## Reuse model — what is new vs a pass-through
17
+ * | Exported (kit name) | Source |
18
+ * | --------------------- | --------------------------------------------------- |
19
+ * | `MultiSelect` | `Combobox.Root` with `multiple` defaulted to `true` |
20
+ * | `MultiSelectInput` | **NEW** chips field: `Combobox.Chips` (field chrome) wrapping a `Combobox.Value` chip flow + `Combobox.Input` |
21
+ * | `MultiSelectChip` | **NEW** one selected-value chip: `Combobox.Chip` + `Combobox.ChipRemove` |
22
+ * | `MultiSelectContent` | pass-through re-export of `ComboboxContent` |
23
+ * | `MultiSelectList` | pass-through re-export of `ComboboxList` |
24
+ * | `MultiSelectItem` | pass-through re-export of `ComboboxItem` |
25
+ * | `MultiSelectGroup` | pass-through re-export of `ComboboxGroup` |
26
+ * | `MultiSelectLabel` | pass-through re-export of `ComboboxLabel` |
27
+ * | `MultiSelectEmpty` | pass-through re-export of `ComboboxEmpty` |
28
+ * | `MultiSelectSeparator`| pass-through re-export of `ComboboxSeparator` |
29
+ *
30
+ * ## Divergences (all deliberate)
31
+ * 1. **The popup parts are literal re-exports, so they keep their
32
+ * `data-slot="combobox-…"` values** — they ARE the Combobox parts, aliased for
33
+ * a coherent `MultiSelect*` call site. Only the two parts this file owns get
34
+ * `data-slot="multi-select-…"` (`multi-select-input-wrapper`,
35
+ * `multi-select-input`, `multi-select-chip`, `multi-select-chip-remove`). This
36
+ * avoids forking identical popup markup and keeps the registry item lean
37
+ * (`registryDependencies: ["utils", "combobox"]`, like `pagination` depends on
38
+ * `button`). Selected items still render the right-side `CheckmarkRegular` from
39
+ * `ComboboxItem` — in `multiple` mode Base UI mounts the indicator for every
40
+ * selected option.
41
+ * 2. **`MultiSelectInput` is NOT `ComboboxInput`.** `ComboboxInput` is the
42
+ * single-line surface (h-8, one `<input>` + Clear + chevron). The chips field
43
+ * is a wrapping token field: `min-h-8` + `flex-wrap` + `gap-1` + `py-1` so
44
+ * chips reflow to multiple rows, with the text `<input>` sitting inline at the
45
+ * end. It reuses `combobox-input-wrapper`'s field-chrome recipe verbatim — the
46
+ * `border-input`/`border-b-stroke-accessible` rest edge, the
47
+ * `has-[input:focus-visible]` bottom brand accent, the
48
+ * `has-[input[aria-invalid]]` destructive form, the `has-[input:disabled]`
49
+ * opacity — because focus/invalid/disabled all still land on the inner input.
50
+ * 3. **No baked-in Clear or chevron `Trigger`.** The single-select field bakes a
51
+ * `Combobox.Clear` + chevron in; a tags picker's affordances are the chips
52
+ * themselves (each removable) and Backspace-clears-last, so the chips field
53
+ * stays chrome-light. Open with focus + type or `ArrowDown`. Consumers who want
54
+ * a clear-all/chevron can compose `@base-ui/react/combobox`'s `Clear`/`Trigger`
55
+ * directly.
56
+ * 4. **Chip visuals are Fluent Tag (Small, Filled), Badge-adjacent.** A chip is
57
+ * `bg-secondary` `text-foreground-2`, `rounded-md`, `h-6`, `px-1.5`, `text-xs` — the
58
+ * neutral filled look of `Badge variant="secondary"` but sized for an inline
59
+ * token (Badge is a static 20px caption chip; a removable token needs the extra
60
+ * height + a trailing dismiss target). The remove button hover is
61
+ * `text-destructive` to signal "this deletes", paired with the `DismissRegular`
62
+ * glyph (not color alone — conventions §5).
63
+ *
64
+ * ## Chips wiring — what Base UI provides vs what is wired here
65
+ * The chip flow is rendered from `Combobox.Value`'s render-function child, which
66
+ * receives the live `selectedValue` array and re-renders on every selection — so
67
+ * an UNCONTROLLED `MultiSelect` (the preview / RSC case) shows chips without any
68
+ * consumer `useState`. Base UI owns the keyboard model for free:
69
+ * - **Backspace on an empty input removes the last chip** (Base UI `ComboboxInput`).
70
+ * - **ArrowLeft/Right move between chips; Backspace/Delete on a focused chip
71
+ * removes it** (Base UI `ComboboxChip`).
72
+ * - **Clicking a `ChipRemove` deselects that value and refocuses the input**
73
+ * (Base UI `ComboboxChipRemove`).
74
+ * - **The popup stays OPEN after selecting** in `multiple` mode (Base UI default),
75
+ * so several tags can be picked in a row.
76
+ * This file only styles those parts and maps each value to a chip.
77
+ *
78
+ * ## `"use client"` — required
79
+ * `@fluentui/react-icons` forces it (the `DismissRegular` import): the package's
80
+ * shared icon-sizing module calls `@griffel/react`'s `__styles()` at module scope
81
+ * without its own directive, so importing an icon into a Server Component breaks
82
+ * `next build` (conventions §9; same fix as `select.tsx`/`combobox.tsx`).
83
+ */
84
+ declare function MultiSelect<Value, Multiple extends boolean | undefined = true>({ multiple, ...props }: Combobox.Root.Props<Value, Multiple>): react.JSX.Element;
85
+ /**
86
+ * Chip — one selected value, rendered from `MultiSelectInput`'s chip flow (or
87
+ * composed by a caller who passes their own render child). `Combobox.Chip` holds
88
+ * the label; the trailing `Combobox.ChipRemove` button deselects it. Base UI maps
89
+ * a chip to `selectedValue[index]` via its composite-list order, so chips must be
90
+ * rendered in selection order (which `Combobox.Value`'s array is).
91
+ */
92
+ declare function MultiSelectChip({ className, children, ...props }: ComponentProps<typeof Combobox.Chip>): react.JSX.Element;
93
+ /**
94
+ * Input — the Fluent chips field (divergence 2). `Combobox.Chips` is the
95
+ * field-chrome container (flex-wrap token row) holding the chip flow and the
96
+ * inline text `<input>`. `children`, when given, is a render function
97
+ * `(value) => ReactNode` that maps each selected value to its chip content —
98
+ * use it to look up a label for `{ value, label }` items or to style the chip.
99
+ * With no `children`, each value is rendered as a `MultiSelectChip` showing
100
+ * `String(value)` (the flat-string case). `className`/`ref`/`...props` forward to
101
+ * the inner `<input>`; style the field with `wrapperClassName`.
102
+ */
103
+ declare function MultiSelectInput({ className, wrapperClassName, children, ...props }: Omit<ComponentProps<typeof Combobox.Input>, "children"> & {
104
+ wrapperClassName?: string;
105
+ children?: (value: unknown) => ReactNode;
106
+ }): react.JSX.Element;
107
+ declare const MultiSelectContent: typeof ComboboxContent;
108
+ declare const MultiSelectList: typeof ComboboxList;
109
+ declare const MultiSelectItem: typeof ComboboxItem;
110
+ declare const MultiSelectGroup: typeof ComboboxGroup;
111
+ declare const MultiSelectLabel: typeof ComboboxLabel;
112
+ declare const MultiSelectEmpty: typeof ComboboxEmpty;
113
+ declare const MultiSelectSeparator: typeof ComboboxSeparator;
114
+
115
+ export { MultiSelect, MultiSelectChip, MultiSelectContent, MultiSelectEmpty, MultiSelectGroup, MultiSelectInput, MultiSelectItem, MultiSelectLabel, MultiSelectList, MultiSelectSeparator };
@@ -0,0 +1,133 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox";
4
+ import { DismissRegular } from "@fluentui/react-icons";
5
+ import { cn } from "../../lib/utils.js";
6
+ import {
7
+ ComboboxContent,
8
+ ComboboxEmpty,
9
+ ComboboxGroup,
10
+ ComboboxItem,
11
+ ComboboxLabel,
12
+ ComboboxList,
13
+ ComboboxSeparator
14
+ } from "./combobox.js";
15
+ function MultiSelect({
16
+ multiple,
17
+ ...props
18
+ }) {
19
+ return /* @__PURE__ */ jsx(
20
+ ComboboxPrimitive.Root,
21
+ {
22
+ multiple: multiple ?? true,
23
+ ...props
24
+ }
25
+ );
26
+ }
27
+ function MultiSelectChip({
28
+ className,
29
+ children,
30
+ ...props
31
+ }) {
32
+ return /* @__PURE__ */ jsxs(
33
+ ComboboxPrimitive.Chip,
34
+ {
35
+ "data-slot": "multi-select-chip",
36
+ className: cn(
37
+ // Fluent Tag (Small, Filled): 24px, #f5f5f5 fill, 4px radius
38
+ // (rounded-md), Caption-1 text in NeutralForeground2 (text-foreground-2)
39
+ // — exact token values from Figma validation pass 3, node 9112:10360.
40
+ "inline-flex h-6 items-center gap-1 rounded-md bg-secondary px-1.5 text-xs text-foreground-2 select-none",
41
+ // highlighted (chip keyboard focus) + disabled
42
+ "data-[highlighted]:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
43
+ className
44
+ ),
45
+ ...props,
46
+ children: [
47
+ children,
48
+ /* @__PURE__ */ jsx(
49
+ ComboboxPrimitive.ChipRemove,
50
+ {
51
+ "data-slot": "multi-select-chip-remove",
52
+ "aria-label": "Remove",
53
+ className: cn(
54
+ "-mr-0.5 flex size-4 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none",
55
+ // hover reads destructive — this action deletes (paired with the glyph, not color alone)
56
+ "transition-colors duration-fast ease-ease hover:text-destructive",
57
+ "focus-visible:ring-2 focus-visible:ring-ring",
58
+ "[&_svg]:pointer-events-none [&_svg]:size-3"
59
+ ),
60
+ children: /* @__PURE__ */ jsx(DismissRegular, {})
61
+ }
62
+ )
63
+ ]
64
+ }
65
+ );
66
+ }
67
+ function MultiSelectInput({
68
+ className,
69
+ wrapperClassName,
70
+ children,
71
+ ...props
72
+ }) {
73
+ return /* @__PURE__ */ jsxs(
74
+ ComboboxPrimitive.Chips,
75
+ {
76
+ "data-slot": "multi-select-input-wrapper",
77
+ className: cn(
78
+ // layout — wrapping token field. min-h-8 (chips reflow to rows), flex-wrap,
79
+ // gap-1, py-1. Resting bottom edge uses NeutralStrokeAccessible; the other
80
+ // sides stay border-input. Focus + aria-invalid override it (below).
81
+ "flex min-h-8 w-full flex-wrap items-center gap-1 rounded-md border border-input border-b-stroke-accessible bg-background px-1.5 py-1 text-sm",
82
+ // motion — color + box-shadow so the focus accent animates (§4)
83
+ "transition-[color,box-shadow] duration-fast ease-ease",
84
+ // focus — Fluent bottom brand accent via inset box-shadow, no reflow (§4).
85
+ // has-[input:focus-visible] because focus is on the inner input.
86
+ "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)]",
87
+ // invalid — shadcn aria-invalid treatment; after focus so it wins the border
88
+ "has-[input[aria-invalid='true']]:border-destructive has-[input[aria-invalid='true']]:ring-destructive/20 dark:has-[input[aria-invalid='true']]:ring-destructive/40",
89
+ "has-[input[aria-invalid='true']:focus-visible]:shadow-[inset_0_-2px_0_0_var(--destructive)]",
90
+ // disabled — opacity read when the whole control is disabled
91
+ "has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50",
92
+ wrapperClassName
93
+ ),
94
+ children: [
95
+ /* @__PURE__ */ jsx(ComboboxPrimitive.Value, { children: (selected) => (Array.isArray(selected) ? selected : []).map(
96
+ (value, index) => children ? children(value) : /* @__PURE__ */ jsx(MultiSelectChip, { children: String(value) }, index)
97
+ ) }),
98
+ /* @__PURE__ */ jsx(
99
+ ComboboxPrimitive.Input,
100
+ {
101
+ "data-slot": "multi-select-input",
102
+ className: cn(
103
+ "h-6 min-w-16 flex-1 bg-transparent px-1 outline-none",
104
+ "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
105
+ className
106
+ ),
107
+ ...props
108
+ }
109
+ )
110
+ ]
111
+ }
112
+ );
113
+ }
114
+ const MultiSelectContent = ComboboxContent;
115
+ const MultiSelectList = ComboboxList;
116
+ const MultiSelectItem = ComboboxItem;
117
+ const MultiSelectGroup = ComboboxGroup;
118
+ const MultiSelectLabel = ComboboxLabel;
119
+ const MultiSelectEmpty = ComboboxEmpty;
120
+ const MultiSelectSeparator = ComboboxSeparator;
121
+ export {
122
+ MultiSelect,
123
+ MultiSelectChip,
124
+ MultiSelectContent,
125
+ MultiSelectEmpty,
126
+ MultiSelectGroup,
127
+ MultiSelectInput,
128
+ MultiSelectItem,
129
+ MultiSelectLabel,
130
+ MultiSelectList,
131
+ MultiSelectSeparator
132
+ };
133
+ //# sourceMappingURL=multi-select.js.map