@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
@@ -8,36 +8,36 @@ import { formatDateLongItalyTimezone, getReadingMinutes } from '../../lib/utils'
8
8
 
9
9
  // Props
10
10
  const props = withDefaults(defineProps<{
11
- title: string;
12
- description?: string;
13
- image?: string;
14
- author?: string;
15
- authorImageUrl?: string;
16
- datePublished?: string;
17
- categoryText?: string;
18
- categorySlug?: string;
19
- tags?: Array<{
20
- name: string;
21
- slug: string;
22
- }>;
23
- body?: unknown;
24
- readingMinutes?: number | null;
25
- blogPath?: string;
26
- blogLabel?: string;
27
- animated?: boolean;
11
+ title: string;
12
+ description?: string;
13
+ image?: string;
14
+ author?: string;
15
+ authorImageUrl?: string;
16
+ datePublished?: string;
17
+ categoryText?: string;
18
+ categorySlug?: string;
19
+ tags?: Array<{
20
+ name: string;
21
+ slug: string;
22
+ }>;
23
+ body?: unknown;
24
+ readingMinutes?: number | null;
25
+ blogPath?: string;
26
+ blogLabel?: string;
27
+ animated?: boolean;
28
28
  }>(), {
29
- description: '',
30
- image: '',
31
- author: '',
32
- authorImageUrl: '',
33
- datePublished: '',
34
- categoryText: '',
35
- categorySlug: '',
36
- tags: () => [],
37
- readingMinutes: null,
38
- blogPath: '/blog',
39
- blogLabel: '',
40
- animated: true
29
+ description: '',
30
+ image: '',
31
+ author: '',
32
+ authorImageUrl: '',
33
+ datePublished: '',
34
+ categoryText: '',
35
+ categorySlug: '',
36
+ tags: () => [],
37
+ readingMinutes: null,
38
+ blogPath: '/blog',
39
+ blogLabel: '',
40
+ animated: true
41
41
  });
42
42
 
43
43
  const { t, locale } = useI18n();
