@cl-light-ds/ui 0.1.0

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 (254) hide show
  1. package/README.md +45 -0
  2. package/package.json +59 -0
  3. package/src/components/accordion/Accordion.astro +248 -0
  4. package/src/components/accordion/AccordionContent.astro +26 -0
  5. package/src/components/accordion/AccordionItem.astro +36 -0
  6. package/src/components/accordion/AccordionTrigger.astro +118 -0
  7. package/src/components/accordion/CHANGELOG.md +41 -0
  8. package/src/components/accordion/index.ts +4 -0
  9. package/src/components/alert/Alert.astro +167 -0
  10. package/src/components/alert/AlertDescription.astro +24 -0
  11. package/src/components/alert/AlertTitle.astro +24 -0
  12. package/src/components/alert/CHANGELOG.md +5 -0
  13. package/src/components/alert/index.ts +4 -0
  14. package/src/components/avatar/Avatar.astro +197 -0
  15. package/src/components/avatar/AvatarBadge.astro +44 -0
  16. package/src/components/avatar/AvatarFallback.astro +26 -0
  17. package/src/components/avatar/AvatarGroup.astro +26 -0
  18. package/src/components/avatar/AvatarGroupCount.astro +30 -0
  19. package/src/components/avatar/AvatarImage.astro +46 -0
  20. package/src/components/avatar/CHANGELOG.md +44 -0
  21. package/src/components/avatar/index.ts +6 -0
  22. package/src/components/badge/Badge.astro +167 -0
  23. package/src/components/badge/CHANGELOG.md +49 -0
  24. package/src/components/badge/index.ts +2 -0
  25. package/src/components/breadcrumb/Breadcrumb.astro +76 -0
  26. package/src/components/breadcrumb/BreadcrumbItem.astro +32 -0
  27. package/src/components/breadcrumb/BreadcrumbList.astro +26 -0
  28. package/src/components/breadcrumb/BreadcrumbSeparator.astro +26 -0
  29. package/src/components/breadcrumb/CHANGELOG.md +24 -0
  30. package/src/components/breadcrumb/index.ts +5 -0
  31. package/src/components/button/Button.astro +233 -0
  32. package/src/components/button/CHANGELOG.md +41 -0
  33. package/src/components/button/index.ts +2 -0
  34. package/src/components/button-group/ButtonGroup.astro +130 -0
  35. package/src/components/button-group/ButtonGroupSeparator.astro +30 -0
  36. package/src/components/button-group/ButtonGroupText.astro +21 -0
  37. package/src/components/button-group/CHANGELOG.md +34 -0
  38. package/src/components/button-group/index.ts +3 -0
  39. package/src/components/card/CHANGELOG.md +119 -0
  40. package/src/components/card/Card.astro +182 -0
  41. package/src/components/card/CardAction.astro +26 -0
  42. package/src/components/card/CardContent.astro +28 -0
  43. package/src/components/card/CardDescription.astro +28 -0
  44. package/src/components/card/CardFooter.astro +24 -0
  45. package/src/components/card/CardHeader.astro +30 -0
  46. package/src/components/card/CardMedia.astro +27 -0
  47. package/src/components/card/CardOverlay.astro +32 -0
  48. package/src/components/card/CardTitle.astro +21 -0
  49. package/src/components/card/index.ts +10 -0
  50. package/src/components/carousel/CHANGELOG.md +5 -0
  51. package/src/components/carousel/Carousel.astro +106 -0
  52. package/src/components/carousel/CarouselSlide.astro +22 -0
  53. package/src/components/carousel/index.ts +3 -0
  54. package/src/components/checkbox/CHANGELOG.md +58 -0
  55. package/src/components/checkbox/Checkbox.astro +159 -0
  56. package/src/components/checkbox/index.ts +1 -0
  57. package/src/components/collapsible/CHANGELOG.md +5 -0
  58. package/src/components/collapsible/Collapsible.astro +202 -0
  59. package/src/components/collapsible/CollapsibleContent.astro +36 -0
  60. package/src/components/collapsible/CollapsibleTrigger.astro +34 -0
  61. package/src/components/collapsible/index.ts +4 -0
  62. package/src/components/combobox/CHANGELOG.md +27 -0
  63. package/src/components/combobox/Combobox.astro +365 -0
  64. package/src/components/combobox/ComboboxContent.astro +37 -0
  65. package/src/components/combobox/ComboboxEmpty.astro +18 -0
  66. package/src/components/combobox/ComboboxGroup.astro +19 -0
  67. package/src/components/combobox/ComboboxInput.astro +74 -0
  68. package/src/components/combobox/ComboboxItem.astro +39 -0
  69. package/src/components/combobox/ComboboxLabel.astro +18 -0
  70. package/src/components/combobox/ComboboxList.astro +21 -0
  71. package/src/components/combobox/ComboboxSeparator.astro +17 -0
  72. package/src/components/combobox/ComboboxTrigger.astro +30 -0
  73. package/src/components/combobox/ComboboxValue.astro +22 -0
  74. package/src/components/combobox/index.ts +11 -0
  75. package/src/components/date/CHANGELOG.md +18 -0
  76. package/src/components/date/Date.astro +137 -0
  77. package/src/components/date/index.ts +2 -0
  78. package/src/components/dialog/CHANGELOG.md +39 -0
  79. package/src/components/dialog/Dialog.astro +196 -0
  80. package/src/components/dialog/DialogClose.astro +23 -0
  81. package/src/components/dialog/DialogContent.astro +42 -0
  82. package/src/components/dialog/DialogDescription.astro +18 -0
  83. package/src/components/dialog/DialogFooter.astro +18 -0
  84. package/src/components/dialog/DialogHeader.astro +18 -0
  85. package/src/components/dialog/DialogOverlay.astro +19 -0
  86. package/src/components/dialog/DialogTitle.astro +18 -0
  87. package/src/components/dialog/DialogTrigger.astro +37 -0
  88. package/src/components/dialog/index.ts +9 -0
  89. package/src/components/dropdown-menu/CHANGELOG.md +194 -0
  90. package/src/components/dropdown-menu/DropdownMenu.astro +226 -0
  91. package/src/components/dropdown-menu/DropdownMenuCheckboxItem.astro +114 -0
  92. package/src/components/dropdown-menu/DropdownMenuContent.astro +42 -0
  93. package/src/components/dropdown-menu/DropdownMenuGroup.astro +19 -0
  94. package/src/components/dropdown-menu/DropdownMenuItem.astro +220 -0
  95. package/src/components/dropdown-menu/DropdownMenuItemContent.astro +79 -0
  96. package/src/components/dropdown-menu/DropdownMenuLabel.astro +20 -0
  97. package/src/components/dropdown-menu/DropdownMenuPortal.astro +13 -0
  98. package/src/components/dropdown-menu/DropdownMenuRadioGroup.astro +13 -0
  99. package/src/components/dropdown-menu/DropdownMenuRadioItem.astro +101 -0
  100. package/src/components/dropdown-menu/DropdownMenuSeparator.astro +18 -0
  101. package/src/components/dropdown-menu/DropdownMenuShortcut.astro +18 -0
  102. package/src/components/dropdown-menu/DropdownMenuTrigger.astro +49 -0
  103. package/src/components/dropdown-menu/dropdown-menu-item-variants.ts +24 -0
  104. package/src/components/dropdown-menu/index.ts +12 -0
  105. package/src/components/field/CHANGELOG.md +33 -0
  106. package/src/components/field/Field.astro +664 -0
  107. package/src/components/field/FieldContent.astro +20 -0
  108. package/src/components/field/FieldDescription.astro +22 -0
  109. package/src/components/field/FieldError.astro +51 -0
  110. package/src/components/field/FieldGroup.astro +20 -0
  111. package/src/components/field/FieldLabel.astro +22 -0
  112. package/src/components/field/FieldLegend.astro +23 -0
  113. package/src/components/field/FieldSeparator.astro +30 -0
  114. package/src/components/field/FieldSet.astro +20 -0
  115. package/src/components/field/FieldTitle.astro +20 -0
  116. package/src/components/field/index.ts +10 -0
  117. package/src/components/hover-card/CHANGELOG.md +20 -0
  118. package/src/components/hover-card/HoverCard.astro +235 -0
  119. package/src/components/hover-card/HoverCardContent.astro +54 -0
  120. package/src/components/hover-card/HoverCardTrigger.astro +38 -0
  121. package/src/components/hover-card/index.ts +3 -0
  122. package/src/components/input/CHANGELOG.md +29 -0
  123. package/src/components/input/Input.astro +130 -0
  124. package/src/components/input/index.ts +2 -0
  125. package/src/components/input-group/CHANGELOG.md +49 -0
  126. package/src/components/input-group/InputGroup.astro +135 -0
  127. package/src/components/input-group/InputGroupAddon.astro +49 -0
  128. package/src/components/input-group/InputGroupButton.astro +58 -0
  129. package/src/components/input-group/InputGroupInput.astro +18 -0
  130. package/src/components/input-group/InputGroupText.astro +21 -0
  131. package/src/components/input-group/InputGroupTextarea.astro +17 -0
  132. package/src/components/input-group/controlClasses.ts +2 -0
  133. package/src/components/input-group/index.ts +6 -0
  134. package/src/components/item/CHANGELOG.md +27 -0
  135. package/src/components/item/Item.astro +122 -0
  136. package/src/components/item/ItemActions.astro +21 -0
  137. package/src/components/item/ItemContent.astro +25 -0
  138. package/src/components/item/ItemDescription.astro +25 -0
  139. package/src/components/item/ItemFooter.astro +24 -0
  140. package/src/components/item/ItemGroup.astro +24 -0
  141. package/src/components/item/ItemHeader.astro +24 -0
  142. package/src/components/item/ItemMedia.astro +47 -0
  143. package/src/components/item/ItemSeparator.astro +21 -0
  144. package/src/components/item/ItemTitle.astro +25 -0
  145. package/src/components/item/index.ts +10 -0
  146. package/src/components/kbd/CHANGELOG.md +5 -0
  147. package/src/components/kbd/Kbd.astro +145 -0
  148. package/src/components/kbd/KbdGroup.astro +55 -0
  149. package/src/components/kbd/index.ts +2 -0
  150. package/src/components/label/CHANGELOG.md +28 -0
  151. package/src/components/label/Label.astro +97 -0
  152. package/src/components/label/index.ts +2 -0
  153. package/src/components/link-group/CHANGELOG.md +75 -0
  154. package/src/components/link-group/LinkGroup.astro +67 -0
  155. package/src/components/link-group/LinkGroupItem.astro +32 -0
  156. package/src/components/link-group/LinkGroupList.astro +25 -0
  157. package/src/components/link-group/LinkGroupTitle.astro +22 -0
  158. package/src/components/link-group/index.ts +5 -0
  159. package/src/components/link-group/link-group-list-variants.ts +15 -0
  160. package/src/components/marquee/CHANGELOG.md +5 -0
  161. package/src/components/marquee/Marquee.astro +119 -0
  162. package/src/components/marquee/index.ts +2 -0
  163. package/src/components/native-select/CHANGELOG.md +5 -0
  164. package/src/components/native-select/NativeSelect.astro +125 -0
  165. package/src/components/native-select/NativeSelectOptGroup.astro +14 -0
  166. package/src/components/native-select/NativeSelectOption.astro +9 -0
  167. package/src/components/native-select/index.ts +3 -0
  168. package/src/components/navigation-menu/CHANGELOG.md +63 -0
  169. package/src/components/navigation-menu/NavigationMenu.astro +414 -0
  170. package/src/components/navigation-menu/NavigationMenuContent.astro +37 -0
  171. package/src/components/navigation-menu/NavigationMenuIndicator.astro +24 -0
  172. package/src/components/navigation-menu/NavigationMenuItem.astro +22 -0
  173. package/src/components/navigation-menu/NavigationMenuLink.astro +18 -0
  174. package/src/components/navigation-menu/NavigationMenuList.astro +21 -0
  175. package/src/components/navigation-menu/NavigationMenuPositioner.astro +44 -0
  176. package/src/components/navigation-menu/NavigationMenuTrigger.astro +30 -0
  177. package/src/components/navigation-menu/NavigationMenuViewport.astro +19 -0
  178. package/src/components/navigation-menu/index.ts +10 -0
  179. package/src/components/popover/CHANGELOG.md +5 -0
  180. package/src/components/popover/Popover.astro +210 -0
  181. package/src/components/popover/PopoverClose.astro +18 -0
  182. package/src/components/popover/PopoverContent.astro +49 -0
  183. package/src/components/popover/PopoverDescription.astro +18 -0
  184. package/src/components/popover/PopoverHeader.astro +18 -0
  185. package/src/components/popover/PopoverTitle.astro +18 -0
  186. package/src/components/popover/PopoverTrigger.astro +29 -0
  187. package/src/components/popover/index.ts +7 -0
  188. package/src/components/radio-group/CHANGELOG.md +9 -0
  189. package/src/components/radio-group/RadioGroup.astro +81 -0
  190. package/src/components/radio-group/RadioGroupItem.astro +42 -0
  191. package/src/components/radio-group/index.ts +3 -0
  192. package/src/components/select/CHANGELOG.md +39 -0
  193. package/src/components/select/Select.astro +373 -0
  194. package/src/components/select/SelectContent.astro +91 -0
  195. package/src/components/select/SelectGroup.astro +19 -0
  196. package/src/components/select/SelectItem.astro +48 -0
  197. package/src/components/select/SelectLabel.astro +18 -0
  198. package/src/components/select/SelectSeparator.astro +17 -0
  199. package/src/components/select/SelectTrigger.astro +34 -0
  200. package/src/components/select/SelectValue.astro +22 -0
  201. package/src/components/select/index.ts +8 -0
  202. package/src/components/separator/CHANGELOG.md +5 -0
  203. package/src/components/separator/Separator.astro +60 -0
  204. package/src/components/separator/index.ts +2 -0
  205. package/src/components/skeleton/CHANGELOG.md +5 -0
  206. package/src/components/skeleton/Skeleton.astro +121 -0
  207. package/src/components/skeleton/index.ts +1 -0
  208. package/src/components/slider/CHANGELOG.md +5 -0
  209. package/src/components/slider/Slider.astro +198 -0
  210. package/src/components/slider/index.ts +1 -0
  211. package/src/components/spinner/CHANGELOG.md +5 -0
  212. package/src/components/spinner/Spinner.astro +134 -0
  213. package/src/components/spinner/index.ts +1 -0
  214. package/src/components/sticky-surface/CHANGELOG.md +5 -0
  215. package/src/components/sticky-surface/StickySurface.astro +152 -0
  216. package/src/components/sticky-surface/index.ts +2 -0
  217. package/src/components/switch/CHANGELOG.md +9 -0
  218. package/src/components/switch/Switch.astro +106 -0
  219. package/src/components/switch/index.ts +1 -0
  220. package/src/components/table/CHANGELOG.md +5 -0
  221. package/src/components/table/Table.astro +250 -0
  222. package/src/components/table/TableBody.astro +18 -0
  223. package/src/components/table/TableCaption.astro +17 -0
  224. package/src/components/table/TableCell.astro +21 -0
  225. package/src/components/table/TableFooter.astro +21 -0
  226. package/src/components/table/TableHead.astro +21 -0
  227. package/src/components/table/TableHeader.astro +17 -0
  228. package/src/components/table/TableRow.astro +21 -0
  229. package/src/components/table/index.ts +9 -0
  230. package/src/components/tabs/CHANGELOG.md +5 -0
  231. package/src/components/tabs/Tabs.astro +306 -0
  232. package/src/components/tabs/TabsContent.astro +47 -0
  233. package/src/components/tabs/TabsList.astro +69 -0
  234. package/src/components/tabs/TabsTrigger.astro +40 -0
  235. package/src/components/tabs/index.ts +6 -0
  236. package/src/components/textarea/CHANGELOG.md +5 -0
  237. package/src/components/textarea/Textarea.astro +36 -0
  238. package/src/components/textarea/index.ts +2 -0
  239. package/src/components/toggle/CHANGELOG.md +5 -0
  240. package/src/components/toggle/Toggle.astro +138 -0
  241. package/src/components/toggle/index.ts +1 -0
  242. package/src/components/toggle-group/CHANGELOG.md +5 -0
  243. package/src/components/toggle-group/ToggleGroup.astro +224 -0
  244. package/src/components/toggle-group/ToggleGroupItem.astro +48 -0
  245. package/src/components/toggle-group/index.ts +2 -0
  246. package/src/components/tooltip/CHANGELOG.md +5 -0
  247. package/src/components/tooltip/Tooltip.astro +331 -0
  248. package/src/components/tooltip/TooltipContent.astro +60 -0
  249. package/src/components/tooltip/TooltipTrigger.astro +34 -0
  250. package/src/components/tooltip/index.ts +3 -0
  251. package/src/lib/toggle-group-controller.ts +113 -0
  252. package/src/lib/toggle-shared.ts +26 -0
  253. package/src/lib/utils.ts +6 -0
  254. package/src/styles/globals.css +177 -0
