@brumaombra/ui-vintage 0.9.1 → 0.9.2

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 (334) hide show
  1. package/module.mjs +43 -43
  2. package/package.json +175 -175
  3. package/src/components/background-grid/BackgroundGrid.vue +4 -4
  4. package/src/components/blog/AllPostsSection.vue +33 -33
  5. package/src/components/blog/BlogContentRenderer.vue +111 -111
  6. package/src/components/blog/BlogFAQSection.vue +19 -19
  7. package/src/components/blog/BlogHeaderSection.vue +80 -80
  8. package/src/components/blog/BlogInfoSection.vue +47 -47
  9. package/src/components/blog/BlogPostHeader.vue +135 -135
  10. package/src/components/blog/BlogProgressBar.vue +33 -33
  11. package/src/components/blog/BlogSectionTitle.vue +22 -22
  12. package/src/components/blog/BlogTagsSection.vue +25 -25
  13. package/src/components/blog/CategoriesList.vue +14 -14
  14. package/src/components/blog/CategoriesSection.vue +11 -11
  15. package/src/components/blog/CategoryCard.vue +25 -25
  16. package/src/components/blog/HeaderCarousel.vue +97 -97
  17. package/src/components/blog/PostCard.vue +33 -33
  18. package/src/components/blog/PostsList.vue +15 -15
  19. package/src/components/blog/SocialShareSidebar.vue +84 -84
  20. package/src/components/blog/TableOfContents.vue +307 -307
  21. package/src/components/busy-indicator/BusyIndicator.vue +7 -7
  22. package/src/components/busy-indicator/busy-indicator-state.ts +56 -56
  23. package/src/components/card-grid/CardGrid.vue +34 -34
  24. package/src/components/chip/Chip.vue +15 -15
  25. package/src/components/confirm-dialog/ConfirmDialog.vue +50 -50
  26. package/src/components/confirm-dialog/confirm-dialog-state.ts +131 -131
  27. package/src/components/content/BlogList.vue +30 -30
  28. package/src/components/content/BlogTable.vue +36 -36
  29. package/src/components/content/CodeBlock.vue +136 -136
  30. package/src/components/content/Flow.vue +60 -60
  31. package/src/components/content/ProseHr.vue +15 -15
  32. package/src/components/content/ProsePre.vue +12 -12
  33. package/src/components/content/Terminal.vue +59 -59
  34. package/src/components/cookie-consent/CookieConsent.vue +36 -36
  35. package/src/components/cookie-consent/useCookieConsent.ts +37 -37
  36. package/src/components/dashboard-shell/DashboardShell.vue +171 -171
  37. package/src/components/data-list/DataList.vue +6 -6
  38. package/src/components/data-list/DataListItem.vue +4 -4
  39. package/src/components/data-list/DataListLabel.vue +4 -4
  40. package/src/components/data-list/DataListText.vue +4 -4
  41. package/src/components/data-list/DataListValue.vue +4 -4
  42. package/src/components/empty-state-card/EmptyStateCard.vue +40 -40
  43. package/src/components/error-page/ErrorPage.vue +72 -72
  44. package/src/components/info-card/InfoCard.vue +31 -31
  45. package/src/components/landing/landing-content/LandingContent.vue +4 -4
  46. package/src/components/landing/landing-footer/LandingFooter.vue +128 -128
  47. package/src/components/landing/landing-navbar/LandingNavbar.vue +50 -50
  48. package/src/components/landing/landing-shell/LandingShell.vue +36 -36
  49. package/src/components/language-flag/LanguageFlag.vue +25 -25
  50. package/src/components/language-flag/flags/ChineseFlag.vue +8 -8
  51. package/src/components/language-flag/flags/EnglishFlag.vue +7 -7
  52. package/src/components/language-flag/flags/FrenchFlag.vue +5 -5
  53. package/src/components/language-flag/flags/GermanFlag.vue +5 -5
  54. package/src/components/language-flag/flags/ItalianFlag.vue +7 -7
  55. package/src/components/language-flag/flags/JapaneseFlag.vue +4 -4
  56. package/src/components/language-flag/flags/PortugueseFlag.vue +57 -57
  57. package/src/components/language-flag/flags/RussianFlag.vue +5 -5
  58. package/src/components/language-flag/flags/SpanishFlag.vue +4 -4
  59. package/src/components/language-selector/LanguageSelector.vue +76 -76
  60. package/src/components/load-more-button/LoadMoreButton.vue +17 -17
  61. package/src/components/loading-state-card/LoadingStateCard.vue +28 -28
  62. package/src/components/message-dialog/MessageDialog.vue +59 -59
  63. package/src/components/message-dialog/message-dialog-state.ts +105 -105
  64. package/src/components/message-toast/MessageToast.vue +149 -149
  65. package/src/components/message-toast/message-toast-state.ts +146 -146
  66. package/src/components/page-header/PageHeader.vue +34 -34
  67. package/src/components/progress-component/ProgressComponent.vue +33 -33
  68. package/src/components/single-value-card/SingleValueCard.vue +57 -57
  69. package/src/components/slider-form-component/SliderFormComponent.vue +36 -36
  70. package/src/components/stat-strip/StatStrip.vue +22 -22
  71. package/src/components/switch-form-component/SwitchFormComponent.vue +20 -20
  72. package/src/components/text-link/TextLink.vue +14 -14
  73. package/src/components/theme-selector/ThemeSelector.vue +61 -61
  74. package/src/components/theme-selector/theme-transition.ts +63 -63
  75. package/src/components/ui/accordion/Accordion.vue +38 -38
  76. package/src/components/ui/alert/Alert.vue +5 -5
  77. package/src/components/ui/alert/AlertDescription.vue +4 -4
  78. package/src/components/ui/alert/AlertTitle.vue +4 -4
  79. package/src/components/ui/alert/index.ts +2 -2
  80. package/src/components/ui/alert-dialog/AlertDialog.vue +3 -3
  81. package/src/components/ui/alert-dialog/AlertDialogAction.vue +3 -3
  82. package/src/components/ui/alert-dialog/AlertDialogCancel.vue +3 -3
  83. package/src/components/ui/alert-dialog/AlertDialogContent.vue +7 -7
  84. package/src/components/ui/alert-dialog/AlertDialogDescription.vue +3 -3
  85. package/src/components/ui/alert-dialog/AlertDialogFooter.vue +4 -4
  86. package/src/components/ui/alert-dialog/AlertDialogHeader.vue +4 -4
  87. package/src/components/ui/alert-dialog/AlertDialogTitle.vue +3 -3
  88. package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +3 -3
  89. package/src/components/ui/animated-number/AnimatedNumber.vue +49 -48
  90. package/src/components/ui/avatar/Avatar.vue +28 -28
  91. package/src/components/ui/avatar/AvatarFallback.vue +6 -6
  92. package/src/components/ui/avatar/AvatarGroup.vue +40 -40
  93. package/src/components/ui/avatar/AvatarImage.vue +3 -3
  94. package/src/components/ui/avatar/index.ts +17 -17
  95. package/src/components/ui/badge/Badge.vue +20 -20
  96. package/src/components/ui/breadcrumb/Breadcrumb.vue +4 -4
  97. package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +9 -9
  98. package/src/components/ui/breadcrumb/BreadcrumbItem.vue +4 -4
  99. package/src/components/ui/breadcrumb/BreadcrumbLink.vue +4 -4
  100. package/src/components/ui/breadcrumb/BreadcrumbList.vue +4 -4
  101. package/src/components/ui/breadcrumb/BreadcrumbPage.vue +4 -4
  102. package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +4 -4
  103. package/src/components/ui/busy/Busy.vue +33 -33
  104. package/src/components/ui/button/Button.vue +23 -23
  105. package/src/components/ui/button/index.ts +56 -56
  106. package/src/components/ui/calendar/Calendar.vue +138 -138
  107. package/src/components/ui/calendar/CalendarCell.vue +7 -7
  108. package/src/components/ui/calendar/CalendarCellTrigger.vue +18 -18
  109. package/src/components/ui/calendar/CalendarGrid.vue +3 -3
  110. package/src/components/ui/calendar/CalendarGridBody.vue +3 -3
  111. package/src/components/ui/calendar/CalendarGridHead.vue +3 -3
  112. package/src/components/ui/calendar/CalendarGridRow.vue +3 -3
  113. package/src/components/ui/calendar/CalendarHeadCell.vue +7 -7
  114. package/src/components/ui/calendar/CalendarHeader.vue +7 -7
  115. package/src/components/ui/calendar/CalendarHeading.vue +6 -6
  116. package/src/components/ui/calendar/CalendarNextButton.vue +15 -15
  117. package/src/components/ui/calendar/CalendarPrevButton.vue +15 -15
  118. package/src/components/ui/calendar/index.ts +4 -4
  119. package/src/components/ui/card/Card.vue +20 -20
  120. package/src/components/ui/card/CardAction.vue +8 -8
  121. package/src/components/ui/card/CardContent.vue +4 -4
  122. package/src/components/ui/card/CardDescription.vue +12 -12
  123. package/src/components/ui/card/CardFooter.vue +4 -4
  124. package/src/components/ui/card/CardHeader.vue +8 -8
  125. package/src/components/ui/card/CardText.vue +8 -8
  126. package/src/components/ui/card/CardTitle.vue +8 -8
  127. package/src/components/ui/checkbox/Checkbox.vue +27 -27
  128. package/src/components/ui/collapsible/Collapsible.vue +3 -3
  129. package/src/components/ui/collapsible/CollapsibleContent.vue +3 -3
  130. package/src/components/ui/collapsible/CollapsibleTrigger.vue +3 -3
  131. package/src/components/ui/combobox/Combobox.vue +4 -4
  132. package/src/components/ui/combobox/ComboboxAnchor.vue +16 -16
  133. package/src/components/ui/combobox/ComboboxEmpty.vue +3 -3
  134. package/src/components/ui/combobox/ComboboxGroup.vue +9 -9
  135. package/src/components/ui/combobox/ComboboxInput.vue +6 -6
  136. package/src/components/ui/combobox/ComboboxItem.vue +17 -17
  137. package/src/components/ui/combobox/ComboboxList.vue +19 -19
  138. package/src/components/ui/combobox/ComboboxSelect.vue +77 -77
  139. package/src/components/ui/combobox/ComboboxSeparator.vue +1 -1
  140. package/src/components/ui/combobox/ComboboxTrigger.vue +10 -10
  141. package/src/components/ui/combobox/types.ts +5 -5
  142. package/src/components/ui/command/Command.vue +48 -48
  143. package/src/components/ui/command/CommandDialog.vue +14 -14
  144. package/src/components/ui/command/CommandEmpty.vue +4 -4
  145. package/src/components/ui/command/CommandGroup.vue +23 -23
  146. package/src/components/ui/command/CommandInput.vue +9 -9
  147. package/src/components/ui/command/CommandItem.vue +40 -40
  148. package/src/components/ui/command/CommandList.vue +9 -9
  149. package/src/components/ui/command/CommandSeparator.vue +3 -3
  150. package/src/components/ui/command/CommandShortcut.vue +8 -8
  151. package/src/components/ui/command/index.ts +11 -11
  152. package/src/components/ui/data-table/DataTable.vue +145 -145
  153. package/src/components/ui/data-table/index.ts +11 -11
  154. package/src/components/ui/date-picker/DatePicker.vue +31 -31
  155. package/src/components/ui/date-time-picker/DateTimePicker.vue +97 -97
  156. package/src/components/ui/dialog/Dialog.vue +3 -3
  157. package/src/components/ui/dialog/DialogClose.vue +3 -3
  158. package/src/components/ui/dialog/DialogContent.vue +21 -21
  159. package/src/components/ui/dialog/DialogDescription.vue +7 -7
  160. package/src/components/ui/dialog/DialogFooter.vue +8 -8
  161. package/src/components/ui/dialog/DialogHeader.vue +4 -4
  162. package/src/components/ui/dialog/DialogOverlay.vue +7 -7
  163. package/src/components/ui/dialog/DialogScrollContent.vue +32 -32
  164. package/src/components/ui/dialog/DialogTitle.vue +3 -3
  165. package/src/components/ui/dialog/DialogTrigger.vue +3 -3
  166. package/src/components/ui/dropdown-menu/DropdownMenu.vue +3 -3
  167. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +11 -11
  168. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +15 -15
  169. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +3 -3
  170. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +12 -12
  171. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +9 -9
  172. package/src/components/ui/dropdown-menu/DropdownMenuPortal.vue +3 -3
  173. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +3 -3
  174. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +11 -11
  175. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
  176. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +4 -4
  177. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +3 -3
  178. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +15 -15
  179. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +15 -15
  180. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +3 -3
  181. package/src/components/ui/dropdown-menu/classes.ts +16 -16
  182. package/src/components/ui/field/Field.vue +5 -5
  183. package/src/components/ui/field/FieldContent.vue +8 -8
  184. package/src/components/ui/field/FieldDescription.vue +10 -10
  185. package/src/components/ui/field/FieldError.vue +39 -39
  186. package/src/components/ui/field/FieldGroup.vue +8 -8
  187. package/src/components/ui/field/FieldLabel.vue +10 -10
  188. package/src/components/ui/field/FieldLegend.vue +11 -11
  189. package/src/components/ui/field/FieldSeparator.vue +11 -11
  190. package/src/components/ui/field/FieldSet.vue +9 -9
  191. package/src/components/ui/field/FieldTitle.vue +8 -8
  192. package/src/components/ui/field/field-context.ts +36 -36
  193. package/src/components/ui/field/index.ts +22 -22
  194. package/src/components/ui/file-dropzone/FileDropzone.vue +144 -144
  195. package/src/components/ui/file-dropzone/index.ts +19 -19
  196. package/src/components/ui/hover-card/HoverCard.vue +5 -5
  197. package/src/components/ui/hover-card/HoverCardContent.vue +13 -13
  198. package/src/components/ui/hover-card/HoverCardTrigger.vue +3 -3
  199. package/src/components/ui/input/Input.vue +13 -13
  200. package/src/components/ui/kbd/Kbd.vue +10 -10
  201. package/src/components/ui/kbd/KbdGroup.vue +49 -49
  202. package/src/components/ui/label/Label.vue +7 -7
  203. package/src/components/ui/native-select/NativeSelect.vue +16 -16
  204. package/src/components/ui/native-select/NativeSelectOptGroup.vue +3 -3
  205. package/src/components/ui/native-select/NativeSelectOption.vue +3 -3
  206. package/src/components/ui/number-field/NumberField.vue +88 -88
  207. package/src/components/ui/pagination/Pagination.vue +4 -4
  208. package/src/components/ui/pagination/PaginationContent.vue +4 -4
  209. package/src/components/ui/pagination/PaginationEllipsis.vue +9 -9
  210. package/src/components/ui/pagination/PaginationFirst.vue +8 -8
  211. package/src/components/ui/pagination/PaginationItem.vue +14 -14
  212. package/src/components/ui/pagination/PaginationLast.vue +8 -8
  213. package/src/components/ui/pagination/PaginationNext.vue +8 -8
  214. package/src/components/ui/pagination/PaginationPrevious.vue +8 -8
  215. package/src/components/ui/pagination/SimplePagination.vue +25 -25
  216. package/src/components/ui/pin-input/PinInput.vue +20 -20
  217. package/src/components/ui/pin-input/PinInputInput.vue +17 -17
  218. package/src/components/ui/pin-input/context.ts +1 -1
  219. package/src/components/ui/popover/Popover.vue +3 -3
  220. package/src/components/ui/popover/PopoverAnchor.vue +3 -3
  221. package/src/components/ui/popover/PopoverContent.vue +12 -12
  222. package/src/components/ui/popover/PopoverTrigger.vue +3 -3
  223. package/src/components/ui/progress/Progress.vue +16 -16
  224. package/src/components/ui/radio-group/RadioGroup.vue +7 -7
  225. package/src/components/ui/radio-group/RadioGroupCard.vue +39 -39
  226. package/src/components/ui/radio-group/RadioGroupItem.vue +20 -20
  227. package/src/components/ui/scroll-area/ScrollArea.vue +14 -14
  228. package/src/components/ui/scroll-area/ScrollBar.vue +12 -12
  229. package/src/components/ui/select/Select.vue +3 -3
  230. package/src/components/ui/select/SelectContent.vue +25 -25
  231. package/src/components/ui/select/SelectGroup.vue +3 -3
  232. package/src/components/ui/select/SelectItem.vue +24 -24
  233. package/src/components/ui/select/SelectItemContent.vue +32 -32
  234. package/src/components/ui/select/SelectItemText.vue +3 -3
  235. package/src/components/ui/select/SelectLabel.vue +3 -3
  236. package/src/components/ui/select/SelectScrollDownButton.vue +9 -9
  237. package/src/components/ui/select/SelectScrollUpButton.vue +9 -9
  238. package/src/components/ui/select/SelectSeparator.vue +2 -2
  239. package/src/components/ui/select/SelectTrigger.vue +14 -14
  240. package/src/components/ui/select/SelectValue.vue +3 -3
  241. package/src/components/ui/select/SelectValueContent.vue +39 -39
  242. package/src/components/ui/separator/Separator.vue +7 -7
  243. package/src/components/ui/sheet/Sheet.vue +3 -3
  244. package/src/components/ui/sheet/SheetClose.vue +3 -3
  245. package/src/components/ui/sheet/SheetContent.vue +22 -22
  246. package/src/components/ui/sheet/SheetDescription.vue +3 -3
  247. package/src/components/ui/sheet/SheetFooter.vue +3 -3
  248. package/src/components/ui/sheet/SheetHeader.vue +3 -3
  249. package/src/components/ui/sheet/SheetOverlay.vue +7 -7
  250. package/src/components/ui/sheet/SheetTitle.vue +3 -3
  251. package/src/components/ui/sheet/SheetTrigger.vue +3 -3
  252. package/src/components/ui/sidebar/Sidebar.vue +57 -57
  253. package/src/components/ui/sidebar/SidebarContent.vue +8 -8
  254. package/src/components/ui/sidebar/SidebarFooter.vue +8 -8
  255. package/src/components/ui/sidebar/SidebarGroup.vue +8 -8
  256. package/src/components/ui/sidebar/SidebarGroupAction.vue +9 -9
  257. package/src/components/ui/sidebar/SidebarGroupContent.vue +4 -4
  258. package/src/components/ui/sidebar/SidebarGroupLabel.vue +8 -8
  259. package/src/components/ui/sidebar/SidebarHeader.vue +8 -8
  260. package/src/components/ui/sidebar/SidebarInput.vue +8 -8
  261. package/src/components/ui/sidebar/SidebarInset.vue +9 -9
  262. package/src/components/ui/sidebar/SidebarMenu.vue +4 -4
  263. package/src/components/ui/sidebar/SidebarMenuAction.vue +18 -18
  264. package/src/components/ui/sidebar/SidebarMenuBadge.vue +14 -14
  265. package/src/components/ui/sidebar/SidebarMenuButton.vue +21 -21
  266. package/src/components/ui/sidebar/SidebarMenuButtonChild.vue +10 -10
  267. package/src/components/ui/sidebar/SidebarMenuItem.vue +4 -4
  268. package/src/components/ui/sidebar/SidebarMenuSkeleton.vue +10 -10
  269. package/src/components/ui/sidebar/SidebarMenuSub.vue +9 -9
  270. package/src/components/ui/sidebar/SidebarMenuSubButton.vue +17 -17
  271. package/src/components/ui/sidebar/SidebarMenuSubItem.vue +4 -4
  272. package/src/components/ui/sidebar/SidebarProvider.vue +46 -46
  273. package/src/components/ui/sidebar/SidebarRail.vue +13 -13
  274. package/src/components/ui/sidebar/SidebarSeparator.vue +4 -4
  275. package/src/components/ui/sidebar/SidebarTrigger.vue +5 -5
  276. package/src/components/ui/sidebar/index.ts +29 -29
  277. package/src/components/ui/sidebar/utils.ts +7 -7
  278. package/src/components/ui/skeleton/Skeleton.vue +6 -6
  279. package/src/components/ui/slider/Slider.vue +14 -14
  280. package/src/components/ui/spinner/Spinner.vue +21 -21
  281. package/src/components/ui/stepper/SimpleStepper.vue +26 -26
  282. package/src/components/ui/stepper/Stepper.vue +6 -6
  283. package/src/components/ui/stepper/StepperDescription.vue +5 -5
  284. package/src/components/ui/stepper/StepperIndicator.vue +17 -17
  285. package/src/components/ui/stepper/StepperItem.vue +4 -4
  286. package/src/components/ui/stepper/StepperSeparator.vue +7 -7
  287. package/src/components/ui/stepper/StepperTitle.vue +5 -5
  288. package/src/components/ui/stepper/StepperTrigger.vue +4 -4
  289. package/src/components/ui/stepper/index.ts +3 -3
  290. package/src/components/ui/switch/Switch.vue +15 -15
  291. package/src/components/ui/table/Table.vue +6 -6
  292. package/src/components/ui/table/TableBody.vue +4 -4
  293. package/src/components/ui/table/TableCaption.vue +4 -4
  294. package/src/components/ui/table/TableCell.vue +8 -8
  295. package/src/components/ui/table/TableEmpty.vue +14 -14
  296. package/src/components/ui/table/TableFooter.vue +8 -8
  297. package/src/components/ui/table/TableHead.vue +8 -8
  298. package/src/components/ui/table/TableHeader.vue +4 -4
  299. package/src/components/ui/table/TableRow.vue +8 -8
  300. package/src/components/ui/tabs/Tabs.vue +4 -4
  301. package/src/components/ui/tabs/TabsContent.vue +4 -4
  302. package/src/components/ui/tabs/TabsList.vue +15 -15
  303. package/src/components/ui/tabs/TabsTrigger.vue +10 -10
  304. package/src/components/ui/tags-input/TagsInput.vue +17 -17
  305. package/src/components/ui/tags-input/TagsInputInput.vue +6 -6
  306. package/src/components/ui/tags-input/TagsInputItem.vue +15 -15
  307. package/src/components/ui/tags-input/TagsInputItemDelete.vue +12 -12
  308. package/src/components/ui/tags-input/TagsInputItemText.vue +4 -4
  309. package/src/components/ui/textarea/Textarea.vue +12 -12
  310. package/src/components/ui/time-picker/TimePicker.vue +25 -25
  311. package/src/components/ui/toggle-group/Toggle.vue +10 -10
  312. package/src/components/ui/toggle-group/ToggleGroup.vue +38 -38
  313. package/src/components/ui/toggle-group/ToggleGroupItem.vue +9 -9
  314. package/src/components/ui/toggle-group/index.ts +46 -46
  315. package/src/components/ui/tooltip/Tooltip.vue +3 -3
  316. package/src/components/ui/tooltip/TooltipContent.vue +13 -13
  317. package/src/components/ui/tooltip/TooltipProvider.vue +4 -4
  318. package/src/components/ui/tooltip/TooltipTrigger.vue +3 -3
  319. package/src/i18n/de.json +185 -185
  320. package/src/i18n/en.json +185 -185
  321. package/src/i18n/es.json +185 -185
  322. package/src/i18n/fr.json +185 -185
  323. package/src/i18n/it.json +185 -185
  324. package/src/i18n/ja.json +185 -185
  325. package/src/i18n/pt.json +185 -185
  326. package/src/i18n/ru.json +185 -185
  327. package/src/i18n/zh.json +185 -185
  328. package/src/lib/color-tokens.ts +24 -24
  329. package/src/lib/i18n.ts +65 -65
  330. package/src/lib/utils.ts +33 -33
  331. package/src/nuxt-components.d.ts +2 -2
  332. package/src/runtime/ui-vintage-i18n-plugin.ts +49 -49
  333. package/src/shims-vue.d.ts +3 -3
  334. package/src/styles.css +377 -377
