@brumaombra/ui-vintage 0.7.13 → 0.8.1

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 (194) hide show
  1. package/COMPONENTS.md +3643 -0
  2. package/README.md +50 -10
  3. package/package.json +30 -8
  4. package/skills/ui-vintage/SKILL.md +42 -0
  5. package/src/components/blog/BlogFAQSection.vue +2 -2
  6. package/src/components/blog/BlogHeaderSection.vue +2 -2
  7. package/src/components/blog/BlogInfoSection.vue +3 -3
  8. package/src/components/blog/BlogProgressBar.vue +7 -6
  9. package/src/components/blog/BlogSectionTitle.vue +1 -1
  10. package/src/components/blog/BlogTagsSection.vue +22 -0
  11. package/src/components/blog/CategoryCard.vue +1 -1
  12. package/src/components/blog/HeaderCarousel.vue +6 -6
  13. package/src/components/blog/PostCard.vue +2 -2
  14. package/src/components/blog/SocialShareSidebar.vue +1 -1
  15. package/src/components/blog/TableOfContents.vue +6 -6
  16. package/src/components/blog/index.ts +1 -0
  17. package/src/components/busy-indicator/busy-indicator-state.ts +3 -2
  18. package/src/components/chip/Chip.vue +9 -5
  19. package/src/components/confirm-dialog/ConfirmDialog.vue +17 -6
  20. package/src/components/confirm-dialog/confirm-dialog-state.ts +4 -3
  21. package/src/components/content/BlogList.vue +3 -3
  22. package/src/components/content/BlogTable.vue +7 -7
  23. package/src/components/content/CodeBlock.vue +4 -4
  24. package/src/components/content/Flow.vue +3 -3
  25. package/src/components/content/ProseHr.vue +2 -2
  26. package/src/components/content/Terminal.vue +4 -4
  27. package/src/components/cookie-consent/CookieConsent.vue +82 -0
  28. package/src/components/cookie-consent/index.ts +3 -0
  29. package/src/components/cookie-consent/useCookieConsent.ts +55 -0
  30. package/src/components/dashboard-shell/DashboardShell.vue +3 -3
  31. package/src/components/empty-state-card/EmptyStateCard.vue +4 -4
  32. package/src/components/error-page/ErrorPage.vue +16 -14
  33. package/src/components/info-card/InfoCard.vue +1 -1
  34. package/src/components/landing/landing-navbar/LandingNavbar.vue +2 -1
  35. package/src/components/load-more-button/LoadMoreButton.vue +2 -2
  36. package/src/components/loading-state-card/LoadingStateCard.vue +4 -4
  37. package/src/components/message-dialog/MessageDialog.vue +4 -4
  38. package/src/components/message-toast/MessageToast.vue +222 -50
  39. package/src/components/message-toast/index.ts +2 -2
  40. package/src/components/message-toast/message-toast-state.ts +107 -81
  41. package/src/components/page-header/PageHeader.vue +1 -1
  42. package/src/components/progress-component/ProgressComponent.vue +4 -4
  43. package/src/components/single-value-card/SingleValueCard.vue +47 -11
  44. package/src/components/theme-selector/ThemeSelector.vue +20 -36
  45. package/src/components/theme-selector/index.ts +3 -1
  46. package/src/components/theme-selector/theme-transition.ts +86 -0
  47. package/src/components/ui/accordion/Accordion.vue +41 -26
  48. package/src/components/ui/alert-dialog/AlertDialogContent.vue +2 -2
  49. package/src/components/ui/alert-dialog/AlertDialogFooter.vue +1 -1
  50. package/src/components/ui/animated-number/AnimatedNumber.vue +102 -0
  51. package/src/components/ui/animated-number/index.ts +1 -0
  52. package/src/components/ui/avatar/Avatar.vue +66 -0
  53. package/src/components/ui/avatar/AvatarFallback.vue +31 -0
  54. package/src/components/ui/avatar/AvatarGroup.vue +75 -0
  55. package/src/components/ui/avatar/AvatarImage.vue +23 -0
  56. package/src/components/ui/avatar/index.ts +47 -0
  57. package/src/components/ui/badge/Badge.vue +13 -2
  58. package/src/components/ui/breadcrumb/Breadcrumb.vue +4 -1
  59. package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +7 -1
  60. package/src/components/ui/busy/Busy.vue +45 -20
  61. package/src/components/ui/button/Button.vue +20 -3
  62. package/src/components/ui/button/index.ts +12 -2
  63. package/src/components/ui/card/Card.vue +21 -8
  64. package/src/components/ui/card/CardText.vue +1 -1
  65. package/src/components/ui/checkbox/Checkbox.vue +48 -0
  66. package/src/components/ui/checkbox/index.ts +1 -0
  67. package/src/components/ui/collapsible/Collapsible.vue +22 -0
  68. package/src/components/ui/collapsible/CollapsibleContent.vue +22 -0
  69. package/src/components/ui/collapsible/CollapsibleTrigger.vue +19 -0
  70. package/src/components/ui/collapsible/index.ts +3 -0
  71. package/src/components/ui/combobox/Combobox.vue +24 -0
  72. package/src/components/ui/combobox/ComboboxAnchor.vue +39 -0
  73. package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
  74. package/src/components/ui/combobox/ComboboxGroup.vue +26 -0
  75. package/src/components/ui/combobox/ComboboxInput.vue +28 -0
  76. package/src/components/ui/combobox/ComboboxItem.vue +39 -0
  77. package/src/components/ui/combobox/ComboboxList.vue +43 -0
  78. package/src/components/ui/combobox/ComboboxSelect.vue +162 -0
  79. package/src/components/ui/combobox/ComboboxSeparator.vue +16 -0
  80. package/src/components/ui/combobox/ComboboxTrigger.vue +28 -0
  81. package/src/components/ui/combobox/index.ts +11 -0
  82. package/src/components/ui/combobox/types.ts +16 -0
  83. package/src/components/ui/command/CommandDialog.vue +18 -16
  84. package/src/components/ui/command/CommandGroup.vue +1 -1
  85. package/src/components/ui/command/CommandInput.vue +2 -2
  86. package/src/components/ui/command/CommandItem.vue +7 -5
  87. package/src/components/ui/data-table/DataTable.vue +230 -0
  88. package/src/components/ui/data-table/index.ts +20 -0
  89. package/src/components/ui/date-picker/DatePicker.vue +11 -15
  90. package/src/components/ui/date-time-picker/DateTimePicker.vue +2 -7
  91. package/src/components/ui/dialog/DialogContent.vue +9 -4
  92. package/src/components/ui/dialog/DialogOverlay.vue +1 -1
  93. package/src/components/ui/dialog/DialogScrollContent.vue +10 -5
  94. package/src/components/ui/dropdown-menu/DropdownMenu.vue +18 -0
  95. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +34 -0
  96. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -0
  97. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +13 -0
  98. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +34 -0
  99. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +26 -0
  100. package/src/components/ui/dropdown-menu/DropdownMenuPortal.vue +13 -0
  101. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +18 -0
  102. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +32 -0
  103. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +16 -0
  104. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +15 -0
  105. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
  106. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +39 -0
  107. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +36 -0
  108. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +15 -0
  109. package/src/components/ui/dropdown-menu/classes.ts +24 -0
  110. package/src/components/ui/dropdown-menu/index.ts +15 -0
  111. package/src/components/ui/field/FieldDescription.vue +1 -1
  112. package/src/components/ui/field/FieldError.vue +4 -1
  113. package/src/components/ui/file-dropzone/FileDropzone.vue +250 -0
  114. package/src/components/ui/file-dropzone/index.ts +35 -0
  115. package/src/components/ui/hover-card/HoverCard.vue +21 -0
  116. package/src/components/ui/hover-card/HoverCardContent.vue +33 -0
  117. package/src/components/ui/hover-card/HoverCardTrigger.vue +13 -0
  118. package/src/components/ui/hover-card/index.ts +3 -0
  119. package/src/components/ui/input/Input.vue +5 -5
  120. package/src/components/ui/kbd/Kbd.vue +21 -0
  121. package/src/components/ui/kbd/KbdGroup.vue +72 -0
  122. package/src/components/ui/kbd/index.ts +2 -0
  123. package/src/components/ui/number-field/NumberField.vue +130 -0
  124. package/src/components/ui/number-field/index.ts +1 -0
  125. package/src/components/ui/pagination/Pagination.vue +27 -0
  126. package/src/components/ui/pagination/PaginationContent.vue +20 -0
  127. package/src/components/ui/pagination/PaginationEllipsis.vue +31 -0
  128. package/src/components/ui/pagination/PaginationFirst.vue +32 -0
  129. package/src/components/ui/pagination/PaginationItem.vue +41 -0
  130. package/src/components/ui/pagination/PaginationLast.vue +32 -0
  131. package/src/components/ui/pagination/PaginationNext.vue +32 -0
  132. package/src/components/ui/pagination/PaginationPrevious.vue +32 -0
  133. package/src/components/ui/pagination/SimplePagination.vue +49 -0
  134. package/src/components/ui/pagination/index.ts +9 -0
  135. package/src/components/ui/pin-input/PinInput.vue +48 -0
  136. package/src/components/ui/pin-input/PinInputInput.vue +49 -0
  137. package/src/components/ui/pin-input/context.ts +8 -0
  138. package/src/components/ui/pin-input/index.ts +2 -0
  139. package/src/components/ui/popover/PopoverContent.vue +1 -1
  140. package/src/components/ui/progress/Progress.vue +22 -7
  141. package/src/components/ui/radio-group/RadioGroup.vue +27 -0
  142. package/src/components/ui/radio-group/RadioGroupCard.vue +56 -0
  143. package/src/components/ui/radio-group/RadioGroupItem.vue +40 -0
  144. package/src/components/ui/radio-group/index.ts +3 -0
  145. package/src/components/ui/select/SelectContent.vue +1 -1
  146. package/src/components/ui/select/SelectItem.vue +2 -2
  147. package/src/components/ui/select/SelectItemContent.vue +1 -1
  148. package/src/components/ui/select/SelectTrigger.vue +6 -6
  149. package/src/components/ui/select/SelectValueContent.vue +1 -1
  150. package/src/components/ui/sheet/SheetContent.vue +5 -5
  151. package/src/components/ui/sheet/SheetOverlay.vue +1 -1
  152. package/src/components/ui/sidebar/Sidebar.vue +7 -5
  153. package/src/components/ui/sidebar/SidebarRail.vue +3 -1
  154. package/src/components/ui/sidebar/SidebarTrigger.vue +3 -1
  155. package/src/components/ui/skeleton/Skeleton.vue +4 -4
  156. package/src/components/ui/slider/Slider.vue +8 -6
  157. package/src/components/ui/spinner/Spinner.vue +47 -0
  158. package/src/components/ui/spinner/index.ts +1 -0
  159. package/src/components/ui/stepper/SimpleStepper.vue +56 -0
  160. package/src/components/ui/stepper/Stepper.vue +30 -0
  161. package/src/components/ui/stepper/StepperDescription.vue +20 -0
  162. package/src/components/ui/stepper/StepperIndicator.vue +41 -0
  163. package/src/components/ui/stepper/StepperItem.vue +21 -0
  164. package/src/components/ui/stepper/StepperSeparator.vue +34 -0
  165. package/src/components/ui/stepper/StepperTitle.vue +20 -0
  166. package/src/components/ui/stepper/StepperTrigger.vue +20 -0
  167. package/src/components/ui/stepper/index.ts +17 -0
  168. package/src/components/ui/switch/Switch.vue +8 -6
  169. package/src/components/ui/tabs/TabsContent.vue +1 -1
  170. package/src/components/ui/tabs/TabsList.vue +13 -5
  171. package/src/components/ui/tabs/TabsTrigger.vue +5 -3
  172. package/src/components/ui/tags-input/TagsInput.vue +49 -0
  173. package/src/components/ui/tags-input/TagsInputInput.vue +23 -0
  174. package/src/components/ui/tags-input/TagsInputItem.vue +35 -0
  175. package/src/components/ui/tags-input/TagsInputItemDelete.vue +31 -0
  176. package/src/components/ui/tags-input/TagsInputItemText.vue +21 -0
  177. package/src/components/ui/tags-input/index.ts +5 -0
  178. package/src/components/ui/textarea/Textarea.vue +4 -5
  179. package/src/components/ui/toggle-group/Toggle.vue +33 -0
  180. package/src/components/ui/toggle-group/ToggleGroup.vue +93 -0
  181. package/src/components/ui/toggle-group/ToggleGroupItem.vue +34 -0
  182. package/src/components/ui/toggle-group/index.ts +69 -0
  183. package/src/components/ui/tooltip/TooltipContent.vue +1 -1
  184. package/src/i18n/de.json +83 -0
  185. package/src/i18n/en.json +83 -0
  186. package/src/i18n/es.json +83 -0
  187. package/src/i18n/fr.json +83 -0
  188. package/src/i18n/it.json +83 -0
  189. package/src/i18n/ja.json +83 -0
  190. package/src/i18n/pt.json +83 -0
  191. package/src/i18n/ru.json +83 -0
  192. package/src/i18n/zh.json +83 -0
  193. package/src/lib/color-tokens.ts +14 -14
  194. package/src/styles.css +289 -5
