@brumaombra/ui-vintage 0.7.13 → 0.8.0
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.
- package/COMPONENTS.md +3602 -0
- package/README.md +49 -9
- package/package.json +29 -8
- package/skills/ui-vintage/SKILL.md +42 -0
- package/src/components/blog/BlogFAQSection.vue +2 -2
- package/src/components/blog/BlogHeaderSection.vue +2 -2
- package/src/components/blog/BlogInfoSection.vue +3 -3
- package/src/components/blog/BlogProgressBar.vue +7 -6
- package/src/components/blog/BlogSectionTitle.vue +1 -1
- package/src/components/blog/BlogTagsSection.vue +22 -0
- package/src/components/blog/CategoryCard.vue +1 -1
- package/src/components/blog/HeaderCarousel.vue +6 -6
- package/src/components/blog/PostCard.vue +2 -2
- package/src/components/blog/SocialShareSidebar.vue +1 -1
- package/src/components/blog/TableOfContents.vue +6 -6
- package/src/components/blog/index.ts +1 -0
- package/src/components/busy-indicator/busy-indicator-state.ts +3 -2
- package/src/components/chip/Chip.vue +9 -5
- package/src/components/confirm-dialog/ConfirmDialog.vue +17 -6
- package/src/components/confirm-dialog/confirm-dialog-state.ts +4 -3
- package/src/components/content/BlogList.vue +3 -3
- package/src/components/content/BlogTable.vue +7 -7
- package/src/components/content/CodeBlock.vue +4 -4
- package/src/components/content/Flow.vue +3 -3
- package/src/components/content/ProseHr.vue +2 -2
- package/src/components/content/Terminal.vue +4 -4
- package/src/components/dashboard-shell/DashboardShell.vue +3 -3
- package/src/components/empty-state-card/EmptyStateCard.vue +4 -4
- package/src/components/error-page/ErrorPage.vue +16 -14
- package/src/components/info-card/InfoCard.vue +1 -1
- package/src/components/landing/landing-navbar/LandingNavbar.vue +2 -1
- package/src/components/load-more-button/LoadMoreButton.vue +2 -2
- package/src/components/loading-state-card/LoadingStateCard.vue +4 -4
- package/src/components/message-dialog/MessageDialog.vue +4 -4
- package/src/components/message-toast/MessageToast.vue +222 -50
- package/src/components/message-toast/index.ts +2 -2
- package/src/components/message-toast/message-toast-state.ts +107 -81
- package/src/components/page-header/PageHeader.vue +1 -1
- package/src/components/progress-component/ProgressComponent.vue +4 -4
- package/src/components/single-value-card/SingleValueCard.vue +47 -11
- package/src/components/theme-selector/ThemeSelector.vue +20 -36
- package/src/components/theme-selector/index.ts +3 -1
- package/src/components/theme-selector/theme-transition.ts +86 -0
- package/src/components/ui/accordion/Accordion.vue +41 -26
- package/src/components/ui/alert-dialog/AlertDialogContent.vue +2 -2
- package/src/components/ui/alert-dialog/AlertDialogFooter.vue +1 -1
- package/src/components/ui/animated-number/AnimatedNumber.vue +88 -0
- package/src/components/ui/animated-number/index.ts +1 -0
- package/src/components/ui/avatar/Avatar.vue +66 -0
- package/src/components/ui/avatar/AvatarFallback.vue +31 -0
- package/src/components/ui/avatar/AvatarGroup.vue +75 -0
- package/src/components/ui/avatar/AvatarImage.vue +23 -0
- package/src/components/ui/avatar/index.ts +47 -0
- package/src/components/ui/badge/Badge.vue +13 -2
- package/src/components/ui/breadcrumb/Breadcrumb.vue +4 -1
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +7 -1
- package/src/components/ui/busy/Busy.vue +45 -20
- package/src/components/ui/button/Button.vue +20 -3
- package/src/components/ui/button/index.ts +12 -2
- package/src/components/ui/card/Card.vue +21 -8
- package/src/components/ui/card/CardText.vue +1 -1
- package/src/components/ui/checkbox/Checkbox.vue +48 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/collapsible/Collapsible.vue +22 -0
- package/src/components/ui/collapsible/CollapsibleContent.vue +22 -0
- package/src/components/ui/collapsible/CollapsibleTrigger.vue +19 -0
- package/src/components/ui/collapsible/index.ts +3 -0
- package/src/components/ui/combobox/Combobox.vue +24 -0
- package/src/components/ui/combobox/ComboboxAnchor.vue +39 -0
- package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
- package/src/components/ui/combobox/ComboboxGroup.vue +26 -0
- package/src/components/ui/combobox/ComboboxInput.vue +28 -0
- package/src/components/ui/combobox/ComboboxItem.vue +39 -0
- package/src/components/ui/combobox/ComboboxList.vue +43 -0
- package/src/components/ui/combobox/ComboboxSelect.vue +162 -0
- package/src/components/ui/combobox/ComboboxSeparator.vue +16 -0
- package/src/components/ui/combobox/ComboboxTrigger.vue +28 -0
- package/src/components/ui/combobox/index.ts +11 -0
- package/src/components/ui/combobox/types.ts +16 -0
- package/src/components/ui/command/CommandDialog.vue +18 -16
- package/src/components/ui/command/CommandGroup.vue +1 -1
- package/src/components/ui/command/CommandInput.vue +2 -2
- package/src/components/ui/command/CommandItem.vue +7 -5
- package/src/components/ui/data-table/DataTable.vue +230 -0
- package/src/components/ui/data-table/index.ts +20 -0
- package/src/components/ui/date-picker/DatePicker.vue +11 -15
- package/src/components/ui/date-time-picker/DateTimePicker.vue +2 -7
- package/src/components/ui/dialog/DialogContent.vue +9 -4
- package/src/components/ui/dialog/DialogOverlay.vue +1 -1
- package/src/components/ui/dialog/DialogScrollContent.vue +10 -5
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +18 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +34 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +34 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +26 -0
- package/src/components/ui/dropdown-menu/DropdownMenuPortal.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +18 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +32 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +16 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +15 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +39 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +36 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +15 -0
- package/src/components/ui/dropdown-menu/classes.ts +24 -0
- package/src/components/ui/dropdown-menu/index.ts +15 -0
- package/src/components/ui/field/FieldDescription.vue +1 -1
- package/src/components/ui/field/FieldError.vue +4 -1
- package/src/components/ui/file-dropzone/FileDropzone.vue +250 -0
- package/src/components/ui/file-dropzone/index.ts +35 -0
- package/src/components/ui/hover-card/HoverCard.vue +21 -0
- package/src/components/ui/hover-card/HoverCardContent.vue +33 -0
- package/src/components/ui/hover-card/HoverCardTrigger.vue +13 -0
- package/src/components/ui/hover-card/index.ts +3 -0
- package/src/components/ui/input/Input.vue +5 -5
- package/src/components/ui/kbd/Kbd.vue +21 -0
- package/src/components/ui/kbd/KbdGroup.vue +72 -0
- package/src/components/ui/kbd/index.ts +2 -0
- package/src/components/ui/number-field/NumberField.vue +130 -0
- package/src/components/ui/number-field/index.ts +1 -0
- package/src/components/ui/pagination/Pagination.vue +27 -0
- package/src/components/ui/pagination/PaginationContent.vue +20 -0
- package/src/components/ui/pagination/PaginationEllipsis.vue +31 -0
- package/src/components/ui/pagination/PaginationFirst.vue +32 -0
- package/src/components/ui/pagination/PaginationItem.vue +41 -0
- package/src/components/ui/pagination/PaginationLast.vue +32 -0
- package/src/components/ui/pagination/PaginationNext.vue +32 -0
- package/src/components/ui/pagination/PaginationPrevious.vue +32 -0
- package/src/components/ui/pagination/SimplePagination.vue +49 -0
- package/src/components/ui/pagination/index.ts +9 -0
- package/src/components/ui/pin-input/PinInput.vue +48 -0
- package/src/components/ui/pin-input/PinInputInput.vue +49 -0
- package/src/components/ui/pin-input/context.ts +8 -0
- package/src/components/ui/pin-input/index.ts +2 -0
- package/src/components/ui/popover/PopoverContent.vue +1 -1
- package/src/components/ui/progress/Progress.vue +22 -7
- package/src/components/ui/radio-group/RadioGroup.vue +27 -0
- package/src/components/ui/radio-group/RadioGroupCard.vue +56 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +40 -0
- package/src/components/ui/radio-group/index.ts +3 -0
- package/src/components/ui/select/SelectContent.vue +1 -1
- package/src/components/ui/select/SelectItem.vue +2 -2
- package/src/components/ui/select/SelectItemContent.vue +1 -1
- package/src/components/ui/select/SelectTrigger.vue +6 -6
- package/src/components/ui/select/SelectValueContent.vue +1 -1
- package/src/components/ui/sheet/SheetContent.vue +5 -5
- package/src/components/ui/sheet/SheetOverlay.vue +1 -1
- package/src/components/ui/sidebar/Sidebar.vue +7 -5
- package/src/components/ui/sidebar/SidebarRail.vue +3 -1
- package/src/components/ui/sidebar/SidebarTrigger.vue +3 -1
- package/src/components/ui/skeleton/Skeleton.vue +4 -4
- package/src/components/ui/slider/Slider.vue +8 -6
- package/src/components/ui/spinner/Spinner.vue +47 -0
- package/src/components/ui/spinner/index.ts +1 -0
- package/src/components/ui/stepper/SimpleStepper.vue +56 -0
- package/src/components/ui/stepper/Stepper.vue +30 -0
- package/src/components/ui/stepper/StepperDescription.vue +20 -0
- package/src/components/ui/stepper/StepperIndicator.vue +41 -0
- package/src/components/ui/stepper/StepperItem.vue +21 -0
- package/src/components/ui/stepper/StepperSeparator.vue +34 -0
- package/src/components/ui/stepper/StepperTitle.vue +20 -0
- package/src/components/ui/stepper/StepperTrigger.vue +20 -0
- package/src/components/ui/stepper/index.ts +17 -0
- package/src/components/ui/switch/Switch.vue +8 -6
- package/src/components/ui/tabs/TabsContent.vue +1 -1
- package/src/components/ui/tabs/TabsList.vue +13 -5
- package/src/components/ui/tabs/TabsTrigger.vue +5 -3
- package/src/components/ui/tags-input/TagsInput.vue +49 -0
- package/src/components/ui/tags-input/TagsInputInput.vue +23 -0
- package/src/components/ui/tags-input/TagsInputItem.vue +35 -0
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +31 -0
- package/src/components/ui/tags-input/TagsInputItemText.vue +21 -0
- package/src/components/ui/tags-input/index.ts +5 -0
- package/src/components/ui/textarea/Textarea.vue +4 -5
- package/src/components/ui/toggle-group/Toggle.vue +33 -0
- package/src/components/ui/toggle-group/ToggleGroup.vue +93 -0
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +34 -0
- package/src/components/ui/toggle-group/index.ts +69 -0
- package/src/components/ui/tooltip/TooltipContent.vue +1 -1
- package/src/i18n/de.json +76 -0
- package/src/i18n/en.json +76 -0
- package/src/i18n/es.json +76 -0
- package/src/i18n/fr.json +76 -0
- package/src/i18n/it.json +76 -0
- package/src/i18n/ja.json +76 -0
- package/src/i18n/pt.json +76 -0
- package/src/i18n/ru.json +76 -0
- package/src/i18n/zh.json +76 -0
- package/src/lib/color-tokens.ts +14 -14
- package/src/styles.css +289 -5
package/README.md
CHANGED
|
@@ -85,8 +85,9 @@ This is a Nuxt library, not a generic Vue component bundle. Components rely on N
|
|
|
85
85
|
- Vue 3 components built with Composition API and TypeScript source.
|
|
86
86
|
- Primitive UI controls based on Reka UI where accessible behavior is required.
|
|
87
87
|
- Reusable dashboard, landing, blog, card, field, and data-display components.
|
|
88
|
-
- Built-in busy, confirm-dialog, message-dialog, and message-toast flows.
|
|
89
|
-
-
|
|
88
|
+
- Built-in busy, confirm-dialog, message-dialog, and stacked, swipeable message-toast flows.
|
|
89
|
+
- A spring-based motion system (pure CSS `linear()` easings) that respects `prefers-reduced-motion`.
|
|
90
|
+
- Theme selector with light, dark, and automatic modes and a circular View Transitions reveal.
|
|
90
91
|
- Library locale messages merged into an existing Vue I18n instance when available.
|
|
91
92
|
- Automatic `@nuxt/image` installation for components that use `NuxtImg`.
|
|
92
93
|
- Source publishing with no required library build step before installation.
|
|
@@ -190,11 +191,10 @@ const confirmed = await showConfirmDialog({
|
|
|
190
191
|
});
|
|
191
192
|
|
|
192
193
|
if (confirmed) {
|
|
193
|
-
// Keep the shared loading
|
|
194
|
-
setBusy({
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
});
|
|
194
|
+
// Keep the shared loading overlay visible while the request is running
|
|
195
|
+
setBusy(true, { label: 'Deleting project...' });
|
|
196
|
+
await deleteProject();
|
|
197
|
+
setBusy(false);
|
|
198
198
|
}
|
|
199
199
|
```
|
|
200
200
|
|
|
@@ -231,7 +231,7 @@ The package exposes components through explicit subpaths. The complete public ex
|
|
|
231
231
|
|
|
232
232
|
### 🎛️ UI primitives
|
|
233
233
|
|
|
234
|
-
`alert`, `alert-dialog`, `accordion`, `badge`, `breadcrumb`, `button`, `calendar`, `card`, `command`, `dialog`, `field`, `input`, `label`, `popover`, `progress`, `scroll-area`, `select`, `separator`, `sheet`, `sidebar`, `skeleton`, `slider`, `switch`, `table`, `tabs`, `textarea`, `time-picker`, and `tooltip`.
|
|
234
|
+
`alert`, `alert-dialog`, `accordion`, `animated-number`, `avatar`, `badge`, `breadcrumb`, `button`, `calendar`, `card`, `checkbox`, `collapsible`, `combobox`, `command`, `data-table`, `date-picker`, `date-time-picker`, `dialog`, `dropdown-menu`, `field`, `file-dropzone`, `hover-card`, `input`, `kbd`, `label`, `native-select`, `number-field`, `pagination`, `pin-input`, `popover`, `progress`, `radio-group`, `scroll-area`, `select`, `separator`, `sheet`, `sidebar`, `skeleton`, `slider`, `spinner`, `stepper`, `switch`, `table`, `tabs`, `tags-input`, `textarea`, `time-picker`, `toggle-group`, and `tooltip`.
|
|
235
235
|
|
|
236
236
|
### 🧱 Composite components
|
|
237
237
|
|
|
@@ -241,10 +241,49 @@ The package exposes components through explicit subpaths. The complete public ex
|
|
|
241
241
|
|
|
242
242
|
`busy`, `busy-indicator`, `confirm-dialog`, `message-dialog`, `message-toast`, `language-flag`, `language-selector`, `blog`, `content`, and `utils`.
|
|
243
243
|
|
|
244
|
+
### 📚 Component catalog
|
|
245
|
+
|
|
246
|
+
[`COMPONENTS.md`](COMPONENTS.md) lists every entry point with its import line, components, props and defaults, events, slots, helpers, and usage examples. It is generated from the source and ships inside the package, so apps can always read the version they installed at `node_modules/@brumaombra/ui-vintage/COMPONENTS.md`.
|
|
247
|
+
|
|
248
|
+
Regenerate it after changing a public API, adding an entry point, or editing `scripts/components-meta.mjs`:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Rewrite COMPONENTS.md from the source
|
|
252
|
+
npm run docs:components
|
|
253
|
+
|
|
254
|
+
# Fail when COMPONENTS.md is out of date (runs in the publish workflow)
|
|
255
|
+
npm run docs:components:check
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
New entry points need a description and category in `scripts/components-meta.mjs`; the generator fails until they have one.
|
|
259
|
+
|
|
260
|
+
### 🤖 AI assistant skill
|
|
261
|
+
|
|
262
|
+
The package also ships a skill that teaches coding assistants to reuse UI Vintage and to read the catalog before writing UI. To enable it in a consuming app with Claude Code, copy it into the app's skills directory:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Copy the skill shipped with the installed version
|
|
266
|
+
mkdir -p .claude/skills
|
|
267
|
+
cp -r node_modules/@brumaombra/ui-vintage/skills/ui-vintage .claude/skills/
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The skill only points to `COMPONENTS.md`, so it stays valid when you update the package. Copy it again only when the skill itself changes.
|
|
271
|
+
|
|
272
|
+
### 🎬 Motion system
|
|
273
|
+
|
|
274
|
+
`src/styles.css` ships a small motion vocabulary that every component uses and that apps can reuse:
|
|
275
|
+
|
|
276
|
+
- Easing utilities: `ease-spring`, `ease-bounce`, `ease-out-expo`, and `ease-snappy`.
|
|
277
|
+
- Surface utilities: `uv-floating-motion` (popovers, menus, tooltips), `uv-modal-motion`, `uv-overlay-motion`, `uv-collapsible-motion`, and `uv-field` (focus glow and invalid states for inputs).
|
|
278
|
+
- Animations: `animate-uv-pop`, `animate-uv-fade-up`, `animate-uv-shake`, `animate-uv-shimmer`, `animate-uv-float`, and `animate-uv-ping-soft`.
|
|
279
|
+
- Elevation: `shadow-elevated-sm` through `shadow-elevated-xl`, plus `shadow-glow`.
|
|
280
|
+
|
|
281
|
+
All durations collapse automatically when the user prefers reduced motion. Tailwind v4 animates `scale`, `rotate`, and `translate` as individual properties, so list those names (not `transform`) in custom transitions.
|
|
282
|
+
|
|
244
283
|
<a id="demo-app"></a>
|
|
245
284
|
## 🖥️ Demo App
|
|
246
285
|
|
|
247
|
-
The private `demo-app/` directory is a Nuxt 4 showcase for manual verification. It
|
|
286
|
+
The private `demo-app/` directory is a Nuxt 4 documentation-style showcase for manual verification. It is organized by category (Foundations, Actions, Forms, Data display, Navigation, Overlays, Feedback, Layouts), shows every component with a live preview and a copyable code tab, includes a live mini-app on the overview page, and has a global command palette (`Ctrl/⌘ + K`) to jump to any section. The navigation model lives in `demo-app/app/utils/demo-navigation.ts`.
|
|
248
287
|
|
|
249
288
|
The demo app is not part of the published package and is not intended to be installed by consumers. It imports the package through `file:..`, so it exercises the same source that is published to npm.
|
|
250
289
|
|
|
@@ -309,6 +348,7 @@ UI Vintage publishes the Nuxt module entrypoint and source files directly:
|
|
|
309
348
|
- `module.mjs` is the package entrypoint registered by Nuxt.
|
|
310
349
|
- `src/` contains the published components, styles, helpers, locale messages, and runtime plugin.
|
|
311
350
|
- `package.json` defines the public component subpath exports.
|
|
351
|
+
- `COMPONENTS.md` and `skills/` ship the generated component catalog and the AI assistant skill.
|
|
312
352
|
- `dist/` is generated repository output and is not the source of truth for consumers.
|
|
313
353
|
|
|
314
354
|
There is no required library build step before publishing. The normal verification command is:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brumaombra/ui-vintage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Source-published Nuxt UI runtime with shared theme tokens and built-in Nuxt integrations.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Mauro Brambilla",
|
|
@@ -28,41 +28,53 @@
|
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"src",
|
|
31
|
-
"module.mjs"
|
|
31
|
+
"module.mjs",
|
|
32
|
+
"COMPONENTS.md",
|
|
33
|
+
"skills"
|
|
32
34
|
],
|
|
33
35
|
"exports": {
|
|
34
36
|
".": {
|
|
35
37
|
"import": "./module.mjs"
|
|
36
38
|
},
|
|
37
39
|
"./style.css": "./src/styles.css",
|
|
40
|
+
"./accordion": "./src/components/ui/accordion/index.ts",
|
|
38
41
|
"./alert": "./src/components/ui/alert/index.ts",
|
|
39
42
|
"./alert-dialog": "./src/components/ui/alert-dialog/index.ts",
|
|
40
|
-
"./
|
|
43
|
+
"./animated-number": "./src/components/ui/animated-number/index.ts",
|
|
44
|
+
"./avatar": "./src/components/ui/avatar/index.ts",
|
|
41
45
|
"./background-grid": "./src/components/background-grid/index.ts",
|
|
42
46
|
"./badge": "./src/components/ui/badge/index.ts",
|
|
43
47
|
"./blog": "./src/components/blog/index.ts",
|
|
48
|
+
"./breadcrumb": "./src/components/ui/breadcrumb/index.ts",
|
|
44
49
|
"./busy": "./src/components/ui/busy/index.ts",
|
|
45
50
|
"./busy-indicator": "./src/components/busy-indicator/index.ts",
|
|
46
|
-
"./breadcrumb": "./src/components/ui/breadcrumb/index.ts",
|
|
47
51
|
"./button": "./src/components/ui/button/index.ts",
|
|
48
52
|
"./calendar": "./src/components/ui/calendar/index.ts",
|
|
49
53
|
"./card": "./src/components/ui/card/index.ts",
|
|
50
54
|
"./card-grid": "./src/components/card-grid/index.ts",
|
|
55
|
+
"./checkbox": "./src/components/ui/checkbox/index.ts",
|
|
51
56
|
"./chip": "./src/components/chip/index.ts",
|
|
57
|
+
"./collapsible": "./src/components/ui/collapsible/index.ts",
|
|
58
|
+
"./combobox": "./src/components/ui/combobox/index.ts",
|
|
52
59
|
"./command": "./src/components/ui/command/index.ts",
|
|
60
|
+
"./common-types": "./src/lib/common-types.ts",
|
|
53
61
|
"./confirm-dialog": "./src/components/confirm-dialog/index.ts",
|
|
54
62
|
"./content": "./src/components/content/index.ts",
|
|
55
63
|
"./dashboard-shell": "./src/components/dashboard-shell/index.ts",
|
|
56
64
|
"./data-list": "./src/components/data-list/index.ts",
|
|
65
|
+
"./data-table": "./src/components/ui/data-table/index.ts",
|
|
57
66
|
"./date-picker": "./src/components/ui/date-picker/index.ts",
|
|
58
67
|
"./date-time-picker": "./src/components/ui/date-time-picker/index.ts",
|
|
59
68
|
"./dialog": "./src/components/ui/dialog/index.ts",
|
|
69
|
+
"./dropdown-menu": "./src/components/ui/dropdown-menu/index.ts",
|
|
60
70
|
"./empty-state-card": "./src/components/empty-state-card/index.ts",
|
|
61
71
|
"./error-page": "./src/components/error-page/index.ts",
|
|
62
72
|
"./field": "./src/components/ui/field/index.ts",
|
|
73
|
+
"./file-dropzone": "./src/components/ui/file-dropzone/index.ts",
|
|
74
|
+
"./hover-card": "./src/components/ui/hover-card/index.ts",
|
|
63
75
|
"./info-card": "./src/components/info-card/index.ts",
|
|
64
|
-
"./common-types": "./src/lib/common-types.ts",
|
|
65
76
|
"./input": "./src/components/ui/input/index.ts",
|
|
77
|
+
"./kbd": "./src/components/ui/kbd/index.ts",
|
|
66
78
|
"./label": "./src/components/ui/label/index.ts",
|
|
67
79
|
"./landing": "./src/components/landing/index.ts",
|
|
68
80
|
"./landing-content": "./src/components/landing/landing-content/index.ts",
|
|
@@ -76,10 +88,14 @@
|
|
|
76
88
|
"./message-dialog": "./src/components/message-dialog/index.ts",
|
|
77
89
|
"./message-toast": "./src/components/message-toast/index.ts",
|
|
78
90
|
"./native-select": "./src/components/ui/native-select/index.ts",
|
|
91
|
+
"./number-field": "./src/components/ui/number-field/index.ts",
|
|
79
92
|
"./page-header": "./src/components/page-header/index.ts",
|
|
93
|
+
"./pagination": "./src/components/ui/pagination/index.ts",
|
|
94
|
+
"./pin-input": "./src/components/ui/pin-input/index.ts",
|
|
80
95
|
"./popover": "./src/components/ui/popover/index.ts",
|
|
81
96
|
"./progress": "./src/components/ui/progress/index.ts",
|
|
82
97
|
"./progress-component": "./src/components/progress-component/index.ts",
|
|
98
|
+
"./radio-group": "./src/components/ui/radio-group/index.ts",
|
|
83
99
|
"./scroll-area": "./src/components/ui/scroll-area/index.ts",
|
|
84
100
|
"./select": "./src/components/ui/select/index.ts",
|
|
85
101
|
"./separator": "./src/components/ui/separator/index.ts",
|
|
@@ -89,14 +105,18 @@
|
|
|
89
105
|
"./skeleton": "./src/components/ui/skeleton/index.ts",
|
|
90
106
|
"./slider": "./src/components/ui/slider/index.ts",
|
|
91
107
|
"./slider-form-component": "./src/components/slider-form-component/index.ts",
|
|
108
|
+
"./spinner": "./src/components/ui/spinner/index.ts",
|
|
109
|
+
"./stepper": "./src/components/ui/stepper/index.ts",
|
|
92
110
|
"./switch": "./src/components/ui/switch/index.ts",
|
|
93
111
|
"./switch-form-component": "./src/components/switch-form-component/index.ts",
|
|
94
112
|
"./table": "./src/components/ui/table/index.ts",
|
|
95
113
|
"./tabs": "./src/components/ui/tabs/index.ts",
|
|
114
|
+
"./tags-input": "./src/components/ui/tags-input/index.ts",
|
|
96
115
|
"./text-link": "./src/components/text-link/index.ts",
|
|
97
116
|
"./textarea": "./src/components/ui/textarea/index.ts",
|
|
98
117
|
"./theme-selector": "./src/components/theme-selector/index.ts",
|
|
99
118
|
"./time-picker": "./src/components/ui/time-picker/index.ts",
|
|
119
|
+
"./toggle-group": "./src/components/ui/toggle-group/index.ts",
|
|
100
120
|
"./tooltip": "./src/components/ui/tooltip/index.ts",
|
|
101
121
|
"./utils": "./src/lib/utils.ts"
|
|
102
122
|
},
|
|
@@ -104,7 +124,9 @@
|
|
|
104
124
|
"**/*.css"
|
|
105
125
|
],
|
|
106
126
|
"scripts": {
|
|
107
|
-
"typecheck": "vue-tsc --noEmit"
|
|
127
|
+
"typecheck": "vue-tsc --noEmit",
|
|
128
|
+
"docs:components": "node scripts/generate-components-doc.mjs",
|
|
129
|
+
"docs:components:check": "node scripts/generate-components-doc.mjs --check"
|
|
108
130
|
},
|
|
109
131
|
"peerDependencies": {
|
|
110
132
|
"@nuxt/image": "^2.0.0",
|
|
@@ -123,8 +145,7 @@
|
|
|
123
145
|
"reka-ui": "2.9.2",
|
|
124
146
|
"shiki": "4.4.3",
|
|
125
147
|
"tailwind-merge": "3.5.0",
|
|
126
|
-
"tailwindcss": "4.2.4"
|
|
127
|
-
"tw-animate-css": "1.4.0"
|
|
148
|
+
"tailwindcss": "4.2.4"
|
|
128
149
|
},
|
|
129
150
|
"devDependencies": {
|
|
130
151
|
"@types/node": "24.0.0",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-vintage
|
|
3
|
+
description: Build Nuxt UI with the @brumaombra/ui-vintage component library. Use whenever creating or changing pages, layouts, forms, tables, dialogs, toasts, or any other UI in an app that depends on @brumaombra/ui-vintage, before writing custom components or styles.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UI Vintage
|
|
7
|
+
|
|
8
|
+
This app uses `@brumaombra/ui-vintage`, a Nuxt component library with its own design tokens, motion system, and runtime flows. Reuse it instead of building UI from scratch.
|
|
9
|
+
|
|
10
|
+
## 1. Read the catalog first
|
|
11
|
+
|
|
12
|
+
The complete, version-matched API reference is installed with the package:
|
|
13
|
+
|
|
14
|
+
`node_modules/@brumaombra/ui-vintage/COMPONENTS.md`
|
|
15
|
+
|
|
16
|
+
Search it (for example for `### \`@brumaombra/ui-vintage/`, a component name, or a prop) instead of reading it top to bottom. It lists every entry point with its import line, components, props and defaults, events, slots, helpers, and usage examples. Never guess prop names: check them there.
|
|
17
|
+
|
|
18
|
+
## 2. Choose the component
|
|
19
|
+
|
|
20
|
+
- Before writing a custom component, check the catalog's Contents list. Common mappings:
|
|
21
|
+
- Searchable select → `combobox` (`ComboboxSelect`); plain select → `select`.
|
|
22
|
+
- Table with sorting or selection → `data-table`; KPI tiles → `single-value-card`.
|
|
23
|
+
- Confirmation → `showConfirmDialog` (`confirm-dialog`); notification → `showMessageToast` (`message-toast`); blocking load → `setBusy` (`busy-indicator`). These mount themselves, so do not add them to the template.
|
|
24
|
+
- App layout with sidebar → `dashboard-shell`; marketing or blog layout → `landing`.
|
|
25
|
+
- Empty and loading placeholders → `empty-state-card`, `loading-state-card`, `skeleton`.
|
|
26
|
+
- Only build something new when no entry point fits, and compose it from library primitives (`card`, `button`, `field`, ...).
|
|
27
|
+
|
|
28
|
+
## 3. Follow the library conventions
|
|
29
|
+
|
|
30
|
+
- Import from explicit subpaths: `import { Button } from '@brumaombra/ui-vintage/button'`. Never import from `@brumaombra/ui-vintage/src/...`.
|
|
31
|
+
- Style with the semantic tokens (`bg-card`, `bg-surface`, `bg-accent`, `bg-primary`, `text-muted-foreground`, `border-border`, `border-border-strong`, ...). They adapt to dark mode, so do not write `dark:` color variants or hard-coded colors for surfaces and text.
|
|
32
|
+
- Customize components through their props and the `class` prop (merged with tailwind-merge) rather than wrapping them in extra styled elements.
|
|
33
|
+
- For motion, reuse the library utilities (`ease-spring`, `ease-out-expo`, `animate-uv-fade-up`, `animate-uv-pop`, `uv-floating-motion`, `shadow-elevated-*`). In custom transitions, name `scale`, `rotate`, and `translate` explicitly; Tailwind v4 does not animate them through `transform`.
|
|
34
|
+
- Icons are Hugeicons definitions: import from `@hugeicons/core-free-icons` and pass them to `:icon` props, or render them with `HugeiconsIcon` from `@hugeicons/vue`.
|
|
35
|
+
- Form controls use `v-model`. Mark invalid fields with `aria-invalid` and show messages with `FieldError`.
|
|
36
|
+
- Library strings are translated through vue-i18n (`uiVintage.*` keys). App copy belongs in the app's own locale files.
|
|
37
|
+
|
|
38
|
+
## 4. Check your work
|
|
39
|
+
|
|
40
|
+
- Every import path you used appears as an entry point in `COMPONENTS.md`.
|
|
41
|
+
- Every prop and event you used appears in that component's table.
|
|
42
|
+
- No new CSS reimplements something a component or token already provides.
|
|
@@ -24,14 +24,14 @@ const hasValidFAQs = computed(() => props.faqs && props.faqs.length > 0);
|
|
|
24
24
|
<BlogSectionTitle :title="t('uiVintage.blog.faq.title')" />
|
|
25
25
|
|
|
26
26
|
<!-- Description -->
|
|
27
|
-
<p class="text-base md:text-lg text-
|
|
27
|
+
<p class="text-base md:text-lg text-muted-foreground mb-8">
|
|
28
28
|
{{ t('uiVintage.blog.faq.description') }}
|
|
29
29
|
</p>
|
|
30
30
|
|
|
31
31
|
<!-- FAQ Items -->
|
|
32
32
|
<div class="space-y-4">
|
|
33
33
|
<Accordion v-for="(faq, index) in props.faqs" :key="index" :title="faq.question" :initially-expanded="index === 0">
|
|
34
|
-
<p class="text-xs md:text-sm text-
|
|
34
|
+
<p class="text-xs md:text-sm text-muted-foreground leading-relaxed">
|
|
35
35
|
{{ faq.answer }}
|
|
36
36
|
</p>
|
|
37
37
|
</Accordion>
|
|
@@ -26,12 +26,12 @@ const props = withDefaults(defineProps<{
|
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<!-- Title -->
|
|
29
|
-
<h1 class="text-3xl md:text-6xl font-bold text-
|
|
29
|
+
<h1 class="text-3xl md:text-6xl font-bold text-foreground leading-tight">
|
|
30
30
|
{{ props.title }}
|
|
31
31
|
</h1>
|
|
32
32
|
|
|
33
33
|
<!-- Description -->
|
|
34
|
-
<p class="text-sm md:text-xl text-
|
|
34
|
+
<p class="text-sm md:text-xl text-muted-foreground leading-relaxed">
|
|
35
35
|
{{ props.description }}
|
|
36
36
|
</p>
|
|
37
37
|
</div>
|
|
@@ -38,8 +38,8 @@ const formattedModifiedDate = computed(() => formatDateLongItalyTimezone(props.d
|
|
|
38
38
|
|
|
39
39
|
<!-- Author name -->
|
|
40
40
|
<div>
|
|
41
|
-
<p class="text-sm text-
|
|
42
|
-
<p class="font-bold text-
|
|
41
|
+
<p class="text-sm text-muted-foreground">{{ t('uiVintage.blog.author') }}</p>
|
|
42
|
+
<p class="font-bold text-foreground">
|
|
43
43
|
<NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="hover:underline">
|
|
44
44
|
{{ props.author }}
|
|
45
45
|
</NuxtLink>
|
|
@@ -49,7 +49,7 @@ const formattedModifiedDate = computed(() => formatDateLongItalyTimezone(props.d
|
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
51
|
<!-- Date information -->
|
|
52
|
-
<div class="flex flex-col sm:items-end text-xs md:text-sm text-
|
|
52
|
+
<div class="flex flex-col sm:items-end text-xs md:text-sm text-muted-foreground">
|
|
53
53
|
<!-- Published date -->
|
|
54
54
|
<div v-if="props.datePublished" class="mb-1">
|
|
55
55
|
<span class="font-semibold">{{ t('uiVintage.blog.publishedDate') }}:</span>
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { onMounted, onUnmounted, ref } from 'vue';
|
|
2
|
+
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { useI18n } from 'vue-i18n';
|
|
4
5
|
import { cn } from '../../lib/utils';
|
|
5
6
|
|
|
6
7
|
// Props
|
|
7
|
-
const props =
|
|
8
|
+
const props = defineProps<{
|
|
8
9
|
class?: HTMLAttributes['class'];
|
|
9
10
|
ariaLabel?: string;
|
|
10
|
-
}>()
|
|
11
|
-
ariaLabel: 'Reading progress'
|
|
12
|
-
});
|
|
11
|
+
}>();
|
|
13
12
|
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
const resolvedAriaLabel = computed(() => props.ariaLabel || t('uiVintage.blog.readingProgress'));
|
|
14
15
|
const progress = ref(0);
|
|
15
16
|
let animationFrame = 0;
|
|
16
17
|
|
|
@@ -44,7 +45,7 @@ onUnmounted(() => {
|
|
|
44
45
|
<template>
|
|
45
46
|
<div :class="cn('relative h-1 w-full border-t border-(--border-light) bg-(--bg-selected-light) dark:border-(--border-dark) dark:bg-(--bg-selected-dark) landing-navbar-progress', props.class)"
|
|
46
47
|
role="progressbar"
|
|
47
|
-
:aria-label="
|
|
48
|
+
:aria-label="resolvedAriaLabel"
|
|
48
49
|
aria-valuemin="0"
|
|
49
50
|
aria-valuemax="100"
|
|
50
51
|
:aria-valuenow="Math.round(progress)">
|
|
@@ -6,7 +6,7 @@ const props = defineProps<{
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
|
-
<h2 class="text-xl md:text-2xl font-bold text-
|
|
9
|
+
<h2 class="text-xl md:text-2xl font-bold text-foreground mb-5">
|
|
10
10
|
{{ props.title }}
|
|
11
11
|
</h2>
|
|
12
12
|
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Badge } from '../ui/badge';
|
|
3
|
+
|
|
4
|
+
// Props
|
|
5
|
+
const props = withDefaults(defineProps<{
|
|
6
|
+
tags?: Array<{
|
|
7
|
+
name: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
count: number;
|
|
10
|
+
}>;
|
|
11
|
+
}>(), {
|
|
12
|
+
tags: () => []
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div v-if="props.tags.length" class="flex flex-wrap gap-3">
|
|
18
|
+
<NuxtLinkLocale v-for="(tag, index) in props.tags" :key="tag.slug" :to="`/blog/tags/${tag.slug}`" data-aos="fade-up" :data-aos-delay="Math.min(index * 100, 500)">
|
|
19
|
+
<Badge color="gray" :text="`${tag.name} (${tag.count})`" class="transition-transform duration-200 ease-out hover:scale-105 motion-reduce:transition-none motion-reduce:hover:scale-100" />
|
|
20
|
+
</NuxtLinkLocale>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{
|
|
|
24
24
|
<!-- Content -->
|
|
25
25
|
<div class="p-4 flex items-center justify-between">
|
|
26
26
|
<!-- Name -->
|
|
27
|
-
<span class="text-sm font-bold text-
|
|
27
|
+
<span class="text-sm font-bold text-foreground truncate mr-3">
|
|
28
28
|
{{ props.name }}
|
|
29
29
|
</span>
|
|
30
30
|
|
|
@@ -100,13 +100,13 @@ onUnmounted(() => {
|
|
|
100
100
|
|
|
101
101
|
<!-- Title -->
|
|
102
102
|
<NuxtLink :to="post.path">
|
|
103
|
-
<h3 class="text-base md:text-3xl font-bold text-
|
|
103
|
+
<h3 class="text-base md:text-3xl font-bold text-foreground mb-3 line-clamp-2 hover:opacity-75 transition-opacity duration-200">
|
|
104
104
|
{{ post.title }}
|
|
105
105
|
</h3>
|
|
106
106
|
</NuxtLink>
|
|
107
107
|
|
|
108
108
|
<!-- Description -->
|
|
109
|
-
<p class="text-xs md:text-base! text-
|
|
109
|
+
<p class="text-xs md:text-base! text-muted-foreground mb-6 line-clamp-2 leading-relaxed">
|
|
110
110
|
{{ post.description }}
|
|
111
111
|
</p>
|
|
112
112
|
|
|
@@ -144,16 +144,16 @@ onUnmounted(() => {
|
|
|
144
144
|
<button v-if="props.featuredPosts.length > 1"
|
|
145
145
|
@click="prevSlide"
|
|
146
146
|
:aria-label="t('uiVintage.blog.previousSlide')"
|
|
147
|
-
class="absolute left-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-
|
|
148
|
-
<HugeiconsIcon :icon="ArrowLeft01Icon" class="size-4 text-
|
|
147
|
+
class="absolute left-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-border bg-card px-3 py-3 rounded-[3px] transition-colors duration-200 hover:border-border-strong cursor-pointer">
|
|
148
|
+
<HugeiconsIcon :icon="ArrowLeft01Icon" class="size-4 text-muted-foreground" />
|
|
149
149
|
</button>
|
|
150
150
|
|
|
151
151
|
<!-- Next slide -->
|
|
152
152
|
<button v-if="props.featuredPosts.length > 1"
|
|
153
153
|
@click="nextSlide"
|
|
154
154
|
:aria-label="t('uiVintage.blog.nextSlide')"
|
|
155
|
-
class="absolute right-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-
|
|
156
|
-
<HugeiconsIcon :icon="ArrowRight01Icon" class="size-4 text-
|
|
155
|
+
class="absolute right-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-border bg-card px-3 py-3 rounded-[3px] transition-colors duration-200 hover:border-border-strong cursor-pointer">
|
|
156
|
+
<HugeiconsIcon :icon="ArrowRight01Icon" class="size-4 text-muted-foreground" />
|
|
157
157
|
</button>
|
|
158
158
|
</div>
|
|
159
159
|
</template>
|
|
@@ -39,12 +39,12 @@ const props = withDefaults(defineProps<{
|
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
<!-- Post Title -->
|
|
42
|
-
<h2 class="text-base font-bold text-
|
|
42
|
+
<h2 class="text-base font-bold text-foreground mb-2 line-clamp-2">
|
|
43
43
|
{{ props.title }}
|
|
44
44
|
</h2>
|
|
45
45
|
|
|
46
46
|
<!-- Post Description -->
|
|
47
|
-
<p class="text-xs md:text-sm text-
|
|
47
|
+
<p class="text-xs md:text-sm text-muted-foreground leading-relaxed mb-4 line-clamp-3 grow">
|
|
48
48
|
{{ props.description }}
|
|
49
49
|
</p>
|
|
50
50
|
|
|
@@ -59,7 +59,7 @@ const socialPlatforms = computed(() => {
|
|
|
59
59
|
<Card class="p-5! flex flex-col space-y-4 items-center">
|
|
60
60
|
<CardContent class="p-0! gap-4 items-center">
|
|
61
61
|
<!-- Share icon -->
|
|
62
|
-
<HugeiconsIcon :icon="Share08Icon" class="size-5 text-
|
|
62
|
+
<HugeiconsIcon :icon="Share08Icon" class="size-5 text-muted-foreground mb-2" />
|
|
63
63
|
|
|
64
64
|
<!-- Social share links (desktop) -->
|
|
65
65
|
<template v-for="platform in socialPlatforms" :key="platform.name">
|
|
@@ -286,18 +286,18 @@ const openMobileToc = () => {
|
|
|
286
286
|
// Get classes for heading button
|
|
287
287
|
const getHeadingButtonClasses = (level: number, isActive: boolean) => {
|
|
288
288
|
// Base classes for all titles
|
|
289
|
-
const baseClasses = 'block w-full text-left px-2.5 py-2 md:px-3 md:py-2 rounded border-l-2 border-transparent transition-all duration-200 hover:bg-
|
|
289
|
+
const baseClasses = 'block w-full text-left px-2.5 py-2 md:px-3 md:py-2 rounded border-l-2 border-transparent transition-all duration-200 hover:bg-accent cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 motion-reduce:transition-none';
|
|
290
290
|
|
|
291
291
|
// Add classes based on heading level
|
|
292
292
|
let levelClasses = '';
|
|
293
293
|
if (level === 2) {
|
|
294
|
-
levelClasses = 'text-
|
|
294
|
+
levelClasses = 'text-foreground font-semibold text-xs md:text-sm';
|
|
295
295
|
} else {
|
|
296
|
-
levelClasses = 'text-
|
|
296
|
+
levelClasses = 'text-muted-foreground pl-6! text-xs md:text-sm';
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
// Add classes if the heading is active
|
|
300
|
-
const activeClasses = isActive ? 'bg-
|
|
300
|
+
const activeClasses = isActive ? 'bg-accent border-l-primary text-foreground' : '';
|
|
301
301
|
|
|
302
302
|
// Return combined classes
|
|
303
303
|
return [baseClasses, levelClasses, activeClasses];
|
|
@@ -367,12 +367,12 @@ onUnmounted(() => {
|
|
|
367
367
|
<div class="flex min-w-0 items-center gap-3">
|
|
368
368
|
<!-- Mobile table of contents icon -->
|
|
369
369
|
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded border border-(--border-light) bg-(--bg-card-light) dark:border-(--border-dark) dark:bg-(--bg-card-dark)">
|
|
370
|
-
<HugeiconsIcon :icon="Bookmark01Icon" class="size-4 text-
|
|
370
|
+
<HugeiconsIcon :icon="Bookmark01Icon" class="size-4 text-muted-foreground" />
|
|
371
371
|
</div>
|
|
372
372
|
|
|
373
373
|
<!-- Mobile table of contents title and description -->
|
|
374
374
|
<div class="min-w-0">
|
|
375
|
-
<SheetTitle class="text-sm md:text-base! font-semibold text-
|
|
375
|
+
<SheetTitle class="text-sm md:text-base! font-semibold text-foreground">
|
|
376
376
|
{{ tocTitle }}
|
|
377
377
|
</SheetTitle>
|
|
378
378
|
<SheetDescription class="sr-only">
|
|
@@ -4,6 +4,7 @@ export { default as BlogFAQSection } from './BlogFAQSection.vue';
|
|
|
4
4
|
export { default as BlogInfoSection } from './BlogInfoSection.vue';
|
|
5
5
|
export { default as BlogHeaderSection } from './BlogHeaderSection.vue';
|
|
6
6
|
export { default as BlogSectionTitle } from './BlogSectionTitle.vue';
|
|
7
|
+
export { default as BlogTagsSection } from './BlogTagsSection.vue';
|
|
7
8
|
export { default as CategoriesList } from './CategoriesList.vue';
|
|
8
9
|
export { default as CategoriesSection } from './CategoriesSection.vue';
|
|
9
10
|
export { default as CategoryCard } from './CategoryCard.vue';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { h, reactive, render, type HTMLAttributes } from 'vue';
|
|
2
|
+
import { getUiVintageRuntimeMessage } from '../../lib/i18n';
|
|
2
3
|
|
|
3
4
|
export interface SetBusyOptions {
|
|
4
5
|
label?: string;
|
|
@@ -20,7 +21,7 @@ let busyIndicatorMountPromise: Promise<void> | null = null;
|
|
|
20
21
|
// Shared busy state
|
|
21
22
|
export const busyIndicatorState: BusyIndicatorState = reactive({
|
|
22
23
|
show: false,
|
|
23
|
-
label: 'Loading...',
|
|
24
|
+
label: getUiVintageRuntimeMessage('uiVintage.common.loading.title', 'Loading...'),
|
|
24
25
|
class: undefined,
|
|
25
26
|
overlayClass: undefined
|
|
26
27
|
});
|
|
@@ -70,7 +71,7 @@ const ensureBusyIndicatorMounted = () => {
|
|
|
70
71
|
export const setBusy = (show: boolean, options: SetBusyOptions = {}) => {
|
|
71
72
|
// Prepare the overlay before showing it
|
|
72
73
|
if (show) {
|
|
73
|
-
busyIndicatorState.label = options.label ??
|
|
74
|
+
busyIndicatorState.label = options.label ?? getUiVintageRuntimeMessage('uiVintage.common.loading.title', 'Loading...');
|
|
74
75
|
busyIndicatorState.class = options.class;
|
|
75
76
|
busyIndicatorState.overlayClass = options.overlayClass;
|
|
76
77
|
void ensureBusyIndicatorMounted();
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
|
3
3
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
4
|
+
import { computed } from 'vue';
|
|
4
5
|
import type { HTMLAttributes } from 'vue';
|
|
6
|
+
import { useI18n } from 'vue-i18n';
|
|
5
7
|
import { getSurfaceToneClasses, type ToneColor } from '../../lib/color-tokens';
|
|
6
8
|
import { cn } from '../../lib/utils';
|
|
7
9
|
|
|
@@ -12,10 +14,12 @@ const props = withDefaults(defineProps<{
|
|
|
12
14
|
removeLabel?: string;
|
|
13
15
|
class?: HTMLAttributes['class'];
|
|
14
16
|
}>(), {
|
|
15
|
-
color: 'gray'
|
|
16
|
-
removeLabel: 'Remove'
|
|
17
|
+
color: 'gray'
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
const { t } = useI18n();
|
|
21
|
+
const resolvedRemoveLabel = computed(() => props.removeLabel || t('uiVintage.buttons.remove'));
|
|
22
|
+
|
|
19
23
|
// Emits
|
|
20
24
|
const emits = defineEmits<{
|
|
21
25
|
remove: []
|
|
@@ -28,13 +32,13 @@ const handleRemove = () => {
|
|
|
28
32
|
</script>
|
|
29
33
|
|
|
30
34
|
<template>
|
|
31
|
-
<div data-slot="chip" :class="cn('inline-flex items-center gap-2 rounded border px-3 py-2 text-sm font-semibold transition-colors duration-
|
|
35
|
+
<div data-slot="chip" :class="cn('inline-flex items-center gap-2 rounded border px-3 py-2 text-sm font-semibold transition-colors duration-150', getSurfaceToneClasses(props.color), props.class)">
|
|
32
36
|
<!-- Text content -->
|
|
33
37
|
<span class="leading-none">{{ props.text }}</span>
|
|
34
38
|
|
|
35
39
|
<!-- Remove button -->
|
|
36
|
-
<button type="button" :aria-label="
|
|
37
|
-
<HugeiconsIcon :icon="Cancel01Icon" class="size-3" />
|
|
40
|
+
<button type="button" :aria-label="resolvedRemoveLabel" class="group/remove inline-flex size-4 cursor-pointer items-center justify-center rounded-sm opacity-70 [transition:opacity_150ms,background-color_150ms,scale_300ms_var(--ease-spring)] hover:bg-black/5 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/20 active:scale-75 dark:hover:bg-white/10" @click="handleRemove">
|
|
41
|
+
<HugeiconsIcon :icon="Cancel01Icon" class="size-3 transition-transform duration-300 ease-spring group-hover/remove:rotate-90" />
|
|
38
42
|
</button>
|
|
39
43
|
</div>
|
|
40
44
|
</template>
|