@brumaombra/ui-vintage 0.7.7 → 0.7.9

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 (273) hide show
  1. package/README.md +308 -48
  2. package/module.mjs +1 -1
  3. package/package.json +2 -1
  4. package/src/components/background-grid/BackgroundGrid.vue +11 -2
  5. package/src/components/background-grid/index.ts +2 -2
  6. package/src/components/blog/AllPostsSection.vue +25 -9
  7. package/src/components/blog/BlogFAQSection.vue +8 -3
  8. package/src/components/blog/BlogHeaderSection.vue +14 -11
  9. package/src/components/blog/BlogInfoSection.vue +13 -11
  10. package/src/components/blog/BlogSectionTitle.vue +5 -5
  11. package/src/components/blog/CategoriesList.vue +11 -3
  12. package/src/components/blog/CategoriesSection.vue +11 -3
  13. package/src/components/blog/CategoryCard.vue +10 -8
  14. package/src/components/blog/HeaderCarousel.vue +24 -15
  15. package/src/components/blog/PostCard.vue +16 -11
  16. package/src/components/blog/PostsList.vue +12 -3
  17. package/src/components/blog/SocialShareSidebar.vue +11 -11
  18. package/src/components/blog/TableOfContents.vue +29 -13
  19. package/src/components/busy-indicator/BusyIndicator.vue +2 -2
  20. package/src/components/busy-indicator/busy-indicator-state.ts +11 -12
  21. package/src/components/busy-indicator/index.ts +3 -3
  22. package/src/components/card-grid/CardGrid.vue +25 -11
  23. package/src/components/chip/Chip.vue +15 -15
  24. package/src/components/chip/index.ts +2 -2
  25. package/src/components/confirm-dialog/ConfirmDialog.vue +9 -9
  26. package/src/components/confirm-dialog/confirm-dialog-state.ts +15 -14
  27. package/src/components/confirm-dialog/index.ts +3 -12
  28. package/src/components/content/BlogList.vue +11 -8
  29. package/src/components/content/BlogTable.vue +18 -14
  30. package/src/components/content/ProseHr.vue +10 -7
  31. package/src/components/content/Terminal.vue +70 -0
  32. package/src/components/content/index.ts +2 -1
  33. package/src/components/dashboard-shell/DashboardShell.vue +49 -65
  34. package/src/components/dashboard-shell/index.ts +2 -2
  35. package/src/components/data-list/DataList.vue +11 -13
  36. package/src/components/data-list/DataListItem.vue +9 -11
  37. package/src/components/data-list/DataListLabel.vue +9 -11
  38. package/src/components/data-list/DataListText.vue +9 -11
  39. package/src/components/data-list/DataListValue.vue +9 -11
  40. package/src/components/data-list/index.ts +5 -5
  41. package/src/components/empty-state-card/EmptyStateCard.vue +21 -11
  42. package/src/components/error-page/ErrorPage.vue +24 -27
  43. package/src/components/error-page/index.ts +2 -2
  44. package/src/components/info-card/InfoCard.vue +17 -11
  45. package/src/components/landing/index.ts +6 -6
  46. package/src/components/landing/landing-content/LandingContent.vue +5 -7
  47. package/src/components/landing/landing-content/index.ts +2 -2
  48. package/src/components/landing/landing-footer/LandingFooter.vue +26 -29
  49. package/src/components/landing/landing-footer/index.ts +3 -3
  50. package/src/components/landing/landing-navbar/LandingNavbar.vue +15 -17
  51. package/src/components/landing/landing-navbar/index.ts +2 -2
  52. package/src/components/landing/landing-shell/LandingShell.vue +10 -13
  53. package/src/components/landing/landing-shell/index.ts +2 -2
  54. package/src/components/language-flag/LanguageFlag.vue +6 -5
  55. package/src/components/language-flag/index.ts +1 -0
  56. package/src/components/language-selector/LanguageSelector.vue +20 -21
  57. package/src/components/load-more-button/LoadMoreButton.vue +18 -14
  58. package/src/components/loading-state-card/LoadingStateCard.vue +13 -9
  59. package/src/components/message-dialog/MessageDialog.vue +19 -19
  60. package/src/components/message-dialog/index.ts +3 -11
  61. package/src/components/message-dialog/message-dialog-state.ts +12 -11
  62. package/src/components/message-toast/MessageToast.vue +16 -16
  63. package/src/components/message-toast/index.ts +2 -9
  64. package/src/components/message-toast/message-toast-state.ts +9 -9
  65. package/src/components/page-header/PageHeader.vue +8 -5
  66. package/src/components/progress-component/ProgressComponent.vue +13 -10
  67. package/src/components/single-value-card/SingleValueCard.vue +22 -19
  68. package/src/components/slider-form-component/SliderFormComponent.vue +11 -20
  69. package/src/components/slider-form-component/index.ts +2 -2
  70. package/src/components/switch-form-component/SwitchFormComponent.vue +7 -9
  71. package/src/components/switch-form-component/index.ts +2 -2
  72. package/src/components/text-link/TextLink.vue +11 -7
  73. package/src/components/theme-selector/ThemeSelector.vue +4 -4
  74. package/src/components/ui/accordion/Accordion.vue +12 -15
  75. package/src/components/ui/accordion/index.ts +1 -1
  76. package/src/components/ui/alert/Alert.vue +11 -10
  77. package/src/components/ui/alert/AlertDescription.vue +9 -8
  78. package/src/components/ui/alert/AlertTitle.vue +9 -8
  79. package/src/components/ui/alert/index.ts +10 -10
  80. package/src/components/ui/alert-dialog/AlertDialog.vue +13 -9
  81. package/src/components/ui/alert-dialog/AlertDialogAction.vue +14 -12
  82. package/src/components/ui/alert-dialog/AlertDialogCancel.vue +14 -16
  83. package/src/components/ui/alert-dialog/AlertDialogContent.vue +24 -27
  84. package/src/components/ui/alert-dialog/AlertDialogDescription.vue +13 -13
  85. package/src/components/ui/alert-dialog/AlertDialogFooter.vue +9 -12
  86. package/src/components/ui/alert-dialog/AlertDialogHeader.vue +9 -8
  87. package/src/components/ui/alert-dialog/AlertDialogTitle.vue +13 -11
  88. package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +8 -7
  89. package/src/components/ui/alert-dialog/index.ts +9 -9
  90. package/src/components/ui/badge/Badge.vue +11 -12
  91. package/src/components/ui/badge/index.ts +1 -1
  92. package/src/components/ui/breadcrumb/Breadcrumb.vue +9 -12
  93. package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +15 -19
  94. package/src/components/ui/breadcrumb/BreadcrumbItem.vue +10 -12
  95. package/src/components/ui/breadcrumb/BreadcrumbLink.vue +13 -17
  96. package/src/components/ui/breadcrumb/BreadcrumbList.vue +10 -12
  97. package/src/components/ui/breadcrumb/BreadcrumbPage.vue +10 -15
  98. package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +14 -18
  99. package/src/components/ui/breadcrumb/index.ts +7 -7
  100. package/src/components/ui/busy/Busy.vue +36 -29
  101. package/src/components/ui/busy/index.ts +1 -1
  102. package/src/components/ui/button/Button.vue +18 -19
  103. package/src/components/ui/button/index.ts +54 -35
  104. package/src/components/ui/calendar/Calendar.vue +167 -121
  105. package/src/components/ui/calendar/CalendarCell.vue +18 -17
  106. package/src/components/ui/calendar/CalendarCellTrigger.vue +31 -33
  107. package/src/components/ui/calendar/CalendarGrid.vue +14 -17
  108. package/src/components/ui/calendar/CalendarGridBody.vue +9 -11
  109. package/src/components/ui/calendar/CalendarGridHead.vue +10 -12
  110. package/src/components/ui/calendar/CalendarGridRow.vue +14 -16
  111. package/src/components/ui/calendar/CalendarHeadCell.vue +18 -17
  112. package/src/components/ui/calendar/CalendarHeader.vue +18 -17
  113. package/src/components/ui/calendar/CalendarHeading.vue +18 -22
  114. package/src/components/ui/calendar/CalendarNextButton.vue +29 -26
  115. package/src/components/ui/calendar/CalendarPrevButton.vue +29 -26
  116. package/src/components/ui/calendar/index.ts +17 -13
  117. package/src/components/ui/card/Card.vue +21 -13
  118. package/src/components/ui/card/CardAction.vue +12 -7
  119. package/src/components/ui/card/CardContent.vue +8 -7
  120. package/src/components/ui/card/CardDescription.vue +16 -7
  121. package/src/components/ui/card/CardFooter.vue +8 -7
  122. package/src/components/ui/card/CardHeader.vue +12 -7
  123. package/src/components/ui/card/CardText.vue +12 -7
  124. package/src/components/ui/card/CardTitle.vue +9 -7
  125. package/src/components/ui/card/index.ts +8 -8
  126. package/src/components/ui/command/Command.vue +71 -61
  127. package/src/components/ui/command/CommandDialog.vue +33 -23
  128. package/src/components/ui/command/CommandEmpty.vue +19 -20
  129. package/src/components/ui/command/CommandGroup.vue +32 -35
  130. package/src/components/ui/command/CommandInput.vue +25 -21
  131. package/src/components/ui/command/CommandItem.vue +62 -59
  132. package/src/components/ui/command/CommandList.vue +21 -18
  133. package/src/components/ui/command/CommandSeparator.vue +13 -11
  134. package/src/components/ui/command/CommandShortcut.vue +13 -8
  135. package/src/components/ui/command/index.ts +24 -20
  136. package/src/components/ui/date-picker/DatePicker.vue +46 -37
  137. package/src/components/ui/date-picker/index.ts +2 -2
  138. package/src/components/ui/date-time-picker/DateTimePicker.vue +99 -95
  139. package/src/components/ui/date-time-picker/index.ts +2 -2
  140. package/src/components/ui/dialog/Dialog.vue +12 -9
  141. package/src/components/ui/dialog/DialogClose.vue +8 -10
  142. package/src/components/ui/dialog/DialogContent.vue +36 -42
  143. package/src/components/ui/dialog/DialogDescription.vue +16 -11
  144. package/src/components/ui/dialog/DialogFooter.vue +13 -12
  145. package/src/components/ui/dialog/DialogHeader.vue +9 -8
  146. package/src/components/ui/dialog/DialogOverlay.vue +16 -11
  147. package/src/components/ui/dialog/DialogScrollContent.vue +47 -45
  148. package/src/components/ui/dialog/DialogTitle.vue +13 -12
  149. package/src/components/ui/dialog/DialogTrigger.vue +8 -7
  150. package/src/components/ui/dialog/index.ts +10 -10
  151. package/src/components/ui/field/Field.vue +12 -14
  152. package/src/components/ui/field/FieldContent.vue +13 -11
  153. package/src/components/ui/field/FieldDescription.vue +15 -13
  154. package/src/components/ui/field/FieldError.vue +43 -40
  155. package/src/components/ui/field/FieldGroup.vue +13 -11
  156. package/src/components/ui/field/FieldLabel.vue +16 -14
  157. package/src/components/ui/field/FieldLegend.vue +16 -14
  158. package/src/components/ui/field/FieldSeparator.vue +17 -15
  159. package/src/components/ui/field/FieldSet.vue +14 -12
  160. package/src/components/ui/field/FieldTitle.vue +13 -11
  161. package/src/components/ui/field/index.ts +36 -36
  162. package/src/components/ui/input/Input.vue +23 -20
  163. package/src/components/ui/input/index.ts +1 -1
  164. package/src/components/ui/label/Label.vue +16 -15
  165. package/src/components/ui/label/index.ts +1 -1
  166. package/src/components/ui/native-select/NativeSelect.vue +31 -41
  167. package/src/components/ui/native-select/NativeSelectOptGroup.vue +8 -7
  168. package/src/components/ui/native-select/NativeSelectOption.vue +8 -7
  169. package/src/components/ui/native-select/index.ts +3 -3
  170. package/src/components/ui/popover/Popover.vue +12 -9
  171. package/src/components/ui/popover/PopoverAnchor.vue +8 -7
  172. package/src/components/ui/popover/PopoverContent.vue +27 -31
  173. package/src/components/ui/popover/PopoverTrigger.vue +8 -7
  174. package/src/components/ui/popover/index.ts +4 -4
  175. package/src/components/ui/progress/Progress.vue +18 -32
  176. package/src/components/ui/progress/index.ts +1 -1
  177. package/src/components/ui/scroll-area/ScrollArea.vue +23 -26
  178. package/src/components/ui/scroll-area/ScrollBar.vue +22 -26
  179. package/src/components/ui/scroll-area/index.ts +2 -2
  180. package/src/components/ui/select/Select.vue +12 -9
  181. package/src/components/ui/select/SelectContent.vue +39 -40
  182. package/src/components/ui/select/SelectGroup.vue +8 -7
  183. package/src/components/ui/select/SelectItem.vue +36 -41
  184. package/src/components/ui/select/SelectItemContent.vue +22 -15
  185. package/src/components/ui/select/SelectItemText.vue +8 -7
  186. package/src/components/ui/select/SelectLabel.vue +10 -9
  187. package/src/components/ui/select/SelectScrollDownButton.vue +21 -17
  188. package/src/components/ui/select/SelectScrollUpButton.vue +21 -17
  189. package/src/components/ui/select/SelectSeparator.vue +11 -9
  190. package/src/components/ui/select/SelectTrigger.vue +27 -25
  191. package/src/components/ui/select/SelectValue.vue +8 -7
  192. package/src/components/ui/select/SelectValueContent.vue +30 -18
  193. package/src/components/ui/select/index.ts +13 -13
  194. package/src/components/ui/separator/Separator.vue +17 -18
  195. package/src/components/ui/separator/index.ts +1 -1
  196. package/src/components/ui/sheet/Sheet.vue +12 -9
  197. package/src/components/ui/sheet/SheetClose.vue +8 -7
  198. package/src/components/ui/sheet/SheetContent.vue +36 -37
  199. package/src/components/ui/sheet/SheetDescription.vue +12 -11
  200. package/src/components/ui/sheet/SheetFooter.vue +8 -7
  201. package/src/components/ui/sheet/SheetHeader.vue +8 -7
  202. package/src/components/ui/sheet/SheetOverlay.vue +16 -11
  203. package/src/components/ui/sheet/SheetTitle.vue +12 -11
  204. package/src/components/ui/sheet/SheetTrigger.vue +8 -7
  205. package/src/components/ui/sheet/index.ts +8 -8
  206. package/src/components/ui/sidebar/Sidebar.vue +68 -61
  207. package/src/components/ui/sidebar/SidebarContent.vue +13 -8
  208. package/src/components/ui/sidebar/SidebarFooter.vue +13 -8
  209. package/src/components/ui/sidebar/SidebarGroup.vue +13 -8
  210. package/src/components/ui/sidebar/SidebarGroupAction.vue +16 -16
  211. package/src/components/ui/sidebar/SidebarGroupContent.vue +9 -8
  212. package/src/components/ui/sidebar/SidebarGroupLabel.vue +15 -14
  213. package/src/components/ui/sidebar/SidebarHeader.vue +13 -8
  214. package/src/components/ui/sidebar/SidebarInput.vue +14 -12
  215. package/src/components/ui/sidebar/SidebarInset.vue +14 -12
  216. package/src/components/ui/sidebar/SidebarMenu.vue +9 -8
  217. package/src/components/ui/sidebar/SidebarMenuAction.vue +25 -23
  218. package/src/components/ui/sidebar/SidebarMenuBadge.vue +19 -17
  219. package/src/components/ui/sidebar/SidebarMenuButton.vue +33 -32
  220. package/src/components/ui/sidebar/SidebarMenuButtonChild.vue +19 -18
  221. package/src/components/ui/sidebar/SidebarMenuItem.vue +9 -8
  222. package/src/components/ui/sidebar/SidebarMenuSkeleton.vue +17 -12
  223. package/src/components/ui/sidebar/SidebarMenuSub.vue +14 -12
  224. package/src/components/ui/sidebar/SidebarMenuSubButton.vue +24 -21
  225. package/src/components/ui/sidebar/SidebarMenuSubItem.vue +9 -8
  226. package/src/components/ui/sidebar/SidebarProvider.vue +63 -53
  227. package/src/components/ui/sidebar/SidebarRail.vue +20 -18
  228. package/src/components/ui/sidebar/SidebarSeparator.vue +10 -9
  229. package/src/components/ui/sidebar/SidebarTrigger.vue +15 -14
  230. package/src/components/ui/sidebar/index.ts +62 -58
  231. package/src/components/ui/sidebar/utils.ts +16 -16
  232. package/src/components/ui/skeleton/Skeleton.vue +12 -9
  233. package/src/components/ui/skeleton/index.ts +1 -1
  234. package/src/components/ui/slider/Slider.vue +24 -19
  235. package/src/components/ui/slider/index.ts +1 -1
  236. package/src/components/ui/switch/Switch.vue +26 -23
  237. package/src/components/ui/switch/index.ts +1 -1
  238. package/src/components/ui/table/Table.vue +11 -10
  239. package/src/components/ui/table/TableBody.vue +9 -11
  240. package/src/components/ui/table/TableCaption.vue +9 -11
  241. package/src/components/ui/table/TableCell.vue +13 -16
  242. package/src/components/ui/table/TableEmpty.vue +22 -25
  243. package/src/components/ui/table/TableFooter.vue +13 -11
  244. package/src/components/ui/table/TableHead.vue +13 -11
  245. package/src/components/ui/table/TableHeader.vue +9 -11
  246. package/src/components/ui/table/TableRow.vue +13 -11
  247. package/src/components/ui/table/index.ts +9 -9
  248. package/src/components/ui/tabs/Tabs.vue +18 -18
  249. package/src/components/ui/tabs/TabsContent.vue +14 -15
  250. package/src/components/ui/tabs/TabsList.vue +18 -18
  251. package/src/components/ui/tabs/TabsTrigger.vue +19 -20
  252. package/src/components/ui/tabs/index.ts +4 -4
  253. package/src/components/ui/textarea/Textarea.vue +23 -20
  254. package/src/components/ui/textarea/index.ts +1 -1
  255. package/src/components/ui/time-picker/TimePicker.vue +33 -38
  256. package/src/components/ui/time-picker/index.ts +2 -2
  257. package/src/components/ui/tooltip/Tooltip.vue +12 -9
  258. package/src/components/ui/tooltip/TooltipContent.vue +28 -20
  259. package/src/components/ui/tooltip/TooltipProvider.vue +9 -8
  260. package/src/components/ui/tooltip/TooltipTrigger.vue +8 -7
  261. package/src/components/ui/tooltip/index.ts +4 -4
  262. package/src/i18n/de.json +5 -0
  263. package/src/i18n/en.json +5 -0
  264. package/src/i18n/es.json +5 -0
  265. package/src/i18n/fr.json +5 -0
  266. package/src/i18n/it.json +5 -0
  267. package/src/i18n/ja.json +5 -0
  268. package/src/i18n/pt.json +5 -0
  269. package/src/i18n/ru.json +5 -0
  270. package/src/i18n/zh.json +5 -0
  271. package/src/lib/common-types.ts +2 -0
  272. package/src/lib/i18n.ts +0 -5
  273. package/src/runtime/{ui-vintage-i18n-plugin.js → ui-vintage-i18n-plugin.ts} +30 -8