@@ -10,31 +10,31 @@ import { Button } from '../ui/button';
10
10
  import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '../ui/sheet';
11
11
 
12
12
  interface BlogTocLink {
13
- id?: string;
14
- text?: string;
15
- depth?: number;
16
- children?: BlogTocLink[];
13
+ id?: string;
14
+ text?: string;
15
+ depth?: number;
16
+ children?: BlogTocLink[];
17
17
  }
18
18
 
19
19
  interface TocHeading {
20
- id: string;
21
- text: string;
22
- level: number;
23
- number: string;
20
+ id: string;
21
+ text: string;
22
+ level: number;
23
+ number: string;
24
24
  }
25
25
 
26
26
  // Props
27
27
  const props = defineProps<{
28
- content: {
29
- body?: {
30
- toc?: {
31
- links?: BlogTocLink[];
32
- };
33
- };
34
- toc?: {
35
- links?: BlogTocLink[];
36
- };
37
- };
28
+ content: {
29
+ body?: {
30
+ toc?: {
31
+ links?: BlogTocLink[];
32
+ };
33
+ };
34
+ toc?: {
35
+ links?: BlogTocLink[];
36
+ };
37
+ };
38
38
  }>();
39
39
 
40
40
  const { t } = useI18n();
@@ -62,33 +62,33 @@ let restoreMobileTriggerFocus = true;
62
62
 
