@brumaombra/ui-vintage 0.9.1 → 0.9.3

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 (344) hide show
  1. package/COMPONENTS.md +102 -2
  2. package/module.mjs +43 -43
  3. package/package.json +175 -175
  4. package/src/components/background-grid/BackgroundGrid.vue +4 -4
  5. package/src/components/blog/AllPostsSection.vue +33 -33
  6. package/src/components/blog/BlogCategoriesPage.vue +46 -0
  7. package/src/components/blog/BlogContentRenderer.vue +111 -111
  8. package/src/components/blog/BlogFAQSection.vue +19 -19
  9. package/src/components/blog/BlogHeaderSection.vue +80 -80
  10. package/src/components/blog/BlogIndexPage.vue +118 -0
  11. package/src/components/blog/BlogInfoSection.vue +47 -47
  12. package/src/components/blog/BlogPageBreadcrumbs.vue +43 -0
  13. package/src/components/blog/BlogPostHeader.vue +135 -135
  14. package/src/components/blog/BlogPostPage.vue +116 -0
  15. package/src/components/blog/BlogPostsPage.vue +74 -0
  16. package/src/components/blog/BlogProgressBar.vue +33 -33
  17. package/src/components/blog/BlogSectionTitle.vue +22 -22
  18. package/src/components/blog/BlogTagsPage.vue +46 -0
  19. package/src/components/blog/BlogTagsSection.vue +25 -25
  20. package/src/components/blog/BlogViewAllLink.vue +18 -0
  21. package/src/components/blog/CategoriesList.vue +14 -14
  22. package/src/components/blog/CategoriesSection.vue +11 -11
  23. package/src/components/blog/CategoryCard.vue +25 -25
  24. package/src/components/blog/HeaderCarousel.vue +97 -97
  25. package/src/components/blog/PostCard.vue +33 -33
  26. package/src/components/blog/PostsList.vue +15 -15
  27. package/src/components/blog/SocialShareSidebar.vue +84 -84
  28. package/src/components/blog/TableOfContents.vue +307 -307
  29. package/src/components/blog/blogPageUtils.ts +98 -0
  30. package/src/components/blog/index.ts +12 -1
  31. package/src/components/busy-indicator/BusyIndicator.vue +7 -7
  32. package/src/components/busy-indicator/busy-indicator-state.ts +56 -56
  33. package/src/components/card-grid/CardGrid.vue +34 -34
  34. package/src/components/chip/Chip.vue +15 -15
  35. package/src/components/confirm-dialog/ConfirmDialog.vue +50 -50
  36. package/src/components/confirm-dialog/confirm-dialog-state.ts +131 -131
  37. package/src/components/content/BlogList.vue +30 -30
  38. package/src/components/content/BlogTable.vue +36 -36
  39. package/src/components/content/CodeBlock.vue +136 -136
  40. package/src/components/content/Flow.vue +60 -60
  41. package/src/components/content/ProseHr.vue +15 -15
  42. package/src/components/content/ProsePre.vue +12 -12
  43. package/src/components/content/Terminal.vue +59 -59
  44. package/src/components/cookie-consent/CookieConsent.vue +36 -36
  45. package/src/components/cookie-consent/useCookieConsent.ts +37 -37
  46. package/src/components/dashboard-shell/DashboardShell.vue +171 -171
  47. package/src/components/data-list/DataList.vue +6 -6
  48. package/src/components/data-list/DataListItem.vue +4 -4
  49. package/src/components/data-list/DataListLabel.vue +4 -4
  50. package/src/components/data-list/DataListText.vue +4 -4
  51. package/src/components/data-list/DataListValue.vue +4 -4
  52. package/src/components/empty-state-card/EmptyStateCard.vue +40 -40
  53. package/src/components/error-page/ErrorPage.vue +72 -72
  54. package/src/components/info-card/InfoCard.vue +31 -31
  55. package/src/components/landing/landing-content/LandingContent.vue +4 -4
  56. package/src/components/landing/landing-footer/LandingFooter.vue +128 -128
  57. package/src/components/landing/landing-navbar/LandingNavbar.vue +50 -50
  58. package/src/components/landing/landing-shell/LandingShell.vue +36 -36
  59. package/src/components/language-flag/LanguageFlag.vue +25 -25
  60. package/src/components/language-flag/flags/ChineseFlag.vue +8 -8
  61. package/src/components/language-flag/flags/EnglishFlag.vue +7 -7
  62. package/src/components/language-flag/flags/FrenchFlag.vue +5 -5
  63. package/src/components/language-flag/flags/GermanFlag.vue +5 -5
  64. package/src/components/language-flag/flags/ItalianFlag.vue +7 -7
  65. package/src/components/language-flag/flags/JapaneseFlag.vue +4 -4
  66. package/src/components/language-flag/flags/PortugueseFlag.vue +57 -57
  67. package/src/components/language-flag/flags/RussianFlag.vue +5 -5
  68. package/src/components/language-flag/flags/SpanishFlag.vue +4 -4
  69. package/src/components/language-selector/LanguageSelector.vue +76 -76
  70. package/src/components/load-more-button/LoadMoreButton.vue +17 -17
  71. package/src/components/loading-state-card/LoadingStateCard.vue +28 -28
  72. package/src/components/message-dialog/MessageDialog.vue +59 -59
  73. package/src/components/message-dialog/message-dialog-state.ts +105 -105
  74. package/src/components/message-toast/MessageToast.vue +149 -149
  75. package/src/components/message-toast/message-toast-state.ts +146 -146
  76. package/src/components/page-header/PageHeader.vue +34 -34
  77. package/src/components/progress-component/ProgressComponent.vue +33 -33
  78. package/src/components/single-value-card/SingleValueCard.vue +57 -57
  79. package/src/components/slider-form-component/SliderFormComponent.vue +36 -36
  80. package/src/components/stat-strip/StatStrip.vue +22 -22
  81. package/src/components/switch-form-component/SwitchFormComponent.vue +20 -20
  82. package/src/components/text-link/TextLink.vue +14 -14
  83. package/src/components/theme-selector/ThemeSelector.vue +61 -61
  84. package/src/components/theme-selector/theme-transition.ts +63 -63
  85. package/src/components/ui/accordion/Accordion.vue +38 -38
  86. package/src/components/ui/alert/Alert.vue +5 -5
  87. package/src/components/ui/alert/AlertDescription.vue +4 -4
  88. package/src/components/ui/alert/AlertTitle.vue +4 -4
  89. package/src/components/ui/alert/index.ts +2 -2
  90. package/src/components/ui/alert-dialog/AlertDialog.vue +3 -3
  91. package/src/components/ui/alert-dialog/AlertDialogAction.vue +3 -3
  92. package/src/components/ui/alert-dialog/AlertDialogCancel.vue +3 -3
  93. package/src/components/ui/alert-dialog/AlertDialogContent.vue +7 -7
  94. package/src/components/ui/alert-dialog/AlertDialogDescription.vue +3 -3
  95. package/src/components/ui/alert-dialog/AlertDialogFooter.vue +4 -4
  96. package/src/components/ui/alert-dialog/AlertDialogHeader.vue +4 -4
  97. package/src/components/ui/alert-dialog/AlertDialogTitle.vue +3 -3
  98. package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +3 -3
  99. package/src/components/ui/animated-number/AnimatedNumber.vue +49 -48
  100. package/src/components/ui/avatar/Avatar.vue +28 -28
  101. package/src/components/ui/avatar/AvatarFallback.vue +6 -6
  102. package/src/components/ui/avatar/AvatarGroup.vue +40 -40
  103. package/src/components/ui/avatar/AvatarImage.vue +3 -3
  104. package/src/components/ui/avatar/index.ts +17 -17
  105. package/src/components/ui/badge/Badge.vue +20 -20
  106. package/src/components/ui/breadcrumb/Breadcrumb.vue +4 -4
  107. package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +9 -9
  108. package/src/components/ui/breadcrumb/BreadcrumbItem.vue +4 -4
  109. package/src/components/ui/breadcrumb/BreadcrumbLink.vue +4 -4
  110. package/src/components/ui/breadcrumb/BreadcrumbList.vue +4 -4
  111. package/src/components/ui/breadcrumb/BreadcrumbPage.vue +4 -4
  112. package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +4 -4
  113. package/src/components/ui/busy/Busy.vue +33 -33
  114. package/src/components/ui/button/Button.vue +23 -23
  115. package/src/components/ui/button/index.ts +56 -56
  116. package/src/components/ui/calendar/Calendar.vue +138 -138
  117. package/src/components/ui/calendar/CalendarCell.vue +7 -7
  118. package/src/components/ui/calendar/CalendarCellTrigger.vue +18 -18
  119. package/src/components/ui/calendar/CalendarGrid.vue +3 -3
  120. package/src/components/ui/calendar/CalendarGridBody.vue +3 -3
  121. package/src/components/ui/calendar/CalendarGridHead.vue +3 -3
  122. package/src/components/ui/calendar/CalendarGridRow.vue +3 -3
  123. package/src/components/ui/calendar/CalendarHeadCell.vue +7 -7
  124. package/src/components/ui/calendar/CalendarHeader.vue +7 -7
  125. package/src/components/ui/calendar/CalendarHeading.vue +6 -6
  126. package/src/components/ui/calendar/CalendarNextButton.vue +15 -15
  127. package/src/components/ui/calendar/CalendarPrevButton.vue +15 -15
  128. package/src/components/ui/calendar/index.ts +4 -4
  129. package/src/components/ui/card/Card.vue +20 -20
  130. package/src/components/ui/card/CardAction.vue +8 -8
  131. package/src/components/ui/card/CardContent.vue +4 -4
  132. package/src/components/ui/card/CardDescription.vue +12 -12
  133. package/src/components/ui/card/CardFooter.vue +4 -4
  134. package/src/components/ui/card/CardHeader.vue +8 -8
  135. package/src/components/ui/card/CardText.vue +8 -8
  136. package/src/components/ui/card/CardTitle.vue +8 -8
  137. package/src/components/ui/checkbox/Checkbox.vue +27 -27
  138. package/src/components/ui/collapsible/Collapsible.vue +3 -3
  139. package/src/components/ui/collapsible/CollapsibleContent.vue +3 -3
  140. package/src/components/ui/collapsible/CollapsibleTrigger.vue +3 -3
  141. package/src/components/ui/combobox/Combobox.vue +4 -4
  142. package/src/components/ui/combobox/ComboboxAnchor.vue +16 -16
  143. package/src/components/ui/combobox/ComboboxEmpty.vue +3 -3
  144. package/src/components/ui/combobox/ComboboxGroup.vue +9 -9
  145. package/src/components/ui/combobox/ComboboxInput.vue +6 -6
  146. package/src/components/ui/combobox/ComboboxItem.vue +17 -17
  147. package/src/components/ui/combobox/ComboboxList.vue +19 -19
  148. package/src/components/ui/combobox/ComboboxSelect.vue +77 -77
  149. package/src/components/ui/combobox/ComboboxSeparator.vue +1 -1
  150. package/src/components/ui/combobox/ComboboxTrigger.vue +10 -10
  151. package/src/components/ui/combobox/types.ts +5 -5
  152. package/src/components/ui/command/Command.vue +48 -48
  153. package/src/components/ui/command/CommandDialog.vue +14 -14
  154. package/src/components/ui/command/CommandEmpty.vue +4 -4
  155. package/src/components/ui/command/CommandGroup.vue +23 -23
  156. package/src/components/ui/command/CommandInput.vue +9 -9
  157. package/src/components/ui/command/CommandItem.vue +40 -40
  158. package/src/components/ui/command/CommandList.vue +9 -9
  159. package/src/components/ui/command/CommandSeparator.vue +3 -3
  160. package/src/components/ui/command/CommandShortcut.vue +8 -8
  161. package/src/components/ui/command/index.ts +11 -11
  162. package/src/components/ui/data-table/DataTable.vue +145 -145
  163. package/src/components/ui/data-table/index.ts +11 -11
  164. package/src/components/ui/date-picker/DatePicker.vue +31 -31
  165. package/src/components/ui/date-time-picker/DateTimePicker.vue +97 -97
  166. package/src/components/ui/dialog/Dialog.vue +3 -3
  167. package/src/components/ui/dialog/DialogClose.vue +3 -3
  168. package/src/components/ui/dialog/DialogContent.vue +21 -21
  169. package/src/components/ui/dialog/DialogDescription.vue +7 -7
  170. package/src/components/ui/dialog/DialogFooter.vue +8 -8
  171. package/src/components/ui/dialog/DialogHeader.vue +4 -4
  172. package/src/components/ui/dialog/DialogOverlay.vue +7 -7
  173. package/src/components/ui/dialog/DialogScrollContent.vue +32 -32
  174. package/src/components/ui/dialog/DialogTitle.vue +3 -3
  175. package/src/components/ui/dialog/DialogTrigger.vue +3 -3
  176. package/src/components/ui/dropdown-menu/DropdownMenu.vue +3 -3
  177. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +11 -11
  178. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +15 -15
  179. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +3 -3
  180. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +12 -12
  181. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +9 -9
  182. package/src/components/ui/dropdown-menu/DropdownMenuPortal.vue +3 -3
  183. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +3 -3
  184. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +11 -11
  185. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
  186. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +4 -4
  187. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +3 -3
  188. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +15 -15
  189. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +15 -15
  190. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +3 -3
  191. package/src/components/ui/dropdown-menu/classes.ts +16 -16
  192. package/src/components/ui/field/Field.vue +5 -5
  193. package/src/components/ui/field/FieldContent.vue +8 -8
  194. package/src/components/ui/field/FieldDescription.vue +10 -10
  195. package/src/components/ui/field/FieldError.vue +39 -39
  196. package/src/components/ui/field/FieldGroup.vue +8 -8
  197. package/src/components/ui/field/FieldLabel.vue +10 -10
  198. package/src/components/ui/field/FieldLegend.vue +11 -11
  199. package/src/components/ui/field/FieldSeparator.vue +11 -11
  200. package/src/components/ui/field/FieldSet.vue +9 -9
  201. package/src/components/ui/field/FieldTitle.vue +8 -8
  202. package/src/components/ui/field/field-context.ts +36 -36
  203. package/src/components/ui/field/index.ts +22 -22
  204. package/src/components/ui/file-dropzone/FileDropzone.vue +144 -144
  205. package/src/components/ui/file-dropzone/index.ts +19 -19
  206. package/src/components/ui/hover-card/HoverCard.vue +5 -5
  207. package/src/components/ui/hover-card/HoverCardContent.vue +13 -13
  208. package/src/components/ui/hover-card/HoverCardTrigger.vue +3 -3
  209. package/src/components/ui/input/Input.vue +13 -13
  210. package/src/components/ui/kbd/Kbd.vue +10 -10
  211. package/src/components/ui/kbd/KbdGroup.vue +49 -49
  212. package/src/components/ui/label/Label.vue +7 -7
  213. package/src/components/ui/native-select/NativeSelect.vue +16 -16
  214. package/src/components/ui/native-select/NativeSelectOptGroup.vue +3 -3
  215. package/src/components/ui/native-select/NativeSelectOption.vue +3 -3
  216. package/src/components/ui/number-field/NumberField.vue +88 -88
  217. package/src/components/ui/pagination/Pagination.vue +4 -4
  218. package/src/components/ui/pagination/PaginationContent.vue +4 -4
  219. package/src/components/ui/pagination/PaginationEllipsis.vue +9 -9
  220. package/src/components/ui/pagination/PaginationFirst.vue +8 -8
  221. package/src/components/ui/pagination/PaginationItem.vue +14 -14
  222. package/src/components/ui/pagination/PaginationLast.vue +8 -8
  223. package/src/components/ui/pagination/PaginationNext.vue +8 -8
  224. package/src/components/ui/pagination/PaginationPrevious.vue +8 -8
  225. package/src/components/ui/pagination/SimplePagination.vue +25 -25
  226. package/src/components/ui/pin-input/PinInput.vue +20 -20
  227. package/src/components/ui/pin-input/PinInputInput.vue +17 -17
  228. package/src/components/ui/pin-input/context.ts +1 -1
  229. package/src/components/ui/popover/Popover.vue +3 -3
  230. package/src/components/ui/popover/PopoverAnchor.vue +3 -3
  231. package/src/components/ui/popover/PopoverContent.vue +12 -12
  232. package/src/components/ui/popover/PopoverTrigger.vue +3 -3
  233. package/src/components/ui/progress/Progress.vue +16 -16
  234. package/src/components/ui/radio-group/RadioGroup.vue +7 -7
  235. package/src/components/ui/radio-group/RadioGroupCard.vue +39 -39
  236. package/src/components/ui/radio-group/RadioGroupItem.vue +20 -20
  237. package/src/components/ui/scroll-area/ScrollArea.vue +14 -14
  238. package/src/components/ui/scroll-area/ScrollBar.vue +12 -12
  239. package/src/components/ui/select/Select.vue +3 -3
  240. package/src/components/ui/select/SelectContent.vue +25 -25
  241. package/src/components/ui/select/SelectGroup.vue +3 -3
  242. package/src/components/ui/select/SelectItem.vue +24 -24
  243. package/src/components/ui/select/SelectItemContent.vue +32 -32
  244. package/src/components/ui/select/SelectItemText.vue +3 -3
  245. package/src/components/ui/select/SelectLabel.vue +3 -3
  246. package/src/components/ui/select/SelectScrollDownButton.vue +9 -9
  247. package/src/components/ui/select/SelectScrollUpButton.vue +9 -9
  248. package/src/components/ui/select/SelectSeparator.vue +2 -2
  249. package/src/components/ui/select/SelectTrigger.vue +14 -14
  250. package/src/components/ui/select/SelectValue.vue +3 -3
  251. package/src/components/ui/select/SelectValueContent.vue +39 -39
  252. package/src/components/ui/separator/Separator.vue +7 -7
  253. package/src/components/ui/sheet/Sheet.vue +3 -3
  254. package/src/components/ui/sheet/SheetClose.vue +3 -3
  255. package/src/components/ui/sheet/SheetContent.vue +22 -22
  256. package/src/components/ui/sheet/SheetDescription.vue +3 -3
  257. package/src/components/ui/sheet/SheetFooter.vue +3 -3
  258. package/src/components/ui/sheet/SheetHeader.vue +3 -3
  259. package/src/components/ui/sheet/SheetOverlay.vue +7 -7
  260. package/src/components/ui/sheet/SheetTitle.vue +3 -3
  261. package/src/components/ui/sheet/SheetTrigger.vue +3 -3
  262. package/src/components/ui/sidebar/Sidebar.vue +57 -57
  263. package/src/components/ui/sidebar/SidebarContent.vue +8 -8
  264. package/src/components/ui/sidebar/SidebarFooter.vue +8 -8
  265. package/src/components/ui/sidebar/SidebarGroup.vue +8 -8
  266. package/src/components/ui/sidebar/SidebarGroupAction.vue +9 -9
  267. package/src/components/ui/sidebar/SidebarGroupContent.vue +4 -4
  268. package/src/components/ui/sidebar/SidebarGroupLabel.vue +8 -8
  269. package/src/components/ui/sidebar/SidebarHeader.vue +8 -8
  270. package/src/components/ui/sidebar/SidebarInput.vue +8 -8
  271. package/src/components/ui/sidebar/SidebarInset.vue +9 -9
  272. package/src/components/ui/sidebar/SidebarMenu.vue +4 -4
  273. package/src/components/ui/sidebar/SidebarMenuAction.vue +18 -18
  274. package/src/components/ui/sidebar/SidebarMenuBadge.vue +14 -14
  275. package/src/components/ui/sidebar/SidebarMenuButton.vue +21 -21
  276. package/src/components/ui/sidebar/SidebarMenuButtonChild.vue +10 -10
  277. package/src/components/ui/sidebar/SidebarMenuItem.vue +4 -4
  278. package/src/components/ui/sidebar/SidebarMenuSkeleton.vue +10 -10
  279. package/src/components/ui/sidebar/SidebarMenuSub.vue +9 -9
  280. package/src/components/ui/sidebar/SidebarMenuSubButton.vue +17 -17
  281. package/src/components/ui/sidebar/SidebarMenuSubItem.vue +4 -4
  282. package/src/components/ui/sidebar/SidebarProvider.vue +46 -46
  283. package/src/components/ui/sidebar/SidebarRail.vue +13 -13
  284. package/src/components/ui/sidebar/SidebarSeparator.vue +4 -4
  285. package/src/components/ui/sidebar/SidebarTrigger.vue +5 -5
  286. package/src/components/ui/sidebar/index.ts +29 -29
  287. package/src/components/ui/sidebar/utils.ts +7 -7
  288. package/src/components/ui/skeleton/Skeleton.vue +6 -6
  289. package/src/components/ui/slider/Slider.vue +14 -14
  290. package/src/components/ui/spinner/Spinner.vue +21 -21
  291. package/src/components/ui/stepper/SimpleStepper.vue +26 -26
  292. package/src/components/ui/stepper/Stepper.vue +6 -6
  293. package/src/components/ui/stepper/StepperDescription.vue +5 -5
  294. package/src/components/ui/stepper/StepperIndicator.vue +17 -17
  295. package/src/components/ui/stepper/StepperItem.vue +4 -4
  296. package/src/components/ui/stepper/StepperSeparator.vue +7 -7
  297. package/src/components/ui/stepper/StepperTitle.vue +5 -5
  298. package/src/components/ui/stepper/StepperTrigger.vue +4 -4
  299. package/src/components/ui/stepper/index.ts +3 -3
  300. package/src/components/ui/switch/Switch.vue +15 -15
  301. package/src/components/ui/table/Table.vue +6 -6
  302. package/src/components/ui/table/TableBody.vue +4 -4
  303. package/src/components/ui/table/TableCaption.vue +4 -4
  304. package/src/components/ui/table/TableCell.vue +8 -8
  305. package/src/components/ui/table/TableEmpty.vue +14 -14
  306. package/src/components/ui/table/TableFooter.vue +8 -8
  307. package/src/components/ui/table/TableHead.vue +8 -8
  308. package/src/components/ui/table/TableHeader.vue +4 -4
  309. package/src/components/ui/table/TableRow.vue +8 -8
  310. package/src/components/ui/tabs/Tabs.vue +4 -4
  311. package/src/components/ui/tabs/TabsContent.vue +4 -4
  312. package/src/components/ui/tabs/TabsList.vue +15 -15
  313. package/src/components/ui/tabs/TabsTrigger.vue +10 -10
  314. package/src/components/ui/tags-input/TagsInput.vue +17 -17
  315. package/src/components/ui/tags-input/TagsInputInput.vue +6 -6
  316. package/src/components/ui/tags-input/TagsInputItem.vue +15 -15
  317. package/src/components/ui/tags-input/TagsInputItemDelete.vue +12 -12
  318. package/src/components/ui/tags-input/TagsInputItemText.vue +4 -4
  319. package/src/components/ui/textarea/Textarea.vue +12 -12
  320. package/src/components/ui/time-picker/TimePicker.vue +25 -25
  321. package/src/components/ui/toggle-group/Toggle.vue +10 -10
  322. package/src/components/ui/toggle-group/ToggleGroup.vue +38 -38
  323. package/src/components/ui/toggle-group/ToggleGroupItem.vue +9 -9
  324. package/src/components/ui/toggle-group/index.ts +46 -46
  325. package/src/components/ui/tooltip/Tooltip.vue +3 -3
  326. package/src/components/ui/tooltip/TooltipContent.vue +13 -13
  327. package/src/components/ui/tooltip/TooltipProvider.vue +4 -4
  328. package/src/components/ui/tooltip/TooltipTrigger.vue +3 -3
  329. package/src/i18n/de.json +206 -185
  330. package/src/i18n/en.json +206 -185
  331. package/src/i18n/es.json +206 -185
  332. package/src/i18n/fr.json +206 -185
  333. package/src/i18n/it.json +206 -185
  334. package/src/i18n/ja.json +206 -185
  335. package/src/i18n/pt.json +206 -185
  336. package/src/i18n/ru.json +206 -185
  337. package/src/i18n/zh.json +206 -185
  338. package/src/lib/color-tokens.ts +24 -24
  339. package/src/lib/i18n.ts +65 -65
  340. package/src/lib/utils.ts +33 -33
  341. package/src/nuxt-components.d.ts +2 -2
  342. package/src/runtime/ui-vintage-i18n-plugin.ts +49 -49
  343. package/src/shims-vue.d.ts +3 -3
  344. package/src/styles.css +377 -377