package/README.md CHANGED
@@ -1,85 +1,345 @@
1
- # UI Vintage
1
+ <div align="center">
2
2
 
3
- `@brumaombra/ui-vintage` is a source-published Nuxt runtime package. Instead of building a separate component library output, it ships the actual `src` files and lets the consuming Nuxt app compile them as part of its own runtime.
3
+ # 🎨 UI Vintage
4
4
 
5
- ## Features
5
+ ### A source-published Nuxt UI runtime for focused, reusable interfaces
6
6
 
7
- - Nuxt module entrypoint for stylesheet, i18n, and Nuxt runtime integration
8
- - automatic stylesheet injection from `src/styles.css`
9
- - explicit source-based subpath imports for components and helpers
10
- - built-in locale merge for the library messages when `vue-i18n` is present
11
- - real Nuxt integrations, including `@nuxt/image` components inside the shared source
7
+ `@brumaombra/ui-vintage` is a Nuxt 4 component library and runtime module for building consistent dashboards, forms, landing pages, overlays, and content experiences. It combines reusable Vue components, shared theme tokens, localization, and Nuxt integrations in one package that is compiled by the consuming application.
12
8
 
13
- ## Nuxt Only
9
+ <p>
10
+ <a href="https://github.com/brumaombra/ui-vintage"><img alt="GitHub Repo" src="https://img.shields.io/badge/github-brumaombra%2Fui--vintage-111111?logo=github"></a>
11
+ <img alt="Nuxt 4" src="https://img.shields.io/badge/nuxt-4-00DC82?logo=nuxt&logoColor=white">
12
+ <img alt="Vue 3" src="https://img.shields.io/badge/vue-3-42B883?logo=vue.js&logoColor=white">
13
+ <img alt="npm" src="https://img.shields.io/npm/v/@brumaombra/ui-vintage?color=CB3837&logo=npm">
14
+ <img alt="License MIT" src="https://img.shields.io/badge/license-MIT-2563EB">
15
+ </p>
14
16
 