@@ -50,8 +50,8 @@ const formattedDate = computed(() => formatDateLongItalyTimezone(props.datePubli
50
50
 
51
51
  // Explicit reading time wins, otherwise estimate it from the body
52
52
  const resolvedReadingMinutes = computed(() => {
53
- if (props.readingMinutes) return props.readingMinutes;
54
- return props.body ? getReadingMinutes(props.body) : null;
53
+ if (props.readingMinutes) return props.readingMinutes;
54
+ return props.body ? getReadingMinutes(props.body) : null;
55
55
  });
56
56
 
57
57
  // Links built from the blog base path
@@ -75,107 +75,107 @@ const detailLabelClasses = 'text-[11px] font-semibold tracking-wider text-muted-
75
75
  </script>
76
76
 
77
77
  <template>
78
- <header class="relative isolate mb-10 md:mb-14">
79
- <!-- Breadcrumbs -->
80
- <Breadcrumb :class="enter('animate-uv-fade-up')">
81
- <BreadcrumbList>
82
- <BreadcrumbItem>
83
- <BreadcrumbLink as-child>
84
- <NuxtLinkLocale :to="props.blogPath">
85
- {{ props.blogLabel || t('uiVintage.blog.breadcrumbBlog') }}
86
- </NuxtLinkLocale>
87
- </BreadcrumbLink>
88
- </BreadcrumbItem>
89
- <BreadcrumbSeparator />
90
- <template v-if="hasCategory">
91
- <BreadcrumbItem>
92
- <BreadcrumbLink as-child>
93
- <NuxtLinkLocale :to="categoryPath">
94
- {{ props.categoryText }}
95
- </NuxtLinkLocale>
96
- </BreadcrumbLink>
97
- </BreadcrumbItem>
98
- <BreadcrumbSeparator />
99
- </template>
100
- <BreadcrumbItem class="min-w-0">
101
- <BreadcrumbPage class="truncate">
102
- {{ props.title }}
103
- </BreadcrumbPage>
104
- </BreadcrumbItem>
105
- </BreadcrumbList>
106
- </Breadcrumb>
107
-
108
- <!-- Title and lede, closed by a rule with a primary segment -->
109
- <div class="relative mt-8 border-b border-border pb-8">
110
- <span aria-hidden="true" :class="['absolute -bottom-px left-0 h-0.5 w-20 origin-left bg-primary', enter('animate-uv-grow-x')]" :style="props.animated ? { animationDelay: '500ms' } : undefined" />
111
-
112
- <!-- Title (each word rises in) -->
113
- <h1 class="text-3xl leading-[1.08] font-semibold tracking-tight text-foreground sm:text-4xl md:text-5xl">
114
- <template v-for="(word, index) in titleWords" :key="`${word}-${index}`">
115
- <span :class="['inline-block', enter('animate-uv-word-in')]" :style="props.animated ? { animationDelay: `${120 + Math.min(index, 12) * 50}ms` } : undefined">{{ word }}</span>{{ index < titleWords.length - 1 ? ' ' : '' }}
116
- </template>
117
- </h1>
118
-
119
- <!-- Lede -->
120
- <p v-if="props.description" :class="['mt-5 max-w-3xl text-sm leading-relaxed text-muted-foreground md:text-lg', enter('animate-uv-fade-up [animation-delay:450ms]')]">
121
- {{ props.description }}
122
- </p>
123
- </div>
124
-
125
- <!-- Labelled details (columns without data are left out; the meta slot adds more) -->
126
- <div v-if="hasDetails || $slots.meta" :class="['overflow-hidden border-b border-border', enter('animate-uv-fade-up [animation-delay:550ms]')]">
127
- <dl class="-mt-px -ml-[calc(1rem+1px)] grid grid-cols-2 md:grid-flow-col md:auto-cols-fr md:grid-cols-none">
128
- <!-- Author -->
129
- <div v-if="props.author" :class="detailCellClasses">
130
- <dt :class="detailLabelClasses">{{ t('uiVintage.blog.author') }}</dt>
131
- <dd class="mt-2 flex min-w-0 items-center gap-2 text-sm font-semibold text-foreground">
132
- <NuxtImg v-if="props.authorImageUrl" :src="props.authorImageUrl" :alt="props.author" width="24" height="24" format="avif" quality="40" class="size-6 shrink-0 rounded border border-border object-cover" />
133
- <span class="truncate">{{ props.author }}</span>
134
- </dd>
135
- </div>
136
-
137
- <!-- Publication date -->
138
- <div v-if="formattedDate" :class="detailCellClasses">
139
- <dt :class="detailLabelClasses">{{ t('uiVintage.blog.publishedDate') }}</dt>
140
- <dd class="mt-2 text-sm text-foreground">
141
- <time :datetime="props.datePublished">{{ formattedDate }}</time>
142
- </dd>
143
- </div>
144
-
145
- <!-- Reading time -->
146
- <div v-if="resolvedReadingMinutes" :class="detailCellClasses">
147
- <dt :class="detailLabelClasses">{{ t('uiVintage.blog.readingTime') }}</dt>
148
- <dd class="mt-2 text-sm text-foreground">{{ t('uiVintage.blog.readingTimeValue', { minutes: resolvedReadingMinutes }) }}</dd>
149
- </div>
150
-
151
- <!-- Category -->
152
- <div v-if="hasCategory" :class="detailCellClasses">
153
- <dt :class="detailLabelClasses">{{ t('uiVintage.blog.category') }}</dt>
154
- <dd class="mt-1.5">
155
- <NuxtLinkLocale :to="categoryPath" :class="['inline-flex', badgeLinkClasses]">
156
- <Badge color="yellow" :text="props.categoryText" pulse />
157
- </NuxtLinkLocale>
158
- </dd>
159
- </div>
160
-
161
- <!-- Extra columns from the app (use the same cell structure: a div with a dt and a dd) -->
162
- <slot name="meta" :cell-classes="detailCellClasses" :label-classes="detailLabelClasses" />
163
- </dl>
164
- </div>
165
-
166
- <!-- Tags -->
167
- <div v-if="props.tags.length > 0" :class="['mt-4 flex flex-wrap items-center gap-2', enter('animate-uv-fade-up [animation-delay:600ms]')]">
168
- <NuxtLinkLocale v-for="tag in props.tags" :key="tag.slug" :to="tagPath(tag.slug)" :class="badgeLinkClasses">
169
- <Badge :text="`#${tag.name}`" />
170
- </NuxtLinkLocale>
171
- </div>
172
-
173
- <!-- Featured image (replaceable through the media slot) -->
174
- <slot name="media">
175
- <div v-if="props.image" :class="['relative mt-8 aspect-video w-full overflow-hidden rounded border border-border shadow-elevated-xl', enter('animate-uv-fade-up [animation-delay:650ms]')]">
176
- <NuxtImg :src="props.image" :alt="props.title" format="avif" quality="50" :sizes="{ 480: '480px', 1536: '896px' }" loading="eager" fetchpriority="high" preload :class="['size-full object-cover', enter('animate-uv-ken-burns')]" />
177
- <div aria-hidden="true" class="pointer-events-none absolute inset-0 bg-linear-to-t from-black/20 to-transparent" />
178
- </div>
179
- </slot>
180
- </header>
181
- </template>
78
+ <header class="relative isolate mb-10 md:mb-14">
79
+ <!-- Breadcrumbs -->
80
+ <Breadcrumb :class="enter('animate-uv-fade-up')">
81
+ <BreadcrumbList>
82
+ <BreadcrumbItem>
83
+ <BreadcrumbLink as-child>
84
+ <NuxtLinkLocale :to="props.blogPath">
85
+ {{ props.blogLabel || t('uiVintage.blog.breadcrumbBlog') }}
86
+ </NuxtLinkLocale>
87
+ </BreadcrumbLink>
88
+ </BreadcrumbItem>
89
+ <BreadcrumbSeparator />
90
+ <template v-if="hasCategory">
91
+ <BreadcrumbItem>
92
+ <BreadcrumbLink as-child>
93
+ <NuxtLinkLocale :to="categoryPath">
94
+ {{ props.categoryText }}
95
+ </NuxtLinkLocale>
96
+ </BreadcrumbLink>
97
+ </BreadcrumbItem>
98
+ <BreadcrumbSeparator />
99
+ </template>
100
+ <BreadcrumbItem class="min-w-0">
101
+ <BreadcrumbPage class="truncate">
102
+ {{ props.title }}
103
+ </BreadcrumbPage>
104
+ </BreadcrumbItem>
105
+ </BreadcrumbList>
106
+ </Breadcrumb>
107
+
108
+ <!-- Title and lede, closed by a rule with a primary segment -->
109
+ <div class="relative mt-8 border-b border-border pb-8">
110
+ <span aria-hidden="true" :class="['absolute -bottom-px left-0 h-0.5 w-20 origin-left bg-primary', enter('animate-uv-grow-x')]" :style="props.animated ? { animationDelay: '500ms' } : undefined" />
111
+
112
+ <!-- Title (each word rises in) -->
113
+ <h1 class="text-3xl leading-[1.08] font-semibold tracking-tight text-foreground sm:text-4xl md:text-5xl">
114
+ <template v-for="(word, index) in titleWords" :key="`${word}-${index}`">
115
+ <span :class="['inline-block', enter('animate-uv-word-in')]" :style="props.animated ? { animationDelay: `${120 + Math.min(index, 12) * 50}ms` } : undefined">{{ word }}</span>{{ index < titleWords.length - 1 ? ' ' : '' }}
116
+ </template>
117
+ </h1>
118
+
119
+ <!-- Lede -->
120
+ <p v-if="props.description" :class="['mt-5 max-w-3xl text-sm leading-relaxed text-muted-foreground md:text-lg', enter('animate-uv-fade-up [animation-delay:450ms]')]">
121
+ {{ props.description }}
122
+ </p>
123
+ </div>
124
+
125
+ <!-- Labelled details (columns without data are left out; the meta slot adds more) -->
126
+ <div v-if="hasDetails || $slots.meta" :class="['overflow-hidden border-b border-border', enter('animate-uv-fade-up [animation-delay:550ms]')]">
127
+ <dl class="-mt-px -ml-[calc(1rem+1px)] grid grid-cols-2 md:grid-flow-col md:auto-cols-fr md:grid-cols-none">
128
+ <!-- Author -->
129
+ <div v-if="props.author" :class="detailCellClasses">
130
+ <dt :class="detailLabelClasses">{{ t('uiVintage.blog.author') }}</dt>
131
+ <dd class="mt-2 flex min-w-0 items-center gap-2 text-sm font-semibold text-foreground">
132
+ <NuxtImg v-if="props.authorImageUrl" :src="props.authorImageUrl" :alt="props.author" width="24" height="24" format="avif" quality="40" class="size-6 shrink-0 rounded border border-border object-cover" />
133
+ <span class="truncate">{{ props.author }}</span>
134
+ </dd>
135
+ </div>
136
+
137
+ <!-- Publication date -->
138
+ <div v-if="formattedDate" :class="detailCellClasses">
139
+ <dt :class="detailLabelClasses">{{ t('uiVintage.blog.publishedDate') }}</dt>
140
+ <dd class="mt-2 text-sm text-foreground">
141
+ <time :datetime="props.datePublished">{{ formattedDate }}</time>
142
+ </dd>
143
+ </div>
144
+
145
+ <!-- Reading time -->
146
+ <div v-if="resolvedReadingMinutes" :class="detailCellClasses">
147
+ <dt :class="detailLabelClasses">{{ t('uiVintage.blog.readingTime') }}</dt>
148
+ <dd class="mt-2 text-sm text-foreground">{{ t('uiVintage.blog.readingTimeValue', { minutes: resolvedReadingMinutes }) }}</dd>
149
+ </div>
150
+
151
+ <!-- Category -->
152
+ <div v-if="hasCategory" :class="detailCellClasses">
153
+ <dt :class="detailLabelClasses">{{ t('uiVintage.blog.category') }}</dt>
154
+ <dd class="mt-1.5">
155
+ <NuxtLinkLocale :to="categoryPath" :class="['inline-flex', badgeLinkClasses]">
156
+ <Badge color="yellow" :text="props.categoryText" pulse />
157
+ </NuxtLinkLocale>
158
+ </dd>
159
+ </div>
160
+
161
+ <!-- Extra columns from the app (use the same cell structure: a div with a dt and a dd) -->
162
+ <slot name="meta" :cell-classes="detailCellClasses" :label-classes="detailLabelClasses" />
163
+ </dl>
164
+ </div>
165
+
166
+ <!-- Tags -->
167
+ <div v-if="props.tags.length > 0" :class="['mt-4 flex flex-wrap items-center gap-2', enter('animate-uv-fade-up [animation-delay:600ms]')]">
168
+ <NuxtLinkLocale v-for="tag in props.tags" :key="tag.slug" :to="tagPath(tag.slug)" :class="badgeLinkClasses">
169
+ <Badge :text="`#${tag.name}`" />
170
+ </NuxtLinkLocale>
171
+ </div>
172
+
173
+ <!-- Featured image (replaceable through the media slot) -->
174
+ <slot name="media">
175
+ <div v-if="props.image" :class="['relative mt-8 aspect-video w-full overflow-hidden rounded border border-border shadow-elevated-xl', enter('animate-uv-fade-up [animation-delay:650ms]')]">
176
+ <NuxtImg :src="props.image" :alt="props.title" format="avif" quality="50" :sizes="{ 480: '480px', 1536: '896px' }" loading="eager" fetchpriority="high" preload :class="['size-full object-cover', enter('animate-uv-ken-burns')]" />
177
+ <div aria-hidden="true" class="pointer-events-none absolute inset-0 bg-linear-to-t from-black/20 to-transparent" />
178
+ </div>
179
+ </slot>
180
+ </header>
181
+ </template>
@@ -0,0 +1,116 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+ import { slugify } from './blogPageUtils';
5
+ import type { BlogCategoryData } from './blogPageUtils';
6
+ import BlogContentRenderer from './BlogContentRenderer.vue';
7
+ import BlogFAQSection from './BlogFAQSection.vue';
8
+ import BlogInfoSection from './BlogInfoSection.vue';
9
+ import BlogPostHeader from './BlogPostHeader.vue';
10
+ import BlogSectionTitle from './BlogSectionTitle.vue';
11
+ import BlogViewAllLink from './BlogViewAllLink.vue';
12
+ import CategoryCard from './CategoryCard.vue';
13
+ import PostCard from './PostCard.vue';
14
+ import SocialShareSidebar from './SocialShareSidebar.vue';
15
+ import TableOfContents from './TableOfContents.vue';
16
+
17
+ // Props (post is the Nuxt Content entry of the blog collection, url its absolute address for sharing)
18
+ const props = withDefaults(defineProps<{
19
+ post: Record<string, any>;
20
+ url: string;
21
+ relatedPosts?: Array<{
22
+ path: string;
23
+ title: string;
24
+ description?: string;
25
+ image?: string;
26
+ categoryText?: string;
27
+ }>;
28
+ relatedCategories?: BlogCategoryData[];
29
+ }>(), {
30
+ relatedPosts: () => [],
31
+ relatedCategories: () => []
32
+ });
33
+
34
+ const { t } = useI18n();
35
+
36
+ // Tags of the post as badges (tags that can't produce a URL slug are skipped)
37
+ const postTags = computed(() => ((props.post.tags as string[] | undefined) || [])
38
+ .map(tag => ({ name: tag, slug: slugify(tag) }))
39
+ .filter(tag => tag.slug));
40
+ </script>
41
+
42
+ <template>
43
+ <div class="mx-auto max-w-4xl">
44
+ <article>
45
+ <!-- Article header -->
46
+ <BlogPostHeader :title="props.post.title"
47
+ :description="props.post.description"
48
+ :image="props.post.image"
49
+ :author="props.post.author"
50
+ :author-image-url="props.post.authorImageUrl"
51
+ :date-published="props.post.datePublished"
52
+ :category-text="props.post.categoryText"
53
+ :category-slug="props.post.categorySlug"
54
+ :tags="postTags"
55
+ :body="props.post.body" />
56
+
57
+ <!-- Table of contents -->
58
+ <TableOfContents :content="props.post" />
59
+
60
+ <!-- Social share sidebar -->
61
+ <SocialShareSidebar :title="props.post.title" :url="props.url" />
62
+
63
+ <!-- Article content -->
64
+ <BlogContentRenderer :value="props.post" />
65
+
66
+ <!-- Divider (the app's own ProseHr content component, lazy like the copy Nuxt Content loads for markdown) -->
67
+ <LazyProseHr />
68
+
69
+ <!-- FAQ section -->
70
+ <BlogFAQSection v-if="props.post.faqs?.length" :faqs="props.post.faqs" />
71
+
72
+ <!-- Divider -->
73
+ <LazyProseHr />
74
+
75
+ <!-- Blog info section -->
76
+ <BlogInfoSection :author="props.post.author"
77
+ :author-url="props.post.authorUrl"
78
+ :author-image-url="props.post.authorImageUrl"
79
+ :date-published="props.post.datePublished"
80
+ :date-modified="props.post.dateModified" />
81
+
82
+ <!-- App content after the article (e.g. a call to action) -->
83
+ <slot name="after-article" />
84
+ </article>
85
+
86
+ <!-- Related posts -->
87
+ <section v-if="props.relatedPosts.length" class="mt-16 md:mt-24">
88
+ <BlogSectionTitle :title="t('uiVintage.blog.readAlso')" />
89
+ <div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
90
+ <NuxtLinkLocale v-for="(relatedPost, index) in props.relatedPosts" :key="relatedPost.path" :to="relatedPost.path" data-aos="blur-up" :data-aos-delay="(index % 2) * 90" class="block h-full rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
91
+ <PostCard :image="relatedPost.image"
92
+ :category="relatedPost.categoryText"
93
+ :title="relatedPost.title"
94
+ :description="relatedPost.description"
95
+ class="h-full" />
96
+ </NuxtLinkLocale>
97
+ </div>
98
+ </section>
99
+
100
+ <!-- Related categories -->
101
+ <section v-if="props.relatedCategories.length" class="mt-16 md:mt-24">
102
+ <BlogSectionTitle :title="t('uiVintage.blog.exploreCategories')">
103
+ <template #action>
104
+ <BlogViewAllLink to="/blog/categories" :label="t('uiVintage.blog.exploreAllCategories')" />
105
+ </template>
106
+ </BlogSectionTitle>
107
+ <div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
108
+ <NuxtLinkLocale v-for="(category, index) in props.relatedCategories" :key="category.slug" :to="`/blog/categories/${category.slug}`" data-aos="blur-up" :data-aos-delay="(index % 2) * 90" class="block h-full rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
109
+ <CategoryCard :name="category.name"
110
+ :image="category.image"
111
+ :count="category.count" />
112
+ </NuxtLinkLocale>
113
+ </div>
114
+ </section>
115
+ </div>
116
+ </template>
@@ -0,0 +1,74 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+ import News01Icon from '@hugeicons/core-free-icons/News01Icon';
5
+ import { LoadMoreButton } from '../load-more-button';
6
+ import BlogHeaderSection from './BlogHeaderSection.vue';
7
+ import BlogPageBreadcrumbs from './BlogPageBreadcrumbs.vue';
8
+ import PostsList from './PostsList.vue';
9
+
10
+ // Props (the posts of a category or a tag, their paths already localized)
11
+ const props = withDefaults(defineProps<{
12
+ section: 'categories' | 'tags';
13
+ title: string;
14
+ description: string;
15
+ posts?: Array<{
16
+ path: string;
17
+ title: string;
18
+ description?: string;
19
+ image?: string;
20
+ categoryText?: string;
21
+ }>;
22
+ totalPosts?: number;
23
+ currentPage?: number;
24
+ postsPerPage?: number;
25
+ hasMorePosts?: boolean;
26
+ isLoading?: boolean;
27
+ }>(), {
28
+ posts: () => [],
29
+ totalPosts: 0,
30
+ currentPage: 1,
31
+ postsPerPage: 9,
32
+ hasMorePosts: false,
33
+ isLoading: false
34
+ });
35
+
36
+ // Emits
37
+ const emit = defineEmits<{
38
+ 'load-more': [];
39
+ }>();
40
+
41
+ const { t } = useI18n();
42
+
43
+ // Breadcrumbs: the section list, then the category or tag
44
+ const breadcrumbs = computed(() => [
45
+ props.section === 'tags' ? { label: t('uiVintage.blog.breadcrumbTags'), to: '/blog/tags' } : { label: t('uiVintage.blog.breadcrumbCategories'), to: '/blog/categories' },
46
+ { label: props.title }
47
+ ]);
48
+
49
+ // Number of posts shown under the header
50
+ const headerStats = computed(() => [{ icon: News01Icon, value: props.totalPosts, label: t('uiVintage.blog.stats.posts', props.totalPosts) }]);
51
+ </script>
52
+
53
+ <template>
54
+ <div>
55
+ <!-- Breadcrumbs -->
56
+ <BlogPageBreadcrumbs :items="breadcrumbs" />
57
+
58
+ <!-- Blog header -->
59
+ <BlogHeaderSection :title="props.title"
60
+ :highlight="props.title"
61
+ :description="props.description"
62
+ :stats="headerStats"
63
+ class="mb-12 md:mb-16" />
64
+
65
+ <!-- Posts -->
66
+ <PostsList :posts="props.posts" />
67
+
68
+ <!-- Load more button -->
69
+ <LoadMoreButton v-if="props.hasMorePosts" :busy="props.isLoading" :text="t('uiVintage.blog.loadMoreWithNumbers', {
70
+ current: Math.min(props.currentPage * props.postsPerPage, props.totalPosts),
71
+ total: props.totalPosts
72
+ })" @load-more="emit('load-more')" class="mt-8" data-aos="blur-up" />
73
+ </div>
74
+ </template>
@@ -6,8 +6,8 @@ import { cn } from '../../lib/utils';
6
6
 
7
7
  // Props
8
8
  const props = defineProps<{
9
- class?: HTMLAttributes['class'];
10
- ariaLabel?: string;
9
+ class?: HTMLAttributes['class'];
10
+ ariaLabel?: string;
11
11
  }>();
12
12
 
13
13
  const { t } = useI18n();
@@ -17,63 +17,63 @@ let animationFrame = 0;
17
17
 
18
18
  // Update progress based on scroll position
19
19
  const updateProgress = () => {
20
- const documentHeight = document.documentElement.scrollHeight - window.innerHeight;
21
- progress.value = documentHeight > 0 ? Math.min(100, Math.max(0, (window.scrollY / documentHeight) * 100)) : 100;
20
+ const documentHeight = document.documentElement.scrollHeight - window.innerHeight;
21
+ progress.value = documentHeight > 0 ? Math.min(100, Math.max(0, (window.scrollY / documentHeight) * 100)) : 100;
22
22
  };
23
23
 
24
24
  // Handle scroll event with requestAnimationFrame for performance
25
25
  const handleScroll = () => {
26
- cancelAnimationFrame(animationFrame);
27
- animationFrame = requestAnimationFrame(updateProgress);
26
+ cancelAnimationFrame(animationFrame);
27
+ animationFrame = requestAnimationFrame(updateProgress);
28
28
  };
29
29
 
30
30
  // On component mounted
31
31
  onMounted(() => {
32
- updateProgress();
33
- window.addEventListener('scroll', handleScroll, { passive: true });
34
- window.addEventListener('resize', updateProgress);
32
+ updateProgress();
33
+ window.addEventListener('scroll', handleScroll, { passive: true });
34
+ window.addEventListener('resize', updateProgress);
35
35
  });
36
36
 
37
37
  // On component unmounted
38
38
  onUnmounted(() => {
39
- cancelAnimationFrame(animationFrame);
40
- window.removeEventListener('scroll', handleScroll);
41
- window.removeEventListener('resize', updateProgress);
39
+ cancelAnimationFrame(animationFrame);
40
+ window.removeEventListener('scroll', handleScroll);
41
+ window.removeEventListener('resize', updateProgress);
42
42
  });
43
43
  </script>
44
44
 
45
45
  <template>
46
- <div :class="cn('h-0.5 w-full bg-border/60 landing-navbar-progress', props.class)"
47
- role="progressbar"
48
- :aria-label="resolvedAriaLabel"
49
- aria-valuemin="0"
50
- aria-valuemax="100"
51
- :aria-valuenow="Math.round(progress)">
52
- <!-- Solid fill -->
53
- <div class="h-full bg-primary transition-[width] duration-150 ease-out" :style="{ width: `${progress}%` }" />
54
- </div>
46
+ <div :class="cn('h-0.5 w-full bg-border/60 landing-navbar-progress', props.class)"
47
+ role="progressbar"
48
+ :aria-label="resolvedAriaLabel"
49
+ aria-valuemin="0"
50
+ aria-valuemax="100"
51
+ :aria-valuenow="Math.round(progress)">
52
+ <!-- Solid fill -->
53
+ <div class="h-full bg-primary transition-[width] duration-150 ease-out" :style="{ width: `${progress}%` }" />
54
+ </div>
55
55
  </template>
56
56
 
57
57
  <style scoped>
58
58
  .landing-navbar-progress {
59
- animation: landing-navbar-progress-fade linear both;
60
- animation-range: 0 160px;
61
- animation-timeline: scroll(root);
59
+ animation: landing-navbar-progress-fade linear both;
60
+ animation-range: 0 160px;
61
+ animation-timeline: scroll(root);
62
62
  }
63
63
 
64
64
  @keyframes landing-navbar-progress-fade {
65
- from {
66
- opacity: 0;
67
- }
65
+ from {
66
+ opacity: 0;
67
+ }
68
68
 
69
- to {
70
- opacity: 1;
71
- }
69
+ to {
70
+ opacity: 1;
71
+ }
72
72
  }
73
73
 
74
74
  @supports not (animation-timeline: scroll()) {
75
- .landing-navbar-progress {
76
- opacity: 1;
77
- }
75
+ .landing-navbar-progress {
76
+ opacity: 1;
77
+ }
78
78
  }
79
79
  </style>
@@ -1,33 +1,33 @@
1
1
  <script setup lang="ts">
2
2
  // Props
3
3
  const props = withDefaults(defineProps<{
4
- title: string;
5
- description?: string;
4
+ title: string;
5
+ description?: string;
6
6
  }>(), {
7
- description: ''
7
+ description: ''
8
8
  });
9
9
  </script>
10
10
 
11
11
  <template>
12
- <div data-aos="blur-up" class="relative mb-6 flex flex-col gap-3 border-b border-border pb-4 sm:flex-row sm:items-end sm:justify-between md:mb-8">
13
- <!-- Primary segment at the start of the rule -->
14
- <span aria-hidden="true" class="absolute -bottom-px left-0 h-0.5 w-12 bg-primary" />
12
+ <div data-aos="blur-up" class="relative mb-6 flex flex-col gap-3 border-b border-border pb-4 sm:flex-row sm:items-end sm:justify-between md:mb-8">
13
+ <!-- Primary segment at the start of the rule -->
14
+ <span aria-hidden="true" class="absolute -bottom-px left-0 h-0.5 w-12 bg-primary" />
15
15
 
16
- <div class="min-w-0">
17
- <!-- Title -->
18
- <h2 class="text-xl font-semibold tracking-tight text-foreground md:text-2xl">
19
- {{ props.title }}
20
- </h2>
16
+ <div class="min-w-0">
17
+ <!-- Title -->
18
+ <h2 class="text-xl font-semibold tracking-tight text-foreground md:text-2xl">
19
+ {{ props.title }}
20
+ </h2>
21
21
 
22
- <!-- Description -->
23
- <p v-if="props.description" class="mt-1 max-w-2xl text-xs leading-6 text-muted-foreground md:text-sm">
24
- {{ props.description }}
25
- </p>
26
- </div>
22
+ <!-- Description -->
23
+ <p v-if="props.description" class="mt-1 max-w-2xl text-xs leading-6 text-muted-foreground md:text-sm">
24
+ {{ props.description }}
25
+ </p>
26
+ </div>
27
27
 
28
- <!-- Optional trailing action (for example a "View all" link) -->
29
- <div v-if="$slots.action" class="shrink-0">
30
- <slot name="action" />
31
- </div>
32
- </div>
33
- </template>
28
+ <!-- Optional trailing action (for example a "View all" link) -->
29
+ <div v-if="$slots.action" class="shrink-0">
30
+ <slot name="action" />
31
+ </div>
32
+ </div>
33
+ </template>