@@ -11,51 +11,51 @@ import { StatStrip } from '../stat-strip';
11
11
 
12
12
  // Props
13
13
  const props = withDefaults(defineProps<{
14
- badges?: Array<{
15
- color: ToneColor;
16
- text: string;
17
- }>;
18
- announcement?: {
19
- text: string;
20
- to: string;
21
- label?: string;
22
- } | null;
23
- title: string;
24
- highlight?: string;
25
- description: string;
26
- stats?: Array<{
27
- label: string;
28
- value: string | number;
29
- icon?: HugeiconsIconDefinition;
30
- }>;
31
- align?: 'left' | 'center' | 'right';
14
+ badges?: Array<{
15
+ color: ToneColor;
16
+ text: string;
17
+ }>;
18
+ announcement?: {
19
+ text: string;
20
+ to: string;
21
+ label?: string;
22
+ } | null;
23
+ title: string;
24
+ highlight?: string;
25
+ description: string;
26
+ stats?: Array<{
27
+ label: string;
28
+ value: string | number;
29
+ icon?: HugeiconsIconDefinition;
30
+ }>;
31
+ align?: 'left' | 'center' | 'right';
32
32
  }>(), {
33
- badges: () => [],
34
- announcement: null,
35
- highlight: '',
36
- stats: () => [],
37
- align: 'center'
33
+ badges: () => [],
34
+ announcement: null,
35
+ highlight: '',
36
+ stats: () => [],
37
+ align: 'center'
38
38
  });