15
- This package is intentionally Nuxt-specific. The shared components use Nuxt runtime features such as `#components`, `NuxtImg`, and module-driven app integration. If you need framework-agnostic Vue components, this package is the wrong shape.
17
+ <p>
18
+ 🧩 Reusable Vue components • 🎨 Shared theme tokens • 🌍 Built-in locale messages • 🖼️ Nuxt Image integration • ⚡ Source-based publishing
19
+ </p>
16
20
 
17
- ## Install
21
+ <p>
22
+ <a href="#overview"><strong>Overview</strong></a> •
23
+ <a href="#images"><strong>Images</strong></a> •
24
+ <a href="#features"><strong>Features</strong></a> •
25
+ <a href="#architecture"><strong>Architecture</strong></a> •
26
+ <a href="#quick-start"><strong>Quick Start</strong></a> •
27
+ <a href="#usage"><strong>Usage</strong></a> •
28
+ <a href="#components"><strong>Components</strong></a> •
29
+ <a href="#demo-app"><strong>Demo App</strong></a> •
30
+ <a href="#configuration"><strong>Configuration</strong></a> •
31
+ <a href="#publishing"><strong>Publishing</strong></a> •
32
+ <a href="#troubleshooting"><strong>Troubleshooting</strong></a> •
33
+ <a href="#license"><strong>License</strong></a>
34
+ </p>
35
+
36
+ </div>
37
+
38
+ <a id="overview"></a>
39
+ ## 📘 Overview
40
+
41
+ UI Vintage is designed for Nuxt applications that need a coherent interface without rebuilding the same primitives for every project. The package provides low-level controls such as buttons, inputs, selects, dialogs, tabs, switches, and sidebars alongside higher-level building blocks such as dashboard shells, landing layouts, cards, data lists, and message flows.
42
+
43
+ The package is intentionally published as source. The consuming Nuxt application compiles the library together with its own runtime, which keeps Nuxt-specific integrations available and avoids maintaining a separate framework-agnostic build artifact.
44
+
45
+ This is a Nuxt library, not a generic Vue component bundle. Components rely on Nuxt runtime features including `#components`, `NuxtImg`, and module lifecycle hooks.
46
+
47
+ <a id="images"></a>
48
+ ## 🖼️ Images
49
+
50
+ ### Screenshots
51
+
52
+ <div align="center">
53
+ <table width="100%" style="max-width: 768px;">
54
+ <tr>
55
+ <td align="center" valign="top">
56
+ <img src="https://raw.githubusercontent.com/brumaombra/ui-vintage/main/docs/images/screen-1.png" alt="UI Vintage component demo controls and primary actions" width="100%">
57
+ </td>
58
+ <td align="center" valign="top">
59
+ <img src="https://raw.githubusercontent.com/brumaombra/ui-vintage/main/docs/images/screen-2.png" alt="UI Vintage select, accordion, tabs, and component previews" width="100%">
60
+ </td>
61
+ </tr>
62
+ <tr>
63
+ <td align="center" valign="top">
64
+ <img src="https://raw.githubusercontent.com/brumaombra/ui-vintage/main/docs/images/screen-3.png" alt="UI Vintage surface variants, badges, dialogs, and feedback previews" width="100%">
65
+ </td>
66
+ <td align="center" valign="top">
67
+ <img src="https://raw.githubusercontent.com/brumaombra/ui-vintage/main/docs/images/screen-4.png" alt="UI Vintage loading, toast, and current state previews" width="100%">
68
+ </td>
69
+ </tr>
70
+ </table>
71
+ <p>
72
+ <img src="https://raw.githubusercontent.com/brumaombra/ui-vintage/main/docs/images/blog.png" alt="UI Vintage landing and blog showcase" width="100%" style="max-width: 768px;">
73
+ </p>
74
+ <p>
75
+ <sub>Component showcase, interactive states, landing surface, and full-width blog showcase</sub>
76
+ </p>
77
+ </div>
78
+
79
+ <a id="features"></a>
80
+ ## ✨ Features
81
+
82
+ - Nuxt 4 module with automatic runtime integration.
83
+ - Shared stylesheet and design tokens injected from `src/styles.css`.
84
+ - Explicit subpath imports that keep application dependencies clear.
85
+ - Vue 3 components built with Composition API and TypeScript source.
86
+ - Primitive UI controls based on Reka UI where accessible behavior is required.
87
+ - Reusable dashboard, landing, blog, card, field, and data-display components.
88
+ - Built-in busy, confirm-dialog, message-dialog, and message-toast flows.
89
+ - Theme selector with light, dark, and automatic system modes.
90
+ - Library locale messages merged into an existing Vue I18n instance when available.
91
+ - Automatic `@nuxt/image` installation for components that use `NuxtImg`.
92
+ - Source publishing with no required library build step before installation.
93
+
94
+ <a id="architecture"></a>
95
+ ## 🏗️ Architecture
96
+
97
+ UI Vintage is split into three cooperating layers:
98
+
99
+ - 🧩 **Components** in `src/components/` contain the public Vue UI and composite layouts.
100
+ - 🎨 **Styles and helpers** in `src/styles.css` and `src/lib/` provide theme tokens, class utilities, and shared behavior.
101
+ - 🔌 **Nuxt runtime** in `module.mjs` and `src/runtime/` integrates the package with the consuming app.
102
+
103
+ ### 🔄 Module Behavior
104
+
105
+ When the module is registered, it:
106
+
107
+ 1. Injects `src/styles.css` into the Nuxt application once.
108
+ 2. Adds the published `src/` directory to Nuxt transpilation.
109
+ 3. Installs `@nuxt/image` when the consuming app has not already registered it.
110
+ 4. Registers the library i18n plugin.
111
+ 5. Merges the library’s locale messages into the app’s Vue I18n composer when Vue I18n is present.
112
+
113
+ The module does not auto-register every component. Import components and helpers explicitly from their public subpaths.
114
+
115
+ ### 📁 Repository Layout
116
+
117
+ ```text
118
+ src/
119
+ components/ # Public components and UI primitives
120
+ i18n/ # Library locale messages
121
+ lib/ # Shared helpers and token utilities
122
+ runtime/ # Nuxt runtime plugins
123
+ styles.css # Shared design tokens and component styles
124
+ module.mjs # Nuxt module entrypoint
125
+ demo-app/ # Private Nuxt showcase and manual verification app
126
+ ```
127
+
128
+ <a id="quick-start"></a>
129
+ ## 🚀 Quick Start
130
+
131
+ ### 📦 Install the package
132
+
133
+ Install the UI Vintage library and the peer integrations used by the package in an existing Nuxt 4 application:
18
134
 