@@ -1,12 +1,23 @@
1
1
  <script setup lang="ts">
2
2
  import { HugeiconsIcon } from '@hugeicons/vue';
3
- import { computed } from 'vue';
3
+ import { computed, ref } from 'vue';
4
4
  import { InformationCircleIcon } from '@hugeicons/core-free-icons';
5
5
  import { AlertDialog, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '../ui/alert-dialog';
6
6
  import { Button } from '../ui/button';
7
7
  import { cancelActiveConfirmDialog, closeConfirmDialog, confirmActiveDialog, confirmDialogState } from './confirm-dialog-state';
8
8
 
9
9
  const currentDialog = computed(() => confirmDialogState.current);
10
+ const pendingAction = ref<'confirm' | 'cancel' | null>(null);
11
+
12
+ // Run an action while showing a spinner on the button that triggered it
13
+ const runAction = async (action: 'confirm' | 'cancel') => {
14
+ pendingAction.value = action;
15
+ try {
16
+ await (action === 'confirm' ? confirmActiveDialog() : cancelActiveConfirmDialog());
17
+ } finally {
18
+ pendingAction.value = null;
19
+ }
20
+ };
10
21
 
11
22
  // Close the dialog when the shell requests it
12
23
  const handleOpenChange = (open: boolean) => {
@@ -24,9 +35,9 @@ const handleOpenChange = (open: boolean) => {
24
35
  <div class="px-5 pb-5 pt-5 sm:px-6 sm:pb-4 sm:pt-6">
25
36
  <div class="flex flex-col gap-4 sm:flex-row sm:items-start">
26
37
  <!-- Dialog icon -->
27
- <div class="flex size-12 shrink-0 self-center items-center justify-center rounded border border-(--border-light) bg-(--bg-surface-light) sm:size-10 sm:self-start dark:border-(--border-dark) dark:bg-(--bg-surface-dark)">
28
- <HugeiconsIcon v-if="currentDialog.options.icon" :icon="currentDialog.options.icon" :stroke-width="1.8" class="size-6 text-(--button-primary-light) dark:text-(--button-primary-dark)" />
29
- <HugeiconsIcon v-else :icon="InformationCircleIcon" :stroke-width="1.8" class="size-6 text-(--button-primary-light) dark:text-(--button-primary-dark)" />
38
+ <div class="flex size-12 shrink-0 self-center items-center justify-center rounded border border-primary/30 bg-primary/10 sm:size-10 sm:self-start">
39
+ <HugeiconsIcon v-if="currentDialog.options.icon" :icon="currentDialog.options.icon" :stroke-width="1.8" class="size-6 animate-uv-pop text-primary [animation-delay:140ms]" />
40
+ <HugeiconsIcon v-else :icon="InformationCircleIcon" :stroke-width="1.8" class="size-6 animate-uv-pop text-primary [animation-delay:140ms]" />
30
41
  </div>
31
42
 
32
43
  <!-- Dialog header -->
@@ -44,13 +55,13 @@ const handleOpenChange = (open: boolean) => {
44
55
  <!-- Dialog actions -->
45
56
  <AlertDialogFooter>
46
57
  <!-- Cancel button -->
47
- <Button :variant="currentDialog.options.cancelButtonType" :disabled="confirmDialogState.busy" @click="cancelActiveConfirmDialog">
58
+ <Button :variant="currentDialog.options.cancelButtonType" :disabled="confirmDialogState.busy && pendingAction !== 'cancel'" :loading="pendingAction === 'cancel'" @click="runAction('cancel')">
48
59
  <HugeiconsIcon v-if="currentDialog.options.cancelButtonIcon" :icon="currentDialog.options.cancelButtonIcon" class="size-4" />
49
60
  {{ currentDialog.options.cancelText }}
50
61
  </Button>
51
62
 
52
63
  <!-- Confirm button -->
53
- <Button :variant="currentDialog.options.confirmButtonType" :disabled="confirmDialogState.busy" @click="confirmActiveDialog">
64
+ <Button :variant="currentDialog.options.confirmButtonType" :disabled="confirmDialogState.busy && pendingAction !== 'confirm'" :loading="pendingAction === 'confirm'" @click="runAction('confirm')">
54
65
  <HugeiconsIcon v-if="currentDialog.options.confirmButtonIcon" :icon="currentDialog.options.confirmButtonIcon" class="size-4" />
55
66
  {{ currentDialog.options.confirmText }}
56
67
  </Button>
@@ -1,6 +1,7 @@
1
1
  import { h, markRaw, reactive, render } from 'vue';
2
2
  import type { ButtonVariants } from '../ui/button';
3
3
  import type { HugeiconsIconDefinition } from '../../lib/common-types';
4
+ import { getUiVintageRuntimeMessage } from '../../lib/i18n';
4
5
 
5
6
  export type DialogButtonVariant = NonNullable<ButtonVariants['variant']>;
6
7
  export type { HugeiconsIconDefinition };
@@ -183,10 +184,10 @@ export const showConfirmDialog = (options: ShowConfirmDialogOptions) => {
183
184
  enqueueConfirmDialog({
184
185
  id: nextConfirmDialogId += 1,
185
186
  options: {
186
- title: options.title ?? 'Confirm',
187
+ title: options.title ?? getUiVintageRuntimeMessage('uiVintage.messageDialog.confirm', 'Confirm'),
187
188
  message: options.message,
188
- confirmText: options.confirmText ?? 'Confirm',
189
- cancelText: options.cancelText ?? 'Cancel',
189
+ confirmText: options.confirmText ?? getUiVintageRuntimeMessage('uiVintage.buttons.confirm', 'Confirm'),
190
+ cancelText: options.cancelText ?? getUiVintageRuntimeMessage('uiVintage.buttons.cancel', 'Cancel'),
190
191
  confirmButtonType: options.confirmButtonType ?? 'primary',
191
192
  cancelButtonType: options.cancelButtonType ?? 'secondary',
192
193
  icon: markIcon(options.icon),
@@ -14,12 +14,12 @@ const props = withDefaults(defineProps<{
14
14
 
15
15
  // Icon container classes
16
16
  const getIconContainerClasses = () => {
17
- return 'flex items-center justify-center w-8 h-8 bg-(--bg-card-light) dark:bg-(--bg-card-dark) border border-(--border-light) dark:border-(--border-dark) rounded mr-4 shrink-0';
17
+ return 'flex items-center justify-center w-8 h-8 bg-card border border-border rounded mr-4 shrink-0';
18
18
  };
19
19
 
20
20
  // Icon color classes
21
21
  const getIconClasses = () => {
22
- return 'text-(--text-secondary-light) dark:text-(--text-secondary-dark) text-xs';
22
+ return 'text-muted-foreground text-xs';
23
23
  };
24
24
  </script>
25
25
 
@@ -51,7 +51,7 @@ const getIconClasses = () => {
51
51
 
52
52
  <!-- Content -->
53
53
  <div class="flex-1">
54
- <div class="text-(--text-primary-light) dark:text-(--text-primary-dark) leading-relaxed">
54
+ <div class="text-foreground leading-relaxed">
55
55
  {{ item }}
56
56
  </div>
57
57
  </div>
@@ -15,16 +15,16 @@ const props = withDefaults(defineProps<{
15
15
 
16
16
  // Header cell classes
17
17
  const getHeaderClasses = (colIndex: number) => {
18
- const baseClasses = 'text-left font-semibold text-(--text-primary-light) dark:text-(--text-primary-dark)';
19
- const highlightClass = props.highlightCol === colIndex ? 'bg-(--bg-selected-light) dark:bg-(--bg-selected-dark)' : '';
18
+ const baseClasses = 'text-left font-semibold text-foreground';
19
+ const highlightClass = props.highlightCol === colIndex ? 'bg-accent' : '';
20
20
  return [baseClasses, highlightClass];
21
21
  };
22
22
 
23
23
  // Body cell classes
24
24
  const getCellClasses = (colIndex: number) => {
25
- const baseClasses = 'align-top text-(--text-primary-light) dark:text-(--text-primary-dark) leading-relaxed whitespace-normal';
26
- const firstColClass = colIndex === 0 ? 'font-semibold text-(--text-secondary-light) dark:text-(--text-secondary-dark)' : '';
27
- const highlightClass = props.highlightCol === colIndex ? 'bg-(--bg-selected-light) dark:bg-(--bg-selected-dark)' : '';
25
+ const baseClasses = 'align-top text-foreground leading-relaxed whitespace-normal';
26
+ const firstColClass = colIndex === 0 ? 'font-semibold text-muted-foreground' : '';
27
+ const highlightClass = props.highlightCol === colIndex ? 'bg-accent' : '';
28
28
  return [baseClasses, firstColClass, highlightClass];
29
29
  };
30
30
  </script>
@@ -37,7 +37,7 @@ const getCellClasses = (colIndex: number) => {
37
37
  <Table class="w-full text-sm text-left">
38
38
  <!-- Table header -->
39
39
  <TableHeader>
40
- <TableRow class="bg-transparent text-sm text-(--text-secondary-light) dark:text-(--text-secondary-dark) hover:bg-transparent">
40
+ <TableRow class="bg-transparent text-sm text-muted-foreground hover:bg-transparent">
41
41
  <TableHead v-for="(header, colIndex) in props.headers" :key="colIndex" :class="getHeaderClasses(colIndex)">
42
42
  {{ header }}
43
43
  </TableHead>
@@ -46,7 +46,7 @@ const getCellClasses = (colIndex: number) => {
46
46
 
47
47
  <!-- Table body -->
48
48
  <TableBody>
49
- <TableRow v-for="(row, rowIndex) in props.rows" :key="rowIndex" class="text-(--text-secondary-light) dark:text-(--text-secondary-dark) hover:bg-transparent">
49
+ <TableRow v-for="(row, rowIndex) in props.rows" :key="rowIndex" class="text-muted-foreground hover:bg-transparent">
50
50
  <TableCell v-for="(cell, colIndex) in row" :key="colIndex" :class="getCellClasses(colIndex)">
51
51
  {{ cell }}
52
52
  </TableCell>
@@ -76,12 +76,12 @@ const copyCode = async () => {
76
76
 
77
77
  <template>
78
78
  <div class="not-prose my-6 sm:my-8">
79
- <Card class="gap-0! sm:gap-0! overflow-hidden p-0! text-(--text-primary-light) dark:text-(--text-primary-dark)">
79
+ <Card class="gap-0! sm:gap-0! overflow-hidden p-0! text-foreground">
80
80
  <!-- Code header -->
81
81
  <div class="flex items-center justify-between gap-3 border-b border-(--border-light) bg-(--bg-card-light) px-3 py-2 sm:px-4 dark:border-(--border-dark) dark:bg-(--bg-card-dark)">
82
82
  <div class="flex min-w-0 items-center gap-2">
83
- <HugeiconsIcon :icon="CodeIcon" class="size-4 shrink-0 text-(--text-secondary-light) dark:text-(--text-secondary-dark)" />
84
- <span class="truncate text-xs font-semibold text-(--text-secondary-light) dark:text-(--text-secondary-dark)">{{ resolvedTitle }}</span>
83
+ <HugeiconsIcon :icon="CodeIcon" class="size-4 shrink-0 text-muted-foreground" />
84
+ <span class="truncate text-xs font-semibold text-muted-foreground">{{ resolvedTitle }}</span>
85
85
  </div>
86
86
 
87
87
  <!-- Copy code button -->
@@ -98,7 +98,7 @@ const copyCode = async () => {
98
98
  </div>
99
99
 
100
100
  <!-- Code content -->
101
- <div v-if="highlightedCode" class="code-block-content overflow-x-auto bg-(--bg-surface-light) dark:bg-(--bg-surface-dark)" v-html="highlightedCode" />
101
+ <div v-if="highlightedCode" class="code-block-content overflow-x-auto bg-surface" v-html="highlightedCode" />
102
102
  <pre v-else class="code-block-content overflow-x-auto bg-(--bg-surface-light) px-4 py-3 text-xs leading-6 dark:bg-(--bg-surface-dark) sm:px-5 sm:py-4 sm:text-sm"><code>{{ displayedCode }}</code></pre>
103
103
  </Card>
104
104
  </div>
@@ -65,7 +65,7 @@ const isVertical = () => {
65
65
  <Card class="flex-1 overflow-hidden md:flex md:items-center">
66
66
  <CardContent class="w-full gap-3 md:justify-center">
67
67
  <!-- Flow item header -->
68
- <div class="inline-flex items-center gap-3 text-xs font-semibold text-(--text-primary-light) dark:text-(--text-primary-dark)">
68
+ <div class="inline-flex items-center gap-3 text-xs font-semibold text-foreground">
69
69
  <!-- Flow item number -->
70
70
  <span class="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded border border-(--border-light) bg-(--bg-card-light) text-xs font-semibold text-(--text-secondary-light) dark:border-(--border-dark) dark:bg-(--bg-card-dark) dark:text-(--text-secondary-dark)">
71
71
  {{ index + 1 }}
@@ -76,14 +76,14 @@ const isVertical = () => {
76
76
  </div>
77
77
 
78
78
  <!-- Flow item description -->
79
- <p v-if="getItemDescription(item)" class="text-xs leading-6 text-(--text-secondary-light) dark:text-(--text-secondary-dark) md:leading-5">
79
+ <p v-if="getItemDescription(item)" class="text-xs leading-6 text-muted-foreground md:leading-5">
80
80
  {{ getItemDescription(item) }}
81
81
  </p>
82
82
  </CardContent>
83
83
  </Card>
84
84
 
85
85
  <!-- Flow item separator -->
86
- <div v-if="index < props.items.length - 1" class="flex items-center justify-center text-(--text-secondary-light) dark:text-(--text-secondary-dark)">
86
+ <div v-if="index < props.items.length - 1" class="flex items-center justify-center text-muted-foreground">
87
87
  <!-- Flow item separator for vertical orientation -->
88
88
  <HugeiconsIcon v-if="isVertical()" :icon="ArrowDown01Icon" class="size-5 shrink-0" />
89
89
 
@@ -18,8 +18,8 @@ const props = withDefaults(defineProps<{
18
18
  <div class="h-px flex-1 bg-linear-to-r from-transparent via-(--border-light) to-(--border-light) dark:via-(--border-dark) dark:to-(--border-dark)" />
19
19
 
20
20
  <!-- Icon in the middle -->
21
- <div class="flex h-9 w-9 items-center justify-center rounded bg-(--bg-card-light) dark:bg-(--bg-card-dark) border border-(--border-light) dark:border-(--border-dark)">
22
- <HugeiconsIcon :icon="props.icon" class="size-4 text-(--text-secondary-light) dark:text-(--text-secondary-dark)" />
21
+ <div class="flex h-9 w-9 items-center justify-center rounded bg-card border border-border">
22
+ <HugeiconsIcon :icon="props.icon" class="size-4 text-muted-foreground" />
23
23
  </div>
24
24
 
25
25
  <!-- Right line -->
@@ -42,12 +42,12 @@ const copyCommands = async () => {
42
42
 
43
43
  <template>
44
44
  <div class="not-prose my-6 sm:my-8">
45
- <Card class="gap-0! sm:gap-0! overflow-hidden p-0! text-(--text-primary-light) dark:text-(--text-primary-dark)">
45
+ <Card class="gap-0! sm:gap-0! overflow-hidden p-0! text-foreground">
46
46
  <!-- Terminal header -->
47
47
  <div class="flex items-center justify-between gap-3 border-b border-(--border-light) bg-(--bg-card-light) px-3 py-2 sm:px-4 dark:border-(--border-dark) dark:bg-(--bg-card-dark)">
48
48
  <div class="flex min-w-0 items-center gap-2">
49
- <HugeiconsIcon :icon="TerminalIcon" class="size-4 shrink-0 text-(--text-secondary-light) dark:text-(--text-secondary-dark)" />
50
- <span class="truncate text-xs font-semibold text-(--text-secondary-light) dark:text-(--text-secondary-dark)">{{ resolvedTitle }}</span>
49
+ <HugeiconsIcon :icon="TerminalIcon" class="size-4 shrink-0 text-muted-foreground" />
50
+ <span class="truncate text-xs font-semibold text-muted-foreground">{{ resolvedTitle }}</span>
51
51
  </div>
52
52
 
53
53
  <!-- Copy command button -->
@@ -64,7 +64,7 @@ const copyCommands = async () => {
64
64
  </div>
65
65
 
66
66
  <!-- Terminal commands -->
67
- <pre class="overflow-x-auto bg-(--bg-surface-light) px-4 py-3 text-xs leading-6 dark:bg-(--bg-surface-dark) sm:px-5 sm:py-4 sm:text-sm"><code><span v-for="(command, index) in props.commands" :key="`${command}-${index}`" class="flex min-w-max"><span class="mr-3 select-none text-(--text-secondary-light) dark:text-(--text-secondary-dark)" aria-hidden="true">{{ props.prompt }}</span><span>{{ command }}</span></span></code></pre>
67
+ <pre class="overflow-x-auto bg-(--bg-surface-light) px-4 py-3 text-xs leading-6 dark:bg-(--bg-surface-dark) sm:px-5 sm:py-4 sm:text-sm"><code><span v-for="(command, index) in props.commands" :key="`${command}-${index}`" class="flex min-w-max"><span class="mr-3 select-none text-muted-foreground" aria-hidden="true">{{ props.prompt }}</span><span>{{ command }}</span></span></code></pre>
68
68
  </Card>
69
69
  </div>
70
70
  </template>
@@ -0,0 +1,82 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref, type HTMLAttributes } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+ import { Button } from '../ui/button';
5
+ import { Card } from '../ui/card';
6
+ import { cn } from '../../lib/utils';
7
+ import { useCookieConsent } from './useCookieConsent';
8
+
9
+ // Extra attributes go on the banner (the root is the Teleport)
10
+ defineOptions({ inheritAttrs: false });
11
+
12
+ // Props
13
+ const props = withDefaults(defineProps<{
14
+ storageKey?: string;
15
+ message?: string;
16
+ policyTo?: string;
17
+ policyLabel?: string;
18
+ acceptLabel?: string;
19
+ declineLabel?: string;
20
+ class?: HTMLAttributes['class'];
21
+ }>(), {
22
+ storageKey: 'cookieConsent',
23
+ message: undefined,
24
+ policyTo: undefined,
25
+ policyLabel: undefined,
26
+ acceptLabel: undefined,
27
+ declineLabel: undefined,
28
+ class: undefined
29
+ });
30
+
31
+ // Emits
32
+ const emit = defineEmits<{
33
+ accept: [];
34
+ decline: [];
35
+ }>();
36
+
37
+ const { t } = useI18n();
38
+ const { consent, accept, decline } = useCookieConsent(props.storageKey);
39
+ const hasMounted = ref(false);
40
+
41
+ // Shown in the browser only, until the visitor chooses (the stored choice is not known on the server)
42
+ const isVisible = computed(() => hasMounted.value && consent.value === null);
43
+
44
+ // Texts, with translated defaults
45
+ const resolvedMessage = computed(() => props.message || t('uiVintage.cookieConsent.message'));
46
+ const resolvedPolicyLabel = computed(() => props.policyLabel || t('uiVintage.cookieConsent.policy'));
47
+ const resolvedAcceptLabel = computed(() => props.acceptLabel || t('uiVintage.cookieConsent.accept'));
48
+ const resolvedDeclineLabel = computed(() => props.declineLabel || t('uiVintage.cookieConsent.decline'));
49
+
50
+ // Save the choice and notify
51
+ const handleAccept = () => {
52
+ accept();
53
+ emit('accept');
54
+ };
55
+ const handleDecline = () => {
56
+ decline();
57
+ emit('decline');
58
+ };
59
+
60
+ // Wait for the browser before showing the banner
61
+ onMounted(() => {
62
+ hasMounted.value = true;
63
+ });
64
+ </script>
65
+
66
+ <template>
67
+ <!-- Rendered in the body: a transformed or filtered ancestor (page transitions, reveal animations) would otherwise pin the fixed banner to itself instead of the viewport -->
68
+ <Teleport to="body">
69
+ <div v-if="isVisible" v-bind="$attrs" data-slot="cookie-consent" role="region" :aria-label="t('uiVintage.cookieConsent.region')" :class="cn('fixed inset-x-4 bottom-4 z-50 sm:left-auto sm:right-4 sm:max-w-md', props.class)">
70
+ <Card class="gap-3! p-4 shadow-elevated-sm">
71
+ <p class="text-sm text-foreground">
72
+ {{ resolvedMessage }}
73
+ <NuxtLinkLocale v-if="props.policyTo" :to="props.policyTo" class="font-bold underline underline-offset-2">{{ resolvedPolicyLabel }}</NuxtLinkLocale>
74
+ </p>
75
+ <div class="flex flex-wrap justify-end gap-2">
76
+ <Button variant="secondary" size="sm" @click="handleDecline">{{ resolvedDeclineLabel }}</Button>
77
+ <Button variant="primary" size="sm" @click="handleAccept">{{ resolvedAcceptLabel }}</Button>
78
+ </div>
79
+ </Card>
80
+ </div>
81
+ </Teleport>
82
+ </template>
@@ -0,0 +1,3 @@
1
+ export { default } from './CookieConsent.vue';
2
+ export { default as CookieConsent } from './CookieConsent.vue';
3
+ export { useCookieConsent, type CookieConsentValue } from './useCookieConsent';
@@ -0,0 +1,55 @@
1
+ import { ref, type Ref } from 'vue';
2
+
3
+ // The visitor's choice: 'granted', 'denied' or null (not asked yet)
4
+ export type CookieConsentValue = 'granted' | 'denied' | null;
5
+
6
+ // One shared state per storage key, so the banner and the code loading the scripts stay in sync (browser only)
7
+ const consentStates = new Map<string, Ref<CookieConsentValue>>();
8
+
9
+ // Read the stored choice (storage can be unavailable, e.g. blocked in private windows)
10
+ const readStoredConsent = (storageKey: string): CookieConsentValue => {
11
+ try {
12
+ const value = localStorage.getItem(storageKey);
13
+ return value === 'granted' || value === 'denied' ? value : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ };
18
+
19
+ // Cookie consent state: gate optional scripts (analytics, ads) on `consent.value === 'granted'` and call `reset()` to ask again
20
+ export const useCookieConsent = (storageKey = 'cookieConsent') => {
21
+ // On the server there is no stored choice (and no state must be shared between requests)
22
+ if (typeof window === 'undefined') {
23
+ const consent = ref<CookieConsentValue>(null);
24
+ const noop = () => {};
25
+ return { consent, accept: noop, decline: noop, reset: noop };
26
+ }
27
+
28
+ // Shared state for this key
29
+ let consent = consentStates.get(storageKey);
30
+ if (!consent) {
31
+ consent = ref(readStoredConsent(storageKey));
32
+ consentStates.set(storageKey, consent);
33
+ }
34
+
35
+ // Save a choice (null removes it, so the banner asks again)
36
+ const state = consent;
37
+ const setConsent = (value: CookieConsentValue) => {
38
+ state.value = value;
39
+ try {
40
+ if (value) {
41
+ localStorage.setItem(storageKey, value);
42
+ } else {
43
+ localStorage.removeItem(storageKey);
44
+ }
45
+ } catch { }
46
+ };
47
+
48
+ // Return the state and the actions
49
+ return {
50
+ consent: state,
51
+ accept: () => setConsent('granted'),
52
+ decline: () => setConsent('denied'),
53
+ reset: () => setConsent(null)
54
+ };
55
+ };
@@ -107,10 +107,10 @@ const getSidebarItemLinkProps = (item: NonNullable<typeof props.sidebarSections>
107
107
  <SidebarGroupContent>
108
108
  <SidebarMenu :class="cn('gap-3', props.compact && 'gap-2')">
109
109
  <SidebarMenuItem v-for="item in section.items" :key="item.id">
110
- <SidebarMenuButton as-child :is-active="item.active" :class="cn('group h-auto min-h-12 rounded border border-(--border-light) px-3 py-2.5 text-(--text-secondary-light) transition-all duration-200 hover:border-(--border-hover-light) hover:bg-(--bg-selected-light) hover:text-(--text-primary-light) data-[active=true]:border-(--border-hover-light) data-[active=true]:bg-(--bg-selected-light) data-[active=true]:text-(--text-primary-light) dark:border-(--border-dark) dark:text-(--text-secondary-dark) dark:hover:border-(--border-hover-dark) dark:hover:bg-(--bg-selected-dark) dark:hover:text-(--text-primary-dark) dark:data-[active=true]:border-(--border-hover-dark) dark:data-[active=true]:bg-(--bg-selected-dark) dark:data-[active=true]:text-(--text-primary-dark) sm:px-4 sm:py-3', item.description ? 'items-start' : 'items-center')">
110
+ <SidebarMenuButton as-child :is-active="item.active" :class="cn('group/nav relative h-auto min-h-12 overflow-hidden rounded border border-border px-3 py-2.5 text-muted-foreground [transition:color_150ms,background-color_150ms,border-color_150ms,scale_300ms_var(--ease-spring)] hover:border-border-strong hover:bg-accent hover:text-foreground active:scale-[0.98] data-[active=true]:border-border-strong data-[active=true]:bg-accent data-[active=true]:text-foreground sm:px-4 sm:py-3', 'before:absolute before:inset-y-2 before:left-0 before:w-[3px] before:origin-center before:scale-y-0 before:rounded-r-full before:bg-primary before:transition-transform before:duration-[420ms] before:ease-spring data-[active=true]:before:scale-y-100', item.description ? 'items-start' : 'items-center')">
111
111
  <component :is="props.sidebarLinkComponent" v-bind="getSidebarItemLinkProps(item)">
112
112
  <!-- Icon -->
113
- <HugeiconsIcon v-if="item.icon" :icon="item.icon" :stroke-width="1.8" :class="cn('shrink-0 opacity-90 transition-opacity duration-200 group-hover:opacity-100', item.description ? 'mt-0.5 size-5 sm:h-5 sm:w-5' : 'size-5 self-center sm:h-6 sm:w-6')" />
113
+ <HugeiconsIcon v-if="item.icon" :icon="item.icon" :stroke-width="1.8" :class="cn('shrink-0 opacity-90 transition-[opacity,translate,color] duration-300 ease-spring group-hover/nav:translate-x-0.5 group-hover/nav:opacity-100 group-data-[active=true]/nav:text-primary', item.description ? 'mt-0.5 size-5 sm:h-5 sm:w-5' : 'size-5 self-center sm:h-6 sm:w-6')" />
114
114
 
115
115
  <!-- Label and optional description -->
116
116
  <div :class="cn('min-w-0 flex-1 text-left leading-tight', item.description ? 'grid' : 'flex items-center')">
@@ -146,7 +146,7 @@ const getSidebarItemLinkProps = (item: NonNullable<typeof props.sidebarSections>
146
146
  <BackgroundGrid v-if="props.showBackground" />
147
147
 
148
148
  <!-- Topbar -->
149
- <header :class="cn('sticky top-0 z-20 flex h-16 shrink-0 items-center gap-2 border-b border-sidebar-border bg-sidebar px-4 backdrop-blur', props.topbarClass)">
149
+ <header :class="cn('sticky top-0 z-20 flex h-16 shrink-0 items-center gap-2 border-b border-sidebar-border bg-sidebar/80 px-4 backdrop-blur-md backdrop-saturate-150', props.topbarClass)">
150
150
  <!-- Sidebar trigger -->
151
151
  <SidebarTrigger class="-ml-1" />
152
152
 
@@ -43,19 +43,19 @@ const resolvedDescription = computed(() => props.description
43
43
  <Card class="flex flex-col items-center justify-center px-4 py-8 text-center md:py-12">
44
44
  <CardContent class="flex flex-col items-center justify-center p-0! text-center">
45
45
  <!-- Icon -->
46
- <div class="flex items-center justify-center text-4xl text-(--text-secondary-light) opacity-40 dark:text-(--text-secondary-dark)">
47
- <HugeiconsIcon :icon="props.icon" class="size-10" />
46
+ <div class="flex items-center justify-center text-4xl text-muted-foreground opacity-40">
47
+ <HugeiconsIcon :icon="props.icon" class="size-10 animate-uv-float" />
48
48
  </div>
49
49
 
50
50
  <!-- Title -->
51
51
  <component :is="`h${props.level}`"
52
52
  v-if="resolvedTitle"
53
- class="text-sm font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) md:text-lg">
53
+ class="text-sm font-bold text-foreground md:text-lg">
54
54
  {{ resolvedTitle }}
55
55
  </component>
56
56
 
57
57
  <!-- Description -->
58
- <p v-if="resolvedDescription" class="max-w-md text-xs text-(--text-secondary-light) dark:text-(--text-secondary-dark) md:text-sm">
58
+ <p v-if="resolvedDescription" class="max-w-md text-xs text-muted-foreground md:text-sm">
59
59
  {{ resolvedDescription }}
60
60
  </p>
61
61
 
@@ -2,6 +2,7 @@
2
2
  import { ArrowLeft01Icon } from '@hugeicons/core-free-icons';
3
3
  import { HugeiconsIcon } from '@hugeicons/vue';
4
4
  import { computed, type HTMLAttributes } from 'vue';
5
+ import { useI18n } from 'vue-i18n';
5
6
  import BackgroundGrid from '../background-grid/BackgroundGrid.vue';
6
7
  import { Button } from '../ui/button';
7
8
  import { Card, CardContent, CardFooter } from '../ui/card';
@@ -22,10 +23,8 @@ const props = withDefaults(defineProps<{
22
23
  footerClass?: HTMLAttributes['class'];
23
24
  }>(), {
24
25
  statusCode: 500,
25
- label: 'Error',
26
26
  title: undefined,
27
27
  message: undefined,
28
- actionLabel: 'Go back',
29
28
  showBackground: true,
30
29
  showDefaultAction: true,
31
30
  class: undefined,
@@ -39,23 +38,26 @@ const emit = defineEmits<{
39
38
  action: [];
40
39
  }>();
41
40
 
42
- const resolvedStatusCode = computed(() => String(props.statusCode ?? 500));
41
+ const { t } = useI18n();
42
+ const resolvedStatusCode = computed(() => String(props.statusCode || 500));
43
+ const resolvedLabel = computed(() => props.label || t('uiVintage.messageDialog.error'));
44
+ const resolvedActionLabel = computed(() => props.actionLabel || t('uiVintage.buttons.goBack'));
43
45
 
44
- // Resolve the heading copy.
46
+ // Resolve the heading copy
45
47
  const resolvedTitle = computed(() => {
46
48
  if (props.title) return props.title;
47
- if (resolvedStatusCode.value === '404') return 'Page not found';
48
- return 'Something went wrong';
49
+ if (resolvedStatusCode.value === '404') return t('uiVintage.errorPage.notFound.title');
50
+ return t('uiVintage.errorPage.unexpected.title');
49
51
  });
50
52
 
51
- // Resolve the supporting message.
53
+ // Resolve the supporting message
52
54
  const resolvedMessage = computed(() => {
53
55
  if (props.message) return props.message;
54
- if (resolvedStatusCode.value === '404') return 'The page you requested could not be found or may have been moved.';
55
- return 'An unexpected error interrupted the current flow. Please try again or go back to a safe page.';
56
+ if (resolvedStatusCode.value === '404') return t('uiVintage.errorPage.notFound.message');
57
+ return t('uiVintage.errorPage.unexpected.message');
56
58
  });
57
59
 
58
- // Emit the default action.
60
+ // Emit the default action
59
61
  const handleAction = () => {
60
62
  emit('action');
61
63
  };
@@ -83,8 +85,8 @@ const handleAction = () => {
83
85
  <!-- Error copy -->
84
86
  <div>
85
87
  <!-- Status code -->
86
- <div class="text-sm font-semibold uppercase tracking-[0.24em] text-(--button-primary-light) dark:text-(--button-primary-dark)">
87
- {{ props.label }} {{ resolvedStatusCode }}
88
+ <div class="text-sm font-semibold uppercase tracking-[0.24em] text-primary">
89
+ {{ resolvedLabel }} {{ resolvedStatusCode }}
88
90
  </div>
89
91
 
90
92
  <!-- Title -->
@@ -93,7 +95,7 @@ const handleAction = () => {
93
95
  </h1>
94
96
 
95
97
  <!-- Message -->
96
- <p class="text-sm leading-7 text-(--text-secondary-light) dark:text-(--text-secondary-dark) sm:text-base mt-4">
98
+ <p class="text-sm leading-7 text-muted-foreground sm:text-base mt-4">
97
99
  {{ resolvedMessage }}
98
100
  </p>
99
101
  </div>
@@ -104,7 +106,7 @@ const handleAction = () => {
104
106
  <slot name="actions" :on-action="handleAction">
105
107
  <Button v-if="props.showDefaultAction" variant="primary" @click="handleAction">
106
108
  <HugeiconsIcon :icon="ArrowLeft01Icon" :stroke-width="1.8" class="size-4" />
107
- {{ props.actionLabel }}
109
+ {{ resolvedActionLabel }}
108
110
  </Button>
109
111
  </slot>
110
112
  </CardFooter>
@@ -28,7 +28,7 @@ const props = withDefaults(defineProps<{
28
28
  <div class="flex w-full flex-1 items-center justify-between text-center sm:w-auto sm:text-left">
29
29
  <div class="flex flex-col w-full gap-2 sm:gap-1">
30
30
  <!-- Title -->
31
- <span class="text-base font-bold text-(--text-primary-light) dark:text-(--text-primary-dark)">
31
+ <span class="text-base font-bold text-foreground">
32
32
  {{ props.title }}
33
33
  </span>
34
34
 
@@ -47,8 +47,9 @@ const props = withDefaults(defineProps<{
47
47
  </div>
48
48
  </nav>
49
49
 
50
+ <!-- Progress bar -->
50
51
  <div v-if="props.showProgress" class="absolute inset-x-0 bottom-0 translate-y-full">
51
- <BlogProgressBar aria-label="Blog reading progress" />
52
+ <BlogProgressBar />
52
53
  </div>
53
54
  </header>
54
55
  </template>
@@ -22,8 +22,8 @@ const emits = defineEmits<{
22
22
  }>();
23
23
 
24
24
  const { t } = useI18n();
25
- const resolvedText = computed(() => props.text || t('uiVintage.buttons.loadMore') || 'Load more');
26
- const resolvedLoadingText = computed(() => props.loadingText || t('uiVintage.buttons.loading') || 'Loading');
25
+ const resolvedText = computed(() => props.text || t('uiVintage.buttons.loadMore'));
26
+ const resolvedLoadingText = computed(() => props.loadingText || t('uiVintage.buttons.loading'));
27
27
 
28
28
  // Handle click event
29
29
  const handleClick = () => {
@@ -20,8 +20,8 @@ const props = withDefaults(defineProps<{
20
20
  });
21
21
 
22
22
  const { t } = useI18n();
23
- const resolvedTitle = computed(() => props.title || t('uiVintage.common.loading.title') || 'Loading...');
24
- const resolvedDescription = computed(() => props.description || t('uiVintage.common.loading.description') || 'Please wait a moment while we load the content.');
23
+ const resolvedTitle = computed(() => props.title || t('uiVintage.common.loading.title'));
24
+ const resolvedDescription = computed(() => props.description || t('uiVintage.common.loading.description'));
25
25
  </script>
26
26
 
27
27
  <template>
@@ -35,12 +35,12 @@ const resolvedDescription = computed(() => props.description || t('uiVintage.com
35
35
  <!-- Title -->
36
36
  <component :is="`h${props.level}`"
37
37
  v-if="resolvedTitle"
38
- class="text-sm font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) md:text-lg">
38
+ class="text-sm font-bold text-foreground md:text-lg">
39
39
  {{ resolvedTitle }}
40
40
  </component>
41
41
 
42
42
  <!-- Description -->
43
- <p v-if="resolvedDescription" class="max-w-md text-xs text-(--text-secondary-light) dark:text-(--text-secondary-dark) md:text-sm">
43
+ <p v-if="resolvedDescription" class="max-w-md text-xs text-muted-foreground md:text-sm">
44
44
  {{ resolvedDescription }}
45
45
  </p>
46
46
 
@@ -36,11 +36,11 @@ const defaultMessageIcon = computed(() => {
36
36
  // Resolved container classes for the message icon
37
37
  const messageIconClasses = computed(() => {
38
38
  const current = currentDialog.value;
39
- if (!current) return 'border-(--border) bg-(--bg-surface-light) dark:bg-(--bg-surface-dark)';
39
+ if (!current) return 'border-(--border) bg-surface';
40
40
  if (current.options.type === 'error') return 'border-red-200 bg-red-50 dark:border-red-900/50 dark:bg-red-950/30';
41
41
  if (current.options.type === 'warning') return 'border-amber-200 bg-amber-50 dark:border-amber-900/50 dark:bg-amber-950/30';
42
42
  if (current.options.type === 'success') return 'border-green-200 bg-green-50 dark:border-green-900/50 dark:bg-green-950/30';
43
- return 'border-(--border) bg-(--bg-surface-light) dark:bg-(--bg-surface-dark)';
43
+ return 'border-(--border) bg-surface';
44
44
  });
45
45
 
46
46
  // Resolved glyph color classes
@@ -70,8 +70,8 @@ const handleOpenChange = (open: boolean) => {
70
70
  <div class="flex flex-col gap-4 sm:flex-row sm:items-start">
71
71
  <!-- Dialog icon -->
72
72
  <div class="flex size-12 shrink-0 self-center items-center justify-center rounded border sm:size-10 sm:self-start" :class="messageIconClasses">
73
- <HugeiconsIcon v-if="currentDialog.options.icon" :icon="currentDialog.options.icon" :stroke-width="1.8" class="size-6" :class="messageGlyphClasses" />
74
- <HugeiconsIcon v-else :icon="defaultMessageIcon" :stroke-width="1.8" class="size-6" :class="messageGlyphClasses" />
73
+ <HugeiconsIcon v-if="currentDialog.options.icon" :icon="currentDialog.options.icon" :stroke-width="1.8" class="size-6 animate-uv-pop [animation-delay:140ms]" :class="messageGlyphClasses" />
74
+ <HugeiconsIcon v-else :icon="defaultMessageIcon" :stroke-width="1.8" class="size-6 animate-uv-pop [animation-delay:140ms]" :class="messageGlyphClasses" />
75
75
  </div>
76
76
 
77
77
  <!-- Dialog header -->