63
63
  // Create the headings list
64
64
  const headings = computed(() => {
65
- const tocLinks = props.content?.body?.toc?.links || props.content?.toc?.links || [];
66
-
67
- // Recursive function to flatten the nested links
68
- const flattenLinks = (links: BlogTocLink[]): TocHeading[] => {
69
- return links.flatMap(link => {
70
- // Extract the current heading
71
- const currentHeading = {
72
- id: link.id ?? '',
73
- text: link.text ?? '',
74
- level: link.depth ?? 0,
75
- number: ''
76
- };
77
-
78
- // Recursively flatten the children links
79
- const children = link.children?.length ? flattenLinks(link.children) : [];
80
- return [currentHeading, ...children];
81
- });
82
- };
83
-
84
- // Keep h2 and h3 headings and number the main sections
85
- let sectionNumber = 0;
86
- return flattenLinks(tocLinks)
87
- .filter(heading => heading.id && heading.text && heading.level >= 2 && heading.level <= 3)
88
- .map(heading => ({
89
- ...heading,
90
- number: heading.level === 2 ? String(++sectionNumber).padStart(2, '0') : ''
91
- }));
65
+ const tocLinks = props.content?.body?.toc?.links || props.content?.toc?.links || [];
66
+
67
+ // Recursive function to flatten the nested links
68
+ const flattenLinks = (links: BlogTocLink[]): TocHeading[] => {
69
+ return links.flatMap(link => {
70
+ // Extract the current heading
71
+ const currentHeading = {
72
+ id: link.id ?? '',
73
+ text: link.text ?? '',
74
+ level: link.depth ?? 0,
75
+ number: ''
76
+ };
77
+
78
+ // Recursively flatten the children links
79
+ const children = link.children?.length ? flattenLinks(link.children) : [];
80
+ return [currentHeading, ...children];
81
+ });
82
+ };
83
+
84
+ // Keep h2 and h3 headings and number the main sections
85
+ let sectionNumber = 0;
86
+ return flattenLinks(tocLinks)
87
+ .filter(heading => heading.id && heading.text && heading.level >= 2 && heading.level <= 3)
88
+ .map(heading => ({
89
+ ...heading,
90
+ number: heading.level === 2 ? String(++sectionNumber).padStart(2, '0') : ''
91
+ }));
92
92
  });