19
135
  ```bash
20
136
  npm install @brumaombra/ui-vintage @nuxt/image vue-i18n
21
137
  ```
22
138
 
23
- Register the module in your Nuxt app:
139
+ ### 🔌 Register the module
140
+
141
+ Add the module to `nuxt.config.js`:
24
142
 
25
- ```ts
143
+ ```js
26
144
  export default defineNuxtConfig({
27
- modules: ['@brumaombra/ui-vintage']
28
- })
145
+ modules: [
146
+ '@brumaombra/ui-vintage'
147
+ ]
148
+ });
29
149
  ```
30
150
 
31
- The module only handles shared runtime integration: styles, library locale messages, source transpilation, and `@nuxt/image` installation when needed. Components and helpers are always imported explicitly.
151
+ `@nuxt/image` is installed by the module when it is not already present in the app’s module list. Install it explicitly when your application also uses image components directly. Vue I18n is optional at runtime, but it is required for the library’s locale messages and translated components.
32
152
 
33
- ## Usage
153
+ <a id="usage"></a>
154
+ ## 🧪 Usage
34
155
 
35
- Once the module is installed:
156
+ ### 🧱 Use a component
36
157
 
37
- - the shared stylesheet is injected automatically
38
- - components are imported from explicit subpaths such as `@brumaombra/ui-vintage/button`
39
- - helpers are imported from explicit subpaths such as `@brumaombra/ui-vintage/message-dialog`
40
-
41
- Example:
158
+ Components are imported from explicit package subpaths:
42
159
 
43
160
  ```vue
