@rimelight/ui 0.0.4 → 0.0.6

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 (310) hide show
  1. package/package.json +36 -6
  2. package/src/components/BlogPost.astro +181 -0
  3. package/src/components/DynamicShowcase.astro +681 -0
  4. package/src/components/ExtendedCarousel.astro +107 -0
  5. package/src/components/Head.astro +150 -0
  6. package/src/components/LanguageSwitcher.astro +128 -0
  7. package/src/components/ProjectPost.astro +183 -0
  8. package/src/components/RLHead.astro +3 -0
  9. package/src/components/ThemeToggle.astro +30 -42
  10. package/src/components/astro/RLAButton.astro +85 -0
  11. package/src/components/astro/RLAHeader.astro +75 -0
  12. package/src/components/vue/RLVButton.vue +90 -0
  13. package/src/components/vue/RLVHeader.vue +78 -0
  14. package/src/composables/index.ts +1 -0
  15. package/src/composables/useUi.ts +27 -0
  16. package/src/config/index.ts +0 -2
  17. package/src/config/site.config.ts +1 -1
  18. package/src/env.d.ts +9 -42
  19. package/src/integrations/index.ts +2 -0
  20. package/src/integrations/ui.ts +87 -0
  21. package/src/themes/button.theme.ts +361 -0
  22. package/src/themes/header.theme.ts +33 -0
  23. package/src/themes/index.ts +13 -0
  24. package/src/utils/index.ts +3 -2
  25. package/i18n-data.json +0 -186
  26. package/src/components/accordion/Accordion.astro +0 -257
  27. package/src/components/accordion/AccordionContent.astro +0 -34
  28. package/src/components/accordion/AccordionItem.astro +0 -29
  29. package/src/components/accordion/AccordionTrigger.astro +0 -33
  30. package/src/components/alert/Alert.astro +0 -34
  31. package/src/components/alert/AlertDescription.astro +0 -15
  32. package/src/components/alert/AlertTitle.astro +0 -17
  33. package/src/components/alert-dialog/AlertDialog.astro +0 -275
  34. package/src/components/alert-dialog/AlertDialogAction.astro +0 -46
  35. package/src/components/alert-dialog/AlertDialogCancel.astro +0 -46
  36. package/src/components/alert-dialog/AlertDialogContent.astro +0 -52
  37. package/src/components/alert-dialog/AlertDialogDescription.astro +0 -18
  38. package/src/components/alert-dialog/AlertDialogFooter.astro +0 -16
  39. package/src/components/alert-dialog/AlertDialogHeader.astro +0 -14
  40. package/src/components/alert-dialog/AlertDialogTitle.astro +0 -20
  41. package/src/components/alert-dialog/AlertDialogTrigger.astro +0 -47
  42. package/src/components/aspect-ratio/AspectRatio.astro +0 -32
  43. package/src/components/avatar/Avatar.astro +0 -29
  44. package/src/components/avatar/AvatarFallback.astro +0 -18
  45. package/src/components/avatar/AvatarImage.astro +0 -66
  46. package/src/components/badge/Badge.astro +0 -57
  47. package/src/components/breadcrumb/Breadcrumb.astro +0 -12
  48. package/src/components/breadcrumb/BreadcrumbEllipsis.astro +0 -29
  49. package/src/components/breadcrumb/BreadcrumbItem.astro +0 -15
  50. package/src/components/breadcrumb/BreadcrumbLink.astro +0 -24
  51. package/src/components/breadcrumb/BreadcrumbList.astro +0 -17
  52. package/src/components/breadcrumb/BreadcrumbPage.astro +0 -22
  53. package/src/components/breadcrumb/BreadcrumbSeparator.astro +0 -24
  54. package/src/components/button/Button.astro +0 -58
  55. package/src/components/button-group/ButtonGroup.astro +0 -64
  56. package/src/components/button-group/ButtonGroupSeparator.astro +0 -20
  57. package/src/components/button-group/ButtonGroupText.astro +0 -19
  58. package/src/components/card/Card.astro +0 -36
  59. package/src/components/card/CardAction.astro +0 -16
  60. package/src/components/card/CardContent.astro +0 -16
  61. package/src/components/card/CardDescription.astro +0 -16
  62. package/src/components/card/CardFooter.astro +0 -16
  63. package/src/components/card/CardHeader.astro +0 -19
  64. package/src/components/card/CardTitle.astro +0 -16
  65. package/src/components/carousel/Carousel.astro +0 -55
  66. package/src/components/carousel/CarouselContent.astro +0 -26
  67. package/src/components/carousel/CarouselItem.astro +0 -26
  68. package/src/components/carousel/CarouselNext.astro +0 -39
  69. package/src/components/carousel/CarouselPrevious.astro +0 -41
  70. package/src/components/carousel/carousel-script.ts +0 -188
  71. package/src/components/carousel/index.ts +0 -1
  72. package/src/components/checkbox/Checkbox.astro +0 -133
  73. package/src/components/collapsible/Collapsible.astro +0 -161
  74. package/src/components/collapsible/CollapsibleContent.astro +0 -22
  75. package/src/components/collapsible/CollapsibleTrigger.astro +0 -44
  76. package/src/components/dialog/Dialog.astro +0 -389
  77. package/src/components/dialog/DialogClose.astro +0 -35
  78. package/src/components/dialog/DialogContent.astro +0 -78
  79. package/src/components/dialog/DialogDescription.astro +0 -14
  80. package/src/components/dialog/DialogFooter.astro +0 -14
  81. package/src/components/dialog/DialogHeader.astro +0 -14
  82. package/src/components/dialog/DialogTitle.astro +0 -18
  83. package/src/components/dialog/DialogTrigger.astro +0 -47
  84. package/src/components/dropdown/Dropdown.astro +0 -604
  85. package/src/components/dropdown/DropdownContent.astro +0 -118
  86. package/src/components/dropdown/DropdownItem.astro +0 -48
  87. package/src/components/dropdown/DropdownLabel.astro +0 -29
  88. package/src/components/dropdown/DropdownSeparator.astro +0 -21
  89. package/src/components/dropdown/DropdownShortcut.astro +0 -17
  90. package/src/components/dropdown/DropdownSub.astro +0 -15
  91. package/src/components/dropdown/DropdownSubContent.astro +0 -39
  92. package/src/components/dropdown/DropdownSubTrigger.astro +0 -34
  93. package/src/components/dropdown/DropdownTrigger.astro +0 -52
  94. package/src/components/dropzone/Dropzone.astro +0 -236
  95. package/src/components/dropzone/DropzoneFilesList.astro +0 -26
  96. package/src/components/dropzone/DropzoneLoadingIndicator.astro +0 -10
  97. package/src/components/dropzone/DropzoneUploadIndicator.astro +0 -10
  98. package/src/components/embeds/YoutubeEmbed.astro +0 -94
  99. package/src/components/hero/Hero.astro +0 -166
  100. package/src/components/icons/Flag.astro +0 -62
  101. package/src/components/image/ExtendedImage.astro +0 -46
  102. package/src/components/image/Image.astro +0 -25
  103. package/src/components/input/Input.astro +0 -27
  104. package/src/components/input-group/InputGroup.astro +0 -28
  105. package/src/components/input-group/InputGroupAddon.astro +0 -76
  106. package/src/components/input-group/InputGroupButton.astro +0 -36
  107. package/src/components/input-group/InputGroupInput.astro +0 -18
  108. package/src/components/input-group/InputGroupText.astro +0 -17
  109. package/src/components/input-group/InputGroupTextarea.astro +0 -22
  110. package/src/components/input-otp/InputOtp.astro +0 -352
  111. package/src/components/input-otp/InputOtpGroup.astro +0 -16
  112. package/src/components/input-otp/InputOtpSeparator.astro +0 -25
  113. package/src/components/input-otp/InputOtpSlot.astro +0 -50
  114. package/src/components/input-otp/InputOtpTypes.ts +0 -6
  115. package/src/components/item/Item.astro +0 -52
  116. package/src/components/item/ItemActions.astro +0 -16
  117. package/src/components/item/ItemContent.astro +0 -16
  118. package/src/components/item/ItemDescription.astro +0 -19
  119. package/src/components/item/ItemFooter.astro +0 -16
  120. package/src/components/item/ItemGroup.astro +0 -16
  121. package/src/components/item/ItemHeader.astro +0 -16
  122. package/src/components/item/ItemMedia.astro +0 -40
  123. package/src/components/item/ItemSeparator.astro +0 -23
  124. package/src/components/item/ItemTitle.astro +0 -16
  125. package/src/components/kbd/Kbd.astro +0 -21
  126. package/src/components/kbd/KbdGroup.astro +0 -16
  127. package/src/components/label/Label.astro +0 -24
  128. package/src/components/marquee/Marquee.astro +0 -73
  129. package/src/components/native-select/NativeSelect.astro +0 -68
  130. package/src/components/native-select/NativeSelectOptGroup.astro +0 -15
  131. package/src/components/native-select/NativeSelectOption.astro +0 -15
  132. package/src/components/pagination/Pagination.astro +0 -20
  133. package/src/components/pagination/PaginationContent.astro +0 -16
  134. package/src/components/pagination/PaginationEllipsis.astro +0 -37
  135. package/src/components/pagination/PaginationItem.astro +0 -16
  136. package/src/components/pagination/PaginationLink.astro +0 -27
  137. package/src/components/pagination/PaginationNext.astro +0 -33
  138. package/src/components/pagination/PaginationPrevious.astro +0 -33
  139. package/src/components/popover/Popover.astro +0 -717
  140. package/src/components/popover/PopoverContent.astro +0 -102
  141. package/src/components/popover/PopoverDescription.astro +0 -14
  142. package/src/components/popover/PopoverHeader.astro +0 -14
  143. package/src/components/popover/PopoverTitle.astro +0 -14
  144. package/src/components/popover/PopoverTrigger.astro +0 -51
  145. package/src/components/progress/Progress.astro +0 -154
  146. package/src/components/prose/Prose.astro +0 -619
  147. package/src/components/radio-group/RadioGroup.astro +0 -162
  148. package/src/components/radio-group/RadioGroupItem.astro +0 -133
  149. package/src/components/radio-group/RadioGroupTypes.ts +0 -6
  150. package/src/components/section/Section.astro +0 -167
  151. package/src/components/select/Select.astro +0 -752
  152. package/src/components/select/SelectContent.astro +0 -94
  153. package/src/components/select/SelectGroup.astro +0 -9
  154. package/src/components/select/SelectItem.astro +0 -51
  155. package/src/components/select/SelectLabel.astro +0 -14
  156. package/src/components/select/SelectSearch.astro +0 -49
  157. package/src/components/select/SelectSeparator.astro +0 -12
  158. package/src/components/select/SelectTrigger.astro +0 -56
  159. package/src/components/select/SelectTypes.ts +0 -13
  160. package/src/components/select/SelectValue.astro +0 -19
  161. package/src/components/separator/Separator.astro +0 -36
  162. package/src/components/sheet/Sheet.astro +0 -15
  163. package/src/components/sheet/SheetClose.astro +0 -15
  164. package/src/components/sheet/SheetContent.astro +0 -89
  165. package/src/components/sheet/SheetDescription.astro +0 -16
  166. package/src/components/sheet/SheetFooter.astro +0 -16
  167. package/src/components/sheet/SheetHeader.astro +0 -16
  168. package/src/components/sheet/SheetTitle.astro +0 -16
  169. package/src/components/sheet/SheetTrigger.astro +0 -15
  170. package/src/components/sidebar/Sidebar.astro +0 -231
  171. package/src/components/sidebar/SidebarContent.astro +0 -25
  172. package/src/components/sidebar/SidebarFooter.astro +0 -22
  173. package/src/components/sidebar/SidebarGroup.astro +0 -22
  174. package/src/components/sidebar/SidebarGroupContent.astro +0 -22
  175. package/src/components/sidebar/SidebarGroupLabel.astro +0 -53
  176. package/src/components/sidebar/SidebarHeader.astro +0 -22
  177. package/src/components/sidebar/SidebarInput.astro +0 -24
  178. package/src/components/sidebar/SidebarInset.astro +0 -22
  179. package/src/components/sidebar/SidebarMenu.astro +0 -22
  180. package/src/components/sidebar/SidebarMenuAction.astro +0 -63
  181. package/src/components/sidebar/SidebarMenuBadge.astro +0 -31
  182. package/src/components/sidebar/SidebarMenuButton.astro +0 -124
  183. package/src/components/sidebar/SidebarMenuItem.astro +0 -22
  184. package/src/components/sidebar/SidebarMenuSkeleton.astro +0 -43
  185. package/src/components/sidebar/SidebarMenuSub.astro +0 -25
  186. package/src/components/sidebar/SidebarMenuSubButton.astro +0 -52
  187. package/src/components/sidebar/SidebarMenuSubItem.astro +0 -17
  188. package/src/components/sidebar/SidebarProvider.astro +0 -215
  189. package/src/components/sidebar/SidebarRail.astro +0 -72
  190. package/src/components/sidebar/SidebarSeparator.astro +0 -25
  191. package/src/components/sidebar/SidebarTrigger.astro +0 -69
  192. package/src/components/skeleton/Skeleton.astro +0 -14
  193. package/src/components/slider/Slider.astro +0 -425
  194. package/src/components/spinner/Spinner.astro +0 -23
  195. package/src/components/switch/Switch.astro +0 -192
  196. package/src/components/switch/SwitchTypes.ts +0 -6
  197. package/src/components/table/Table.astro +0 -20
  198. package/src/components/table/TableBody.astro +0 -18
  199. package/src/components/table/TableCaption.astro +0 -18
  200. package/src/components/table/TableCell.astro +0 -18
  201. package/src/components/table/TableFoot.astro +0 -18
  202. package/src/components/table/TableHead.astro +0 -18
  203. package/src/components/table/TableHeader.astro +0 -18
  204. package/src/components/table/TableRow.astro +0 -17
  205. package/src/components/tabs/Tabs.astro +0 -270
  206. package/src/components/tabs/TabsContent.astro +0 -28
  207. package/src/components/tabs/TabsList.astro +0 -23
  208. package/src/components/tabs/TabsTrigger.astro +0 -34
  209. package/src/components/textarea/Textarea.astro +0 -31
  210. package/src/components/toast/ToastDescription.astro +0 -22
  211. package/src/components/toast/ToastItem.astro +0 -56
  212. package/src/components/toast/ToastTemplate.astro +0 -27
  213. package/src/components/toast/ToastTitle.astro +0 -59
  214. package/src/components/toast/Toaster.astro +0 -981
  215. package/src/components/toast/index.ts +0 -1
  216. package/src/components/toast/toast-manager.ts +0 -234
  217. package/src/components/toggle/Toggle.astro +0 -174
  218. package/src/components/toggle/ToggleTypes.ts +0 -14
  219. package/src/components/tooltip/Tooltip.astro +0 -422
  220. package/src/components/tooltip/TooltipContent.astro +0 -83
  221. package/src/components/tooltip/TooltipTrigger.astro +0 -13
  222. package/src/components/video/Video.astro +0 -121
  223. package/src/config/cookies.ts +0 -5
  224. package/src/config/links.ts +0 -4
  225. package/src/domain/cms/index.ts +0 -1
  226. package/src/domain/cms/types.ts +0 -20
  227. package/src/domain/i18n/constants.ts +0 -21
  228. package/src/domain/i18n/cookie-storage.ts +0 -13
  229. package/src/domain/i18n/country-to-locale-map.ts +0 -67
  230. package/src/domain/i18n/fallbacks.generated.ts +0 -230
  231. package/src/domain/i18n/fetcher.ts +0 -278
  232. package/src/domain/i18n/flags.ts +0 -16
  233. package/src/domain/i18n/format.ts +0 -90
  234. package/src/domain/i18n/index.ts +0 -10
  235. package/src/domain/i18n/middleware/i18n.ts +0 -161
  236. package/src/domain/i18n/resolve-locale.ts +0 -55
  237. package/src/domain/i18n/runtime-constants.ts +0 -14
  238. package/src/domain/i18n/schema.ts +0 -29
  239. package/src/domain/index.ts +0 -4
  240. package/src/domain/seo/constants.ts +0 -6
  241. package/src/domain/seo/index.ts +0 -3
  242. package/src/domain/seo/services/index.ts +0 -3
  243. package/src/domain/seo/services/llms.ts +0 -56
  244. package/src/domain/seo/services/robots.ts +0 -12
  245. package/src/domain/seo/services/sitemap.ts +0 -56
  246. package/src/domain/seo/utils.ts +0 -36
  247. package/src/domain/theme/constants.ts +0 -6
  248. package/src/domain/theme/index.ts +0 -2
  249. package/src/domain/theme/schema.ts +0 -10
  250. package/src/i18n.generated.d.ts +0 -202
  251. package/src/lib/component-preview.ts +0 -73
  252. package/src/lib/index.ts +0 -6
  253. package/src/lib/remark/remark-modified-time.ts +0 -13
  254. package/src/lib/showcase-preview-elements.ts +0 -408
  255. package/src/lib/showcase-renderer.ts +0 -218
  256. package/src/lib/showcase-utils.ts +0 -189
  257. package/src/lib/turnstile.ts +0 -35
  258. package/src/lib/utils/starwind/positioning.ts +0 -318
  259. package/src/locales/de/blog.json +0 -9
  260. package/src/locales/de/common.json +0 -19
  261. package/src/locales/de/errors.json +0 -6
  262. package/src/locales/de/home.json +0 -20
  263. package/src/locales/de/messages.json +0 -8
  264. package/src/locales/de/nav.json +0 -13
  265. package/src/locales/de/projects.json +0 -9
  266. package/src/locales/de/services.json +0 -43
  267. package/src/locales/en/blog.json +0 -9
  268. package/src/locales/en/chat.json +0 -29
  269. package/src/locales/en/common.json +0 -19
  270. package/src/locales/en/cookie_notice.json +0 -11
  271. package/src/locales/en/editor.json +0 -24
  272. package/src/locales/en/errors.json +0 -6
  273. package/src/locales/en/home.json +0 -20
  274. package/src/locales/en/messages.json +0 -8
  275. package/src/locales/en/nav.json +0 -13
  276. package/src/locales/en/projects.json +0 -9
  277. package/src/locales/en/services.json +0 -43
  278. package/src/locales/es/blog.json +0 -9
  279. package/src/locales/es/common.json +0 -19
  280. package/src/locales/es/errors.json +0 -6
  281. package/src/locales/es/home.json +0 -20
  282. package/src/locales/es/messages.json +0 -8
  283. package/src/locales/es/nav.json +0 -13
  284. package/src/locales/es/projects.json +0 -9
  285. package/src/locales/es/services.json +0 -43
  286. package/src/locales/ja/blog.json +0 -9
  287. package/src/locales/ja/common.json +0 -19
  288. package/src/locales/ja/errors.json +0 -6
  289. package/src/locales/ja/home.json +0 -20
  290. package/src/locales/ja/messages.json +0 -8
  291. package/src/locales/ja/nav.json +0 -13
  292. package/src/locales/ja/projects.json +0 -9
  293. package/src/locales/ja/services.json +0 -43
  294. package/src/locales/pt/blog.json +0 -9
  295. package/src/locales/pt/chat.json +0 -29
  296. package/src/locales/pt/common.json +0 -19
  297. package/src/locales/pt/cookie_notice.json +0 -11
  298. package/src/locales/pt/editor.json +0 -24
  299. package/src/locales/pt/errors.json +0 -6
  300. package/src/locales/pt/home.json +0 -20
  301. package/src/locales/pt/messages.json +0 -8
  302. package/src/locales/pt/nav.json +0 -13
  303. package/src/locales/pt/projects.json +0 -9
  304. package/src/locales/pt/services.json +0 -43
  305. package/src/utils/server/cookies.ts +0 -98
  306. package/src/utils/server/index.ts +0 -1
  307. package/src/utils/shared/index.ts +0 -3
  308. /package/src/utils/{shared/deep-merge.ts → deep-merge.ts} +0 -0
  309. /package/src/utils/{shared/embed.ts → embed.ts} +0 -0
  310. /package/src/utils/{shared/parse-boolean.ts → parse-boolean.ts} +0 -0