93
93
 
94
94
  // Number of main sections shown in the header
@@ -101,363 +101,363 @@ const tocSections = computed(() => t('uiVintage.blog.tableOfContentsSections', {
101
101
 
102
102
  // Prefer reduced motion for heading scroll
103
103
  const prefersReducedMotion = () => {
104
- return Boolean(window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches);
104
+ return Boolean(window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches);
105
105
  };
106
106
 
107
107
  // Resolve the heading that should be marked as the current chapter
108
108
  const resolveActiveHeadingId = () => {
109
- if (!headings.value.length) return '';
110
-
111
- // Filter the headings to only those currently visible in the viewport
112
- const visibleHeadings = headings.value.filter(heading => visibleHeadingIds.value.has(heading.id));
113
- if (visibleHeadings.length > 0) {
114
- return visibleHeadings[0].id;
115
- }
116
-
117
- // If no headings are currently visible, fall back to the first heading in the list
118
- let currentId = headings.value[0].id;
119
- for (const heading of headings.value) {
120
- const element = document.getElementById(heading.id);
121
- if (element && element.getBoundingClientRect().top <= ACTIVE_HEADING_OFFSET_PX) {
122
- currentId = heading.id;
123
- }
124
- }
125
-
126
- // Return the resolved current heading ID
127
- return currentId;
109
+ if (!headings.value.length) return '';
110
+
111
+ // Filter the headings to only those currently visible in the viewport
112
+ const visibleHeadings = headings.value.filter(heading => visibleHeadingIds.value.has(heading.id));
113
+ if (visibleHeadings.length > 0) {
114
+ return visibleHeadings[0].id;
115
+ }
116
+
117
+ // If no headings are currently visible, fall back to the first heading in the list
118
+ let currentId = headings.value[0].id;
119
+ for (const heading of headings.value) {
120
+ const element = document.getElementById(heading.id);
121
+ if (element && element.getBoundingClientRect().top <= ACTIVE_HEADING_OFFSET_PX) {
122
+ currentId = heading.id;
123
+ }
124
+ }
125
+
126
+ // Return the resolved current heading ID
127
+ return currentId;
128
128
  };
129
129
 
130
130
  // Update the active heading based on the current scroll position
131
131
  const updateActiveHeading = () => {
132
- activeHeadingId.value = resolveActiveHeadingId();
132
+ activeHeadingId.value = resolveActiveHeadingId();
133
133
  };
134
134
 
135
135
  // Track which headings currently intersect the reading window
136
136
  const handleHeadingIntersect = (entries: IntersectionObserverEntry[]) => {
137
- const nextVisibleIds = new Set(visibleHeadingIds.value);
138
-
139
- // Update the set of visible heading IDs
140
- for (const entry of entries) {
141
- if (entry.isIntersecting) {
142
- nextVisibleIds.add(entry.target.id);
143
- } else {
144
- nextVisibleIds.delete(entry.target.id);
145
- }
146
- }
147
-
148
- // Apply the updated set
149
- visibleHeadingIds.value = nextVisibleIds;
150
- updateActiveHeading();
137
+ const nextVisibleIds = new Set(visibleHeadingIds.value);
138
+
139
+ // Update the set of visible heading IDs
140
+ for (const entry of entries) {
141
+ if (entry.isIntersecting) {
142
+ nextVisibleIds.add(entry.target.id);
143
+ } else {
144
+ nextVisibleIds.delete(entry.target.id);
145
+ }
146
+ }
147
+
148
+ // Apply the updated set
149
+ visibleHeadingIds.value = nextVisibleIds;
150
+ updateActiveHeading();
151
151
  };
152
152
 
153
153
  // Disconnect the heading observer and clear any pending retries
154
154
  const disconnectHeadingObserver = () => {
155
- headingObserver?.disconnect();
156
- headingObserver = null;
157
- window.clearTimeout(headingRetryTimeout);
155
+ headingObserver?.disconnect();
156
+ headingObserver = null;
157
+ window.clearTimeout(headingRetryTimeout);
158
158
  };
159
159
 
160
160
  // Bind the heading observer to track which headings are visible in the viewport
161
161
  const bindHeadingObserver = () => {
162
- disconnectHeadingObserver();
163
-
164
- // If there are no headings, clear the active heading and visible heading IDs
165
- if (!headings.value.length) {
166
- activeHeadingId.value = '';
167
- visibleHeadingIds.value = new Set();
168
- return true;
169
- }
170
-
171
- // Get the DOM elements corresponding to the headings
172
- const elements = headings.value
173
- .map(heading => document.getElementById(heading.id))
174
- .filter((element): element is HTMLElement => Boolean(element));
175
-
176
- // Content renderer mounts after this component, so headings may not exist yet
177
- if (!elements.length) return false;
178
-
179
- headingObserver = new IntersectionObserver(handleHeadingIntersect, {
180
- root: null,
181
- rootMargin: '-20% 0px -65% 0px',
182
- threshold: [0, 1]
183
- });
184
-
185
- for (const element of elements) {
186
- headingObserver.observe(element);
187
- }
188
-
189
- if (!activeHeadingId.value) {
190
- activeHeadingId.value = headings.value[0].id;
191
- }
192
-
193
- updateActiveHeading();
194
- return true;
162
+ disconnectHeadingObserver();
163
+
164
+ // If there are no headings, clear the active heading and visible heading IDs
165
+ if (!headings.value.length) {
166
+ activeHeadingId.value = '';
167
+ visibleHeadingIds.value = new Set();
168
+ return true;
169
+ }
170
+
171
+ // Get the DOM elements corresponding to the headings
172
+ const elements = headings.value
173
+ .map(heading => document.getElementById(heading.id))
174
+ .filter((element): element is HTMLElement => Boolean(element));
175
+
176
+ // Content renderer mounts after this component, so headings may not exist yet
177
+ if (!elements.length) return false;
178
+
179
+ headingObserver = new IntersectionObserver(handleHeadingIntersect, {
180
+ root: null,
181
+ rootMargin: '-20% 0px -65% 0px',
182
+ threshold: [0, 1]
183
+ });
184
+
185
+ for (const element of elements) {
186
+ headingObserver.observe(element);
187
+ }
188
+
189
+ if (!activeHeadingId.value) {
190
+ activeHeadingId.value = headings.value[0].id;
191
+ }
192
+
193
+ updateActiveHeading();
194
+ return true;
195
195
  };
196
196
 
197
197
  // Retry binding until the content renderer has mounted its headings
198
198
  const scheduleHeadingObserver = (attempt = 0) => {
199
- nextTick(() => {
200
- if (bindHeadingObserver()) return;
201
- if (attempt >= MAX_HEADING_OBSERVER_RETRIES) return;
202
-
203
- headingRetryTimeout = window.setTimeout(() => {
204
- scheduleHeadingObserver(attempt + 1);
205
- }, HEADING_OBSERVER_RETRY_MS);
206
- });
199
+ nextTick(() => {
200
+ if (bindHeadingObserver()) return;
201
+ if (attempt >= MAX_HEADING_OBSERVER_RETRIES) return;
202
+
203
+ headingRetryTimeout = window.setTimeout(() => {
204
+ scheduleHeadingObserver(attempt + 1);
205
+ }, HEADING_OBSERVER_RETRY_MS);
206
+ });
207
207
  };
208
208
 
209
209
  // Disconnect the observer that controls the mobile trigger visibility
210
210
  const disconnectSentinelObserver = () => {
211
- sentinelObserver?.disconnect();
212
- sentinelObserver = null;
211
+ sentinelObserver?.disconnect();
212
+ sentinelObserver = null;
213
213
  };
214
214
 
215
215
  // Show the mobile trigger after the in-flow accordion leaves the viewport
216
216
  const bindSentinelObserver = () => {
217
- disconnectSentinelObserver();
218
-
219
- if (!inlineRef.value) {
220
- isDocked.value = false;
221
- return;
222
- }
223
-
224
- // Show the mobile control once the in-flow accordion has scrolled completely out of view
225
- sentinelObserver = new IntersectionObserver(([entry]) => {
226
- const hasScrolledPast = !entry.isIntersecting && entry.boundingClientRect.top < 0;
227
- isDocked.value = hasScrolledPast;
228
- }, {
229
- threshold: 0
230
- });
231
-
232
- sentinelObserver.observe(inlineRef.value);
217
+ disconnectSentinelObserver();
218
+
219
+ if (!inlineRef.value) {
220
+ isDocked.value = false;
221
+ return;
222
+ }
223
+
224
+ // Show the mobile control once the in-flow accordion has scrolled completely out of view
225
+ sentinelObserver = new IntersectionObserver(([entry]) => {
226
+ const hasScrolledPast = !entry.isIntersecting && entry.boundingClientRect.top < 0;
227
+ isDocked.value = hasScrolledPast;
228
+ }, {
229
+ threshold: 0
230
+ });
231
+
232
+ sentinelObserver.observe(inlineRef.value);
233
233
  };
234
234
 
235
235
  // Keep the mobile trigger state in sync with the viewport breakpoint
236
236
  const handleDesktopMediaChange = (event: MediaQueryList | MediaQueryListEvent) => {
237
- isDesktop.value = event.matches;
237
+ isDesktop.value = event.matches;
238
238
 
239
- if (event.matches) {
240
- isMobileOpen.value = false;
241
- }
239
+ if (event.matches) {
240
+ isMobileOpen.value = false;
241
+ }
242
242
 
243
- nextTick(() => {
244
- bindSentinelObserver();
245
- });
243
+ nextTick(() => {
244
+ bindSentinelObserver();
245
+ });
246
246
  };
247
247
 
248
248
  // Synchronize the mobile sheet state and restore focus after dismissal
249
249
  const handleMobileOpenChange = (open: boolean) => {
250
- isMobileOpen.value = open;
250
+ isMobileOpen.value = open;
251
251
 
252
- // Return focus to the persistent control when the overlay is dismissed
253
- if (!open && restoreMobileTriggerFocus) {
254
- mobileTriggerRef.value?.focus();
255
- }
252
+ // Return focus to the persistent control when the overlay is dismissed
253
+ if (!open && restoreMobileTriggerFocus) {
254
+ mobileTriggerRef.value?.focus();
255
+ }
256
256
 
257
- restoreMobileTriggerFocus = true;
257
+ restoreMobileTriggerFocus = true;
258
258
  };
259
259
 
260
260
  // Scroll to a heading and optionally close the mobile sheet first
261
261
  const scrollToHeading = (id: string, options: { closeMobile?: boolean } = {}) => {
262
- const runScroll = () => {
263
- const element = document.getElementById(id);
264
- if (!element) return;
265
-
266
- if (!element.hasAttribute('tabindex')) {
267
- element.setAttribute('tabindex', '-1');
268
- }
269
-
270
- element.scrollIntoView({
271
- behavior: prefersReducedMotion() ? 'auto' : 'smooth',
272
- block: 'start',
273
- inline: 'nearest'
274
- });
275
- element.focus({ preventScroll: true });
276
- activeHeadingId.value = id;
277
-
278
- if (window.location.hash !== `#${id}`) {
279
- history.replaceState(null, '', `#${id}`);
280
- }
281
- };
282
- if (options.closeMobile && isMobileOpen.value) {
283
- // The heading takes focus after the scroll, so skip the trigger focus restore for this close only
284
- restoreMobileTriggerFocus = false;
285
- mobileTriggerRef.value?.focus();
286
- isMobileOpen.value = false;
287
- nextTick(() => {
288
- restoreMobileTriggerFocus = true;
289
- });
290
- window.clearTimeout(mobileScrollTimeout);
291
- mobileScrollTimeout = window.setTimeout(runScroll, MOBILE_CLOSE_SCROLL_DELAY_MS);
292
- return;
293
- }
294
-
295
- runScroll();
262
+ const runScroll = () => {
263
+ const element = document.getElementById(id);
264
+ if (!element) return;
265
+
266
+ if (!element.hasAttribute('tabindex')) {
267
+ element.setAttribute('tabindex', '-1');
268
+ }
269
+
270
+ element.scrollIntoView({
271
+ behavior: prefersReducedMotion() ? 'auto' : 'smooth',
272
+ block: 'start',
273
+ inline: 'nearest'
274
+ });
275
+ element.focus({ preventScroll: true });
276
+ activeHeadingId.value = id;
277
+
278
+ if (window.location.hash !== `#${id}`) {
279
+ history.replaceState(null, '', `#${id}`);
280
+ }
281
+ };
282
+ if (options.closeMobile && isMobileOpen.value) {
283
+ // The heading takes focus after the scroll, so skip the trigger focus restore for this close only
284
+ restoreMobileTriggerFocus = false;
285
+ mobileTriggerRef.value?.focus();
286
+ isMobileOpen.value = false;
287
+ nextTick(() => {
288
+ restoreMobileTriggerFocus = true;
289
+ });
290
+ window.clearTimeout(mobileScrollTimeout);
291
+ mobileScrollTimeout = window.setTimeout(runScroll, MOBILE_CLOSE_SCROLL_DELAY_MS);
292
+ return;
293
+ }
294
+
295
+ runScroll();
296
296
  };
297
297
 
298
298
  // Open the mobile table of contents sheet
299
299
  const openMobileToc = () => {
300
- isMobileOpen.value = true;
300
+ isMobileOpen.value = true;
301
301
  };
302
302
 
303
303
  // Get classes for heading button
304
304
  const getHeadingButtonClasses = (level: number, isActive: boolean) => {
305
- // Base classes for all titles
306
- const baseClasses = 'flex w-full cursor-pointer gap-3 rounded-r py-1.5 pr-2 text-left text-xs outline-none transition-colors duration-200 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/45 md:text-sm';
305
+ // Base classes for all titles
306
+ const baseClasses = 'flex w-full cursor-pointer gap-3 rounded-r py-1.5 pr-2 text-left text-xs outline-none transition-colors duration-200 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/45 md:text-sm';
307
307
 
308
- // Main sections carry a number, subsections align with their text
309
- const levelClasses = level === 2 ? 'pl-4 font-medium' : 'pl-12 md:text-xs';
308
+ // Main sections carry a number, subsections align with their text
309
+ const levelClasses = level === 2 ? 'pl-4 font-medium' : 'pl-12 md:text-xs';
310
310
 
311
- // Add classes based on the active state
312
- let stateClasses = level === 2 ? 'text-foreground/80' : 'text-muted-foreground';
313
- if (isActive) {
314
- stateClasses = 'font-semibold text-foreground';
315
- }
311
+ // Add classes based on the active state
312
+ let stateClasses = level === 2 ? 'text-foreground/80' : 'text-muted-foreground';
313
+ if (isActive) {
314
+ stateClasses = 'font-semibold text-foreground';
315
+ }
316
316
 
317
- // Return combined classes
318
- return [baseClasses, levelClasses, stateClasses];
317
+ // Return combined classes
318
+ return [baseClasses, levelClasses, stateClasses];
319
319
  };
320
320
 
321
321
  // Watch for changes in the headings and update the visible heading IDs and active heading ID accordingly
322
322
  watch(headings, () => {
323
- visibleHeadingIds.value = new Set();
324
- activeHeadingId.value = headings.value[0]?.id ?? '';
325
- scheduleHeadingObserver();
323
+ visibleHeadingIds.value = new Set();
324
+ activeHeadingId.value = headings.value[0]?.id ?? '';
325
+ scheduleHeadingObserver();
326
326
  });
327
327
 
328
328
  // Watch for changes in the inline table of contents reference and bind the sentinel observer accordingly
329
329
  watch(inlineRef, () => {
330
- bindSentinelObserver();
330
+ bindSentinelObserver();
331
331
  }, { flush: 'post' });
332
332
 
333
333
  // On component mounted
334
334
  onMounted(() => {
335
- desktopMediaQuery = window.matchMedia(DESKTOP_MEDIA_QUERY);
336
- handleDesktopMediaChange(desktopMediaQuery);
337
- desktopMediaQuery.addEventListener('change', handleDesktopMediaChange);
335
+ desktopMediaQuery = window.matchMedia(DESKTOP_MEDIA_QUERY);
336
+ handleDesktopMediaChange(desktopMediaQuery);
337
+ desktopMediaQuery.addEventListener('change', handleDesktopMediaChange);
338
338
 
339
- const hashId = window.location.hash.replace(/^#/, '');
340
- if (hashId && headings.value.some(heading => heading.id === hashId)) {
341
- activeHeadingId.value = hashId;
342
- }
339
+ const hashId = window.location.hash.replace(/^#/, '');
340
+ if (hashId && headings.value.some(heading => heading.id === hashId)) {
341
+ activeHeadingId.value = hashId;
342
+ }
343
343
 
344
- scheduleHeadingObserver();
344
+ scheduleHeadingObserver();
345
345
  });
346
346
 
347
347
  // On component unmounted
348
348
  onUnmounted(() => {
349
- desktopMediaQuery?.removeEventListener('change', handleDesktopMediaChange);
350
- disconnectSentinelObserver();
351
- disconnectHeadingObserver();
352
- window.clearTimeout(mobileScrollTimeout);
349
+ desktopMediaQuery?.removeEventListener('change', handleDesktopMediaChange);
350
+ disconnectSentinelObserver();
351
+ disconnectHeadingObserver();
352
+ window.clearTimeout(mobileScrollTimeout);
353
353
  });
354
354
  </script>
355
355
 
356
356
  <template>
357
- <div v-if="headings.length > 0">
358
- <!-- In-flow accordion; replaced by the mobile control after it scrolls away -->
359
- <div ref="inlineRef" class="mb-6" :aria-hidden="isDocked" :inert="isDocked">
360
- <Accordion :title="tocTitle" :icon="Bookmark01Icon">
361
- <!-- Section count -->
362
- <template #trailing>
363
- <span class="shrink-0 text-xs text-muted-foreground">{{ tocSections }}</span>
364
- </template>
365
-
366
- <nav :aria-label="tocTitle">
367
- <ol>
368
- <li v-for="heading in headings" :key="`inline-${heading.id}`" class="relative border-l border-border">
369
- <!-- Active marker on the track -->
370
- <span aria-hidden="true" :class="['absolute inset-y-1 -left-px w-0.5 origin-center bg-primary transition-transform duration-300 ease-spring', heading.id === activeHeadingId ? 'scale-y-100' : 'scale-y-0']" />
371
- <button type="button" :class="getHeadingButtonClasses(heading.level, heading.id === activeHeadingId)" :aria-current="heading.id === activeHeadingId ? 'location' : undefined" @click="scrollToHeading(heading.id)">
372
- <span v-if="heading.number" :class="['w-5 shrink-0 text-[11px] leading-5 tabular-nums transition-colors', heading.id === activeHeadingId ? 'text-primary' : 'text-muted-foreground']">{{ heading.number }}</span>
373
- <span>{{ heading.text }}</span>
374
- </button>
375
- </li>
376
- </ol>
377
- </nav>
378
- </Accordion>
379
- </div>
380
-
381
- <Teleport to="body">
382
- <!-- Mobile persistent control -->
383
- <Transition name="toc-tab">
384
- <button v-if="!isDesktop && isDocked" ref="mobileTriggerRef" type="button" class="group/toc-tab fixed top-4/5 left-0 z-20 flex h-16 w-7 -translate-y-1/2 items-center justify-center rounded-r border border-l-0 border-border bg-card text-primary shadow-elevated-sm transition-[width] duration-300 ease-out-expo hover:w-8 focus-visible:z-30 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45" :aria-label="t('uiVintage.blog.openTableOfContents')" :aria-expanded="isMobileOpen" aria-haspopup="dialog" aria-controls="toc-mobile-overlay" @click="openMobileToc">
385
- <HugeiconsIcon :icon="ArrowRight01Icon" class="size-3.5 transition-[translate] duration-300 ease-spring group-hover/toc-tab:translate-x-0.5" aria-hidden="true" />
386
- </button>
387
- </Transition>
388
-
389
- <!-- Mobile full-screen overlay -->
390
- <Sheet :open="isMobileOpen" @update:open="handleMobileOpenChange">
391
- <SheetContent id="toc-mobile-overlay" side="left" class="flex h-full w-full max-w-none flex-col gap-0 border-0 bg-card p-0 data-[state=open]:[animation-duration:320ms] data-[state=open]:[animation-timing-function:var(--ease-out-expo)] sm:max-w-none">
392
- <SheetHeader class="flex-row items-center justify-between gap-4 border-b border-border p-5">
393
- <!-- Mobile table of contents header -->
394
- <div class="flex min-w-0 items-center gap-3">
395
- <!-- Mobile table of contents icon -->
396
- <div class="flex size-7 shrink-0 items-center justify-center rounded border border-primary/30 text-primary">
397
- <HugeiconsIcon :icon="Bookmark01Icon" class="size-3.5" />
398
- </div>
399
-
400
- <!-- Mobile table of contents title and description -->
401
- <div class="min-w-0">
402
- <SheetTitle class="text-sm md:text-base! font-semibold text-foreground">
403
- {{ tocTitle }}
404
- </SheetTitle>
405
- <SheetDescription class="sr-only">
406
- {{ tocDescription }}
407
- </SheetDescription>
408
- <p aria-hidden="true" class="text-xs text-muted-foreground">
409
- {{ tocSections }}
410
- </p>
411
- </div>
412
- </div>
413
-
414
- <!-- Close button for the mobile table of contents -->
415
- <SheetClose as-child>
416
- <Button type="button" variant="ghost" size="icon-sm" :aria-label="t('uiVintage.blog.closeTableOfContents')">
417
- <HugeiconsIcon :icon="Cancel01Icon" class="size-4" />
418
- </Button>
419
- </SheetClose>
420
- </SheetHeader>
421
-
422
- <!-- Mobile table of contents navigation -->
423
- <nav :aria-label="tocTitle" class="flex-1 overflow-y-auto p-5">
424
- <ol>
425
- <li v-for="heading in headings" :key="`mobile-${heading.id}`" class="relative border-l border-border">
426
- <!-- Active marker on the track -->
427
- <span aria-hidden="true" :class="['absolute inset-y-1 -left-px w-0.5 bg-primary', heading.id === activeHeadingId ? 'opacity-100' : 'opacity-0']" />
428
- <button type="button" :class="getHeadingButtonClasses(heading.level, heading.id === activeHeadingId)" :aria-current="heading.id === activeHeadingId ? 'location' : undefined" @click="scrollToHeading(heading.id, { closeMobile: true })">
429
- <span v-if="heading.number" :class="['w-5 shrink-0 text-[11px] leading-5 tabular-nums', heading.id === activeHeadingId ? 'text-primary' : 'text-muted-foreground']">{{ heading.number }}</span>
430
- <span>{{ heading.text }}</span>
431
- </button>
432
- </li>
433
- </ol>
434
- </nav>
435
- </SheetContent>
436
- </Sheet>
437
- </Teleport>
438
- </div>
357
+ <div v-if="headings.length > 0">
358
+ <!-- In-flow accordion; replaced by the mobile control after it scrolls away -->
359
+ <div ref="inlineRef" class="mb-6" :aria-hidden="isDocked" :inert="isDocked">
360
+ <Accordion :title="tocTitle" :icon="Bookmark01Icon">
361
+ <!-- Section count -->
362
+ <template #trailing>
363
+ <span class="shrink-0 text-xs text-muted-foreground">{{ tocSections }}</span>
364
+ </template>
365
+
366
+ <nav :aria-label="tocTitle">
367
+ <ol>
368
+ <li v-for="heading in headings" :key="`inline-${heading.id}`" class="relative border-l border-border">
369
+ <!-- Active marker on the track -->
370
+ <span aria-hidden="true" :class="['absolute inset-y-1 -left-px w-0.5 origin-center bg-primary transition-transform duration-300 ease-spring', heading.id === activeHeadingId ? 'scale-y-100' : 'scale-y-0']" />
371
+ <button type="button" :class="getHeadingButtonClasses(heading.level, heading.id === activeHeadingId)" :aria-current="heading.id === activeHeadingId ? 'location' : undefined" @click="scrollToHeading(heading.id)">
372
+ <span v-if="heading.number" :class="['w-5 shrink-0 text-[11px] leading-5 tabular-nums transition-colors', heading.id === activeHeadingId ? 'text-primary' : 'text-muted-foreground']">{{ heading.number }}</span>
373
+ <span>{{ heading.text }}</span>
374
+ </button>
375
+ </li>
376
+ </ol>
377
+ </nav>
378
+ </Accordion>
379
+ </div>
380
+
381
+ <Teleport to="body">
382
+ <!-- Mobile persistent control -->
383
+ <Transition name="toc-tab">
384
+ <button v-if="!isDesktop && isDocked" ref="mobileTriggerRef" type="button" class="group/toc-tab fixed top-4/5 left-0 z-20 flex h-16 w-7 -translate-y-1/2 items-center justify-center rounded-r border border-l-0 border-border bg-card text-primary shadow-elevated-sm transition-[width] duration-300 ease-out-expo hover:w-8 focus-visible:z-30 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45" :aria-label="t('uiVintage.blog.openTableOfContents')" :aria-expanded="isMobileOpen" aria-haspopup="dialog" aria-controls="toc-mobile-overlay" @click="openMobileToc">
385
+ <HugeiconsIcon :icon="ArrowRight01Icon" class="size-3.5 transition-[translate] duration-300 ease-spring group-hover/toc-tab:translate-x-0.5" aria-hidden="true" />
386
+ </button>
387
+ </Transition>
388
+
389
+ <!-- Mobile full-screen overlay -->
390
+ <Sheet :open="isMobileOpen" @update:open="handleMobileOpenChange">
391
+ <SheetContent id="toc-mobile-overlay" side="left" class="flex h-full w-full max-w-none flex-col gap-0 border-0 bg-card p-0 data-[state=open]:[animation-duration:320ms] data-[state=open]:[animation-timing-function:var(--ease-out-expo)] sm:max-w-none">
392
+ <SheetHeader class="flex-row items-center justify-between gap-4 border-b border-border p-5">
393
+ <!-- Mobile table of contents header -->
394
+ <div class="flex min-w-0 items-center gap-3">
395
+ <!-- Mobile table of contents icon -->
396
+ <div class="flex size-7 shrink-0 items-center justify-center rounded border border-primary/30 text-primary">
397
+ <HugeiconsIcon :icon="Bookmark01Icon" class="size-3.5" />
398
+ </div>
399
+
400
+ <!-- Mobile table of contents title and description -->
401
+ <div class="min-w-0">
402
+ <SheetTitle class="text-sm md:text-base! font-semibold text-foreground">
403
+ {{ tocTitle }}
404
+ </SheetTitle>
405
+ <SheetDescription class="sr-only">
406
+ {{ tocDescription }}
407
+ </SheetDescription>
408
+ <p aria-hidden="true" class="text-xs text-muted-foreground">
409
+ {{ tocSections }}
410
+ </p>
411
+ </div>
412
+ </div>
413
+
414
+ <!-- Close button for the mobile table of contents -->
415
+ <SheetClose as-child>
416
+ <Button type="button" variant="ghost" size="icon-sm" :aria-label="t('uiVintage.blog.closeTableOfContents')">
417
+ <HugeiconsIcon :icon="Cancel01Icon" class="size-4" />
418
+ </Button>
419
+ </SheetClose>
420
+ </SheetHeader>
421
+
422
+ <!-- Mobile table of contents navigation -->
423
+ <nav :aria-label="tocTitle" class="flex-1 overflow-y-auto p-5">
424
+ <ol>
425
+ <li v-for="heading in headings" :key="`mobile-${heading.id}`" class="relative border-l border-border">
426
+ <!-- Active marker on the track -->
427
+ <span aria-hidden="true" :class="['absolute inset-y-1 -left-px w-0.5 bg-primary', heading.id === activeHeadingId ? 'opacity-100' : 'opacity-0']" />
428
+ <button type="button" :class="getHeadingButtonClasses(heading.level, heading.id === activeHeadingId)" :aria-current="heading.id === activeHeadingId ? 'location' : undefined" @click="scrollToHeading(heading.id, { closeMobile: true })">
429
+ <span v-if="heading.number" :class="['w-5 shrink-0 text-[11px] leading-5 tabular-nums', heading.id === activeHeadingId ? 'text-primary' : 'text-muted-foreground']">{{ heading.number }}</span>
430
+ <span>{{ heading.text }}</span>
431
+ </button>
432
+ </li>
433
+ </ol>
434
+ </nav>
435
+ </SheetContent>
436
+ </Sheet>
437
+ </Teleport>
438
+ </div>
439
439
  </template>
440
440
 
441
441
  <style scoped>
442
442
  .toc-tab-enter-active {
443
- transition: opacity 0.3s var(--ease-out-expo), transform 0.32s var(--ease-out-expo);
443
+ transition: opacity 0.3s var(--ease-out-expo), transform 0.32s var(--ease-out-expo);
444
444
  }
445
445
 
446
446
  .toc-tab-leave-active {
447
- transition: opacity 0.15s var(--ease-snappy), transform 0.15s var(--ease-snappy);
447
+ transition: opacity 0.15s var(--ease-snappy), transform 0.15s var(--ease-snappy);
448
448
  }
449
449
 
450
450
  .toc-tab-enter-from,
451
451
  .toc-tab-leave-to {
452
- opacity: 0;
453
- transform: translateX(-100%);
452
+ opacity: 0;
453
+ transform: translateX(-100%);
454
454
  }
455
455
 
456
456
  @media (prefers-reduced-motion: reduce) {
457
457
 
458
- .toc-tab-enter-active,
459
- .toc-tab-leave-active {
460
- transition: none;
461
- }
458
+ .toc-tab-enter-active,
459
+ .toc-tab-leave-active {
460
+ transition: none;
461
+ }
462
462
  }
463
463
  </style>