44
- <script setup lang="ts">
45
- import { Button } from '@brumaombra/ui-vintage/button'
46
- import { Card, CardContent } from '@brumaombra/ui-vintage/card'
161
+ <script setup>
162
+ import { Button } from '@brumaombra/ui-vintage/button';
163
+ import { Card, CardContent } from '@brumaombra/ui-vintage/card';
47
164
  </script>
48
165
 
49
166
  <template>
50
- <Button>Save</Button>
51
167
  <Card>
52
- <CardContent>Reusable content</CardContent>
168
+ <CardContent class="flex items-center justify-between gap-4">
169
+ <span>Workspace status</span>
170
+
171
+ <!-- For example, the Button component accepts variants such as primary, secondary, gray, and ghost -->
172
+ <Button variant="primary">Save changes</Button>
173
+ </CardContent>
53
174
  </Card>
54
175
  </template>
55
176
  ```
56
177
 
57
- ```ts
58
- import { setBusy } from '@brumaombra/ui-vintage/busy-indicator'
59
- import { showConfirmDialog } from '@brumaombra/ui-vintage/confirm-dialog'
178
+ ### 🔔 Use a message flow
179
+
180
+ The dialog and busy helpers can be imported directly from their subpaths:
181
+
182
+ ```js
183
+ import { showConfirmDialog } from '@brumaombra/ui-vintage/confirm-dialog';
184
+ import { setBusy } from '@brumaombra/ui-vintage/busy-indicator';
185
+
186
+ // Ask for confirmation before starting a destructive action
187
+ const confirmed = await showConfirmDialog({
188
+ title: 'Delete project?',
189
+ message: 'This action cannot be undone.'
190
+ });
191
+
192
+ if (confirmed) {
193
+ // Keep the shared loading state visible while the request is running
194
+ setBusy({
195
+ title: 'Deleting project',
196
+ description: 'Please wait while the project is removed.'
197
+ });
198
+ }
199
+ ```
200
+
201
+ ### 🧭 Use a layout component
202
+
203
+ Higher-level components accept slots so application navigation and content remain app-owned. The shell provides the layout; the application provides its navigation data:
204
+
205
+ ```vue
206
+ <script setup>
207
+ import { DashboardShell } from '@brumaombra/ui-vintage/dashboard-shell';
208
+
209
+ // List of sections
210
+ const sidebarSections = [{
211
+ id: 'workspace',
212
+ label: 'Workspace',
213
+ items: [
214
+ { id: 'overview', label: 'Overview', href: '/', active: true },
215
+ { id: 'settings', label: 'Settings', href: '/settings' }
216
+ ]
217
+ }];
218
+ </script>
219
+
220
+ <template>
221
+ <DashboardShell :sidebar-sections="sidebarSections">
222
+ <slot />
223
+ </DashboardShell>
224
+ </template>
225
+ ```
226
+
227
+ <a id="components"></a>
228
+ ## 🧩 Components
229
+
230
+ The package exposes components through explicit subpaths. The complete public export map is maintained in `package.json`; common groups include:
231
+
232
+ ### 🎛️ UI primitives
233
+
234
+ `alert`, `alert-dialog`, `accordion`, `badge`, `breadcrumb`, `button`, `calendar`, `card`, `command`, `dialog`, `field`, `input`, `label`, `popover`, `progress`, `scroll-area`, `select`, `separator`, `sheet`, `sidebar`, `skeleton`, `slider`, `switch`, `table`, `tabs`, `textarea`, `time-picker`, and `tooltip`.
235
+
236
+ ### 🧱 Composite components
237
+
238
+ `background-grid`, `card-grid`, `chip`, `dashboard-shell`, `data-list`, `empty-state-card`, `error-page`, `info-card`, `landing`, `landing-content`, `landing-footer`, `landing-navbar`, `landing-shell`, `load-more-button`, `loading-state-card`, `page-header`, `progress-component`, `single-value-card`, `text-link`, and `theme-selector`.
239
+
240
+ ### 💬 Runtime flows and integrations
241
+
242
+ `busy`, `busy-indicator`, `confirm-dialog`, `message-dialog`, `message-toast`, `language-flag`, `language-selector`, `blog`, `content`, and `utils`.
243
+
244
+ <a id="demo-app"></a>
245
+ ## 🖥️ Demo App
246
+
247
+ The private `demo-app/` directory is a Nuxt 4 showcase for manual verification. It demonstrates the public components, shared theme behavior, responsive layouts, form states, dialogs, toasts, tabs, accordions, localization, and dashboard composition.
248
+
249
+ The demo app is not part of the published package and is not intended to be installed by consumers. It imports the package through `file:..`, so it exercises the same source that is published to npm.
60
250
 
