@brumaombra/ui-vintage 0.8.1 → 0.9.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/AGENTS.md +41 -0
- package/COMPONENTS.md +91 -17
- package/README.md +296 -262
- package/module.mjs +5 -11
- package/package.json +31 -13
- package/src/components/blog/AllPostsSection.vue +1 -2
- package/src/components/blog/BlogContentRenderer.vue +144 -49
- package/src/components/blog/BlogFAQSection.vue +13 -15
- package/src/components/blog/BlogHeaderSection.vue +103 -11
- package/src/components/blog/BlogInfoSection.vue +46 -42
- package/src/components/blog/BlogPostHeader.vue +181 -0
- package/src/components/blog/BlogProgressBar.vue +3 -2
- package/src/components/blog/BlogSectionTitle.vue +27 -6
- package/src/components/blog/BlogTagsSection.vue +20 -8
- package/src/components/blog/CategoriesList.vue +2 -2
- package/src/components/blog/CategoryCard.vue +23 -19
- package/src/components/blog/HeaderCarousel.vue +117 -145
- package/src/components/blog/PostCard.vue +25 -33
- package/src/components/blog/PostsList.vue +3 -2
- package/src/components/blog/SocialShareSidebar.vue +62 -42
- package/src/components/blog/TableOfContents.vue +71 -34
- package/src/components/blog/index.ts +1 -0
- package/src/components/card-grid/CardGrid.vue +2 -2
- package/src/components/confirm-dialog/ConfirmDialog.vue +5 -0
- package/src/components/confirm-dialog/confirm-dialog-state.ts +22 -22
- package/src/components/content/BlogList.vue +30 -42
- package/src/components/content/BlogTable.vue +26 -30
- package/src/components/content/CodeBlock.vue +96 -22
- package/src/components/content/Flow.vue +30 -36
- package/src/components/content/ProseHr.vue +6 -6
- package/src/components/content/ProsePre.vue +9 -4
- package/src/components/content/Terminal.vue +42 -14
- package/src/components/cookie-consent/CookieConsent.vue +1 -1
- package/src/components/dashboard-shell/DashboardShell.vue +24 -7
- package/src/components/empty-state-card/EmptyStateCard.vue +2 -2
- package/src/components/error-page/ErrorPage.vue +3 -3
- package/src/components/info-card/InfoCard.vue +4 -4
- package/src/components/landing/landing-footer/LandingFooter.vue +16 -6
- package/src/components/landing/landing-navbar/LandingNavbar.vue +63 -51
- package/src/components/language-flag/index.ts +1 -0
- package/src/components/language-selector/LanguageSelector.vue +18 -10
- package/src/components/load-more-button/LoadMoreButton.vue +7 -12
- package/src/components/loading-state-card/LoadingStateCard.vue +2 -2
- package/src/components/message-dialog/MessageDialog.vue +11 -11
- package/src/components/message-dialog/message-dialog-state.ts +1 -4
- package/src/components/message-toast/MessageToast.vue +24 -9
- package/src/components/message-toast/message-toast-state.ts +20 -0
- package/src/components/page-header/PageHeader.vue +1 -1
- package/src/components/progress-component/ProgressComponent.vue +7 -3
- package/src/components/single-value-card/SingleValueCard.vue +37 -39
- package/src/components/slider-form-component/SliderFormComponent.vue +1 -1
- package/src/components/stat-strip/StatStrip.vue +36 -0
- package/src/components/stat-strip/index.ts +2 -0
- package/src/components/switch-form-component/SwitchFormComponent.vue +5 -5
- package/src/components/text-link/TextLink.vue +1 -1
- package/src/components/theme-selector/ThemeSelector.vue +2 -2
- package/src/components/theme-selector/theme-transition.ts +15 -3
- package/src/components/ui/accordion/Accordion.vue +18 -18
- package/src/components/ui/alert/AlertTitle.vue +1 -1
- package/src/components/ui/alert/index.ts +1 -1
- package/src/components/ui/avatar/AvatarFallback.vue +2 -1
- package/src/components/ui/avatar/AvatarGroup.vue +13 -3
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +2 -2
- package/src/components/ui/breadcrumb/BreadcrumbLink.vue +1 -1
- package/src/components/ui/breadcrumb/BreadcrumbList.vue +1 -1
- package/src/components/ui/breadcrumb/BreadcrumbPage.vue +1 -1
- package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +2 -6
- package/src/components/ui/busy/Busy.vue +5 -7
- package/src/components/ui/button/Button.vue +4 -2
- package/src/components/ui/button/index.ts +1 -4
- package/src/components/ui/calendar/Calendar.vue +12 -7
- package/src/components/ui/calendar/CalendarCell.vue +1 -1
- package/src/components/ui/calendar/CalendarCellTrigger.vue +1 -1
- package/src/components/ui/calendar/CalendarHeadCell.vue +1 -1
- package/src/components/ui/calendar/CalendarNextButton.vue +1 -1
- package/src/components/ui/calendar/CalendarPrevButton.vue +1 -1
- package/src/components/ui/card/Card.vue +5 -15
- package/src/components/ui/checkbox/Checkbox.vue +5 -1
- package/src/components/ui/combobox/ComboboxInput.vue +5 -1
- package/src/components/ui/command/CommandGroup.vue +17 -3
- package/src/components/ui/command/CommandInput.vue +1 -1
- package/src/components/ui/command/CommandItem.vue +2 -2
- package/src/components/ui/command/CommandList.vue +5 -1
- package/src/components/ui/data-table/DataTable.vue +11 -3
- package/src/components/ui/date-picker/DatePicker.vue +7 -1
- package/src/components/ui/date-time-picker/DateTimePicker.vue +5 -1
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +1 -1
- package/src/components/ui/dropdown-menu/classes.ts +2 -0
- package/src/components/ui/field/Field.vue +4 -0
- package/src/components/ui/field/FieldLabel.vue +6 -2
- package/src/components/ui/field/field-context.ts +62 -0
- package/src/components/ui/field/index.ts +1 -0
- package/src/components/ui/file-dropzone/FileDropzone.vue +4 -4
- package/src/components/ui/input/Input.vue +5 -1
- package/src/components/ui/native-select/NativeSelect.vue +6 -2
- package/src/components/ui/number-field/NumberField.vue +5 -1
- package/src/components/ui/pagination/PaginationItem.vue +1 -1
- package/src/components/ui/pin-input/PinInputInput.vue +2 -2
- package/src/components/ui/progress/Progress.vue +2 -2
- package/src/components/ui/radio-group/RadioGroupCard.vue +1 -1
- package/src/components/ui/select/SelectItem.vue +1 -1
- package/src/components/ui/select/SelectItemContent.vue +1 -1
- package/src/components/ui/select/SelectTrigger.vue +5 -1
- package/src/components/ui/sidebar/Sidebar.vue +1 -1
- package/src/components/ui/sidebar/SidebarGroupAction.vue +1 -1
- package/src/components/ui/sidebar/SidebarGroupLabel.vue +1 -1
- package/src/components/ui/sidebar/SidebarInset.vue +1 -1
- package/src/components/ui/sidebar/SidebarMenuAction.vue +1 -1
- package/src/components/ui/sidebar/SidebarMenuBadge.vue +1 -1
- package/src/components/ui/sidebar/SidebarMenuSkeleton.vue +2 -2
- package/src/components/ui/sidebar/SidebarMenuSubButton.vue +1 -1
- package/src/components/ui/sidebar/SidebarProvider.vue +17 -6
- package/src/components/ui/sidebar/index.ts +2 -2
- package/src/components/ui/sidebar/utils.ts +1 -1
- package/src/components/ui/slider/Slider.vue +5 -4
- package/src/components/ui/spinner/Spinner.vue +3 -2
- package/src/components/ui/stepper/StepperDescription.vue +5 -3
- package/src/components/ui/stepper/StepperIndicator.vue +2 -2
- package/src/components/ui/stepper/StepperTitle.vue +5 -3
- package/src/components/ui/switch/Switch.vue +6 -2
- package/src/components/ui/tabs/TabsList.vue +1 -1
- package/src/components/ui/tabs/TabsTrigger.vue +1 -1
- package/src/components/ui/textarea/Textarea.vue +5 -1
- package/src/components/ui/time-picker/TimePicker.vue +2 -2
- package/src/components/ui/toggle-group/ToggleGroup.vue +1 -1
- package/src/components/ui/tooltip/TooltipContent.vue +1 -1
- package/src/i18n/de.json +19 -7
- package/src/i18n/en.json +19 -7
- package/src/i18n/es.json +19 -7
- package/src/i18n/fr.json +19 -7
- package/src/i18n/it.json +19 -7
- package/src/i18n/ja.json +19 -7
- package/src/i18n/pt.json +19 -7
- package/src/i18n/ru.json +19 -7
- package/src/i18n/zh.json +19 -7
- package/src/lib/color-tokens.ts +1 -1
- package/src/lib/i18n.ts +32 -4
- package/src/lib/utils.ts +21 -1
- package/src/runtime/ui-vintage-i18n-plugin.ts +11 -1
- package/src/styles.css +52 -16
- package/skills/ui-vintage/SKILL.md +0 -42
|
@@ -4,7 +4,7 @@ import type { HTMLAttributes } from 'vue';
|
|
|
4
4
|
import { ArrowDownRight01Icon, ArrowUpRight01Icon } from '@hugeicons/core-free-icons';
|
|
5
5
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
6
6
|
import { AnimatedNumber } from '../ui/animated-number';
|
|
7
|
-
import { Card
|
|
7
|
+
import { Card } from '../ui/card';
|
|
8
8
|
import { cn } from '../../lib/utils';
|
|
9
9
|
import type { HugeiconsIconDefinition } from '../../lib/common-types';
|
|
10
10
|
|
|
@@ -33,11 +33,11 @@ const props = withDefaults(defineProps<{
|
|
|
33
33
|
const valueClass = computed(() => {
|
|
34
34
|
switch (props.valueColor) {
|
|
35
35
|
case 'green':
|
|
36
|
-
return 'text-
|
|
36
|
+
return 'text-success';
|
|
37
37
|
case 'red':
|
|
38
|
-
return 'text-
|
|
38
|
+
return 'text-destructive';
|
|
39
39
|
case 'gray':
|
|
40
|
-
return 'text-
|
|
40
|
+
return 'text-muted-foreground';
|
|
41
41
|
default:
|
|
42
42
|
return 'text-foreground';
|
|
43
43
|
}
|
|
@@ -46,45 +46,43 @@ const valueClass = computed(() => {
|
|
|
46
46
|
// Trend badge presentation
|
|
47
47
|
const trendIsPositive = computed(() => (props.trend ?? 0) >= 0);
|
|
48
48
|
const formattedTrend = computed(() => `${trendIsPositive.value ? '+' : ''}${props.trend}%`);
|
|
49
|
+
|
|
50
|
+
// Value shown without the count-up (numbers are formatted only when formatOptions is given)
|
|
51
|
+
const staticValue = computed(() => typeof props.value === 'number' && props.formatOptions ? new Intl.NumberFormat(undefined, props.formatOptions).format(props.value) : props.value);
|
|
49
52
|
</script>
|
|
50
53
|
|
|
51
54
|
<template>
|
|
52
|
-
<Card :class="cn('
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
</
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<span class="mb-1 truncate text-xs font-semibold text-muted-foreground">
|
|
63
|
-
{{ props.label }}
|
|
64
|
-
</span>
|
|
55
|
+
<Card :class="cn('gap-0! p-4!', props.class)">
|
|
56
|
+
<!-- Label and icon -->
|
|
57
|
+
<div class="flex items-start justify-between gap-3">
|
|
58
|
+
<span class="min-w-0 text-[11px] font-semibold leading-5 tracking-wider text-muted-foreground uppercase">
|
|
59
|
+
{{ props.label }}
|
|
60
|
+
</span>
|
|
61
|
+
<span aria-hidden="true" class="flex size-8 shrink-0 items-center justify-center rounded border border-primary/30 text-primary">
|
|
62
|
+
<HugeiconsIcon :icon="props.icon" class="size-4" />
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
<!-- Value and trend -->
|
|
67
|
+
<div class="mt-1 flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
68
|
+
<span :class="['text-2xl font-semibold tracking-tight tabular-nums', valueClass]">
|
|
69
|
+
<AnimatedNumber v-if="props.animate && typeof props.value === 'number'" :value="props.value" :format-options="props.formatOptions" />
|
|
70
|
+
<template v-else>{{ staticValue }}</template>
|
|
71
|
+
</span>
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
<!-- Trend badge -->
|
|
74
|
+
<span v-if="props.trend !== null" :class="cn('inline-flex items-center gap-0.5 rounded-sm px-1 py-0.5 text-[11px] font-semibold tabular-nums', trendIsPositive ? 'bg-success/10 text-success' : 'bg-destructive/10 text-destructive')">
|
|
75
|
+
<HugeiconsIcon :icon="trendIsPositive ? ArrowUpRight01Icon : ArrowDownRight01Icon" class="size-3" />
|
|
76
|
+
{{ formattedTrend }}
|
|
77
|
+
</span>
|
|
78
|
+
<span v-if="props.trend !== null && props.trendLabel" class="text-[11px] text-muted-foreground">
|
|
79
|
+
{{ props.trendLabel }}
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</div>
|
|
88
|
-
</CardContent>
|
|
83
|
+
<!-- Description -->
|
|
84
|
+
<span v-if="props.description" class="mt-1 text-xs leading-5 text-muted-foreground">
|
|
85
|
+
{{ props.description }}
|
|
86
|
+
</span>
|
|
89
87
|
</Card>
|
|
90
|
-
</template>
|
|
88
|
+
</template>
|
|
@@ -57,6 +57,6 @@ const handleUpdateModelValue = (value?: number[]) => {
|
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
59
|
<!-- Slider control -->
|
|
60
|
-
<Slider :id="props.id" :model-value="sliderValue" :min="props.min" :max="props.max" :step="props.step" @update:model-value="handleUpdateModelValue" />
|
|
60
|
+
<Slider :id="props.id" :model-value="sliderValue" :min="props.min" :max="props.max" :step="props.step" :thumb-label="props.label" @update:model-value="handleUpdateModelValue" />
|
|
61
61
|
</Card>
|
|
62
62
|
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
4
|
+
import { AnimatedNumber } from '../ui/animated-number';
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
import type { HugeiconsIconDefinition } from '../../lib/common-types';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
items: Array<{
|
|
11
|
+
label: string;
|
|
12
|
+
value: string | number;
|
|
13
|
+
icon?: HugeiconsIconDefinition;
|
|
14
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
15
|
+
}>;
|
|
16
|
+
animate?: boolean;
|
|
17
|
+
class?: HTMLAttributes['class'];
|
|
18
|
+
}>(), {
|
|
19
|
+
animate: true
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<!-- Compact row of stats in one bordered strip -->
|
|
25
|
+
<ul v-if="props.items.length > 0" data-slot="stat-strip" :class="cn('inline-flex max-w-full flex-wrap items-stretch overflow-hidden rounded border border-border bg-card text-xs shadow-elevated-sm', props.class)">
|
|
26
|
+
<li v-for="(item, index) in props.items" :key="item.label" :class="['flex items-center gap-2 px-4 py-2.5', index > 0 && 'border-l border-border']">
|
|
27
|
+
<HugeiconsIcon v-if="item.icon" :icon="item.icon" class="size-4 shrink-0 text-primary" />
|
|
28
|
+
<span class="font-semibold text-foreground tabular-nums">
|
|
29
|
+
<AnimatedNumber v-if="props.animate && typeof item.value === 'number'" :value="item.value" :format-options="item.formatOptions" />
|
|
30
|
+
<template v-else-if="typeof item.value === 'number'">{{ new Intl.NumberFormat(undefined, item.formatOptions).format(item.value) }}</template>
|
|
31
|
+
<template v-else>{{ item.value }}</template>
|
|
32
|
+
</span>
|
|
33
|
+
<span class="text-muted-foreground">{{ item.label }}</span>
|
|
34
|
+
</li>
|
|
35
|
+
</ul>
|
|
36
|
+
</template>
|
|
@@ -20,17 +20,17 @@ const emits = defineEmits<{
|
|
|
20
20
|
<Card class="flex flex-col gap-2 px-4 py-3 sm:gap-3!">
|
|
21
21
|
<!-- Switch label and control -->
|
|
22
22
|
<div class="flex items-center justify-between gap-4">
|
|
23
|
-
<!-- Switch label -->
|
|
24
|
-
<
|
|
23
|
+
<!-- Switch label (names the switch and toggles it on click) -->
|
|
24
|
+
<label :for="props.id" class="cursor-pointer text-sm font-semibold">
|
|
25
25
|
{{ props.label }}
|
|
26
|
-
</
|
|
26
|
+
</label>
|
|
27
27
|
|
|
28
28
|
<!-- Switch control -->
|
|
29
|
-
<Switch :id="props.id" :model-value="props.modelValue" @update:model-value="emits('update:modelValue', $event)" />
|
|
29
|
+
<Switch :id="props.id" :aria-describedby="`${props.id}-description`" :model-value="props.modelValue" @update:model-value="emits('update:modelValue', $event)" />
|
|
30
30
|
</div>
|
|
31
31
|
|
|
32
32
|
<!-- Description -->
|
|
33
|
-
<div class="text-xs text-muted-foreground">
|
|
33
|
+
<div :id="`${props.id}-description`" class="text-xs text-muted-foreground">
|
|
34
34
|
{{ props.description }}
|
|
35
35
|
</div>
|
|
36
36
|
</Card>
|
|
@@ -31,7 +31,7 @@ const handleClick = (event: MouseEvent) => {
|
|
|
31
31
|
</script>
|
|
32
32
|
|
|
33
33
|
<template>
|
|
34
|
-
<component :is="props.linkComponent" v-bind="getLinkProps()" class="cursor-pointer font-semibold text-
|
|
34
|
+
<component :is="props.linkComponent" v-bind="getLinkProps()" class="cursor-pointer font-semibold text-foreground hover:underline" @click="handleClick">
|
|
35
35
|
{{ props.text }}
|
|
36
36
|
</component>
|
|
37
37
|
</template>
|
|
@@ -98,8 +98,8 @@ onUnmounted(() => {
|
|
|
98
98
|
<!-- Theme options -->
|
|
99
99
|
<PopoverContent side="bottom" align="end" :side-offset="8" class="w-44 p-0!">
|
|
100
100
|
<Command :model-value="currentTheme">
|
|
101
|
-
<CommandList>
|
|
102
|
-
<CommandGroup :heading="t('uiVintage.theme.title')">
|
|
101
|
+
<CommandList :aria-label="t('uiVintage.theme.title')">
|
|
102
|
+
<CommandGroup :heading="t('uiVintage.theme.title')" stagger>
|
|
103
103
|
<CommandItem v-for="option in themeOptions" :key="option.key" :value="option.key" @select="handleSelectTheme(option.key)">
|
|
104
104
|
<HugeiconsIcon :icon="option.icon" class="size-4" />
|
|
105
105
|
<span class="flex-1">{{ option.label }}</span>
|
|
@@ -27,8 +27,14 @@ export const isDarkTheme = (theme: ThemeMode) => {
|
|
|
27
27
|
// Read the persisted theme mode
|
|
28
28
|
export const getStoredTheme = (): ThemeMode => {
|
|
29
29
|
if (typeof localStorage === 'undefined') return 'auto';
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
|
|
31
|
+
// Storage can throw when the browser blocks it (privacy settings, sandboxed frames)
|
|
32
|
+
try {
|
|
33
|
+
const storedTheme = localStorage.getItem(THEME_STORAGE_KEY);
|
|
34
|
+
return storedTheme === 'light' || storedTheme === 'dark' ? storedTheme : 'auto';
|
|
35
|
+
} catch {
|
|
36
|
+
return 'auto';
|
|
37
|
+
}
|
|
32
38
|
};
|
|
33
39
|
|
|
34
40
|
// Apply the selected theme to the document and persist it
|
|
@@ -37,7 +43,13 @@ export const applyTheme = (theme: ThemeMode) => {
|
|
|
37
43
|
const root = document.documentElement;
|
|
38
44
|
root.classList.remove('light', 'dark');
|
|
39
45
|
root.classList.add(isDarkTheme(theme) ? 'dark' : 'light');
|
|
40
|
-
|
|
46
|
+
|
|
47
|
+
// Persisting is best effort: the theme still applies when storage is blocked
|
|
48
|
+
try {
|
|
49
|
+
localStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
50
|
+
} catch {
|
|
51
|
+
// Ignore storage errors
|
|
52
|
+
}
|
|
41
53
|
};
|
|
42
54
|
|
|
43
55
|
// Apply a theme with a circular reveal that grows from the origin point (View Transitions API)
|
|
@@ -26,6 +26,7 @@ const emits = defineEmits<{
|
|
|
26
26
|
}>();
|
|
27
27
|
|
|
28
28
|
const contentId = useId();
|
|
29
|
+
const triggerId = useId();
|
|
29
30
|
const internalExpanded = ref(props.initiallyExpanded);
|
|
30
31
|
|
|
31
32
|
// Support both controlled (v-model:open) and uncontrolled usage
|
|
@@ -40,34 +41,33 @@ const toggleExpanded = () => {
|
|
|
40
41
|
</script>
|
|
41
42
|
|
|
42
43
|
<template>
|
|
43
|
-
<Card :data-state="expanded ? 'open' : 'closed'" :class="cn('group/accordion gap-0! overflow-hidden
|
|
44
|
+
<Card :data-state="expanded ? 'open' : 'closed'" :class="cn('group/accordion gap-0! overflow-hidden p-0!', props.class)">
|
|
44
45
|
<!-- Header -->
|
|
45
|
-
<button type="button" :aria-expanded="expanded" :aria-controls="contentId" class="flex w-full cursor-pointer items-center
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
</div>
|
|
46
|
+
<button :id="triggerId" type="button" :aria-expanded="expanded" :aria-controls="contentId" class="group/trigger flex w-full cursor-pointer items-center gap-3 px-5 py-3.5 text-left outline-none focus-visible:bg-surface focus-visible:ring-[3px] focus-visible:ring-inset focus-visible:ring-ring/40" @click="toggleExpanded">
|
|
47
|
+
<!-- Icon -->
|
|
48
|
+
<span v-if="props.icon" class="flex size-7 shrink-0 items-center justify-center rounded border border-primary/30 text-primary">
|
|
49
|
+
<HugeiconsIcon :icon="props.icon" :class="cn('size-3.5', props.iconClass)" />
|
|
50
|
+
</span>
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
<!-- Title -->
|
|
53
|
+
<span class="min-w-0 flex-1 text-sm font-semibold text-foreground">
|
|
54
|
+
{{ props.title }}
|
|
55
|
+
</span>
|
|
56
|
+
|
|
57
|
+
<!-- Optional content before the toggle icon -->
|
|
58
|
+
<slot name="trailing" />
|
|
57
59
|
|
|
58
60
|
<!-- Toggle icon -->
|
|
59
|
-
<
|
|
60
|
-
<HugeiconsIcon :icon="ArrowDown01Icon" class="size-4 text-muted-foreground transition-colors duration-200 group-data-[state=open]/accordion:text-primary" />
|
|
61
|
-
</div>
|
|
61
|
+
<HugeiconsIcon :icon="ArrowDown01Icon" class="size-4 shrink-0 text-muted-foreground transition-[rotate,color] duration-[420ms] ease-spring group-hover/trigger:text-primary group-data-[state=open]/accordion:rotate-180 group-data-[state=open]/accordion:text-primary" />
|
|
62
62
|
</button>
|
|
63
63
|
|
|
64
64
|
<!-- Content -->
|
|
65
|
-
<div :id="contentId" role="region" :aria-hidden="!expanded" :inert="!expanded || undefined" :class="['grid transition-[grid-template-rows,opacity] duration-[380ms] ease-out-expo', expanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0']">
|
|
65
|
+
<div :id="contentId" role="region" :aria-labelledby="triggerId" :aria-hidden="!expanded" :inert="!expanded || undefined" :class="['grid transition-[grid-template-rows,opacity] duration-[380ms] ease-out-expo', expanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0']">
|
|
66
66
|
<div class="overflow-hidden">
|
|
67
|
-
<div :class="['px-5
|
|
67
|
+
<div :class="['border-t border-border px-5 py-4 transition-[translate,filter] duration-[420ms] ease-spring', expanded ? 'translate-y-0 blur-none' : '-translate-y-2 blur-[2px]']">
|
|
68
68
|
<slot />
|
|
69
69
|
</div>
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
72
72
|
</Card>
|
|
73
|
-
</template>
|
|
73
|
+
</template>
|
|
@@ -9,7 +9,7 @@ const props = defineProps<{
|
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
|
-
<div data-slot="alert-title" :class="cn('col-start-2 line-clamp-1 min-h-4 font-
|
|
12
|
+
<div data-slot="alert-title" :class="cn('col-start-2 line-clamp-1 min-h-4 font-semibold tracking-tight text-current', props.class)">
|
|
13
13
|
<slot />
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
@@ -11,5 +11,5 @@ export type AlertColors = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export const alertClasses: (props?: ClassProp) => string = cva(
|
|
14
|
-
'relative w-full rounded px-3 py-2 text-xs transition-colors duration-300 ease-in-out grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current sm:px-4 sm:py-3 sm:text-sm',
|
|
14
|
+
'relative w-full rounded px-3 py-2 text-xs transition-colors duration-300 ease-in-out grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 content-start items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current sm:px-4 sm:py-3 sm:text-sm',
|
|
15
15
|
);
|
|
@@ -25,7 +25,8 @@ const initials = computed(() => getInitials(props.name));
|
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<template>
|
|
28
|
-
|
|
28
|
+
<!-- With a name the initials act as an image of the person, so screen readers announce the name instead of the letters -->
|
|
29
|
+
<AvatarFallback data-slot="avatar-fallback" v-bind="delegatedProps" :role="props.name ? 'img' : undefined" :aria-label="props.name || undefined" :class="cn('flex size-full items-center justify-center bg-surface font-semibold uppercase text-muted-foreground', textClass, shapeClass, props.class)">
|
|
29
30
|
<slot>{{ initials }}</slot>
|
|
30
31
|
</AvatarFallback>
|
|
31
32
|
</template>
|
|
@@ -38,6 +38,15 @@ function getChildren() {
|
|
|
38
38
|
return flatten(slots.default?.() ?? []);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Overlap per size, kept clear of the initials so stacked fallbacks stay readable
|
|
42
|
+
const overlapClasses: Record<AvatarSize, string> = {
|
|
43
|
+
xs: '-space-x-1',
|
|
44
|
+
sm: '-space-x-1.5',
|
|
45
|
+
md: '-space-x-2',
|
|
46
|
+
lg: '-space-x-2.5',
|
|
47
|
+
xl: '-space-x-3'
|
|
48
|
+
};
|
|
49
|
+
|
|
41
50
|
// Whether a max limit applies
|
|
42
51
|
function hasMax() {
|
|
43
52
|
return props.max !== undefined && props.max >= 0;
|
|
@@ -60,15 +69,16 @@ const VNodeRenderer = (renderProps: { node: VNode }) => renderProps.node;
|
|
|
60
69
|
|
|
61
70
|
<template>
|
|
62
71
|
<div data-slot="avatar-group" :class="cn(
|
|
63
|
-
'flex items-center
|
|
64
|
-
|
|
72
|
+
'flex items-center',
|
|
73
|
+
overlapClasses[props.size],
|
|
74
|
+
'*:ring-2 *:ring-background',
|
|
65
75
|
props.class
|
|
66
76
|
)">
|
|
67
77
|
<!-- Visible avatars -->
|
|
68
78
|
<VNodeRenderer v-for="(node, index) in getVisible()" :key="node.key ?? index" :node="node" />
|
|
69
79
|
|
|
70
80
|
<!-- Overflow bubble -->
|
|
71
|
-
<span v-if="getOverflow() > 0" data-slot="avatar-group-overflow" :class="cn('relative inline-flex shrink-0 select-none items-center justify-center bg-surface font-
|
|
81
|
+
<span v-if="getOverflow() > 0" data-slot="avatar-group-overflow" :class="cn('relative inline-flex shrink-0 select-none items-center justify-center bg-surface font-semibold text-muted-foreground', avatarSizeClasses[props.size], avatarShapeClasses[props.shape], avatarTextClasses[props.size])">
|
|
72
82
|
+{{ getOverflow() }}
|
|
73
83
|
</span>
|
|
74
84
|
</div>
|
|
@@ -14,10 +14,10 @@ const { t } = useI18n();
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
<span data-slot="breadcrumb-ellipsis" role="presentation"
|
|
17
|
+
<span data-slot="breadcrumb-ellipsis" role="presentation" :class="cn('flex size-6 items-center justify-center rounded-sm transition-colors duration-150 hover:bg-accent hover:text-primary', props.class)">
|
|
18
18
|
<!-- Default slot content -->
|
|
19
19
|
<slot>
|
|
20
|
-
<HugeiconsIcon :icon="MoreHorizontalIcon" class="size-4" />
|
|
20
|
+
<HugeiconsIcon :icon="MoreHorizontalIcon" class="size-4" aria-hidden="true" />
|
|
21
21
|
</slot>
|
|
22
22
|
|
|
23
23
|
<!-- Screen reader text for accessibility -->
|
|
@@ -11,7 +11,7 @@ const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes
|
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<template>
|
|
14
|
-
<Primitive data-slot="breadcrumb-link" :as="as" :as-child="asChild" :class="cn('text-muted-foreground hover:
|
|
14
|
+
<Primitive data-slot="breadcrumb-link" :as="as" :as-child="asChild" :class="cn('rounded-sm bg-[linear-gradient(currentColor,currentColor)] bg-[length:0%_1px] bg-left-bottom bg-no-repeat text-muted-foreground outline-none transition-[color,background-size] duration-300 ease-out-expo hover:bg-[length:100%_1px] hover:text-primary focus-visible:ring-[3px] focus-visible:ring-ring/45', props.class)">
|
|
15
15
|
<slot />
|
|
16
16
|
</Primitive>
|
|
17
17
|
</template>
|
|
@@ -9,7 +9,7 @@ const props = defineProps<{
|
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
|
-
<ol data-slot="breadcrumb-list" :class="cn('
|
|
12
|
+
<ol data-slot="breadcrumb-list" :class="cn('flex w-fit max-w-full flex-wrap items-center gap-1.5 rounded border border-border bg-card px-3 py-1.5 text-xs wrap-break-word text-muted-foreground shadow-elevated-sm sm:gap-2 sm:text-sm', props.class)">
|
|
13
13
|
<slot />
|
|
14
14
|
</ol>
|
|
15
15
|
</template>
|
|
@@ -9,7 +9,7 @@ const props = defineProps<{
|
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
|
-
<span data-slot="breadcrumb-page" role="link" aria-disabled="true" aria-current="page" :class="cn('
|
|
12
|
+
<span data-slot="breadcrumb-page" role="link" aria-disabled="true" aria-current="page" :class="cn('min-w-0 font-semibold text-foreground', props.class)">
|
|
13
13
|
<slot />
|
|
14
14
|
</span>
|
|
15
15
|
</template>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
|
-
import { ArrowRight01Icon } from '@hugeicons/core-free-icons';
|
|
4
|
-
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
5
3
|
import { cn } from '../../../lib/utils';
|
|
6
4
|
|
|
7
5
|
// Props
|
|
@@ -11,9 +9,7 @@ const props = defineProps<{
|
|
|
11
9
|
</script>
|
|
12
10
|
|
|
13
11
|
<template>
|
|
14
|
-
<li data-slot="breadcrumb-separator" role="presentation" aria-hidden="true" :class="cn('[&>svg]:size-3.5', props.class)">
|
|
15
|
-
<slot>
|
|
16
|
-
<HugeiconsIcon :icon="ArrowRight01Icon" class="size-3.5" />
|
|
17
|
-
</slot>
|
|
12
|
+
<li data-slot="breadcrumb-separator" role="presentation" aria-hidden="true" :class="cn('font-semibold text-primary/50 select-none [&>svg]:size-3.5', props.class)">
|
|
13
|
+
<slot>/</slot>
|
|
18
14
|
</li>
|
|
19
15
|
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, type HTMLAttributes } from 'vue';
|
|
3
|
-
import {
|
|
3
|
+
import { useUiVintageMessage } from '../../../lib/i18n';
|
|
4
4
|
import { cn } from '../../../lib/utils';
|
|
5
5
|
import { Spinner } from '../spinner';
|
|
6
6
|
|
|
@@ -14,7 +14,8 @@ const props = withDefaults(defineProps<{
|
|
|
14
14
|
show: false
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const message = useUiVintageMessage();
|
|
18
|
+
const resolvedLabel = computed(() => props.label || message('uiVintage.common.loading.title', 'Loading...'));
|
|
18
19
|
</script>
|
|
19
20
|
|
|
20
21
|
<template>
|
|
@@ -28,11 +29,8 @@ const resolvedLabel = computed(() => props.label || getUiVintageRuntimeMessage('
|
|
|
28
29
|
props.class,
|
|
29
30
|
)">
|
|
30
31
|
<slot>
|
|
31
|
-
<!-- Spinner
|
|
32
|
-
<
|
|
33
|
-
<span class="absolute size-14 rounded-full bg-primary/15 blur-md" />
|
|
34
|
-
<Spinner data-slot="busy-spinner" size="xl" class="text-primary" />
|
|
35
|
-
</div>
|
|
32
|
+
<!-- Spinner (decorative: the container already announces the label) -->
|
|
33
|
+
<Spinner data-slot="busy-spinner" size="xl" aria-hidden="true" class="text-primary" />
|
|
36
34
|
|
|
37
35
|
<!-- Label -->
|
|
38
36
|
<span v-if="resolvedLabel" data-slot="busy-label" class="text-xs font-semibold text-foreground sm:text-sm">
|
|
@@ -12,15 +12,17 @@ const props = withDefaults(defineProps<PrimitiveProps & {
|
|
|
12
12
|
variant?: ButtonVariants['variant'];
|
|
13
13
|
size?: ButtonVariants['size'];
|
|
14
14
|
loading?: boolean;
|
|
15
|
+
disabled?: boolean;
|
|
15
16
|
class?: HTMLAttributes['class'];
|
|
16
17
|
}>(), {
|
|
17
18
|
as: 'button',
|
|
18
|
-
loading: false
|
|
19
|
+
loading: false,
|
|
20
|
+
disabled: false
|
|
19
21
|
});
|
|
20
22
|
</script>
|
|
21
23
|
|
|
22
24
|
<template>
|
|
23
|
-
<Primitive data-slot="button" :data-variant="variant" :data-size="size" :data-loading="props.loading ? '' : undefined" :aria-busy="props.loading || undefined" :disabled="props.loading || undefined" :as="as" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), props.class)">
|
|
25
|
+
<Primitive data-slot="button" :data-variant="variant" :data-size="size" :data-loading="props.loading ? '' : undefined" :aria-busy="props.loading || undefined" :disabled="props.disabled || props.loading || undefined" :as="as" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), props.class)">
|
|
24
26
|
<!-- As-child buttons delegate rendering to the slotted element -->
|
|
25
27
|
<slot v-if="asChild" />
|
|
26
28
|
|
|
@@ -28,9 +28,6 @@ export type ButtonVariants = {
|
|
|
28
28
|
| undefined;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
// Sheen sweep that crosses solid buttons on hover
|
|
32
|
-
const sheenClasses = "before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:-translate-x-full before:bg-[linear-gradient(105deg,transparent_35%,rgb(255_255_255/0.38)_50%,transparent_65%)] before:transition-transform before:duration-700 before:ease-out-expo hover:before:translate-x-full";
|
|
33
|
-
|
|
34
31
|
export const buttonVariants: (props?: ButtonVariants & ClassProp) => string = cva(
|
|
35
32
|
[
|
|
36
33
|
"relative isolate inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 overflow-hidden whitespace-nowrap rounded text-xs font-semibold outline-none sm:text-sm",
|
|
@@ -43,7 +40,7 @@ export const buttonVariants: (props?: ButtonVariants & ClassProp) => string = cv
|
|
|
43
40
|
{
|
|
44
41
|
variants: {
|
|
45
42
|
variant: {
|
|
46
|
-
primary:
|
|
43
|
+
primary: getButtonVariantClasses('primary'),
|
|
47
44
|
secondary: getButtonVariantClasses('secondary'),
|
|
48
45
|
ghost: getButtonVariantClasses('ghost'),
|
|
49
46
|
link: getButtonVariantClasses('link'),
|
|
@@ -6,7 +6,8 @@ import { getLocalTimeZone, today } from '@internationalized/date';
|
|
|
6
6
|
import { createReusableTemplate, reactiveOmit, useVModel } from '@vueuse/core';
|
|
7
7
|
import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from 'reka-ui';
|
|
8
8
|
import { createYear, createYearRange, toDate } from 'reka-ui/date';
|
|
9
|
-
import { computed, toRaw } from 'vue';
|
|
9
|
+
import { computed, toRaw, watch } from 'vue';
|
|
10
|
+
import { useI18n } from 'vue-i18n';
|
|
10
11
|
import { cn } from '../../../lib/utils';
|
|
11
12
|
import { NativeSelect, NativeSelectOption } from '../native-select';
|
|
12
13
|
import {
|
|
@@ -43,7 +44,11 @@ const placeholder = useVModel(props, 'placeholder', emits, {
|
|
|
43
44
|
defaultValue: props.defaultPlaceholder ?? today(getLocalTimeZone()),
|
|
44
45
|
}) as Ref<DateValue>;
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
// Follow the app language unless a locale is passed explicitly
|
|
48
|
+
const { t, locale: appLocale } = useI18n();
|
|
49
|
+
const resolvedLocale = computed(() => props.locale || appLocale.value || 'en');
|
|
50
|
+
const formatter = useDateFormatter(resolvedLocale.value);
|
|
51
|
+
watch(resolvedLocale, value => formatter.setLocale(value));
|
|
47
52
|
|
|
48
53
|
const yearRange = computed(() => {
|
|
49
54
|
return (
|
|
@@ -81,11 +86,11 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
81
86
|
<template>
|
|
82
87
|
<DefineMonthTemplate v-slot="{ date }">
|
|
83
88
|
<div class="**:data-[slot=native-select-icon]:right-1">
|
|
84
|
-
<div class="relative h-10 rounded border border-
|
|
89
|
+
<div class="relative h-10 rounded border border-border bg-secondary transition-all duration-300 ease-in-out hover:border-border-strong focus-within:border-border-strong">
|
|
85
90
|
<div class="absolute inset-0 z-10 flex h-full items-center text-sm pl-2 pointer-events-none">
|
|
86
91
|
{{ formatter.custom(toDate(date), { month: 'short' }) }}
|
|
87
92
|
</div>
|
|
88
|
-
<NativeSelect class="relative z-20 h-full min-h-0 w-23 rounded-none border-0 bg-transparent pl-2 pr-6 text-xs font-semibold text-transparent shadow-none transition-all duration-300 ease-in-out outline-none ring-0 focus-visible:border-0 focus-visible:ring-0 dark:bg-transparent sm:text-sm" :model-value="date.month" @change="
|
|
93
|
+
<NativeSelect :aria-label="t('uiVintage.datePicker.month')" class="relative z-20 h-full min-h-0 w-23 rounded-none border-0 bg-transparent pl-2 pr-6 text-xs font-semibold text-transparent shadow-none transition-all duration-300 ease-in-out outline-none ring-0 focus-visible:border-0 focus-visible:ring-0 dark:bg-transparent sm:text-sm" :model-value="date.month" @change="
|
|
89
94
|
(e: Event) => {
|
|
90
95
|
placeholder = placeholder.set({
|
|
91
96
|
month: Number((e?.target as any)?.value),
|
|
@@ -104,11 +109,11 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
104
109
|
|
|
105
110
|
<DefineYearTemplate v-slot="{ date }">
|
|
106
111
|
<div class="**:data-[slot=native-select-icon]:right-1">
|
|
107
|
-
<div class="relative h-10 rounded border border-
|
|
112
|
+
<div class="relative h-10 rounded border border-border bg-secondary transition-all duration-300 ease-in-out hover:border-border-strong focus-within:border-border-strong">
|
|
108
113
|
<div class="absolute inset-0 z-10 flex h-full items-center text-sm pl-2 pointer-events-none">
|
|
109
114
|
{{ formatter.custom(toDate(date), { year: 'numeric' }) }}
|
|
110
115
|
</div>
|
|
111
|
-
<NativeSelect class="relative z-20 h-full min-h-0 w-24 rounded-none border-0 bg-transparent pl-2 pr-6 text-xs font-semibold text-transparent shadow-none transition-all duration-300 ease-in-out outline-none ring-0 focus-visible:border-0 focus-visible:ring-0 dark:bg-transparent sm:text-sm" :model-value="date.year" @change="
|
|
116
|
+
<NativeSelect :aria-label="t('uiVintage.datePicker.year')" class="relative z-20 h-full min-h-0 w-24 rounded-none border-0 bg-transparent pl-2 pr-6 text-xs font-semibold text-transparent shadow-none transition-all duration-300 ease-in-out outline-none ring-0 focus-visible:border-0 focus-visible:ring-0 dark:bg-transparent sm:text-sm" :model-value="date.year" @change="
|
|
112
117
|
(e: Event) => {
|
|
113
118
|
placeholder = placeholder.set({
|
|
114
119
|
year: Number((e?.target as any)?.value),
|
|
@@ -125,7 +130,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
125
130
|
</div>
|
|
126
131
|
</DefineYearTemplate>
|
|
127
132
|
|
|
128
|
-
<CalendarRoot v-slot="{ grid, weekDays, date }" v-bind="forwarded" v-model:placeholder="placeholder" data-slot="calendar" :class="cn('p-3', props.class)">
|
|
133
|
+
<CalendarRoot v-slot="{ grid, weekDays, date }" v-bind="forwarded" v-model:placeholder="placeholder" :locale="resolvedLocale" data-slot="calendar" :class="cn('p-3', props.class)">
|
|
129
134
|
<CalendarHeader class="pt-0">
|
|
130
135
|
<nav class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between">
|
|
131
136
|
<CalendarPrevButton>
|
|
@@ -15,7 +15,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
17
|
<CalendarCell data-slot="calendar-cell" :class="cn(
|
|
18
|
-
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 flex-1 [&:has([data-selected])]:rounded
|
|
18
|
+
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 flex-1 [&:has([data-selected])]:rounded',
|
|
19
19
|
props.class,
|
|
20
20
|
)
|
|
21
21
|
" v-bind="forwardedProps">
|
|
@@ -26,7 +26,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
26
26
|
// Disabled
|
|
27
27
|
'data-disabled:text-muted-foreground data-disabled:opacity-50',
|
|
28
28
|
// Unavailable
|
|
29
|
-
'data-unavailable:text-destructive
|
|
29
|
+
'data-unavailable:text-destructive data-unavailable:line-through',
|
|
30
30
|
// Outside months
|
|
31
31
|
'data-outside-view:text-muted-foreground',
|
|
32
32
|
props.class,
|
|
@@ -15,7 +15,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
17
|
<CalendarHeadCell data-slot="calendar-head-cell" :class="cn(
|
|
18
|
-
'text-muted-foreground rounded
|
|
18
|
+
'text-muted-foreground rounded flex-1 font-normal text-[0.8rem]',
|
|
19
19
|
props.class,
|
|
20
20
|
)
|
|
21
21
|
" v-bind="forwardedProps">
|
|
@@ -22,7 +22,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
22
22
|
:class="
|
|
23
23
|
cn(
|
|
24
24
|
buttonVariants({ variant: 'secondary' }),
|
|
25
|
-
'size-7 min-h-10 bg-transparent p-0 text-
|
|
25
|
+
'size-7 min-h-10 bg-transparent p-0 text-muted-foreground hover:text-foreground',
|
|
26
26
|
props.class,
|
|
27
27
|
)
|
|
28
28
|
"
|