@@ -0,0 +1,681 @@
1
+ ---
2
+ /**
3
+ * Dynamic Showcase with Auto-Generated Controls
4
+ *
5
+ * Uses Starwind components for all controls.
6
+ * Shows a single component instance that updates dynamically.
7
+ */
8
+ import Badge from "@rimelight/ui/components/badge/Badge.astro";
9
+ import Button from "@rimelight/ui/components/button/Button.astro";
10
+ import Input from "@rimelight/ui/components/input/Input.astro";
11
+ import Label from "@rimelight/ui/components/label/Label.astro";
12
+ import Switch from "@rimelight/ui/components/switch/Switch.astro";
13
+ import Table from "@rimelight/ui/components/table/Table.astro";
14
+ import TableBody from "@rimelight/ui/components/table/TableBody.astro";
15
+ import TableCell from "@rimelight/ui/components/table/TableCell.astro";
16
+ import TableHead from "@rimelight/ui/components/table/TableHead.astro";
17
+ import TableHeader from "@rimelight/ui/components/table/TableHeader.astro";
18
+ import TableRow from "@rimelight/ui/components/table/TableRow.astro";
19
+ import Select from "@rimelight/ui/components/select/Select.astro";
20
+ import SelectContent from "@rimelight/ui/components/select/SelectContent.astro";
21
+ import SelectItem from "@rimelight/ui/components/select/SelectItem.astro";
22
+ import SelectTrigger from "@rimelight/ui/components/select/SelectTrigger.astro";
23
+ import SelectValue from "@rimelight/ui/components/select/SelectValue.astro";
24
+ import Textarea from "@rimelight/ui/components/textarea/Textarea.astro";
25
+ import Checkbox from "@rimelight/ui/components/checkbox/Checkbox.astro";
26
+
27
+ export interface PropDef {
28
+ name: string;
29
+ type: string;
30
+ default?: any;
31
+ description?: string;
32
+ }
33
+
34
+ export interface DynamicShowcaseProps {
35
+ /** Unique identifier for this showcase instance */
36
+ id: string;
37
+ /** Display name of the component */
38
+ componentName: string;
39
+ /** Description of what the component does */
40
+ description?: string;
41
+ /** Prop definitions */
42
+ props: PropDef[];
43
+ /** Initial prop values */
44
+ initialProps?: Record<string, any>;
45
+ /** Additional classes for the preview container */
46
+ previewClassName?: string;
47
+ }
48
+
49
+ type Props = DynamicShowcaseProps;
50
+
51
+ const {
52
+ id,
53
+ componentName,
54
+ description,
55
+ props,
56
+ initialProps = {},
57
+ previewClassName
58
+ } = Astro.props;
59
+
60
+ // Build initial state
61
+ const initialState: Record<string, { value: any; omitted: boolean }> = {};
62
+ props.forEach(prop => {
63
+ if (prop.name in initialProps) {
64
+ initialState[prop.name] = { value: initialProps[prop.name], omitted: false };
65
+ } else if (prop.default !== undefined) {
66
+ initialState[prop.name] = { value: prop.default, omitted: false };
67
+ } else {
68
+ initialState[prop.name] = { value: undefined, omitted: true };
69
+ }
70
+ });
71
+
72
+ function getPropKind(type: string): 'string' | 'number' | 'boolean' | 'enum' | 'array' | 'object' {
73
+ const lower = type.toLowerCase();
74
+ if (lower === 'boolean') return 'boolean';
75
+ if (lower === 'number') return 'number';
76
+ if (lower.includes('[]')) return 'array';
77
+ if (lower.startsWith('{') || lower.includes('record')) return 'object';
78
+ if (lower.includes('|')) return 'enum';
79
+ return 'string';
80
+ }
81
+
82
+ function extractEnumValues(type: string): string[] {
83
+ const matches = type.match(/'([^']+)'|"([^"]+)"/g);
84
+ if (matches) {
85
+ return matches.map(m => m.replace(/['"]/g, ''));
86
+ }
87
+ return [];
88
+ }
89
+ ---
90
+
91
+ <section class="py-12 space-y-12 border-t border-border first:border-t-0" data-showcase={id}>
92
+ <div class="container space-y-8">
93
+ <!-- Header -->
94
+ <div class="space-y-4">
95
+ <div class="space-y-1">
96
+ <h2 class="text-3xl font-semibold tracking-tight">{componentName}</h2>
97
+ {description && <p class="text-muted-foreground text-lg">{description}</p>}
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Interactive Playground -->
102
+ <div class="grid lg:grid-cols-2 gap-8">
103
+ <!-- Controls Panel -->
104
+ <div class="space-y-6">
105
+ <div class="flex items-center justify-between">
106
+ <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wider">Controls</h3>
107
+ <Button variant="outline" size="sm" data-action="reset" class="text-xs">Reset</Button>
108
+ </div>
109
+
110
+ <div class="space-y-4" data-controls>
111
+ {props.map((prop) => {
112
+ const kind = getPropKind(prop.type);
113
+ const enumValues = extractEnumValues(prop.type);
114
+ const state = initialState[prop.name];
115
+ if (!state) return null;
116
+ const isOmitted = state.omitted;
117
+
118
+ return (
119
+ <div class="space-y-2 p-4 rounded-lg border border-border bg-card/50" data-prop-control={prop.name}>
120
+ <div class="flex items-center justify-between gap-md">
121
+ <Label class="text-sm font-medium flex items-center gap-md">
122
+ {prop.name}
123
+ <Badge variant="outline" class="font-mono text-[10px] uppercase">
124
+ {prop.type}
125
+ </Badge>
126
+ </Label>
127
+ <div class="flex items-center gap-md">
128
+ <Label for={`${id}-omit-${prop.name}`} class="text-xs text-muted-foreground">Omit</Label>
129
+ <Switch
130
+ id={`${id}-omit-${prop.name}`}
131
+ data-prop={prop.name}
132
+ checked={isOmitted}
133
+ data-state={isOmitted ? 'checked' : 'unchecked'}
134
+ size="sm"
135
+ />
136
+ </div>
137
+ </div>
138
+
139
+ {kind === 'boolean' && (
140
+ <div class="flex items-center gap-md">
141
+ <Checkbox
142
+ id={`${id}-value-${prop.name}`}
143
+ data-prop={prop.name}
144
+ data-type="boolean"
145
+ checked={!!state.value}
146
+ disabled={isOmitted}
147
+ size="md"
148
+ />
149
+ <Label for={`${id}-value-${prop.name}`} class="text-sm">
150
+ {state.value ? 'True' : 'False'}
151
+ </Label>
152
+ </div>
153
+ )}
154
+
155
+ {kind === 'string' && (
156
+ <Input
157
+ id={`${id}-value-${prop.name}`}
158
+ data-prop={prop.name}
159
+ data-type="string"
160
+ value={state.value ?? ''}
161
+ placeholder={prop.default ?? `Enter ${prop.name}`}
162
+ class="w-full"
163
+ disabled={isOmitted}
164
+ size="md"
165
+ />
166
+ )}
167
+
168
+ {kind === 'number' && (
169
+ <Input
170
+ id={`${id}-value-${prop.name}`}
171
+ data-prop={prop.name}
172
+ data-type="number"
173
+ type="number"
174
+ value={state.value ?? ''}
175
+ placeholder={String(prop.default ?? '')}
176
+ class="w-full"
177
+ disabled={isOmitted}
178
+ size="md"
179
+ />
180
+ )}
181
+
182
+ {kind === 'enum' && enumValues.length > 0 && (
183
+ <div class="w-full" data-prop-control={prop.name}>
184
+ <Select
185
+ id={`${id}-select-${prop.name}`}
186
+ data-prop={prop.name}
187
+ data-type="enum"
188
+ data-value={state.value ?? ''}
189
+ class="w-full"
190
+ >
191
+ <SelectTrigger size="md">
192
+ <SelectValue placeholder="Select..." />
193
+ </SelectTrigger>
194
+ <SelectContent size="md">
195
+ <SelectItem value="">(default)</SelectItem>
196
+ {enumValues.map((value) => (
197
+ <SelectItem value={value} data-selected={state.value === value ? 'true' : undefined}>
198
+ {value}
199
+ </SelectItem>
200
+ ))}
201
+ </SelectContent>
202
+ </Select>
203
+ </div>
204
+ )}
205
+
206
+ {kind === 'array' && (
207
+ <Textarea
208
+ id={`${id}-value-${prop.name}`}
209
+ data-prop={prop.name}
210
+ data-type="array"
211
+ placeholder="JSON array"
212
+ class="w-full font-mono text-xs"
213
+ disabled={isOmitted}
214
+ size="md"
215
+ >{state.value != null ? JSON.stringify(state.value, null, 2) : ''}</Textarea>
216
+ )}
217
+
218
+ {kind === 'object' && (
219
+ <Textarea
220
+ id={`${id}-value-${prop.name}`}
221
+ data-prop={prop.name}
222
+ data-type="object"
223
+ placeholder="JSON object"
224
+ class="w-full font-mono text-xs"
225
+ disabled={isOmitted}
226
+ size="md"
227
+ >{state.value != null ? JSON.stringify(state.value, null, 2) : ''}</Textarea>
228
+ )}
229
+
230
+ {prop.description && (
231
+ <p class="text-xs text-muted-foreground">{prop.description}</p>
232
+ )}
233
+ </div>
234
+ );
235
+ })}
236
+ </div>
237
+ </div>
238
+
239
+ <!-- Preview Panel -->
240
+ <div class="space-y-4">
241
+ <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wider">Preview</h3>
242
+ <div class:list={[
243
+ "w-full min-h-[400px] rounded-xl border border-border bg-muted/5 p-6 flex items-center justify-center overflow-auto",
244
+ previewClassName
245
+ ]}>
246
+ <div
247
+ id={`${id}-preview`}
248
+ data-preview
249
+ data-showcase-id={id}
250
+ data-component-name={componentName}
251
+ data-initial-props={JSON.stringify(initialState)}
252
+ class="w-full h-full"
253
+ >
254
+ <slot />
255
+ </div>
256
+ </div>
257
+
258
+ <!-- Active Props JSON -->
259
+ <div class="rounded-lg border border-border bg-card overflow-hidden">
260
+ <div class="px-4 py-2 bg-muted/50 border-b border-border flex items-center justify-between">
261
+ <h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wider">Active Props</h4>
262
+ <Button variant="ghost" size="sm" data-action="copy" class="h-6 text-xs">Copy</Button>
263
+ </div>
264
+ <pre class="p-4 text-xs font-mono text-muted-foreground overflow-x-auto max-h-[300px]">
265
+ <code data-active-props>{JSON.stringify(
266
+ Object.fromEntries(
267
+ Object.entries(initialState)
268
+ .filter(([_, v]) => !v.omitted && v.value !== undefined)
269
+ .map(([k, v]) => [k, v.value])
270
+ ), null, 2
271
+ )}</code>
272
+ </pre>
273
+ </div>
274
+ </div>
275
+ </div>
276
+
277
+ <!-- Prop Reference Table -->
278
+ {props.length > 0 && (
279
+ <div>
280
+ <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wider mb-4">Prop Reference</h3>
281
+ <div class="rounded-xl border border-border bg-card overflow-hidden">
282
+ <Table>
283
+ <TableHeader>
284
+ <TableRow>
285
+ <TableHead class="w-[15%]">Prop</TableHead>
286
+ <TableHead class="w-[15%]">Type</TableHead>
287
+ <TableHead class="w-[15%]">Default</TableHead>
288
+ <TableHead>Description</TableHead>
289
+ </TableRow>
290
+ </TableHeader>
291
+ <TableBody>
292
+ {props.map((prop) => (
293
+ <TableRow>
294
+ <TableCell class="font-mono text-sm font-medium text-primary">
295
+ {prop.name}
296
+ </TableCell>
297
+ <TableCell>
298
+ <Badge variant="outline" class="font-mono text-[10px] uppercase">
299
+ {prop.type}
300
+ </Badge>
301
+ </TableCell>
302
+ <TableCell class="font-mono text-xs text-muted-foreground">
303
+ {prop.default !== undefined ? String(prop.default) : "—"}
304
+ </TableCell>
305
+ <TableCell class="text-sm">
306
+ {prop.description || "—"}
307
+ </TableCell>
308
+ </TableRow>
309
+ ))}
310
+ </TableBody>
311
+ </Table>
312
+ </div>
313
+ </div>
314
+ )}
315
+ </div>
316
+ </section>
317
+
318
+ <script>
319
+ interface PropState {
320
+ value: unknown;
321
+ omitted: boolean;
322
+ }
323
+
324
+ interface CurrentState {
325
+ [key: string]: PropState;
326
+ }
327
+
328
+ function initShowcase(showcaseEl: HTMLElement) {
329
+ const id = showcaseEl.dataset.showcase;
330
+ if (!id) return;
331
+
332
+ const controlsContainer = showcaseEl.querySelector('[data-controls]') as HTMLElement | null;
333
+ const previewContainer = showcaseEl.querySelector('[data-preview]') as HTMLElement | null;
334
+ const activePropsCode = showcaseEl.querySelector('[data-active-props]') as HTMLElement | null;
335
+ const resetBtn = showcaseEl.querySelector('[data-action="reset"]') as HTMLElement | null;
336
+ const copyBtn = showcaseEl.querySelector('[data-action="copy"]') as HTMLElement | null;
337
+
338
+ if (!controlsContainer || !previewContainer || !activePropsCode) return;
339
+
340
+ // Parse initial state
341
+ const initialPropsJson = previewContainer.dataset.initialProps;
342
+ if (!initialPropsJson) return;
343
+
344
+ let currentState: CurrentState = JSON.parse(initialPropsJson);
345
+
346
+ function getActiveProps(): Record<string, unknown> {
347
+ const active: Record<string, unknown> = {};
348
+ Object.entries(currentState).forEach(([key, state]) => {
349
+ if (!state.omitted && state.value !== undefined) {
350
+ active[key] = state.value;
351
+ }
352
+ });
353
+ return active;
354
+ }
355
+
356
+ function updateActivePropsDisplay() {
357
+ if (activePropsCode) {
358
+ activePropsCode.textContent = JSON.stringify(getActiveProps(), null, 2);
359
+ }
360
+ }
361
+
362
+ function updatePreview() {
363
+ updateActivePropsDisplay();
364
+
365
+ const activeProps = getActiveProps();
366
+
367
+ // Dispatch custom event with new props
368
+ if (previewContainer) {
369
+ previewContainer.dispatchEvent(new CustomEvent('showcase:props-update', {
370
+ detail: { props: activeProps, allProps: currentState }
371
+ }));
372
+ }
373
+ }
374
+
375
+ function setPropOmitted(propName: string, omitted: boolean) {
376
+ if (currentState[propName]) {
377
+ currentState[propName].omitted = omitted;
378
+ }
379
+
380
+ // Update control disabled state
381
+ const controlEl = controlsContainer?.querySelector(`[data-prop-control="${propName}"]`) as HTMLElement | null;
382
+ if (controlEl) {
383
+ const input = controlEl.querySelector('input, select, textarea, [data-type]') as HTMLElement | null;
384
+ if (input) {
385
+ if (omitted) {
386
+ input.setAttribute('disabled', 'true');
387
+ input.classList.add('opacity-50');
388
+ } else {
389
+ input.removeAttribute('disabled');
390
+ input.classList.remove('opacity-50');
391
+ }
392
+ }
393
+ }
394
+ }
395
+
396
+ function updatePropValue(propName: string, value: unknown) {
397
+ if (currentState[propName]) {
398
+ currentState[propName].value = value;
399
+ }
400
+ }
401
+
402
+ // Handle omit switch changes
403
+ controlsContainer?.querySelectorAll('[id^="-omit-"]').forEach(el => {
404
+ el.addEventListener('starwind-switch:change', (e: Event) => {
405
+ const customEvent = e as CustomEvent<{ checked: boolean }>;
406
+ const { checked: isOmitted } = customEvent.detail;
407
+ const propName = (el as HTMLElement).dataset.prop;
408
+ if (!propName) return;
409
+
410
+ setPropOmitted(propName, isOmitted);
411
+ updatePreview();
412
+ });
413
+ });
414
+
415
+ // Handle Input changes (string, number)
416
+ controlsContainer?.querySelectorAll('[id*="-value-"]').forEach(el => {
417
+ // Handle text input changes
418
+ el.addEventListener('input', (e: Event) => {
419
+ const target = e.target as HTMLInputElement | HTMLTextAreaElement;
420
+ const propName = (el as HTMLElement).dataset.prop;
421
+ const type = (el as HTMLElement).dataset.type;
422
+ if (!propName) return;
423
+
424
+ let value: unknown = target.value;
425
+
426
+ if (type === 'number') {
427
+ value = value === '' ? undefined : Number(value);
428
+ } else if (type === 'array' || type === 'object') {
429
+ try {
430
+ value = value === '' ? undefined : JSON.parse(value as string);
431
+ } catch {
432
+ // Keep as string if invalid JSON
433
+ }
434
+ }
435
+
436
+ updatePropValue(propName, value);
437
+ updatePreview();
438
+ });
439
+
440
+ // Handle checkbox changes (boolean) - Starwind Checkbox
441
+ el.addEventListener('starwind-checkbox:change', (e: Event) => {
442
+ const customEvent = e as CustomEvent<{ checked: boolean }>;
443
+ const { checked } = customEvent.detail;
444
+ const propName = (el as HTMLElement).dataset.prop;
445
+ console.log('[Showcase Checkbox] prop:', propName, 'checked:', checked);
446
+ if (!propName) return;
447
+
448
+ updatePropValue(propName, checked);
449
+ updatePreview();
450
+ });
451
+ });
452
+
453
+ // Handle Starwind Select changes - listen on the wrapper
454
+ controlsContainer?.querySelectorAll('[data-prop-control]').forEach((controlEl: Element) => {
455
+ const selectWrapper = controlEl.querySelector('.starwind-select') as HTMLElement | null;
456
+ if (selectWrapper) {
457
+ selectWrapper.addEventListener('starwind-select:change', (e: Event) => {
458
+ const customEvent = e as CustomEvent<{ value: string }>;
459
+ const { value } = customEvent.detail;
460
+ const propName = (controlEl as HTMLElement).dataset.propControl;
461
+ if (!propName) return;
462
+
463
+ updatePropValue(propName, value || undefined);
464
+ updatePreview();
465
+ });
466
+ }
467
+ });
468
+
469
+ // Reset button
470
+ resetBtn?.addEventListener('click', () => {
471
+ const freshState: CurrentState = JSON.parse(initialPropsJson);
472
+ currentState = freshState;
473
+
474
+ // Reset all controls
475
+ Object.keys(freshState).forEach((propName) => {
476
+ const state = freshState[propName];
477
+ if (!state) return;
478
+ const controlEl = controlsContainer?.querySelector(`[data-prop-control="${propName}"]`) as HTMLElement | null;
479
+ if (!controlEl) return;
480
+
481
+ const omitSwitch = controlEl.querySelector('[id^*="-omit-"]') as HTMLElement | null;
482
+ const valueInput = controlEl.querySelector('[id^*="-value-"]') as HTMLElement | null;
483
+ const selectWrapper = controlEl.querySelector('.starwind-select') as HTMLElement | null;
484
+
485
+ if (omitSwitch) {
486
+ // Reset Switch component
487
+ omitSwitch.setAttribute('data-state', state.omitted ? 'checked' : 'unchecked');
488
+ omitSwitch.setAttribute('aria-checked', state.omitted ? 'true' : 'false');
489
+ omitSwitch.dispatchEvent(new CustomEvent('starwind-switch:change', {
490
+ detail: { checked: state.omitted, switchId: omitSwitch.id },
491
+ bubbles: true
492
+ }));
493
+ }
494
+
495
+ if (valueInput) {
496
+ if (valueInput.tagName === 'INPUT' && (valueInput as HTMLInputElement).type === 'checkbox') {
497
+ // Starwind checkbox
498
+ (valueInput as HTMLInputElement).checked = !!state.value;
499
+ valueInput.setAttribute('aria-checked', state.value ? 'true' : 'false');
500
+ valueInput.dispatchEvent(new CustomEvent('starwind-checkbox:change', {
501
+ detail: { checked: !!state.value },
502
+ bubbles: true
503
+ }));
504
+ } else if (valueInput.tagName === 'INPUT') {
505
+ (valueInput as HTMLInputElement).value = String(state.value ?? '');
506
+ } else if (valueInput.tagName === 'TEXTAREA') {
507
+ (valueInput as HTMLTextAreaElement).value = state.value !== undefined ? JSON.stringify(state.value, null, 2) : '';
508
+ }
509
+ }
510
+
511
+ if (selectWrapper) {
512
+ // Starwind Select - use programmatic select event
513
+ selectWrapper.dispatchEvent(new CustomEvent('starwind-select:select', {
514
+ detail: { value: String(state.value ?? ''), selectId: selectWrapper.id },
515
+ bubbles: true
516
+ }));
517
+ }
518
+
519
+ if (valueInput || selectWrapper) {
520
+ const element: HTMLElement = (valueInput || selectWrapper)!;
521
+ if (state.omitted) {
522
+ element.setAttribute('disabled', 'true');
523
+ element.classList.add('opacity-50');
524
+ } else {
525
+ element.removeAttribute('disabled');
526
+ element.classList.remove('opacity-50');
527
+ }
528
+ }
529
+ });
530
+
531
+ updatePreview();
532
+ });
533
+
534
+ // Copy button
535
+ copyBtn?.addEventListener('click', async () => {
536
+ const text = activePropsCode?.textContent;
537
+ if (text) {
538
+ await navigator.clipboard.writeText(text);
539
+ if (copyBtn) {
540
+ copyBtn.textContent = 'Copied!';
541
+ setTimeout(() => {
542
+ copyBtn.textContent = 'Copy';
543
+ }, 2000);
544
+ }
545
+ }
546
+ });
547
+
548
+ // Initial setup
549
+ Object.entries(currentState).forEach(([propName, state]) => {
550
+ if (state.omitted) {
551
+ setPropOmitted(propName, true);
552
+ }
553
+ });
554
+
555
+ updateActivePropsDisplay();
556
+ }
557
+
558
+ // Initialize
559
+ if (document.readyState === 'loading') {
560
+ document.addEventListener('DOMContentLoaded', () => {
561
+ document.querySelectorAll('[data-showcase]').forEach(el => initShowcase(el as HTMLElement));
562
+ });
563
+ } else {
564
+ document.querySelectorAll('[data-showcase]').forEach(el => initShowcase(el as HTMLElement));
565
+ }
566
+
567
+ document.addEventListener('astro:page-load', () => {
568
+ document.querySelectorAll('[data-showcase]').forEach(el => initShowcase(el as HTMLElement));
569
+ });
570
+
571
+ // Client-side preview renderer - singleton pattern
572
+ // This script runs once globally, not per-component
573
+
574
+ interface ShowcasePreviewAPI {
575
+ renderers: Map<string, (props: Record<string, unknown>, container: HTMLElement) => void>;
576
+ registerRenderer: (name: string, renderer: (props: Record<string, unknown>, container: HTMLElement) => void) => void;
577
+ }
578
+
579
+ declare global {
580
+ interface Window {
581
+ ShowcasePreview?: ShowcasePreviewAPI;
582
+ }
583
+ }
584
+
585
+ if (!window.ShowcasePreview) {
586
+ window.ShowcasePreview = {
587
+ renderers: new Map<string, (props: Record<string, unknown>, container: HTMLElement) => void>(),
588
+ registerRenderer: function(name: string, renderer: (props: Record<string, unknown>, container: HTMLElement) => void) {
589
+ this.renderers.set(name, renderer);
590
+ }
591
+ };
592
+ }
593
+
594
+ // Initialize preview handlers
595
+ function initPreviewHandlers() {
596
+ // Initialize showcase controls for each showcase section
597
+ document.querySelectorAll('[data-showcase]').forEach(el => {
598
+ initShowcase(el as HTMLElement);
599
+ });
600
+
601
+ // Initialize preview containers
602
+ document.querySelectorAll('div[data-preview]').forEach(el => {
603
+ if (!el.hasAttribute('data-preview-initialized')) {
604
+ el.setAttribute('data-preview-initialized', 'true');
605
+ const componentName = (el as HTMLElement).dataset.componentName;
606
+ const initialPropsJson = (el as HTMLElement).dataset.initialProps;
607
+
608
+ if (componentName && initialPropsJson) {
609
+ const props: Record<string, PropState> = JSON.parse(initialPropsJson);
610
+
611
+ // Look up renderer at event time, not initialization time
612
+ el.addEventListener('showcase:props-update', (e: Event) => {
613
+ const newProps = (e as CustomEvent<{ props: Record<string, unknown> }>).detail.props;
614
+ const renderer = window.ShowcasePreview?.renderers.get(componentName);
615
+ if (renderer) {
616
+ renderer(newProps, el as HTMLElement);
617
+ } else {
618
+ // Update data attributes
619
+ Object.entries(newProps).forEach(([key, value]) => {
620
+ if (value === undefined || value === null) {
621
+ el.removeAttribute(`data-${key}`);
622
+ } else if (typeof value === 'boolean') {
623
+ if (value) {
624
+ el.setAttribute(`data-${key}`, 'true');
625
+ } else {
626
+ el.removeAttribute(`data-${key}`);
627
+ }
628
+ } else {
629
+ el.setAttribute(`data-${key}`, String(value));
630
+ }
631
+ });
632
+ }
633
+ });
634
+
635
+ // Initial render - look up renderer
636
+ const renderer = window.ShowcasePreview?.renderers.get(componentName);
637
+ if (renderer) {
638
+ renderer(Object.fromEntries(
639
+ Object.entries(props).filter(([_, v]) => !v.omitted && v.value !== undefined).map(([k, v]) => [k, v.value])
640
+ ), el as HTMLElement);
641
+ }
642
+ }
643
+ }
644
+ });
645
+ }
646
+
647
+ // Initialize on load - defer until DOM is ready
648
+ function initShowcasePreview() {
649
+ if (document.readyState === 'loading') {
650
+ document.addEventListener('DOMContentLoaded', () => {
651
+ initPreviewHandlers();
652
+ });
653
+ } else {
654
+ initPreviewHandlers();
655
+ }
656
+ }
657
+
658
+ initShowcasePreview();
659
+ document.addEventListener('astro:page-load', initPreviewHandlers);
660
+
661
+ // Export function for page to trigger initial render
662
+ window.dispatchEvent(new CustomEvent('showcase-preview:ready'));
663
+ </script>
664
+
665
+ <style>
666
+ [data-showcase] [data-controls] [disabled] {
667
+ cursor: not-allowed;
668
+ }
669
+
670
+ [data-showcase] select {
671
+ cursor: pointer;
672
+ }
673
+
674
+ [data-showcase] select:disabled,
675
+ [data-showcase] input:disabled,
676
+ [data-showcase] textarea:disabled {
677
+ opacity: 0.5;
678
+ cursor: not-allowed;
679
+ }
680
+ </style>
681
+