61
- await showConfirmDialog({
62
- title: 'Delete item?',
63
- description: 'This action cannot be undone.'
64
- })
251
+ ### ▶️ Run the showcase locally
65
252
 
66
- setBusy({
67
- title: 'Loading data',
68
- description: 'Please wait while the dashboard refreshes.'
69
- })
253
+ From the repository root:
254
+
255
+ ```bash
256
+ # Install dependencies for the demo app
257
+ npm --prefix demo-app install
258
+
259
+ # Start the Nuxt development server
260
+ npm --prefix demo-app run dev
261
+ ```
262
+
263
+ Open the local URL printed by Nuxt. To create a production build of the showcase:
264
+
265
+ ```bash
266
+ # Build the private verification app
267
+ npm --prefix demo-app run build
70
268
  ```
71
269
 
72
- ## Publish Model
270
+ <a id="configuration"></a>
271
+ ## ⚙️ Configuration
272
+
273
+ ### 🎨 Styles
274
+
275
+ The module injects the shared stylesheet automatically. The stylesheet is also available as an explicit export when an app needs to control loading order:
276
+
277
+ ```js
278
+ // Import the shared tokens and component styles explicitly when needed
279
+ import '@brumaombra/ui-vintage/style.css';
280
+ ```
281
+
282
+ Avoid creating a second theme-token system in the consuming app. Extend the existing CSS custom properties in your application stylesheet when a project needs additional brand values.
283
+
284
+ ### 🌍 Localization
285
+
286
+ The package includes English, Italian, French, Spanish, German, Portuguese, Chinese, Japanese, and Russian library messages. When `@nuxtjs/i18n` or Vue I18n is configured, the runtime plugin merges these messages into the existing composer without replacing application messages.
287
+
288
+ Application-specific translations remain owned by the consuming app. The library only contributes messages under its own `uiVintage` namespace.
289
+
290
+ ### 🧭 Imports
291
+
292
+ Use explicit subpaths for public imports:
293
+
294
+ ```js
295
+ // Import a component from its stable public entrypoint
296
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@brumaombra/ui-vintage/tabs';
297
+
298
+ // Import shared helper behavior from its dedicated entrypoint
299
+ import { showMessageToast } from '@brumaombra/ui-vintage/message-toast';
300
+ ```
301
+
302
+ Do not import internal files from `src/components/` in application code. Internal paths are implementation details and may change independently of public entrypoints.
303
+
304
+ <a id="publishing"></a>
305
+ ## 📦 Publishing Model
306
+
307
+ UI Vintage publishes the Nuxt module entrypoint and source files directly:
308
+
309
+ - `module.mjs` is the package entrypoint registered by Nuxt.
310
+ - `src/` contains the published components, styles, helpers, locale messages, and runtime plugin.
311
+ - `package.json` defines the public component subpath exports.
312
+ - `dist/` is generated repository output and is not the source of truth for consumers.
313
+
314
+ There is no required library build step before publishing. The normal verification command is:
315
+
316
+ ```bash
317
+ # Check the published TypeScript and Vue source without generating dist output
318
+ npm run typecheck
319
+ ```
320
+
321
+ For release details, see [NPM.md](NPM.md). The package is published under the public npm scope `@brumaombra/ui-vintage`.
322
+
323
+ <a id="requirements"></a>
324
+ ## 🧰 Requirements
73
325
 