39
39
 
40
40
  const { t } = useI18n();
41
41
 
42
42
  // Alignment of the whole header: the column, its text, and the rows of badges and stats
43
43
  const alignClasses = {
44
- left: { header: 'items-start text-left', row: 'justify-start' },
45
- center: { header: 'items-center text-center', row: 'justify-center' },
46
- right: { header: 'items-end text-right', row: 'justify-end' }
44
+ left: { header: 'items-start text-left', row: 'justify-start' },
45
+ center: { header: 'items-center text-center', row: 'justify-center' },
46
+ right: { header: 'items-end text-right', row: 'justify-end' }
47
47
  } as const;
48
48
  const alignClass = computed(() => alignClasses[props.align] ?? alignClasses.center);
49
49
 
50
50
  // Split the title around the highlighted part (when it appears in the title)
51
51
  const titleParts = computed(() => {
52
- const index = props.highlight ? props.title.indexOf(props.highlight) : -1;
53
- if (index === -1) return { before: props.title, highlight: '', after: '' };
54
- return {
55
- before: props.title.slice(0, index),
56
- highlight: props.highlight,
57
- after: props.title.slice(index + props.highlight.length)
58
- };
52
+ const index = props.highlight ? props.title.indexOf(props.highlight) : -1;
53
+ if (index === -1) return { before: props.title, highlight: '', after: '' };
54
+ return {
55
+ before: props.title.slice(0, index),
56
+ highlight: props.highlight,
57
+ after: props.title.slice(index + props.highlight.length)
58
+ };
59
59
  });