@@ -0,0 +1,41 @@
1
+ # Changelog for Button.astro
2
+
3
+ All notable changes to the `Button` component will be documented in this file.
4
+
5
+ ## Date 8th Sep 2026
6
+ ## Figma alignment (node 114-6897, prop table 7185-13418)
7
+
8
+ ### Variants
9
+
10
+ - **Primary:** removed the duplicated/conflicting hover utilities (`hover:bg-primary/80` next to `hover:bg-primary/90`); Figma's hover state resolves to `#3785e0cc`, i.e. `bg-primary` at 80% opacity. Kept `hover:bg-primary/80` only.
11
+ - **Destructive:** completely rebuilt. It previously mixed `text-destructive` and `text-white`, and stacked four different hover opacities (`/20`, `/30`, `/60`, `/90`) across light/dark. Figma's destructive is flat: `bg-destructive text-destructive-foreground`, hover `bg-destructive-hover` (`#ef4444`, already an existing token).
12
+ - **Outline:** dropped the leftover `dark:bg-input/30 dark:border-input dark:hover:bg-input/50` and the duplicate `border`/`shadow-xs shadow-none` pair. Now `border border-border bg-background text-foreground`, hover `bg-background-hover`.
13
+ - **Secondary:** hover changed from `hover:bg-secondary/80` to `hover:bg-background-button-hover` (`#e4e4e7`), matching the resolved Figma value and the same token Badge already uses for its secondary hover.
14
+ - **Ghost:** dropped the implicit `bg-secondary`/`bg-muted` hover mix; now `text-foreground hover:bg-background-hover`, matching Figma exactly.
15
+ - **Link:** was rendered with `p-0` (no padding box) and no hover treatment beyond `hover:underline`. Figma's Link symbol keeps the same padding box as every other variant and adds a `hover:bg-background-hover` wash in addition to the underline. Removed `p-0`.
16
+ - Dropped `aria-expanded:*` variant styling that had no corresponding Figma state — components should express open/active state through `variant`/`class`, not baked into every variant.
17
+
18
+ ### States
19
+
20
+ - **Disabled:** was `disabled:opacity-50` (just dims whatever variant color). Figma switches every variant to the same flat `bg-muted`/`text-muted-foreground`/`border-transparent`, confirmed identical across Primary, Secondary, Outline, Ghost and Destructive. Moved to the shared base class so it applies uniformly.
21
+ - **Focus:** ring was `focus-visible:ring-ring/50` (translucent). Figma's focus ring is a solid 2px ring in `chart-1` (`#afcef3`, = `primary-200`) plus a `border-ring` border — both already existing tokens. Now `focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-chart-1`.
22
+
23
+ ### Sizing & Spacing
24
+
25
+ - **Radius:** unified to `rounded-lg` (8px) for every size. Previously `sm` used a one-off `rounded-[min(var(--radius-md),10px)]` and only `lg`/`default` used `rounded-lg` — Figma uses the same 8px radius across all three sizes and both icon-only and labeled buttons.
26
+ - **Font size:** `default`/`sm` were rendering at Tailwind's `text-sm` (14px) but Figma's token for those sizes is 12px (`text-xs` in this Tailwind scale). `lg` was `text-base` (16px) but Figma's large text token is 14px (`text-sm`). Fixed both.
27
+ - **Icon size:** replaced the single blanket `[&_svg:not([class*='size-'])]:size-4` (16px for every size) with per-size rules: `size-3.5` (14px) for `sm`, `size-4` (16px) for `default`, `size-5` (20px) for `lg` — confirmed against Figma's lead/tail icon dimensions per size.
28
+ - **Gap:** simplified nested Figma auto-layout spacing (2px flex gap + 2px label padding on each side ≈ 4px effective gap) down to a single `gap-1` (4px) on the root, applied uniformly across sizes.
29
+ - **Horizontal padding:** `sm` uses `px-2` (8px), `default`/`lg` both use `px-2.5` (10px) — confirmed from Figma's resolved padding tokens (large's extra height comes from its bigger icon/font/fixed `h-10`, not extra horizontal padding).
30
+
31
+ ### New Props
32
+
33
+ - `leadIcon` / `tailIcon` / `badge` (`boolean`): render the `lead-icon` / `tail-icon` / `badge` named slots before/after the label. Auto-detected from slot presence if not explicitly passed — same pattern as Badge's `leadIcon`/`tailIcon`.
34
+ - This replaces the previous implicit convention of dropping an icon in as a bare child with a `data-icon="inline-start"|"inline-end"` attribute and relying on `has-data-[icon=...]`/`has-[.badge:last-child]` selectors for spacing. No existing usages in the repo relied on that convention (grepped for `data-icon=`), so this is a clean cutover.
35
+ - Docs updated: the "With badge" example now also sets `shape="sharp" size="sm"` on the nested `Badge`, matching the compact/square badge Figma nests inside a Button (`rounded-xs`, `px-1 py-0.5`) rather than Badge's own pill/default sizing.
36
+
37
+ ### Tokens (`globals.css`)
38
+
39
+ No new tokens were needed — every color Button uses already exists: `primary`/`primary-foreground`, `secondary`/`secondary-foreground`, `background`/`background-hover`/`background-button-hover`, `destructive`/`destructive-foreground`/`destructive-hover`, `muted`/`muted-foreground`, `border`, `ring`, `chart-1`.
40
+
41
+ ## Unreleased
@@ -0,0 +1,2 @@
1
+ export { default as Button } from "./Button.astro";
2
+
@@ -0,0 +1,130 @@
1
+ ---
2
+ /**
3
+ * @component ButtonGroup
4
+ * @title Button Group
5
+ * @description A set of related buttons grouped together, with support for horizontal or vertical layouts.
6
+ * @figmaUrl https://www.figma.com/design/qXkzdqZ3QPQqgckU3AwIAH/ClickLearn-Lightning-Design-Library?node-id=114-9882
7
+ *
8
+ * @preview
9
+ *
10
+ * <ButtonGroup>
11
+ * <Button variant="outline">Left</Button>
12
+ * <Button variant="outline">Middle</Button>
13
+ * <Button variant="outline">Right</Button>
14
+ * </ButtonGroup>
15
+ *
16
+ * @usage
17
+ *
18
+ * ```astro nocollapse
19
+ * ---
20
+ * import { ButtonGroup } from '@bejamas/ui/components/button-group';
21
+ * import { Button } from '@bejamas/ui/components/button';
22
+ * ---
23
+ * <ButtonGroup>
24
+ * <Button>One</Button>
25
+ * <Button>Two</Button>
26
+ * <Button>Three</Button>
27
+ * </ButtonGroup>
28
+ * ```
29
+ *
30
+ * @props
31
+ * - orientation?: "horizontal" | "vertical" — Default: "horizontal".
32
+ * - class?: string — Additional classes for the container.
33
+ *
34
+ * @examples
35
+ *
36
+ * ### Orientation
37
+ *
38
+ * <ButtonGroup orientation="vertical" aria-label="Media controls" class="h-fit">
39
+ * <Button variant="outline" size="icon">
40
+ * <PlusIcon />
41
+ * </Button>
42
+ * <Button variant="outline" size="icon">
43
+ * <MinusIcon />
44
+ * </Button>
45
+ * </ButtonGroup>
46
+ *
47
+ * ### Size
48
+ *
49
+ * <div class="flex flex-col items-start gap-8">
50
+ * <ButtonGroup>
51
+ * <Button variant="outline" size="sm">
52
+ * Small
53
+ * </Button>
54
+ * <Button variant="outline" size="sm">
55
+ * Button
56
+ * </Button>
57
+ * <Button variant="outline" size="sm">
58
+ * Group
59
+ * </Button>
60
+ * <Button variant="outline" size="icon-sm">
61
+ * <PlusIcon />
62
+ * </Button>
63
+ * </ButtonGroup>
64
+ * <ButtonGroup>
65
+ * <Button variant="outline">Default</Button>
66
+ * <Button variant="outline">Button</Button>
67
+ * <Button variant="outline">Group</Button>
68
+ * <Button variant="outline" size="icon">
69
+ * <PlusIcon />
70
+ * </Button>
71
+ * </ButtonGroup>
72
+ * <ButtonGroup>
73
+ * <Button variant="outline" size="lg">
74
+ * Large
75
+ * </Button>
76
+ * <Button variant="outline" size="lg">
77
+ * Button
78
+ * </Button>
79
+ * <Button variant="outline" size="lg">
80
+ * Group
81
+ * </Button>
82
+ * <Button variant="outline" size="icon-lg">
83
+ * <PlusIcon />
84
+ * </Button>
85
+ * </ButtonGroup>
86
+ * </div>
87
+ */
88
+
89
+ import type { HTMLAttributes } from "astro/types";
90
+ import { cva, type VariantProps } from "class-variance-authority";
91
+ import { cn } from "@cl-light-ds/ui/lib/utils";
92
+
93
+ interface Props
94
+ extends VariantProps<typeof buttonGroupVariants>, HTMLAttributes<"div"> {
95
+ class?: string;
96
+ }
97
+
98
+ const {
99
+ orientation = "horizontal",
100
+ class: className = "",
101
+ ...props
102
+ } = Astro.props as Props;
103
+
104
+ export const buttonGroupVariants = cva(
105
+ "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-e-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
106
+ {
107
+ variants: {
108
+ orientation: {
109
+ horizontal:
110
+ "*:data-slot:rounded-e-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-e-lg! [&>[data-slot]~[data-slot]]:border-s-0 [&>[data-slot]~[data-slot]]:rounded-s-none [&>[data-slot]~[data-slot]_[data-slot]]:border-s-0 [&>[data-slot]~[data-slot]_[data-slot]]:rounded-s-none",
111
+ vertical:
112
+ "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0 [&>[data-slot]~[data-slot]_[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]_[data-slot]]:border-t-0",
113
+ },
114
+ },
115
+ defaultVariants: {
116
+ orientation: "horizontal",
117
+ },
118
+ },
119
+ );
120
+ ---
121
+
122
+ <div
123
+ role="group"
124
+ data-slot="button-group"
125
+ data-orientation={orientation}
126
+ class={cn("has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md", buttonGroupVariants({ orientation }), className)}
127
+ {...props}
128
+ >
129
+ <slot />
130
+ </div>
@@ -0,0 +1,30 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { cn } from "@cl-light-ds/ui/lib/utils";
4
+ import Separator from "../separator/Separator.astro";
5
+
6
+ interface Props extends HTMLAttributes<"div"> {
7
+ class?: string;
8
+ orientation?: "horizontal" | "vertical";
9
+ decorative?: boolean;
10
+ }
11
+
12
+ const {
13
+ class: className = "",
14
+ orientation = "vertical",
15
+ decorative = true,
16
+ ...rest
17
+ } = Astro.props as Props;
18
+ ---
19
+
20
+ <Separator
21
+ {...rest}
22
+ decorative={decorative}
23
+ data-slot="button-group-separator"
24
+ data-orientation={orientation}
25
+ orientation={orientation}
26
+ class={cn(
27
+ "bg-input relative self-stretch data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto",
28
+ className,
29
+ )}
30
+ />
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { cn } from "@cl-light-ds/ui/lib/utils";
4
+
5
+ interface Props extends HTMLAttributes<"div"> {
6
+ class?: string;
7
+ }
8
+
9
+ const { class: className = "", ...rest } = Astro.props as Props;
10
+ ---
11
+
12
+ <div
13
+ {...rest}
14
+ data-slot="button-group-text"
15
+ class={cn(
16
+ "bg-muted flex items-center gap-2 rounded-lg border px-2.5 text-sm font-semibold [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
17
+ className,
18
+ )}
19
+ >
20
+ <slot />
21
+ </div>
@@ -0,0 +1,34 @@
1
+ # Changelog for ButtonGroup.astro
2
+
3
+ All notable changes to the `ButtonGroup` component will be documented in this file.
4
+
5
+ ## Date 8th Sep 2026
6
+ ## Figma alignment (node 114-9882)
7
+
8
+ ### Sizing & Spacing
9
+
10
+ - **Radius fixed:** `Button.astro` was forcing `in-data-[slot=button-group]:rounded-md` (6px) on the `default`, `sm`, `lg`, and `icon-sm` sizes whenever a button sat inside a group, while `icon` and `icon-lg` had no such override. Figma's grouped item corners are unambiguously `--radius/rounded-lg` (8px, same token as `--radius-lg` in `globals.css`) across every size. Removed the override entirely — end-item corners on a `ButtonGroup` now render at 8px consistently, matching standalone `Button` and every size.
11
+ - No changes were needed to `ButtonGroup.astro`'s own merge logic (`rounded-e-lg!`, `border-s-0`, etc.) — it already resolved to 8px correctly; the bug was solely in `Button.astro`'s per-size override.
12
+
13
+ ### States (new, `Button.astro`, scoped via `in-data-[slot=button-group]:`)
14
+
15
+ Previously, grouped buttons had no state styling of their own — they just inherited standalone `outline`-variant hover (`hover:bg-background-hover`) and the shared global focus ring. Figma's `Button Group` states are defined per-size in the `Button Group / Items` master symbols frame (node `114:11217`) — note the separate `States` showcase page (node `2597:6822`) had its `Hover`/`Focus` symbol instances swapped, so the master symbols frame was used as the authoritative source instead:
16
+
17
+ - **Hover:** background becomes `bg-background-hover` (`#f4f4f5`, existing `--background-hover` token), border uses Figma's `accent` variable (`#ebf3fc`) — which doesn't match this codebase's `--accent` token (a neutral gray) but is identical to the existing `--badge-accent`/`--primary-50` token, so bound to `border-badge-accent` (same token/pattern `Badge.astro` already uses for its own `accent` variant). No shadow. New `in-data-[slot=button-group]:hover:*` rule on `Button.astro`.
18
+ - **Focus-visible:** background stays `bg-background` (white, unchanged), border is `border-ring` (`#5f9de6`, existing `--ring` token), plus a 2px `ring-chart-1` shadow (the effect is literally named `border-focus` in the Figma file). This is **exactly** `Button.astro`'s existing global `focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-chart-1` rule — no grouped-context override needed; grouped and standalone buttons share identical focus-visible styling.
19
+ - **Disabled:** unchanged — `bg-muted`/`text-muted-foreground`/`border-transparent` from `Button.astro`'s shared `disabled:` classes already matches Figma's grouped disabled state (`bg-muted`/`border-muted`/`text-muted-foreground`) visually.
20
+ - **Default:** unchanged — Figma's grouped item (`bg-background`/`border-border`/`text-foreground`) is exactly `Button`'s existing `outline` variant, which is what every documented `ButtonGroup` usage already passes.
21
+
22
+ ### Storybook & Docs (`_sections/button-group.astro`)
23
+
24
+ - Added a "Disabled" preview block, alongside the existing `Preview`/`Vertical orientation`/`Sizes` sections, so the disabled state is visible in Storybook.
25
+
26
+ ### Not changed (flagged, not acted on)
27
+
28
+ A few things in Figma's `Button Group` variants don't match `Button`'s already-finalized standalone sizing from its own alignment pass, and read more like authoring artifacts in the Figma file than intentional grouped-only rules — left as-is rather than re-touching `Button.astro`'s validated standalone spacing:
29
+
30
+ - Figma's grouped "Medium/Default" item uses 8px horizontal padding vs. standalone `Button` `default` size's 10px (`px-2.5`). Small and Large sizes match their standalone counterparts exactly, so this looks like one mistyped padding token in the Figma file.
31
+ - Figma's grouped `lg` item shows a 6px icon/label gap vs. `Button`'s current `lg` gap of 4px (`gap-1`).
32
+ - Figma's grouped icons are a flat 14px (text mode) / 16px (icon-only) regardless of size, vs. `Button`'s current per-size icon scaling (14/16/20 for sm/default/lg) — which the prior `Button` alignment pass deliberately validated against `Button`'s own (different) Figma variants.
33
+
34
+ ## Unreleased
@@ -0,0 +1,3 @@
1
+ export { default as ButtonGroup } from "./ButtonGroup.astro";
2
+ export { default as ButtonGroupSeparator } from "./ButtonGroupSeparator.astro";
3
+ export { default as ButtonGroupText } from "./ButtonGroupText.astro";
@@ -0,0 +1,119 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `Card` component will be documented in this file.
4
+
5
+ ## Date 9th Sep 2026
6
+ ## Figma alignment (node 54-1310, prop table 7350-1279)
7
+
8
+ ### Variants
9
+
10
+ - Added an `overlay` variant (`variant: "default" | "ghost" | "overlay"`, was `"default" | "ghost"`). Renders `CardMedia` as a full-bleed background behind the card's content — no separate prop is needed on `CardMedia`, `Card` drives the positioning via a `[&_[data-slot=card-media]]:` selector on its own root, so `<Card variant="overlay">` is the single source of truth.
11
+ - **Fixed `ghost`:** previously only added a `data-[variant=ghost]` marker attribute while the root still always applied `border border-border shadow-lg bg-card` unconditionally — visually a no-op, so "ghost" cards rendered identically to default. `border`/`bg`/`shadow` are now scoped to `variant: "default"` only, so `ghost` (and `overlay`) render fully chrome-less as Figma specifies.
12
+ - Removed the always-on `ring-1 ring-foreground/10 shadow-sm` legacy base styling, which conflicted with the newer `border border-border shadow-lg` layered on top of it. Figma shows no shadow/ring at rest for `default` — the ring only appears in the Focus state.
13
+
14
+ ### States
15
+
16
+ - **Focus:** `focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-chart-1`, resolved from Figma's `border-ring` + `shadow-[0px_0px_0px_2px_var(--chart-1,#afcef3)]`. Wired as a real, reachable `:focus-visible` state (not a forced class) now that `Card` can render as a native `<a>` — see `as` below.
17
+ - **Pressed** (Overlay only): not exposed as a `state` prop. Approximated purely via CSS `:active` on a scrim layer (`bg-card-overlay-pressed/80 backdrop-blur-[3px]`), demonstrated inline in `CardStory.astro`'s Overlay-Pressed composition rather than as a new prop.
18
+
19
+ ### Sizing & Spacing
20
+
21
+ - Added `size: "default" | "large"` — replaces dead `data-[size=sm]` infra that nothing ever wired up (and whose `sm` naming was backwards vs. Figma's own prop table, which calls its compact size `"default"`).
22
+ - Switched from a root-owned `py-*`/`gap-*` vertical-rhythm model to a self-padded-block model, since Large's content padding is asymmetric (`pt-8 pb-6 px-6`) and can't be expressed by a single root `py-*` value: `CardHeader` `pt-6` → `pt-8` (large), `CardContent` `pt-8 pb-6` (constant both sizes), `CardFooter` `p-6` (constant both sizes).
23
+ - `CardMedia` inner image radius fixed from `rounded-lg` (8px) to `rounded-md` (6px) for the inset/padded case, matching Figma's flat `rounded-[6px]` on the image against the card's 8px outer radius. The Overlay full-bleed image keeps `rounded-lg` (8px, matching Figma exactly), since there it touches the card's own edges with no inset padding.
24
+ - `CardTitle`: dropped the `md:text-xl` responsive fallback and the wrong-axis `group-data-[size=sm]/card:text-sm`; now a flat `text-xl`, dropping to `text-base` only in `orientation="horizontal"` (title size varies by orientation, not by size).
25
+ - `CardDescription`: `text-sm` base, `text-base` only for `size="large"` + `orientation="vertical"` (Large-Horizontal stays `text-sm`).
26
+ - `CardContent`: now a real `flex flex-col gap-8` container (was `cva("")`, i.e. no layout at all — consumers previously had to add `space-y-*` manually). **Heads up for existing consumers:** this changes CardContent's default display from block to flex-column; anything relying on block-flow spacing inside it should double check.
27
+
28
+ ### New Props
29
+
30
+ - `size: "default" | "large"` — see above.
31
+ - `orientation: "vertical" | "horizontal"` — horizontal is currently only authored in Figma for `variant="ghost"` + `size="large"`; no `default`/`overlay` horizontal frames exist yet.
32
+ - `as: "div" | "a"` (Button-style polymorphic tag, was fixed to `<div>`). Lets `Card` render as a real, natively focusable `<a>` so the whole card can be a clickable unit with a genuine `:focus-visible` state — resolves the gap between Figma's Focus state / Overlay arrow-icon affordance and the previous non-interactive `<div>`-only implementation.
33
+
34
+ ### Tokens (`globals.css`)
35
+
36
+ - Added `--card-overlay-pressed: #051729` (+ `@theme inline` mapping `--color-card-overlay-pressed`) for the Overlay variant's Pressed-state scrim, used at `bg-card-overlay-pressed/80`. This color doesn't match any existing semantic token (closest was `--foreground` at `#09090b`, a different hue), so a new single-purpose token was added rather than approximated, per design decision.
37
+ - No other new tokens needed — every other color already existed: `card`, `border`, `ring`, `chart-1`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `muted-foreground`.
38
+
39
+ ### Storybook & Docs
40
+
41
+ - New stories: `Large`, `Ghost`, `GhostLarge`, `GhostHorizontal`, `Overlay`, `OverlayLarge`, `OverlayPressed`, `Focus`.
42
+ - Replaced the dead `[.border-t]:pt-*`/`[.border-b]:pb-*` marker-class divider convention (grepped — no consumer relied on it) with an explicit `<Separator />` (existing, token-bound `bg-border` component) placed between `CardContent` and `CardFooter`.
43
+ - Fixed category `Badge` usages in Card's docs/stories to pass `shape="sharp"` (were rendering as pill, not matching Figma's `rounded-xs`).
44
+ - Updated the `apps/web` storybook props table and previews to document `variant` (3 values), `size`, `orientation`, and `as`.
45
+
46
+ ### Not changed
47
+
48
+ - `CardAction.astro` — no Figma delta identified.
49
+ - `CardOverlay.astro` — confirmed distinct/unrelated hover-reveal component (opacity fade on `group-hover/card`), intentionally kept separate from the new `variant="overlay"` (same name, different concept).
50
+
51
+ ### Flagged for the designer (Figma-file gaps, not addressed in this pass)
52
+
53
+ - Avatar size (32px) and background-image height (285px) don't scale between `default`/`large` Card sizes in the current Figma file — matched as authored (unscaled) rather than inventing scaling behavior the design doesn't show.
54
+ - Large-Overlay has no documented Pressed state in Figma (only Small-Overlay does) — worth confirming if intentional.
55
+ - `--radius-card` (24px) token exists in `globals.css` but is unused anywhere in the codebase and doesn't match Card's actual 8px radius (`--radius-lg`) — likely dead/mis-specified, worth a separate cleanup pass.
56
+
57
+ ## Date 9th Sep 2026 (follow-up)
58
+ ## Fix: Ghost/Large/Horizontal spacing (node 2547-9489)
59
+
60
+ ### Fixed
61
+
62
+ - **Layout bug:** `variant="ghost" size="large" orientation="horizontal"` rendered `CardMedia`, `CardHeader`, and `CardContent` as three separate direct children of `Card`. Since `Card`'s root switches to `flex-row` for horizontal orientation, this produced three side-by-side columns instead of two — `CardHeader`'s avatar/date/badge row floated as its own skinny column between the image and the text, instead of stacking above the title. Figma's own structure for this composition confirms there's no separate header concept here at all — it's a single flex-column "Content" area.
63
+ - **Fix:** the horizontal composition (`CardStory.astro`, and the storybook docs preview) now nests the Details row (avatar group + date + badge) directly inside `CardContent`, grouped with the Title/Description block in a `flex flex-col gap-6` wrapper (the 24px gap the designer just retokenized in Figma), with the Buttons row as `CardContent`'s only other child. This gives `Card`'s root exactly two flex-row children (`CardMedia`, `CardContent`), matching Figma, and lets `CardContent`'s existing `justify-between`/`self-stretch` overrides correctly push the Buttons row to the bottom of the image's height.
64
+ - Removed the now-dead `group-data-[orientation=horizontal]/card:px-0 group-data-[orientation=horizontal]/card:pt-0` overrides from `CardHeader.astro`, since no composition uses `CardHeader` in a horizontal layout anymore (matches Figma, which never had a header concept there).
65
+ - No changes needed to `Card.astro` or `CardContent.astro` — the horizontal overrides already there were correct, they just weren't receiving the right children.
66
+
67
+ ## Date 9th Sep 2026 (follow-up)
68
+ ## Fix: Overlay's Pressed state removed (node 2544-3164)
69
+
70
+ ### Removed
71
+
72
+ - The designer updated Figma's Card component set — Overlay now documents exactly two sizes (`Small`/`Large`, matching the existing `default`/`large` prop) and, for now, only **Default** and **Focus** states. The **Pressed** state symbol no longer exists in the file.
73
+ - Removed the `pressed` prop, its inline scrim demo (`bg-card-overlay-pressed/80 backdrop-blur-[3px] ... group-active/card:opacity-100`), and the `OverlayPressed` story from `CardStory.astro`/`Card.stories.ts` — there's no longer a corresponding Figma reference to demonstrate.
74
+ - Removed the now-unused `--card-overlay-pressed` token (and its `@theme inline` mapping) from `globals.css`, per design decision — no dead tokens left dangling; easy to re-add from this changelog entry if Pressed returns later.
75
+ - Overlay's Default/Focus states, both sizes, are otherwise unchanged — this was a pure removal.
76
+
77
+ ## Date 9th Sep 2026 (follow-up)
78
+ ## Fix: Overlay/Small alignment (node 2547-2175)
79
+
80
+ ### Fixed
81
+
82
+ - **Font color:** `CardDescription` hardcoded `text-muted-foreground` (gray) unconditionally, overriding the white text `CardContent` already applies for `variant="overlay"`. Added `group-data-[variant=overlay]/card:text-primary-foreground` to `CardDescription.astro` so overlay descriptions render white like Figma, matching how `CardTitle` (no explicit color) already inherited it correctly.
83
+ - **Avatar/badge row not pinned to the top:** Figma's Small-Overlay structure pins the avatar/badge row to the top and the name/description to the bottom via a `flex-1` spacer between them. The equivalent code composition (`CardHeader` for the avatar/badge row, `CardContent` for the text) only has two flex children, but `Card.astro`'s `variant="overlay" size="default"` compound variant used `justify-end` — with no spacer and only two children, that pushed the whole `[CardHeader, CardContent]` group to the bottom together instead of spreading them apart. Changed to `justify-between` (matching what `size="large"` already correctly used), which alone fixes the pinning.
84
+ - **Missing aspect ratio:** `variant="overlay"` makes `CardMedia` `position: absolute`, taken out of flow — so the card's box height was driven only by `CardHeader`+`CardContent`'s natural content height, collapsing far shorter than Figma's frames. Added `aspect-[360/260]` (small) and `aspect-[481/576]` (large) to `Card.astro`'s overlay compound variants — a real component-level fix, not just a demo tweak, since any consumer of `variant="overlay"` would hit this.
85
+
86
+ ### Storybook & Docs
87
+
88
+ - Header badges in the Overlay composition (`CardStory.astro` and the docs page) changed from `variant="outline" shape="rounded"` to `variant="secondary" shape="pill" size="sm"`, matching the current Figma frame exactly (confirmed by the designer).
89
+ - Added the missing arrow-icon `Button` to the docs page's Overlay preview, matching what `CardStory.astro` and Figma both already show.
90
+
91
+ ## Date 9th Sep 2026 (follow-up)
92
+ ## Fix: Overlay/Default/Small avatar + icon sizing (node 2547-8683)
93
+
94
+ ### Fixed
95
+
96
+ - **Avatar wrong size + doubled border:** the Overlay header avatar rendered at `Avatar`'s default size (32px, `border-[1.5px]`) instead of Figma's `24px`. Worse, `Avatar.astro`'s own base class always applies a hardcoded `border-border` (gray), and the composition additionally wrapped it in `ring-2 ring-background` (a white halo pattern meant for avatars on a plain card background) — stacking a gray border and a white ring instead of Figma's single solid white border. Fixed by using `<Avatar size="sm" class="border-primary-foreground">` (24px, white border override) and dropping the redundant `ring-*` wrapper classes, in both `CardStory.astro` and the docs page.
97
+ - **Arrow icon undersized:** the inline arrow-up-right SVG was `size-4` (16px); Figma's icon is `size-[24px]`. Changed to `size-6` in both files. The 32px button box around it (`Button size="icon-sm"`) was already correct.
98
+
99
+ ## Date 9th Sep 2026 (follow-up)
100
+ ## Fix: Visual re-audit against Figma (default/ghost/overlay, node 54-1310)
101
+
102
+ ### Fixed
103
+
104
+ - **`CardTitle` line-height:** was `leading-normal` (1.5× → 30px for the 20px title, 60px across two lines), overshooting Figma's title text style, which specifies a flat `lineHeight: 24` regardless of font size (confirmed for both the `text-xl` vertical title and the `text-base` horizontal/overlay title). Changed to `leading-6` (a fixed 24px) so two-line titles match Figma's 48px box instead of rendering 12px taller.
105
+ - **Focus ring width:** was `focus-visible:ring-3` (3px); Figma's `border-focus` effect is a `DROP_SHADOW` with `spread: 2` (2px) — the same 2px value this component's own changelog already documented as the target (`shadow-[0px_0px_0px_2px_var(--chart-1)]`) but the implementation used `ring-3` instead of `ring-2`. Changed to `ring-2`.
106
+ - **Overlay avatar group was missing its second avatar:** Figma's header row (both Small and Large Overlay) shows two overlapping avatar photos (`-space-x-2`, white border); `CardStory.astro` and the docs preview only rendered one `Avatar` with an initials fallback. Added a second `<Avatar>` (`https://github.com/thomkrupa.png` + `https://github.com/bejamas.png`, matching the "Thom Krupa, Co-founder at Bejamas" copy) inside the existing `-space-x-2` wrapper.
107
+ - **Overlay avatar/badge sizing now scales with `size`:** Small-Overlay uses 24px avatars + `size="sm"` badges (unchanged); Large-Overlay uses 32px avatars + default-size badges per Figma — previously both were hardcoded to `size="sm"`.
108
+ - **Overlay (size="default") title→description gap:** was `gap-3` (12px); Figma's Small-Overlay "Thom Krupa"/"Co-funder at Bejamas" spacing measures 8px from node positions. Changed to `gap-2`.
109
+ - **Overlay (size="default") description weight/size:** "Co-funder at Bejamas" is a 16px/semibold profile byline in Figma (a local override on that text node, distinct from the shared 14px/regular `CardDescription`), not the plain paragraph style. Added `class="text-base font-semibold"` to that specific `CardDescription` instance in `CardStory.astro` and the docs preview — not a `CardDescription.astro` component change, since Large-Overlay reuses the plain paragraph description as-is (see next point).
110
+ - **Overlay badges were invisible, hidden behind the background photo:** caught during visual verification of the fixes above. `CardMedia` is `position:absolute` (`z-0`) for `variant="overlay"`, and `CardContent` already compensates with `relative z-10` — but `CardHeader` never got the same treatment. Per CSS stacking rules, a positioned `z-index:0` box (`CardMedia`) paints *above* an unpositioned in-flow sibling's static descendants (`Badge`, which has no `position` of its own), regardless of DOM order — so the header's badges silently rendered underneath the photo. `Avatar` happened to survive because it's independently `position:relative`, which is what exposed the bug (avatars visible, badges gone). Added the same `group-data-[variant=overlay]/card:relative group-data-[variant=overlay]/card:z-10` to `CardHeader.astro` that `CardContent.astro` already has. Verified via a live render (Playwright screenshot + `elementFromPoint` at the badge's screen coordinates, which resolved to the `<img>` before the fix and to the badge `<span>` after).
111
+ - **Overlay `size="large"` was rendering the wrong content pattern:** Figma's Large-Overlay is a headline + full paragraph + single icon-only button (same copy pattern as `Default`, just over an image), not the Small-Overlay's name/role profile pattern. `CardStory.astro`'s overlay branch wasn't gated on `size`, so `OverlayLarge` incorrectly showed "Thom Krupa" / "Co-founder at Bejamas". Branched the composition on `size` so `size="large"` now renders the headline/paragraph pattern (reusing the existing `gap-6 group-data-[size=large]/card:gap-3` title/description convention).
112
+
113
+ - **Overlay `CardContent` layout — text and arrow button belong on one row, not stacked:** re-checked against the Figma node positions for both Overlay sizes (Small: "Text" frame has the name/role `Group 1` and the arrow `Button` as side-by-side siblings, `Button` bottom-aligned with the two-line text block; Large: same pattern — "Text" and "Button" siblings inside "Bottom", `Button` bottom-aligned with the text block). `CardContent` was still using its base `flex-col` (text block above, button below) for overlay. Added `group-data-[variant=overlay]/card:flex-row group-data-[variant=overlay]/card:items-end group-data-[variant=overlay]/card:justify-between` so the arrow button now sits to the right of the text block, bottom-aligned, on both Overlay sizes.
114
+
115
+ ### Storybook & Docs
116
+
117
+ - Added an `Overlay, large` preview to the `apps/web` docs page (previously only the Storybook `OverlayLarge` story existed; the docs page never showcased the large-overlay composition, and was showing the same — now-fixed — mismatched content as `CardStory.astro`).
118
+
119
+ ## Unreleased
@@ -0,0 +1,182 @@
1
+ ---
2
+ /**
3
+ * @component Card
4
+ * @title Card
5
+ * @description UI container for grouping content with header, footer, etc.
6
+ * @figmaUrl https://www.figma.com/design/qXkzdqZ3QPQqgckU3AwIAH/ClickLearn-Lightning-Design-Library?node-id=54-1310
7
+ * @preview
8
+ *
9
+ * <Card class="w-full max-w-sm">
10
+ * <CardMedia>
11
+ * <img src="/mount-fuji.png" alt="Mount Fuji at sunset" width="374" height="250" loading="lazy" />
12
+ * </CardMedia>
13
+ * <CardHeader>
14
+ * <div class="flex items-center gap-2 justify-between">
15
+ * <div class="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2">
16
+ * <Avatar>
17
+ * <AvatarImage>
18
+ * <img src="https://gradient.bejamas.com/presets/bejamas/marine.png" alt="grdient avatar" />
19
+ * </AvatarImage>
20
+ * <AvatarFallback>AW</AvatarFallback>
21
+ * </Avatar>
22
+ * </div>
23
+ * <Date date="2025-08-24T12:00:00Z" class="text-sm text-muted-foreground" />
24
+ * </div>
25
+ * </CardHeader>
26
+ * <CardContent>
27
+ * <div class="flex flex-col gap-6 group-data-[size=large]/card:gap-3">
28
+ * <CardTitle>Strong Foundations, Built to Scale</CardTitle>
29
+ * <CardDescription>Like any enduring system, a scalable design system relies on clear structure, solid foundations, and careful layering over time.</CardDescription>
30
+ * </div>
31
+ * <Button variant="outline" as="a" href="#">Read more</Button>
32
+ * </CardContent>
33
+ * <Separator />
34
+ * <CardFooter>
35
+ * <p class="text-sm text-muted-foreground">Categories</p>
36
+ * <div class="flex items-center gap-1">
37
+ * <Badge variant="secondary" shape="sharp">Marketing</Badge>
38
+ * <Badge variant="secondary" shape="sharp">UI Design</Badge>
39
+ * </div>
40
+ * </CardFooter>
41
+ * </Card>
42
+ *
43
+ * @usage
44
+ *
45
+ * ```astro nocollapse
46
+ * ---
47
+ * import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@bejamas/ui/components/card';
48
+ * import { Separator } from '@bejamas/ui/components/separator';
49
+ * ---
50
+ *
51
+ * <Card>
52
+ * <CardHeader>
53
+ * <CardTitle>Card Title</CardTitle>
54
+ * <CardDescription>Card Description</CardDescription>
55
+ * </CardHeader>
56
+ * <CardContent>
57
+ * Card content goes here.
58
+ * </CardContent>
59
+ * <Separator />
60
+ * <CardFooter>
61
+ * <button>OK</button>
62
+ * </CardFooter>
63
+ * </Card>
64
+ * ```
65
+ *
66
+ * @examples
67
+ *
68
+ * ### Sizes
69
+ *
70
+ * Use `size="default"` (compact, the default) or `size="large"` for more generous padding and larger title/description text.
71
+ *
72
+ * <Card size="large">...</Card>
73
+ *
74
+ * ### Ghost
75
+ *
76
+ * `variant="ghost"` removes the card's border, background, and shadow — useful for embedding a card's content inline without visual chrome. Ghost also supports `orientation="horizontal"` for a side-by-side media + content layout.
77
+ *
78
+ * <Card variant="ghost" orientation="horizontal">...</Card>
79
+ *
80
+ * ### Overlay
81
+ *
82
+ * `variant="overlay"` renders `<CardMedia>` as a full-bleed background behind the card's content — no separate prop is needed on `CardMedia` itself, `Card` handles the positioning. Pair with `text-primary-foreground` content since it always sits on a photo.
83
+ *
84
+ * <Card variant="overlay">...</Card>
85
+ *
86
+ * ### As a link
87
+ *
88
+ * Pass `as="a"` (with `href`) to render the whole card as a real, natively focusable anchor — this is how to make Figma's Focus state reachable via keyboard, since a plain `<div>` can't receive `:focus-visible`.
89
+ *
90
+ * <Card as="a" href="#">...</Card>
91
+ */
92
+
93
+ import { cva } from "class-variance-authority";
94
+ import { cn } from "@cl-light-ds/ui/lib/utils";
95
+ import type { HTMLAttributes } from "astro/types";
96
+
97
+ type CardVariant = "default" | "ghost" | "overlay";
98
+ type CardSize = "default" | "large";
99
+ type CardOrientation = "vertical" | "horizontal";
100
+
101
+ type CardAsDiv = {
102
+ as?: "div";
103
+ } & HTMLAttributes<"div">;
104
+
105
+ type CardAsAnchor = {
106
+ as: "a";
107
+ } & HTMLAttributes<"a">;
108
+
109
+ type CardOwnProps = {
110
+ variant?: CardVariant;
111
+ size?: CardSize;
112
+ orientation?: CardOrientation;
113
+ };
114
+
115
+ export type Props = (CardAsDiv | CardAsAnchor) & CardOwnProps;
116
+
117
+ const {
118
+ as: Tag = "div",
119
+ class: className = "",
120
+ variant = "default",
121
+ size = "default",
122
+ orientation = "vertical",
123
+ ...rest
124
+ } = Astro.props as Props;
125
+
126
+ const cardVariants = cva(
127
+ "group/card relative flex overflow-clip rounded-lg text-sm text-card-foreground outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-chart-1",
128
+ {
129
+ variants: {
130
+ variant: {
131
+ default: "bg-card border border-border",
132
+ ghost: "",
133
+ overlay: "",
134
+ },
135
+ size: {
136
+ default: "",
137
+ large: "",
138
+ },
139
+ orientation: {
140
+ vertical: "flex-col",
141
+ horizontal: "flex-row items-start",
142
+ },
143
+ },
144
+ compoundVariants: [
145
+ { variant: "overlay", size: "default", class: "justify-between p-8 aspect-[360/260]" },
146
+ { variant: "overlay", size: "large", class: "justify-between p-8 aspect-[481/576]" },
147
+ { variant: "ghost", orientation: "horizontal", class: "gap-8 p-2" },
148
+ ],
149
+ defaultVariants: {
150
+ variant: "default",
151
+ size: "default",
152
+ orientation: "vertical",
153
+ },
154
+ },
155
+ );
156
+ ---
157
+
158
+ {
159
+ /*
160
+ TS can't reconcile our discriminated union props with a dynamic intrinsic Tag.
161
+ The public Props type *is* strict (href only allowed when as="a"),
162
+ but JSX wants an intersection of button+anchor attributes here.
163
+ We deliberately suppress this internal error and rely on Props for caller safety.
164
+ */
165
+ }
166
+ <!-- @ts-expect-error – union Props + dynamic Tag confuse JSX typing (see comment above) -->
167
+ <Tag
168
+ {...rest}
169
+ data-slot="card"
170
+ data-variant={variant}
171
+ data-size={size}
172
+ data-orientation={orientation}
173
+ class={cn(
174
+ cardVariants({ variant, size, orientation }),
175
+ "*:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
176
+ variant === "overlay" &&
177
+ "[&_[data-slot=card-media]]:absolute [&_[data-slot=card-media]]:inset-0 [&_[data-slot=card-media]]:z-0 [&_[data-slot=card-media]]:rounded-lg [&_[data-slot=card-media]]:p-0 [&_[data-slot=card-media]_img]:rounded-lg",
178
+ className,
179
+ )}
180
+ >
181
+ <slot />
182
+ </Tag>
@@ -0,0 +1,26 @@
1
+ ---
2
+ import { cva } from "class-variance-authority";
3
+ import { cn } from "@cl-light-ds/ui/lib/utils";
4
+ import type { HTMLAttributes } from "astro/types";
5
+
6
+ interface Props extends HTMLAttributes<"div"> {
7
+ class?: string;
8
+ }
9
+
10
+ const {
11
+ class: className = "",
12
+ ...rest
13
+ } = Astro.props as Props;
14
+
15
+ const actionVariants = cva(
16
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
17
+ );
18
+ ---
19
+
20
+ <div
21
+ {...rest}
22
+ data-slot="card-action"
23
+ class={cn("", actionVariants(), className)}
24
+ >
25
+ <slot />
26
+ </div>
@@ -0,0 +1,28 @@
1
+ ---
2
+ import { cva } from "class-variance-authority";
3
+ import { cn } from "@cl-light-ds/ui/lib/utils";
4
+ import type { HTMLAttributes } from "astro/types";
5
+
6
+ interface Props extends HTMLAttributes<"div"> {
7
+ class?: string;
8
+ }
9
+
10
+ const {
11
+ class: className = "",
12
+ ...rest
13
+ } = Astro.props as Props;
14
+
15
+ const contentVariants = cva("");
16
+ ---
17
+
18
+ <div
19
+ {...rest}
20
+ data-slot="card-content"
21
+ class={cn(
22
+ "flex flex-col gap-8 px-6 pt-8 pb-6 group-data-[orientation=horizontal]/card:flex-1 group-data-[orientation=horizontal]/card:justify-between group-data-[orientation=horizontal]/card:self-stretch group-data-[orientation=horizontal]/card:px-0 group-data-[orientation=horizontal]/card:py-3 group-data-[variant=overlay]/card:relative group-data-[variant=overlay]/card:z-10 group-data-[variant=overlay]/card:flex-row group-data-[variant=overlay]/card:items-end group-data-[variant=overlay]/card:justify-between group-data-[variant=overlay]/card:px-0 group-data-[variant=overlay]/card:pt-0 group-data-[variant=overlay]/card:pb-0 group-data-[variant=overlay]/card:text-primary-foreground",
23
+ contentVariants(),
24
+ className,
25
+ )}
26
+ >
27
+ <slot />
28
+ </div>