74
- The npm package publishes `module.mjs` and `src/` directly. There is no required `dist` build step for publishing anymore.
326
+ - Node.js compatible with the Nuxt 4 version used by the consuming app.
327
+ - Nuxt 4.
328
+ - Vue 3.5 or newer.
329
+ - `@nuxt/image` for image-enabled components. The module installs it when needed.
330
+ - `vue-i18n` when using the library’s localization integration.
75
331
 
76
- ## Requirements
332
+ <a id="troubleshooting"></a>
333
+ ## 🛠️ Troubleshooting
77
334
 
78
- - Nuxt 4
79
- - `@nuxt/image`
80
- - Vue 3
81
- - `vue-i18n`
335
+ - **Components cannot resolve Nuxt imports:** confirm the app is Nuxt 4 and that `@brumaombra/ui-vintage` is registered in `nuxt.config.js`.
336
+ - **Styles are missing:** check that the module is registered once and restart the Nuxt dev server after changing `nuxt.config.js`.
337
+ - **`NuxtImg` is unavailable:** install `@nuxt/image` or allow the UI Vintage module to install it during Nuxt setup.
338
+ - **Translations do not appear:** configure Vue I18n or `@nuxtjs/i18n`; the runtime plugin merges messages only when an i18n composer is available.
339
+ - **A component import fails:** use the public subpath listed in `package.json`, such as `@brumaombra/ui-vintage/button`, rather than an internal `src/` path.
340
+ - **The demo app does not start:** run `npm --prefix demo-app install` from the repository root, then retry `npm --prefix demo-app run dev`.
82
341
 
83
- ## License
342
+ <a id="license"></a>
343
+ ## 📄 License
84
344
 
85
- MIT
345
+ This project is released under the MIT License. See [LICENSE](LICENSE) for the full license text.
package/module.mjs CHANGED
@@ -69,7 +69,7 @@ export default defineNuxtModule({
69
69
 
70
70
  // Add the plugin to merge the library translations into the app i18n instance at runtime
71
71
  addPlugin({
72
- src: resolver.resolve('src/runtime/ui-vintage-i18n-plugin.js')
72
+ src: resolver.resolve('src/runtime/ui-vintage-i18n-plugin.ts')
73
73
  });
74
74
  }
75
75
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brumaombra/ui-vintage",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "Source-published Nuxt UI runtime with shared theme tokens and built-in Nuxt integrations.",
5
5
  "private": false,
6
6
  "author": "Mauro Brambilla",
@@ -61,6 +61,7 @@
61
61
  "./error-page": "./src/components/error-page/index.ts",
62
62
  "./field": "./src/components/ui/field/index.ts",
63
63
  "./info-card": "./src/components/info-card/index.ts",
64
+ "./common-types": "./src/lib/common-types.ts",
64
65
  "./input": "./src/components/ui/input/index.ts",
65
66
  "./label": "./src/components/ui/label/index.ts",
66
67
  "./landing": "./src/components/landing/index.ts",
@@ -1,5 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue';
3
+
4
+ // Props
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class'];
7
+ }>();
8
+ </script>
9
+
1
10
  <template>
2
- <div class="pointer-events-none absolute inset-0 z-0 overflow-hidden">
3
- <div class="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:20px_20px] dark:bg-[linear-gradient(to_right,#ffffff0d_1px,transparent_1px),linear-gradient(to_bottom,#ffffff0d_1px,transparent_1px)]" />
11
+ <div :class="['pointer-events-none absolute inset-0 z-0 overflow-hidden', props.class]">
12
+ <div class="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-size-[20px_20px] dark:bg-[linear-gradient(to_right,#ffffff0d_1px,transparent_1px),linear-gradient(to_bottom,#ffffff0d_1px,transparent_1px)]" />
4
13
  </div>
5
14
  </template>
@@ -1,2 +1,2 @@
1
- export { default } from "./BackgroundGrid.vue";
2
- export { default as BackgroundGrid } from "./BackgroundGrid.vue";
1
+ export { default } from './BackgroundGrid.vue';
2
+ export { default as BackgroundGrid } from './BackgroundGrid.vue';
@@ -1,4 +1,4 @@
1
- <script setup>
1
+ <script setup lang="ts">
2
2
  import { computed } from 'vue';
3
3
  import { useI18n } from 'vue-i18n';
4
4
  import { LoadMoreButton } from '../load-more-button';
@@ -7,17 +7,33 @@ import PostsList from './PostsList.vue';
7
7
  const { t } = useI18n();
8
8
 
9
9
  // Props