60
60
 
61
61
  // Words that rise in one by one (the highlight rises in as a single block between them)
@@ -64,8 +64,8 @@ const beforeWords = computed(() => splitWords(titleParts.value.before));
64
64
  const afterWords = computed(() => splitWords(titleParts.value.after));
65
65
  // Space after a word before the highlight: always between words, and before the highlight only when the title has one there
66
66
  const wordGap = (index: number) => {
67
- if (index < beforeWords.value.length - 1) return ' ';
68
- return titleParts.value.highlight && /\s$/.test(titleParts.value.before) ? ' ' : '';
67
+ if (index < beforeWords.value.length - 1) return ' ';
68
+ return titleParts.value.highlight && /\s$/.test(titleParts.value.before) ? ' ' : '';
69
69
  };
70
70
  const wordDelay = (index: number) => `${120 + Math.min(index, 12) * 55}ms`;
71
71
  const highlightDelay = computed(() => wordDelay(beforeWords.value.length));
@@ -79,52 +79,52 @@ const underlineDelay = computed(() => `${beforeWords.value.length * 55 + 600}ms`
79
79
  </script>
80
80
 
81
81
  <template>
82
- <header :class="cn('relative flex flex-col gap-6 pt-6 md:pt-12', alignClass.header)">
83
- <!-- Announcement (links to something new, such as the latest post) -->
84
- <NuxtLink v-if="props.announcement" :to="props.announcement.to" class="group/announce max-w-full animate-uv-fade-up rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
85
- <span class="inline-flex max-w-full items-center gap-2 rounded border border-warning/35 bg-warning/10 px-3 py-1 text-xs text-foreground transition-colors duration-150 group-hover/announce:border-warning/70">
86
- <span aria-hidden="true" class="relative flex size-1.5 shrink-0 text-warning">
87
- <span class="absolute inline-flex size-full animate-uv-ping-soft rounded-full bg-current opacity-60" />
88
- <span class="relative inline-flex size-1.5 rounded-full bg-current" />
89
- </span>
90
- <span class="shrink-0 font-semibold">{{ props.announcement.label || t('uiVintage.blog.new') }}</span>
91
- <span class="truncate opacity-80">{{ props.announcement.text }}</span>
92
- <HugeiconsIcon :icon="ArrowRight01Icon" class="size-3.5 shrink-0 transition-transform duration-300 ease-spring group-hover/announce:translate-x-0.5" />
93
- </span>
94
- </NuxtLink>
82
+ <header :class="cn('relative flex flex-col gap-6 pt-6 md:pt-12', alignClass.header)">
83
+ <!-- Announcement (links to something new, such as the latest post) -->
84
+ <NuxtLink v-if="props.announcement" :to="props.announcement.to" class="group/announce max-w-full animate-uv-fade-up rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
85
+ <span class="inline-flex max-w-full items-center gap-2 rounded border border-warning/35 bg-warning/10 px-3 py-1 text-xs text-foreground transition-colors duration-150 group-hover/announce:border-warning/70">
86
+ <span aria-hidden="true" class="relative flex size-1.5 shrink-0 text-warning">
87
+ <span class="absolute inline-flex size-full animate-uv-ping-soft rounded-full bg-current opacity-60" />
88
+ <span class="relative inline-flex size-1.5 rounded-full bg-current" />
89
+ </span>
90
+ <span class="shrink-0 font-semibold">{{ props.announcement.label || t('uiVintage.blog.new') }}</span>
91
+ <span class="truncate opacity-80">{{ props.announcement.text }}</span>
92
+ <HugeiconsIcon :icon="ArrowRight01Icon" class="size-3.5 shrink-0 transition-transform duration-300 ease-spring group-hover/announce:translate-x-0.5" />
93
+ </span>
94
+ </NuxtLink>
95
95
 
96
- <!-- Badges -->
97
- <div v-if="props.badges.length > 0" :class="cn('flex flex-wrap gap-2', alignClass.row)">
98
- <Badge v-for="(badge, index) in props.badges"
99
- :key="`${badge.color}-${badge.text}`"
100
- :color="badge.color"
101
- :text="badge.text"
102
- :pulse="index === 0 && !props.announcement"
103
- class="animate-uv-fade-up"
104
- :style="{ animationDelay: `${index * 70}ms` }" />
105
- </div>
96
+ <!-- Badges -->
97
+ <div v-if="props.badges.length > 0" :class="cn('flex flex-wrap gap-2', alignClass.row)">
98
+ <Badge v-for="(badge, index) in props.badges"
99
+ :key="`${badge.color}-${badge.text}`"
100
+ :color="badge.color"
101
+ :text="badge.text"
102
+ :pulse="index === 0 && !props.announcement"
103
+ class="animate-uv-fade-up"
104
+ :style="{ animationDelay: `${index * 70}ms` }" />
105
+ </div>
106
106
 
107
- <!-- Title (each word rises in with a spring, the highlight gets a hand-drawn underline) -->
108
- <h1 class="max-w-4xl text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl md:text-6xl">
109
- <template v-for="(word, index) in beforeWords" :key="`before-${word}-${index}`">
110
- <span class="inline-block animate-uv-word-in" :style="{ animationDelay: wordDelay(index) }">{{ word }}</span>{{ wordGap(index) }}
111
- </template>
112
- <!-- The text and the underline sit on one line so no stray space ends up inside the highlight -->
113
- <span v-if="titleParts.highlight" class="relative inline-block animate-uv-word-in text-primary" :style="{ animationDelay: highlightDelay }">{{ titleParts.highlight }}<svg class="absolute -bottom-2 left-0 h-3 w-full text-primary/60" viewBox="0 0 200 12" fill="none" preserveAspectRatio="none" aria-hidden="true">
114
- <path d="M2 9C40 3 80 2 198 7" stroke="currentColor" stroke-width="4" stroke-linecap="round" pathLength="1" class="animate-uv-draw [stroke-dasharray:1]" :style="{ animationDelay: underlineDelay }" />
115
- </svg>
116
- </span>
117
- <template v-for="(word, index) in afterWords" :key="`after-${word}-${index}`">
118
- {{ index === 0 && !/^\s/.test(titleParts.after) ? '' : ' ' }}<span class="inline-block animate-uv-word-in" :style="{ animationDelay: afterDelay(index) }">{{ word }}</span>
119
- </template>
120
- </h1>
107
+ <!-- Title (each word rises in with a spring, the highlight gets a hand-drawn underline) -->
108
+ <h1 class="max-w-4xl text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl md:text-6xl">
109
+ <template v-for="(word, index) in beforeWords" :key="`before-${word}-${index}`">
110
+ <span class="inline-block animate-uv-word-in" :style="{ animationDelay: wordDelay(index) }">{{ word }}</span>{{ wordGap(index) }}
111
+ </template>
112
+ <!-- The text and the underline sit on one line so no stray space ends up inside the highlight -->
113
+ <span v-if="titleParts.highlight" class="relative inline-block animate-uv-word-in text-primary" :style="{ animationDelay: highlightDelay }">{{ titleParts.highlight }}<svg class="absolute -bottom-2 left-0 h-3 w-full text-primary/60" viewBox="0 0 200 12" fill="none" preserveAspectRatio="none" aria-hidden="true">
114
+ <path d="M2 9C40 3 80 2 198 7" stroke="currentColor" stroke-width="4" stroke-linecap="round" pathLength="1" class="animate-uv-draw [stroke-dasharray:1]" :style="{ animationDelay: underlineDelay }" />
115
+ </svg>
116
+ </span>
117
+ <template v-for="(word, index) in afterWords" :key="`after-${word}-${index}`">
118
+ {{ index === 0 && !/^\s/.test(titleParts.after) ? '' : ' ' }}<span class="inline-block animate-uv-word-in" :style="{ animationDelay: afterDelay(index) }">{{ word }}</span>
119
+ </template>
120
+ </h1>
121
121
 
122
- <!-- Description -->
123
- <p class="max-w-2xl animate-uv-fade-up text-sm leading-7 text-muted-foreground sm:text-base" :style="{ animationDelay: descriptionDelay }">
124
- {{ props.description }}
125
- </p>
122
+ <!-- Description -->
123
+ <p class="max-w-2xl animate-uv-fade-up text-sm leading-7 text-muted-foreground sm:text-base" :style="{ animationDelay: descriptionDelay }">
124
+ {{ props.description }}
125
+ </p>
126
126
 
127
- <!-- Stats strip -->
128
- <StatStrip :items="props.stats" :class="cn('animate-uv-fade-up', alignClass.row)" :style="{ animationDelay: statsDelay }" />
129
- </header>
130
- </template>
127
+ <!-- Stats strip -->
128
+ <StatStrip :items="props.stats" :class="cn('animate-uv-fade-up', alignClass.row)" :style="{ animationDelay: statsDelay }" />
129
+ </header>
130
+ </template>
@@ -0,0 +1,118 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+ import FolderLibraryIcon from '@hugeicons/core-free-icons/FolderLibraryIcon';
5
+ import News01Icon from '@hugeicons/core-free-icons/News01Icon';
6
+ import Tag01Icon from '@hugeicons/core-free-icons/Tag01Icon';
7
+ import type { BlogCategoryData, BlogTagData } from './blogPageUtils';
8
+ import AllPostsSection from './AllPostsSection.vue';
9
+ import BlogHeaderSection from './BlogHeaderSection.vue';
10
+ import BlogSectionTitle from './BlogSectionTitle.vue';
11
+ import BlogTagsSection from './BlogTagsSection.vue';
12
+ import BlogViewAllLink from './BlogViewAllLink.vue';
13
+ import CategoriesSection from './CategoriesSection.vue';
14
+ import HeaderCarousel from './HeaderCarousel.vue';
15
+
16
+ // Post shown in the carousel and in the list (paths already localized)
17
+ type BlogListPost = {
18
+ path: string;
19
+ title: string;
20
+ description?: string;
21
+ image?: string;
22
+ categoryText?: string;
23
+ categoryPath?: string;
24
+ };
25
+
26
+ // Props
27
+ const props = withDefaults(defineProps<{
28
+ title: string;
29
+ highlight?: string;
30
+ description: string;
31
+ featuredPosts?: BlogListPost[];
32
+ categories?: BlogCategoryData[];
33
+ tags?: BlogTagData[];
34
+ posts?: BlogListPost[];
35
+ totalPosts?: number;
36
+ currentPage?: number;
37
+ postsPerPage?: number;
38
+ hasMorePosts?: boolean;
39
+ isLoading?: boolean;
40
+ }>(), {
41
+ highlight: '',
42
+ featuredPosts: () => [],
43
+ categories: () => [],
44
+ tags: () => [],
45
+ posts: () => [],
46
+ totalPosts: 0,
47
+ currentPage: 1,
48
+ postsPerPage: 9,
49
+ hasMorePosts: false,
50
+ isLoading: false
51
+ });
52
+
53
+ // Emits
54
+ const emit = defineEmits<{
55
+ 'load-more': [];
56
+ }>();
57
+
58
+ const { t } = useI18n();
59
+
60
+ // Announce the latest post in the header
61
+ const headerAnnouncement = computed(() => props.featuredPosts[0] ? { text: props.featuredPosts[0].title, to: props.featuredPosts[0].path } : null);
62
+
63
+ // Blog totals shown under the header
64
+ const headerStats = computed(() => [
65
+ { icon: News01Icon, value: props.totalPosts, label: t('uiVintage.blog.stats.posts', props.totalPosts) },
66
+ { icon: FolderLibraryIcon, value: props.categories.length, label: t('uiVintage.blog.stats.categories', props.categories.length) },
67
+ { icon: Tag01Icon, value: props.tags.length, label: t('uiVintage.blog.stats.tags', props.tags.length) }
68
+ ]);
69
+ </script>
70
+
71
+ <template>
72
+ <div class="space-y-16 md:space-y-24">
73
+ <!-- Header section -->
74
+ <BlogHeaderSection :announcement="headerAnnouncement"
75
+ :title="props.title"
76
+ :highlight="props.highlight"
77
+ :description="props.description"
78
+ :stats="headerStats" />
79
+
80
+ <!-- Header carousel -->
81
+ <section>
82
+ <BlogSectionTitle :title="t('uiVintage.blog.latestPosts')" :description="t('uiVintage.blog.latestPostsDescription')" />
83
+ <HeaderCarousel :featured-posts="props.featuredPosts" />
84
+ </section>
85
+
86
+ <!-- Categories section -->
87
+ <section>
88
+ <BlogSectionTitle :title="t('uiVintage.blog.categories')" :description="t('uiVintage.blog.categoriesDescription')">
89
+ <template #action>
90
+ <BlogViewAllLink to="/blog/categories" :label="t('uiVintage.blog.viewAll')" />
91
+ </template>
92
+ </BlogSectionTitle>
93
+ <CategoriesSection :categories="props.categories" />
94
+ </section>
95
+
96
+ <!-- Tags section -->
97
+ <section v-if="props.tags.length">
98
+ <BlogSectionTitle :title="t('uiVintage.blog.tags')" :description="t('uiVintage.blog.tagsDescription')">
99
+ <template #action>
100
+ <BlogViewAllLink to="/blog/tags" :label="t('uiVintage.blog.viewAll')" />
101
+ </template>
102
+ </BlogSectionTitle>
103
+ <BlogTagsSection :tags="props.tags" />
104
+ </section>
105
+
106
+ <!-- All posts section -->
107
+ <section>
108
+ <BlogSectionTitle :title="t('uiVintage.blog.allPosts')" :description="t('uiVintage.blog.allPostsDescription')" />
109
+ <AllPostsSection :posts="props.posts"
110
+ :current-page="props.currentPage"
111
+ :posts-per-page="props.postsPerPage"
112
+ :total-posts="props.totalPosts"
113
+ :has-more-posts="props.hasMorePosts"
114
+ :is-loading="props.isLoading"
115
+ @load-more="emit('load-more')" />
116
+ </section>
117
+ </div>
118
+ </template>
@@ -11,15 +11,15 @@ import { formatDateLongItalyTimezone } from '../../lib/utils';
11
11
 
12
12
  // Props
13
13
  const props = withDefaults(defineProps<{
14
- author: string;
15
- authorUrl: string;
16
- authorImageUrl: string;
17
- authorBio?: string;
18
- datePublished: string;
19
- dateModified?: string | null;
14
+ author: string;
15
+ authorUrl: string;
16
+ authorImageUrl: string;
17
+ authorBio?: string;
18
+ datePublished: string;
19
+ dateModified?: string | null;
20
20
  }>(), {
21
- authorBio: '',
22
- dateModified: null
21
+ authorBio: '',
22
+ dateModified: null
23
23
  });
24
24
 
25
25
  const { t, locale } = useI18n();
@@ -33,46 +33,46 @@ const showModifiedDate = computed(() => Boolean(props.dateModified) && formatted
33
33
  </script>
34
34
 
35
35
  <template>
36
- <!-- Author and date information -->
37
- <Card data-aos="blur-up" class="gap-0! overflow-hidden p-0!">
38
- <!-- Author -->
39
- <div :class="['flex gap-3 px-5 py-4', props.authorBio ? 'items-start' : 'items-center']">
40
- <!-- Author image -->
41
- <NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="shrink-0 rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
42
- <NuxtImg :src="props.authorImageUrl" :alt="props.author" height="44" width="44" format="avif" quality="40" :sizes="{ 480: '44px', 1280: '44px' }" loading="lazy" decoding="async" class="size-11 rounded border border-border object-cover" />
43
- </NuxtLink>
36
+ <!-- Author and date information -->
37
+ <Card data-aos="blur-up" class="gap-0! overflow-hidden p-0!">
38
+ <!-- Author -->
39
+ <div :class="['flex gap-3 px-5 py-4', props.authorBio ? 'items-start' : 'items-center']">
40
+ <!-- Author image -->
41
+ <NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="shrink-0 rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
42
+ <NuxtImg :src="props.authorImageUrl" :alt="props.author" height="44" width="44" format="avif" quality="40" :sizes="{ 480: '44px', 1280: '44px' }" loading="lazy" decoding="async" class="size-11 rounded border border-border object-cover" />
43
+ </NuxtLink>
44
44
 
45
- <!-- Author name and bio -->
46
- <div class="min-w-0 flex-1">
47
- <p class="text-[11px] font-semibold tracking-wider text-muted-foreground uppercase">{{ t('uiVintage.blog.author') }}</p>
48
- <NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="group/link inline-flex items-center gap-1 text-sm font-semibold text-foreground outline-none transition-colors hover:text-primary focus-visible:underline md:text-base">
49
- {{ props.author }}
50
- <HugeiconsIcon :icon="ArrowUpRight01Icon" class="size-4 text-muted-foreground transition-[translate,color] duration-300 ease-spring group-hover/link:translate-x-0.5 group-hover/link:-translate-y-0.5 group-hover/link:text-primary" />
51
- </NuxtLink>
52
- <p v-if="props.authorBio" class="mt-1.5 text-xs leading-relaxed text-muted-foreground md:text-sm">
53
- {{ props.authorBio }}
54
- </p>
55
- </div>
56
- </div>
45
+ <!-- Author name and bio -->
46
+ <div class="min-w-0 flex-1">
47
+ <p class="text-[11px] font-semibold tracking-wider text-muted-foreground uppercase">{{ t('uiVintage.blog.author') }}</p>
48
+ <NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="group/link inline-flex items-center gap-1 text-sm font-semibold text-foreground outline-none transition-colors hover:text-primary focus-visible:underline md:text-base">
49
+ {{ props.author }}
50
+ <HugeiconsIcon :icon="ArrowUpRight01Icon" class="size-4 text-muted-foreground transition-[translate,color] duration-300 ease-spring group-hover/link:translate-x-0.5 group-hover/link:-translate-y-0.5 group-hover/link:text-primary" />
51
+ </NuxtLink>
52
+ <p v-if="props.authorBio" class="mt-1.5 text-xs leading-relaxed text-muted-foreground md:text-sm">
53
+ {{ props.authorBio }}
54
+ </p>
55
+ </div>
56
+ </div>
57
57
 
58
- <!-- Date information -->
59
- <div v-if="props.datePublished || showModifiedDate" class="flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-border bg-surface px-5 py-2.5 text-xs text-muted-foreground">
60
- <!-- Published date -->
61
- <span v-if="props.datePublished" class="inline-flex items-center gap-2">
62
- <HugeiconsIcon :icon="Calendar03Icon" class="size-3.5 shrink-0 text-primary" />
63
- {{ t('uiVintage.blog.publishedDate') }}
64
- <time :datetime="props.datePublished" class="text-foreground">{{ formattedPublishedDate }}</time>
65
- </span>
58
+ <!-- Date information -->
59
+ <div v-if="props.datePublished || showModifiedDate" class="flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-border bg-surface px-5 py-2.5 text-xs text-muted-foreground">
60
+ <!-- Published date -->
61
+ <span v-if="props.datePublished" class="inline-flex items-center gap-2">
62
+ <HugeiconsIcon :icon="Calendar03Icon" class="size-3.5 shrink-0 text-primary" />
63
+ {{ t('uiVintage.blog.publishedDate') }}
64
+ <time :datetime="props.datePublished" class="text-foreground">{{ formattedPublishedDate }}</time>
65
+ </span>
66
66
 
67
- <!-- Divider between the dates (hidden when they wrap on small screens) -->
68
- <span v-if="props.datePublished && showModifiedDate" aria-hidden="true" class="hidden h-3.5 w-px bg-border sm:block" />
67
+ <!-- Divider between the dates (hidden when they wrap on small screens) -->
68
+ <span v-if="props.datePublished && showModifiedDate" aria-hidden="true" class="hidden h-3.5 w-px bg-border sm:block" />
69
69
 
70
- <!-- Last updated date -->
71
- <span v-if="showModifiedDate" class="inline-flex items-center gap-2">
72
- <HugeiconsIcon :icon="RefreshIcon" class="size-3.5 shrink-0 text-primary" />
73
- {{ t('uiVintage.blog.lastUpdatedDate') }}
74
- <time :datetime="props.dateModified ?? undefined" class="text-foreground">{{ formattedModifiedDate }}</time>
75
- </span>
76
- </div>
77
- </Card>
78
- </template>
70
+ <!-- Last updated date -->
71
+ <span v-if="showModifiedDate" class="inline-flex items-center gap-2">
72
+ <HugeiconsIcon :icon="RefreshIcon" class="size-3.5 shrink-0 text-primary" />
73
+ {{ t('uiVintage.blog.lastUpdatedDate') }}
74
+ <time :datetime="props.dateModified ?? undefined" class="text-foreground">{{ formattedModifiedDate }}</time>
75
+ </span>
76
+ </div>
77
+ </Card>
78
+ </template>
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '../ui/breadcrumb';
5
+
6
+ // Props (the trail always starts with Home and Blog, the last item is the current page)
7
+ const props = defineProps<{
8
+ items: Array<{
9
+ label: string;
10
+ to?: string;
11
+ }>;
12
+ }>();
13
+
14
+ const { t } = useI18n();
15
+
16
+ // Full trail of the page
17
+ const trail = computed(() => [
18
+ { label: t('uiVintage.blog.breadcrumbHome'), to: '/' },
19
+ { label: t('uiVintage.blog.breadcrumbBlog'), to: '/blog' },
20
+ ...props.items
21
+ ]);
22
+ </script>
23
+
24
+ <template>
25
+ <!-- Breadcrumbs of a blog listing page (the paths are localized) -->
26
+ <Breadcrumb class="mb-2 flex animate-uv-fade-up justify-center">
27
+ <BreadcrumbList>
28
+ <template v-for="(item, index) in trail" :key="item.label">
29
+ <BreadcrumbSeparator v-if="index > 0" />
30
+ <BreadcrumbItem>
31
+ <BreadcrumbLink v-if="item.to && index < trail.length - 1" as-child>
32
+ <NuxtLinkLocale :to="item.to">
33
+ {{ item.label }}
34
+ </NuxtLinkLocale>
35
+ </BreadcrumbLink>
36
+ <BreadcrumbPage v-else>
37
+ {{ item.label }}
38
+ </BreadcrumbPage>
39
+ </BreadcrumbItem>
40
+ </template>
41
+ </BreadcrumbList>
42
+ </Breadcrumb>
43
+ </template>