10
- const props = defineProps({
11
- posts: { type: Array, default: () => [] },
12
- currentPage: { type: Number, default: 1 },
13
- postsPerPage: { type: Number, default: 9 },
14
- totalPosts: { type: Number, default: 0 },
15
- hasMorePosts: { type: Boolean, default: false },
16
- isLoading: { type: Boolean, default: false }
10
+ const props = withDefaults(defineProps<{
11
+ posts?: Array<{
12
+ path: string;
13
+ title: string;
14
+ description?: string;
15
+ image?: string;
16
+ categoryText?: string;
17
+ categoryPath?: string;
18
+ }>;
19
+ currentPage?: number;
20
+ postsPerPage?: number;
21
+ totalPosts?: number;
22
+ hasMorePosts?: boolean;
23
+ isLoading?: boolean;
24
+ }>(), {
25
+ posts: () => [],
26
+ currentPage: 1,
27
+ postsPerPage: 9,
28
+ totalPosts: 0,
29
+ hasMorePosts: false,
30
+ isLoading: false
17
31
  });
18
32
 
19
33
  // Emits
20
- const emits = defineEmits(['load-more']);
34
+ const emits = defineEmits<{
35
+ 'load-more': [];
36
+ }>();
21
37
 
22
38
  // Handle load more posts
23
39
  const handleLoadMorePosts = () => {
@@ -1,12 +1,17 @@
1
- <script setup>
1
+ <script setup lang="ts">
2
2
  import { computed } from 'vue';
3
3
  import { useI18n } from 'vue-i18n';
4
4
  import { Accordion } from '@brumaombra/ui-vintage/accordion';
5
5
  import BlogSectionTitle from './BlogSectionTitle.vue';
6
6
 
7
7
  // Props
8
- const props = defineProps({
9
- faqs: { type: Array, default: () => [] }
8
+ const props = withDefaults(defineProps<{
9
+ faqs?: Array<{
10
+ question: string;
11
+ answer: string;
12
+ }>;
13
+ }>(), {
14
+ faqs: () => []
10
15
  });
11
16
 
12
17
  const { t } = useI18n();
@@ -1,19 +1,22 @@
1
- <script setup>
1
+ <script setup lang="ts">
2
+ import type { ToneColor } from '../../lib/color-tokens';
2
3
  import { Badge } from '../ui/badge';
3
4
 
4
5
  // Props
5
- const props = defineProps({
6
- badges: {
7
- type: Array,
8
- default: () => []
9
- },
10
- title: { type: String, required: true },
11
- description: { type: String, required: true }
6
+ const props = withDefaults(defineProps<{
7
+ badges?: Array<{
8
+ color: ToneColor;
9
+ text: string;
10
+ }>;
11
+ title: string;
12
+ description: string;
13
+ }>(), {
14
+ badges: () => []
12
15
  });
13
16
  </script>
14
17
 
15
18
  <template>
16
- <div class="flex flex-col space-y-6 md:!space-y-8">
19
+ <div class="flex flex-col space-y-6 md:space-y-8!">
17
20
  <!-- Badges -->
18
21
  <div v-if="props.badges.length > 0" class="flex flex-wrap gap-3 mb-6 md:mb-8">
19
22
  <Badge v-for="badge in props.badges"
@@ -23,12 +26,12 @@ const props = defineProps({
23
26
  </div>
24
27
 
25
28
  <!-- Title -->
26
- <h1 class="text-3xl md:text-6xl font-bold text-[var(--text-primary-light)] dark:text-[var(--text-primary-dark)] leading-tight">
29
+ <h1 class="text-3xl md:text-6xl font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) leading-tight">
27
30
  {{ props.title }}
28
31
  </h1>
29
32
 
30
33
  <!-- Description -->
31
- <p class="text-sm md:text-xl text-[var(--text-secondary-light)] dark:text-[var(--text-secondary-dark)] leading-relaxed">
34
+ <p class="text-sm md:text-xl text-(--text-secondary-light) dark:text-(--text-secondary-dark) leading-relaxed">
32
35
  {{ props.description }}
33
36
  </p>
34
37
  </div>
@@ -1,4 +1,4 @@
1
- <script setup>
1
+ <script setup lang="ts">
2
2
  import { NuxtImg } from '#components';
3
3
  import { computed } from 'vue';
4
4
  import { useI18n } from 'vue-i18n';
@@ -6,12 +6,14 @@ import { Card, CardContent } from '../ui/card';
6
6
  import { formatDateLongItalyTimezone } from '../../lib/utils';
7
7
 
8
8
  // Props
9
- const props = defineProps({
10
- author: { type: String, required: true },
11
- authorUrl: { type: String, required: true },
12
- authorImageUrl: { type: String, required: true },
13
- datePublished: { type: String, required: true },
14
- dateModified: { type: String, default: null }
9
+ const props = withDefaults(defineProps<{
10
+ author: string;
11
+ authorUrl: string;
12
+ authorImageUrl: string;
13
+ datePublished: string;
14
+ dateModified?: string | null;
15
+ }>(), {
16
+ dateModified: null
15
17
  });
16
18
 
17
19
  const { t, locale } = useI18n();
@@ -36,9 +38,9 @@ const formattedModifiedDate = computed(() => formatDateLongItalyTimezone(props.d
36
38
 
37
39
  <!-- Author name -->
38
40
  <div>
39
- <p class="text-sm text-[var(--text-secondary-light)] dark:text-[var(--text-secondary-dark)]">{{ t('uiVintage.blog.author') }}</p>
40
- <p class="font-bold text-[var(--text-primary-light)] dark:text-[var(--text-primary-dark)]">
41
- <NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="hover:text-[var(--text-secondary-light)] dark:hover:text-[var(--text-secondary-dark)] transition-colors">
41
+ <p class="text-sm text-(--text-secondary-light) dark:text-(--text-secondary-dark)">{{ t('uiVintage.blog.author') }}</p>
42
+ <p class="font-bold text-(--text-primary-light) dark:text-(--text-primary-dark)">
43
+ <NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="hover:text-(--text-secondary-light) dark:hover:text-(--text-secondary-dark) transition-colors">
42
44
  {{ props.author }}
43
45
  </NuxtLink>
44
46
  </p>
@@ -47,7 +49,7 @@ const formattedModifiedDate = computed(() => formatDateLongItalyTimezone(props.d
47
49
  </div>
48
50
 
49
51
  <!-- Date information -->
50
- <div class="flex flex-col sm:items-end text-xs md:text-sm text-[var(--text-secondary-light)] dark:text-[var(--text-secondary-dark)]">
52
+ <div class="flex flex-col sm:items-end text-xs md:text-sm text-(--text-secondary-light) dark:text-(--text-secondary-dark)">
51
53
  <!-- Published date -->
52
54
  <div v-if="props.datePublished" class="mb-1">
53
55
  <span class="font-semibold">{{ t('uiVintage.blog.publishedDate') }}:</span>
@@ -1,12 +1,12 @@
1
- <script setup>
1
+ <script setup lang="ts">
2
2
  // Props
3
- const props = defineProps({
4
- title: { type: String, required: true }
5
- });
3
+ const props = defineProps<{
4
+ title: string;
5
+ }>();
6
6
  </script>
7
7
 
8
8
  <template>
9
- <h2 class="text-xl md:text-2xl font-bold text-[var(--text-primary-light)] dark:text-[var(--text-primary-dark)] mb-5">
9
+ <h2 class="text-xl md:text-2xl font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) mb-5">
10
10
  {{ props.title }}
11
11
  </h2>
12
12
  </template>
@@ -1,9 +1,17 @@
1
- <script setup>
1
+ <script setup lang="ts">
2
2
  import CategoryCard from './CategoryCard.vue';
3
3
 
4
4
  // Props
5
- const props = defineProps({
6
- categories: { type: Array, default: () => [] }
5
+ const props = withDefaults(defineProps<{
6
+ categories?: Array<{
7
+ path: string;
8
+ slug?: string;
9
+ name: string;
10
+ image?: string;
11
+ count: number;
12
+ }>;
13
+ }>(), {
14
+ categories: () => []
7
15
  });
8
16
  </script>
9
17