@brumaombra/ui-vintage 0.9.6 → 0.9.7
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 +3 -1
- package/COMPONENTS.md +128 -14
- package/package.json +4 -2
- package/src/components/action-bar/ActionBar.vue +38 -0
- package/src/components/action-bar/index.ts +2 -0
- package/src/components/blog/BlogContentRenderer.vue +3 -3
- package/src/components/blog/BlogPostPage.vue +4 -0
- package/src/components/blog/BlogSectionTitle.vue +7 -21
- package/src/components/blog/BlogSummary.vue +31 -0
- package/src/components/blog/blogPageUtils.ts +1 -0
- package/src/components/blog/index.ts +1 -0
- package/src/components/chip/Chip.vue +1 -1
- package/src/components/dashboard-shell/DashboardShell.vue +3 -3
- package/src/components/data-list/DataListText.vue +7 -4
- package/src/components/language-flag/flags/ChineseFlag.vue +1 -1
- package/src/components/language-flag/flags/EnglishFlag.vue +1 -1
- package/src/components/language-flag/flags/FrenchFlag.vue +1 -1
- package/src/components/language-flag/flags/GermanFlag.vue +1 -1
- package/src/components/language-flag/flags/ItalianFlag.vue +1 -1
- package/src/components/language-flag/flags/JapaneseFlag.vue +1 -1
- package/src/components/language-flag/flags/PortugueseFlag.vue +1 -1
- package/src/components/language-flag/flags/RussianFlag.vue +1 -1
- package/src/components/language-flag/flags/SpanishFlag.vue +1 -1
- package/src/components/language-selector/LanguageSelector.vue +2 -2
- package/src/components/section-title/SectionTitle.vue +47 -0
- package/src/components/section-title/index.ts +2 -0
- package/src/components/single-value-card/SingleValueCard.vue +1 -1
- package/src/components/ui/alert-dialog/AlertDialogAction.vue +6 -6
- package/src/components/ui/avatar/Avatar.vue +4 -2
- package/src/components/ui/avatar/AvatarFallback.vue +25 -5
- package/src/components/ui/badge/Badge.vue +6 -2
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +1 -1
- package/src/components/ui/breadcrumb/BreadcrumbLink.vue +1 -1
- package/src/components/ui/button/Button.vue +18 -2
- package/src/components/ui/button/index.ts +4 -0
- package/src/components/ui/card/Card.vue +25 -1
- package/src/components/ui/card/CardMedia.vue +15 -0
- package/src/components/ui/card/CardTitle.vue +8 -3
- package/src/components/ui/card/index.ts +1 -0
- package/src/components/ui/checkbox/Checkbox.vue +1 -1
- package/src/components/ui/combobox/ComboboxSelect.vue +2 -2
- package/src/components/ui/combobox/ComboboxTrigger.vue +1 -1
- package/src/components/ui/data-table/DataTable.vue +1 -1
- package/src/components/ui/dialog/DialogContent.vue +7 -3
- package/src/components/ui/dialog/DialogScrollContent.vue +7 -3
- package/src/components/ui/dialog/index.ts +11 -1
- package/src/components/ui/file-dropzone/FileDropzone.vue +1 -1
- package/src/components/ui/input/Input.vue +42 -9
- package/src/components/ui/kbd/Kbd.vue +8 -4
- package/src/components/ui/number-field/NumberField.vue +2 -2
- package/src/components/ui/progress/Progress.vue +17 -5
- package/src/components/ui/select/SelectItem.vue +1 -1
- package/src/components/ui/skeleton/Skeleton.vue +7 -3
- package/src/components/ui/switch/Switch.vue +1 -1
- package/src/components/ui/table/TableHeader.vue +1 -1
- package/src/components/ui/table/TableRow.vue +11 -4
- package/src/components/ui/tabs/Tabs.vue +1 -1
- package/src/components/ui/tabs/TabsContent.vue +8 -5
- package/src/components/ui/tabs/TabsTrigger.vue +1 -1
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +1 -1
- package/src/components/ui/textarea/Textarea.vue +1 -1
- package/src/components/ui/toggle-group/ToggleGroup.vue +2 -2
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +3 -1
- package/src/components/ui/toggle-group/index.ts +2 -2
- package/src/i18n/de.json +1 -0
- package/src/i18n/en.json +1 -0
- package/src/i18n/es.json +1 -0
- package/src/i18n/fr.json +1 -0
- package/src/i18n/it.json +1 -0
- package/src/i18n/ja.json +1 -0
- package/src/i18n/pt.json +1 -0
- package/src/i18n/ru.json +1 -0
- package/src/i18n/zh.json +1 -0
- package/src/lib/color-tokens.ts +6 -6
- package/src/styles.css +11 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 3" aria-hidden="true" focusable="false">
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-[1.35rem] shrink-0 rounded-[2px]" viewBox="0 0 5 3" aria-hidden="true" focusable="false">
|
|
3
3
|
<rect width="5" height="1" y="0" fill="#000000" />
|
|
4
4
|
<rect width="5" height="1" y="1" fill="#dd0000" />
|
|
5
5
|
<rect width="5" height="1" y="2" fill="#ffce00" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-[1.35rem] shrink-0 rounded-[2px]" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
3
3
|
<g fill-rule="evenodd" stroke-width="1pt">
|
|
4
4
|
<path fill="#fff" d="M0 0h640v480H0z" />
|
|
5
5
|
<path fill="#009246" d="M0 0h213.3v480H0z" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-[1.35rem] shrink-0 rounded-[2px]" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
3
3
|
<path fill="#fff" d="M0 0h640v480H0z" />
|
|
4
4
|
<circle cx="320" cy="240" r="112" fill="#bc002d" />
|
|
5
5
|
</svg>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-[1.35rem] shrink-0 rounded-[2px]" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
3
3
|
<path fill="red" d="M256 0h384v480H256z" />
|
|
4
4
|
<path fill="#060" d="M0 0h256v480H0z" />
|
|
5
5
|
<g fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width=".6">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-[1.35rem] shrink-0 rounded-[2px]" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
|
|
3
3
|
<path fill="#fff" d="M0 0h640v160H0z" />
|
|
4
4
|
<path fill="#0039a6" d="M0 160h640v160H0z" />
|
|
5
5
|
<path fill="#d52b1e" d="M0 320h640v160H0z" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" aria-hidden="true" focusable="false">
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-[1.35rem] shrink-0 rounded-[2px]" viewBox="0 0 750 500" aria-hidden="true" focusable="false">
|
|
3
3
|
<rect width="750" height="500" fill="#c60b1e" />
|
|
4
4
|
<rect y="125" width="750" height="250" fill="#ffc400" />
|
|
5
5
|
</svg>
|
|
@@ -139,7 +139,7 @@ const handleSelectLanguage = (language: string) => {
|
|
|
139
139
|
<!-- Trigger button showing the current language flag -->
|
|
140
140
|
<PopoverTrigger as-child>
|
|
141
141
|
<Button variant="secondary" size="icon" :aria-label="t('uiVintage.language.ariaLabel')">
|
|
142
|
-
<component :is="currentFlagComponent" v-if="currentFlagComponent"
|
|
142
|
+
<component :is="currentFlagComponent" v-if="currentFlagComponent" />
|
|
143
143
|
</Button>
|
|
144
144
|
</PopoverTrigger>
|
|
145
145
|
|
|
@@ -149,7 +149,7 @@ const handleSelectLanguage = (language: string) => {
|
|
|
149
149
|
<CommandList :aria-label="t('uiVintage.language.title')">
|
|
150
150
|
<CommandGroup :heading="t('uiVintage.language.title')" stagger>
|
|
151
151
|
<CommandItem v-for="option in normalizedLanguages" :key="option.code" :value="option.code" @select="handleSelectLanguage(option.code)">
|
|
152
|
-
<component :is="resolveFlagComponent(option.code)"
|
|
152
|
+
<component :is="resolveFlagComponent(option.code)" />
|
|
153
153
|
<span class="flex-1">{{ option.label }}</span>
|
|
154
154
|
<HugeiconsIcon :icon="Tick02Icon" class="ml-auto size-4 shrink-0" :class="props.modelValue === option.code ? 'opacity-100' : 'opacity-0'" />
|
|
155
155
|
</CommandItem>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
3
|
+
import type { HeadingLevel, HugeiconsIconDefinition } from '../../lib/common-types';
|
|
4
|
+
|
|
5
|
+
// Props (a section heading over a rule that starts with a primary segment, with an optional icon tile, description and trailing action)
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
icon?: HugeiconsIconDefinition | null;
|
|
10
|
+
headingLevel?: HeadingLevel;
|
|
11
|
+
}>(), {
|
|
12
|
+
description: '',
|
|
13
|
+
icon: null,
|
|
14
|
+
headingLevel: 2
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<div data-slot="section-title" :class="['relative mb-6 flex gap-3 border-b border-border pb-4 md:mb-8', props.description ? 'flex-col sm:flex-row sm:items-end sm:justify-between' : 'items-center justify-between']">
|
|
20
|
+
<!-- Primary segment at the start of the rule -->
|
|
21
|
+
<span aria-hidden="true" class="absolute -bottom-px left-0 h-0.5 w-12 bg-primary" />
|
|
22
|
+
|
|
23
|
+
<div class="flex min-w-0 items-center gap-3">
|
|
24
|
+
<!-- Icon tile -->
|
|
25
|
+
<span v-if="props.icon" class="flex size-9 shrink-0 items-center justify-center rounded border border-primary/30 text-primary">
|
|
26
|
+
<HugeiconsIcon :icon="props.icon" class="size-4.5" :stroke-width="1.6" />
|
|
27
|
+
</span>
|
|
28
|
+
|
|
29
|
+
<div class="min-w-0">
|
|
30
|
+
<!-- Title -->
|
|
31
|
+
<component :is="`h${props.headingLevel}`" class="text-xl font-semibold tracking-tight text-foreground md:text-2xl">
|
|
32
|
+
{{ props.title }}
|
|
33
|
+
</component>
|
|
34
|
+
|
|
35
|
+
<!-- Description -->
|
|
36
|
+
<p v-if="props.description" class="mt-1 max-w-2xl text-xs leading-6 text-muted-foreground md:text-sm">
|
|
37
|
+
{{ props.description }}
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Optional trailing action (for example a "View all" link) -->
|
|
43
|
+
<div v-if="$slots.action" class="shrink-0">
|
|
44
|
+
<slot name="action" />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
@@ -72,7 +72,7 @@ const staticValue = computed(() => typeof props.value === 'number' && props.form
|
|
|
72
72
|
</span>
|
|
73
73
|
|
|
74
74
|
<!-- Trend badge -->
|
|
75
|
-
<span v-if="props.trend !== null" :class="cn('inline-flex items-center gap-0.5 rounded
|
|
75
|
+
<span v-if="props.trend !== null" :class="cn('inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[11px] font-semibold tabular-nums', trendIsPositive ? 'bg-success/10 text-success' : 'bg-destructive/10 text-destructive')">
|
|
76
76
|
<HugeiconsIcon :icon="trendIsPositive ? ArrowUpRight01Icon : ArrowDownRight01Icon" class="size-3" />
|
|
77
77
|
{{ formattedTrend }}
|
|
78
78
|
</span>
|
|
@@ -4,17 +4,17 @@ import type { HTMLAttributes } from 'vue';
|
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
5
|
import { AlertDialogAction } from 'reka-ui';
|
|
6
6
|
import { cn } from '../../../lib/utils';
|
|
7
|
-
import { buttonVariants } from '../button';
|
|
7
|
+
import { buttonVariants, type ButtonVariants } from '../button';
|
|
8
8
|
|
|
9
|
-
// Props
|
|
10
|
-
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes['class'] }>();
|
|
9
|
+
// Props (variant styles the action like a Button, for example red for a destructive confirmation)
|
|
10
|
+
const props = defineProps<AlertDialogActionProps & { variant?: ButtonVariants['variant']; class?: HTMLAttributes['class'] }>();
|
|
11
11
|
|
|
12
|
-
// Omit local
|
|
13
|
-
const delegatedProps = reactiveOmit(props, 'class');
|
|
12
|
+
// Omit local props from delegated props
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class', 'variant');
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
|
|
17
|
+
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants({ variant: props.variant }), props.class)">
|
|
18
18
|
<slot />
|
|
19
19
|
</AlertDialogAction>
|
|
20
20
|
</template>
|
|
@@ -14,13 +14,15 @@ const props = withDefaults(defineProps<AvatarRootProps & {
|
|
|
14
14
|
size?: AvatarSize;
|
|
15
15
|
shape?: AvatarShape;
|
|
16
16
|
status?: AvatarStatus;
|
|
17
|
+
ring?: boolean;
|
|
17
18
|
class?: HTMLAttributes['class'];
|
|
18
19
|
}>(), {
|
|
19
20
|
size: 'md',
|
|
20
21
|
shape: 'circle',
|
|
22
|
+
ring: false,
|
|
21
23
|
});
|
|
22
24
|
|
|
23
|
-
const delegatedProps = reactiveOmit(props, 'class', 'size', 'shape', 'status');
|
|
25
|
+
const delegatedProps = reactiveOmit(props, 'class', 'size', 'shape', 'status', 'ring');
|
|
24
26
|
|
|
25
27
|
const { t } = useI18n();
|
|
26
28
|
|
|
@@ -49,7 +51,7 @@ const statusPositionClass = computed(() => props.shape === 'square' ? '-right-0.
|
|
|
49
51
|
</script>
|
|
50
52
|
|
|
51
53
|
<template>
|
|
52
|
-
<span data-slot="avatar" :data-size="props.size" :data-shape="props.shape" :data-status="props.status" :class="cn('relative inline-flex shrink-0 select-none align-middle', avatarSizeClasses[props.size], avatarShapeClasses[props.shape], props.class)">
|
|
54
|
+
<span data-slot="avatar" :data-size="props.size" :data-shape="props.shape" :data-status="props.status" :class="cn('relative inline-flex shrink-0 select-none align-middle', avatarSizeClasses[props.size], avatarShapeClasses[props.shape], props.ring && 'ring-2 ring-card', props.class)">
|
|
53
55
|
<!-- Avatar surface -->
|
|
54
56
|
<AvatarRoot v-bind="delegatedProps" :class="cn('relative flex size-full items-center justify-center overflow-hidden bg-surface', avatarShapeClasses[props.shape])">
|
|
55
57
|
<slot />
|
|
@@ -7,13 +7,33 @@ import { computed, inject } from 'vue';
|
|
|
7
7
|
import { cn } from '../../../lib/utils';
|
|
8
8
|
import { avatarContextKey, avatarShapeClasses, avatarTextClasses, getInitials } from '.';
|
|
9
9
|
|
|
10
|
-
// Props
|
|
11
|
-
const props = defineProps<AvatarFallbackProps & {
|
|
10
|
+
// Props (auto tints the initials with a stable color picked from the name, primary uses the brand color, for example for the current user)
|
|
11
|
+
const props = withDefaults(defineProps<AvatarFallbackProps & {
|
|
12
12
|
name?: string;
|
|
13
|
+
color?: 'neutral' | 'auto' | 'primary';
|
|
13
14
|
class?: HTMLAttributes['class'];
|
|
14
|
-
}>()
|
|
15
|
+
}>(), {
|
|
16
|
+
color: 'neutral'
|
|
17
|
+
});
|
|
15
18
|
|
|
16
|
-
const delegatedProps = reactiveOmit(props, 'class', 'name');
|
|
19
|
+
const delegatedProps = reactiveOmit(props, 'class', 'name', 'color');
|
|
20
|
+
|
|
21
|
+
// Tints built on the chart tokens, so they adapt to the theme (the text is mixed with the foreground, so it stays readable on the tint in both themes)
|
|
22
|
+
const autoColorClasses = [
|
|
23
|
+
'bg-[color-mix(in_oklab,var(--chart-1)_22%,var(--card))] text-[color-mix(in_oklab,var(--chart-1)_45%,var(--foreground))]',
|
|
24
|
+
'bg-[color-mix(in_oklab,var(--chart-2)_22%,var(--card))] text-[color-mix(in_oklab,var(--chart-2)_45%,var(--foreground))]',
|
|
25
|
+
'bg-[color-mix(in_oklab,var(--chart-3)_22%,var(--card))] text-[color-mix(in_oklab,var(--chart-3)_45%,var(--foreground))]',
|
|
26
|
+
'bg-[color-mix(in_oklab,var(--chart-4)_22%,var(--card))] text-[color-mix(in_oklab,var(--chart-4)_45%,var(--foreground))]',
|
|
27
|
+
'bg-[color-mix(in_oklab,var(--chart-5)_22%,var(--card))] text-[color-mix(in_oklab,var(--chart-5)_45%,var(--foreground))]'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Surface and text color (the same name always gets the same tint)
|
|
31
|
+
const colorClass = computed(() => {
|
|
32
|
+
if (props.color === 'primary') return 'bg-primary text-primary-foreground';
|
|
33
|
+
if (props.color !== 'auto') return 'bg-surface text-muted-foreground';
|
|
34
|
+
const hash = [...(props.name ?? '')].reduce((total, character) => total + character.charCodeAt(0), 0);
|
|
35
|
+
return autoColorClasses[hash % autoColorClasses.length];
|
|
36
|
+
});
|
|
17
37
|
|
|
18
38
|
// Size and shape come from the parent avatar
|
|
19
39
|
const context = inject(avatarContextKey, null);
|
|
@@ -26,7 +46,7 @@ const initials = computed(() => getInitials(props.name));
|
|
|
26
46
|
|
|
27
47
|
<template>
|
|
28
48
|
<!-- 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
|
|
49
|
+
<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 font-semibold uppercase', colorClass, textClass, shapeClass, props.class)">
|
|
30
50
|
<slot>{{ initials }}</slot>
|
|
31
51
|
</AvatarFallback>
|
|
32
52
|
</template>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { computed } from 'vue';
|
|
3
4
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
4
5
|
import { getSurfaceToneClasses, type ToneColor } from '../../../lib/color-tokens';
|
|
5
6
|
import { cn } from '../../../lib/utils';
|
|
@@ -9,7 +10,7 @@ import type { HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
|
9
10
|
const props = withDefaults(defineProps<{
|
|
10
11
|
text: string;
|
|
11
12
|
icon?: HugeiconsIconDefinition | null;
|
|
12
|
-
color?: ToneColor;
|
|
13
|
+
color?: ToneColor | 'primary';
|
|
13
14
|
dot?: boolean;
|
|
14
15
|
pulse?: boolean;
|
|
15
16
|
class?: HTMLAttributes['class'];
|
|
@@ -19,10 +20,13 @@ const props = withDefaults(defineProps<{
|
|
|
19
20
|
dot: false,
|
|
20
21
|
pulse: false
|
|
21
22
|
});
|
|
23
|
+
|
|
24
|
+
// Surface of the badge: a tone, or a soft primary tint (for highlights like a category)
|
|
25
|
+
const colorClasses = computed(() => props.color === 'primary' ? 'border border-primary/30 bg-primary/10 text-primary' : getSurfaceToneClasses(props.color));
|
|
22
26
|
</script>
|
|
23
27
|
|
|
24
28
|
<template>
|
|
25
|
-
<span data-slot="badge" :class="cn('inline-flex w-fit items-center justify-center gap-1.5 whitespace-nowrap rounded border px-2 py-0.5 text-xs font-semibold transition-colors duration-150',
|
|
29
|
+
<span data-slot="badge" :class="cn('inline-flex w-fit items-center justify-center gap-1.5 whitespace-nowrap rounded border px-2 py-0.5 text-xs font-semibold transition-colors duration-150', colorClasses, props.class)">
|
|
26
30
|
<!-- Status dot -->
|
|
27
31
|
<span v-if="props.dot || props.pulse" class="relative flex size-1.5 shrink-0">
|
|
28
32
|
<span v-if="props.pulse" class="absolute inset-0 animate-uv-ping-soft rounded-full bg-current" />
|
|
@@ -14,7 +14,7 @@ const { t } = useI18n();
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
<span data-slot="breadcrumb-ellipsis" role="presentation" :class="cn('flex size-6 items-center justify-center rounded
|
|
17
|
+
<span data-slot="breadcrumb-ellipsis" role="presentation" :class="cn('flex size-6 items-center justify-center rounded transition-colors duration-150 hover:bg-accent hover:text-primary', props.class)">
|
|
18
18
|
<!-- Default slot content -->
|
|
19
19
|
<slot>
|
|
20
20
|
<HugeiconsIcon :icon="MoreHorizontalIcon" class="size-4" aria-hidden="true" />
|
|
@@ -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('rounded
|
|
14
|
+
<Primitive data-slot="breadcrumb-link" :as="as" :as-child="asChild" :class="cn('rounded 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>
|
|
@@ -4,6 +4,7 @@ import type { PrimitiveProps } from 'reka-ui';
|
|
|
4
4
|
import { computed, type HTMLAttributes } from 'vue';
|
|
5
5
|
import type { ButtonVariants } from '.';
|
|
6
6
|
import { Primitive } from 'reka-ui';
|
|
7
|
+
import { getSurfaceToneClasses } from '../../../lib/color-tokens';
|
|
7
8
|
import { cn } from '../../../lib/utils';
|
|
8
9
|
import { Spinner } from '../spinner';
|
|
9
10
|
import { buttonVariants } from '.';
|
|
@@ -15,11 +16,15 @@ const props = withDefaults(defineProps<PrimitiveProps & {
|
|
|
15
16
|
loading?: boolean;
|
|
16
17
|
disabled?: boolean;
|
|
17
18
|
to?: string;
|
|
19
|
+
count?: number | null;
|
|
20
|
+
countMax?: number;
|
|
18
21
|
class?: HTMLAttributes['class'];
|
|
19
22
|
}>(), {
|
|
20
23
|
as: 'button',
|
|
21
24
|
loading: false,
|
|
22
|
-
disabled: false
|
|
25
|
+
disabled: false,
|
|
26
|
+
count: null,
|
|
27
|
+
countMax: 99
|
|
23
28
|
});
|
|
24
29
|
|
|
25
30
|
// Disabled or loading: the button can't be pressed (or followed, for a navigation button)
|
|
@@ -35,6 +40,10 @@ const element = computed(() => {
|
|
|
35
40
|
return isAnchor.value ? 'a' : NuxtLinkLocale;
|
|
36
41
|
});
|
|
37
42
|
|
|
43
|
+
// Notification count shown in a badge on the corner (capped, like 99+)
|
|
44
|
+
const hasCount = computed(() => !props.asChild && typeof props.count === 'number' && props.count > 0);
|
|
45
|
+
const countText = computed(() => props.count && props.count > props.countMax ? `${props.countMax}+` : String(props.count));
|
|
46
|
+
|
|
38
47
|
// Link attribute for the rendered element (href for a plain anchor, to for the localized link)
|
|
39
48
|
const linkAttributes = computed(() => {
|
|
40
49
|
if (!props.to || isInactive.value) return {};
|
|
@@ -43,7 +52,7 @@ const linkAttributes = computed(() => {
|
|
|
43
52
|
</script>
|
|
44
53
|
|
|
45
54
|
<template>
|
|
46
|
-
<Primitive data-slot="button" :data-variant="variant" :data-size="size" :data-loading="props.loading ? '' : undefined" :aria-busy="props.loading || undefined" :disabled="(!props.to && isInactive) || undefined" :aria-disabled="(props.to && isInactive) || undefined" v-bind="linkAttributes" :as="element" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), props.class)">
|
|
55
|
+
<Primitive data-slot="button" :data-variant="variant" :data-size="size" :data-loading="props.loading ? '' : undefined" :aria-busy="props.loading || undefined" :disabled="(!props.to && isInactive) || undefined" :aria-disabled="(props.to && isInactive) || undefined" v-bind="linkAttributes" :as="element" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), hasCount && 'overflow-visible', props.class)">
|
|
47
56
|
<!-- As-child buttons delegate rendering to the slotted element -->
|
|
48
57
|
<slot v-if="asChild" />
|
|
49
58
|
|
|
@@ -58,6 +67,13 @@ const linkAttributes = computed(() => {
|
|
|
58
67
|
<span v-if="props.loading" class="absolute inset-0 flex items-center justify-center">
|
|
59
68
|
<Spinner size="sm" />
|
|
60
69
|
</span>
|
|
70
|
+
|
|
71
|
+
<!-- Notification count: a soft badge on an opaque backing (so the tint never shows the button through), its digits trimmed to the cap height so they sit centered in any font -->
|
|
72
|
+
<span v-if="hasCount" data-slot="button-count" class="pointer-events-none absolute -top-2 -right-2 z-10 flex rounded bg-background">
|
|
73
|
+
<span :class="cn('flex h-5 min-w-5 items-center justify-center rounded px-1 text-[0.6875rem] font-semibold leading-none tabular-nums', getSurfaceToneClasses('red'))">
|
|
74
|
+
<span class="[text-box:trim-both_cap_alphabetic]">{{ countText }}</span>
|
|
75
|
+
</span>
|
|
76
|
+
</span>
|
|
61
77
|
</template>
|
|
62
78
|
</Primitive>
|
|
63
79
|
</template>
|
|
@@ -59,6 +59,10 @@ export const buttonVariants: (props?: ButtonVariants & ClassProp) => string = cv
|
|
|
59
59
|
'icon-lg': 'size-[52px]',
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
|
+
// Link buttons read as inline links, so they have no box padding
|
|
63
|
+
compoundVariants: [
|
|
64
|
+
{ variant: 'link', class: 'min-h-0 px-0 py-0 has-[>svg]:px-0' },
|
|
65
|
+
],
|
|
62
66
|
defaultVariants: {
|
|
63
67
|
variant: 'primary',
|
|
64
68
|
size: 'default',
|
|
@@ -8,16 +8,36 @@ import {
|
|
|
8
8
|
import { cn } from '../../../lib/utils';
|
|
9
9
|
|
|
10
10
|
type CardColor = 'default' | ToneColor;
|
|
11
|
+
type CardPadding = 'default' | 'none';
|
|
12
|
+
type CardElevation = 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
13
|
|
|
12
14
|
// Props
|
|
13
15
|
const props = withDefaults(defineProps<{
|
|
14
16
|
color?: CardColor;
|
|
17
|
+
padding?: CardPadding;
|
|
18
|
+
elevation?: CardElevation;
|
|
15
19
|
interactive?: boolean;
|
|
16
20
|
class?: HTMLAttributes['class'];
|
|
17
21
|
}>(), {
|
|
22
|
+
padding: 'default',
|
|
18
23
|
interactive: false
|
|
19
24
|
});
|
|
20
25
|
|
|
26
|
+
// Shadow per elevation level
|
|
27
|
+
const elevationClasses: Record<CardElevation, string> = {
|
|
28
|
+
none: 'shadow-none',
|
|
29
|
+
sm: 'shadow-elevated-sm',
|
|
30
|
+
md: 'shadow-elevated-md',
|
|
31
|
+
lg: 'shadow-elevated-lg',
|
|
32
|
+
xl: 'shadow-elevated-xl'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Inner spacing: the default fits CardHeader / CardContent / CardFooter, none is for cards that lay out their own sections edge to edge (clipped to the corners)
|
|
36
|
+
const paddingClasses: Record<CardPadding, string> = {
|
|
37
|
+
default: 'gap-4 sm:gap-6 py-6',
|
|
38
|
+
none: 'gap-0 overflow-hidden'
|
|
39
|
+
};
|
|
40
|
+
|
|
21
41
|
// Get the appropriate classes
|
|
22
42
|
const colorClasses = computed(() => {
|
|
23
43
|
// If color is not specified or is "default", use the default card styles
|
|
@@ -32,8 +52,12 @@ const colorClasses = computed(() => {
|
|
|
32
52
|
|
|
33
53
|
<template>
|
|
34
54
|
<div data-slot="card" :data-interactive="props.interactive ? '' : undefined" :class="cn(
|
|
35
|
-
'relative flex flex-col
|
|
55
|
+
'relative flex flex-col rounded transition-[border-color,box-shadow,background-color,translate] duration-300 ease-out-expo',
|
|
56
|
+
paddingClasses[props.padding],
|
|
57
|
+
// A CardMedia as first child bleeds to the top edge, clipped to the corners
|
|
58
|
+
'has-[>[data-slot=card-media]:first-child]:overflow-hidden has-[>[data-slot=card-media]:first-child]:pt-0',
|
|
36
59
|
colorClasses,
|
|
60
|
+
props.elevation && elevationClasses[props.elevation],
|
|
37
61
|
props.interactive && 'cursor-pointer hover:-translate-y-0.5 hover:shadow-elevated-md active:translate-y-0 active:duration-100',
|
|
38
62
|
props.class,
|
|
39
63
|
)">
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { cn } from '../../../lib/utils';
|
|
4
|
+
|
|
5
|
+
// Props (edge-to-edge media, like an image or a chart: as the first child of a Card it bleeds to the top edge)
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
class?: HTMLAttributes['class'];
|
|
8
|
+
}>();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div data-slot="card-media" :class="cn('overflow-hidden', props.class)">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
3
4
|
import { cn } from '../../../lib/utils';
|
|
4
|
-
import type { HeadingLevel } from '../../../lib/common-types';
|
|
5
|
+
import type { HeadingLevel, HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
5
6
|
|
|
6
7
|
// Props
|
|
7
8
|
const props = withDefaults(defineProps<{
|
|
8
9
|
class?: HTMLAttributes['class'];
|
|
9
10
|
level?: HeadingLevel;
|
|
11
|
+
icon?: HugeiconsIconDefinition | null;
|
|
10
12
|
}>(), {
|
|
11
|
-
level: 3
|
|
13
|
+
level: 3,
|
|
14
|
+
icon: null
|
|
12
15
|
});
|
|
13
16
|
</script>
|
|
14
17
|
|
|
15
18
|
<template>
|
|
16
19
|
<component :is="`h${props.level}`"
|
|
17
20
|
data-slot="card-title"
|
|
18
|
-
:class="cn('leading-none font-semibold text-sm sm:text-base', props.class)">
|
|
21
|
+
:class="cn('leading-none font-semibold text-sm sm:text-base', props.icon && 'flex items-center gap-2', props.class)">
|
|
22
|
+
<!-- Leading icon -->
|
|
23
|
+
<HugeiconsIcon v-if="props.icon" :icon="props.icon" class="size-4 shrink-0" />
|
|
19
24
|
<slot />
|
|
20
25
|
</component>
|
|
21
26
|
</template>
|
|
@@ -5,4 +5,5 @@ export { default as CardText } from './CardText.vue';
|
|
|
5
5
|
export { default as CardDescription } from './CardDescription.vue';
|
|
6
6
|
export { default as CardFooter } from './CardFooter.vue';
|
|
7
7
|
export { default as CardHeader } from './CardHeader.vue';
|
|
8
|
+
export { default as CardMedia } from './CardMedia.vue';
|
|
8
9
|
export { default as CardTitle } from './CardTitle.vue';
|
|
@@ -23,7 +23,7 @@ const fieldControlId = useFieldControlId(() => props.id);
|
|
|
23
23
|
|
|
24
24
|
<template>
|
|
25
25
|
<CheckboxRoot v-slot="slotProps" data-slot="checkbox" v-bind="forwarded" :id="fieldControlId" :class="cn(
|
|
26
|
-
'group/checkbox peer relative inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded
|
|
26
|
+
'group/checkbox peer relative inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded border border-input bg-secondary text-primary-foreground shadow-elevated-sm outline-none',
|
|
27
27
|
'[transition:scale_320ms_var(--ease-spring),background-color_150ms_var(--ease-snappy),border-color_150ms_var(--ease-snappy),box-shadow_150ms_var(--ease-snappy)]',
|
|
28
28
|
'hover:border-border-strong active:scale-[0.86] focus-visible:ring-[3px] focus-visible:ring-ring/45',
|
|
29
29
|
'data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary',
|
|
@@ -134,10 +134,10 @@ const handleKeydown = (event: KeyboardEvent) => {
|
|
|
134
134
|
<div class="flex min-w-0 flex-1 flex-wrap items-center gap-1.5">
|
|
135
135
|
<!-- Selected tags (multiple mode) -->
|
|
136
136
|
<TransitionGroup v-if="props.multiple" leave-active-class="transition-[opacity,scale] duration-150 ease-snappy" leave-to-class="scale-75 opacity-0">
|
|
137
|
-
<span v-for="option in selectedOptions" :key="option.value" data-slot="combobox-select-tag" class="inline-flex h-7 max-w-full animate-uv-pop items-center gap-1 rounded
|
|
137
|
+
<span v-for="option in selectedOptions" :key="option.value" data-slot="combobox-select-tag" class="inline-flex h-7 max-w-full animate-uv-pop items-center gap-1 rounded border border-border bg-surface pr-1 pl-2 text-[11px] font-semibold text-foreground sm:text-xs">
|
|
138
138
|
<HugeiconsIcon v-if="option.icon" :icon="option.icon" class="size-3" />
|
|
139
139
|
<span class="truncate">{{ option.label }}</span>
|
|
140
|
-
<button type="button" :disabled="props.disabled" :aria-label="`${t('uiVintage.buttons.remove')} ${option.label}`" class="flex size-5 cursor-pointer items-center justify-center rounded
|
|
140
|
+
<button type="button" :disabled="props.disabled" :aria-label="`${t('uiVintage.buttons.remove')} ${option.label}`" class="flex size-5 cursor-pointer items-center justify-center rounded text-muted-foreground outline-none transition-[color,background-color,scale] duration-150 ease-spring hover:bg-accent hover:text-destructive focus-visible:ring-[3px] focus-visible:ring-ring/45 active:scale-90 disabled:pointer-events-none" @click.stop="removeValue(option.value)">
|
|
141
141
|
<HugeiconsIcon :icon="Cancel01Icon" class="size-3" />
|
|
142
142
|
</button>
|
|
143
143
|
</span>
|
|
@@ -16,7 +16,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
16
16
|
|
|
17
17
|
<template>
|
|
18
18
|
<ComboboxTrigger data-slot="combobox-trigger" v-bind="forwardedProps" :class="cn(
|
|
19
|
-
'group/combobox-trigger -mr-1 flex size-7 shrink-0 cursor-pointer items-center justify-center rounded
|
|
19
|
+
'group/combobox-trigger -mr-1 flex size-7 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground outline-none transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-60',
|
|
20
20
|
props.class
|
|
21
21
|
)
|
|
22
22
|
">
|
|
@@ -157,7 +157,7 @@ const handleRowKeydown = (row: T, event: KeyboardEvent) => {
|
|
|
157
157
|
<!-- Column headers -->
|
|
158
158
|
<th v-for="column in props.columns" :key="column.key" :style="column.width ? { width: column.width } : undefined" :aria-sort="props.sort?.key === column.key ? (props.sort.direction === 'asc' ? 'ascending' : 'descending') : undefined" :class="cn('h-11 px-4 text-xs font-semibold whitespace-nowrap text-muted-foreground', alignClasses[column.align ?? 'left'], column.headerClass)">
|
|
159
159
|
<slot :name="`header-${column.key}`" :column="column">
|
|
160
|
-
<button v-if="column.sortable" type="button" :class="cn('group/sort -mx-1.5 inline-flex cursor-pointer items-center gap-1.5 rounded
|
|
160
|
+
<button v-if="column.sortable" type="button" :class="cn('group/sort -mx-1.5 inline-flex cursor-pointer items-center gap-1.5 rounded px-1.5 py-1 outline-none transition-colors duration-150 hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/45', props.sort?.key === column.key && 'text-foreground', justifyClasses[column.align ?? 'left'])" @click="toggleSort(column)">
|
|
161
161
|
{{ column.label }}
|
|
162
162
|
<HugeiconsIcon :icon="ArrowUp01Icon" :class="cn('size-3.5 transition-[rotate,opacity,color] duration-300 ease-spring', props.sort?.key === column.key ? 'text-primary opacity-100' : 'opacity-0 group-hover/sort:opacity-50', props.sort?.key === column.key && props.sort.direction === 'desc' && 'rotate-180')" />
|
|
163
163
|
</button>
|
|
@@ -7,6 +7,7 @@ import Cancel01Icon from '@hugeicons/core-free-icons/Cancel01Icon';
|
|
|
7
7
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
8
8
|
import { useI18n } from 'vue-i18n';
|
|
9
9
|
import { cn } from '../../../lib/utils';
|
|
10
|
+
import { dialogSizeClasses, type DialogSize } from '.';
|
|
10
11
|
import { Button } from '../button';
|
|
11
12
|
import DialogOverlay from './DialogOverlay.vue';
|
|
12
13
|
|
|
@@ -18,8 +19,10 @@ defineOptions({
|
|
|
18
19
|
const props = withDefaults(defineProps<DialogContentProps & {
|
|
19
20
|
class?: HTMLAttributes['class'];
|
|
20
21
|
showCloseButton?: boolean;
|
|
22
|
+
size?: DialogSize;
|
|
21
23
|
}>(), {
|
|
22
|
-
showCloseButton: false
|
|
24
|
+
showCloseButton: false,
|
|
25
|
+
size: 'sm'
|
|
23
26
|
});
|
|
24
27
|
|
|
25
28
|
const { t } = useI18n();
|
|
@@ -27,7 +30,7 @@ const { t } = useI18n();
|
|
|
27
30
|
// Emits
|
|
28
31
|
const emits = defineEmits<DialogContentEmits>();
|
|
29
32
|
|
|
30
|
-
const delegatedProps = reactiveOmit(props, 'class');
|
|
33
|
+
const delegatedProps = reactiveOmit(props, 'class', 'size');
|
|
31
34
|
|
|
32
35
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
33
36
|
</script>
|
|
@@ -36,7 +39,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
36
39
|
<DialogPortal>
|
|
37
40
|
<DialogOverlay />
|
|
38
41
|
<DialogContent data-slot="dialog-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn(
|
|
39
|
-
'uv-modal-motion bg-card text-card-foreground fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded border border-border p-6 shadow-elevated-xl outline-none
|
|
42
|
+
'uv-modal-motion bg-card text-card-foreground fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded border border-border p-6 shadow-elevated-xl outline-none',
|
|
43
|
+
dialogSizeClasses[props.size],
|
|
40
44
|
props.class
|
|
41
45
|
)
|
|
42
46
|
">
|
|
@@ -7,6 +7,7 @@ import Cancel01Icon from '@hugeicons/core-free-icons/Cancel01Icon';
|
|
|
7
7
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
8
8
|
import { useI18n } from 'vue-i18n';
|
|
9
9
|
import { cn } from '../../../lib/utils';
|
|
10
|
+
import { dialogSizeClasses, type DialogSize } from '.';
|
|
10
11
|
import { Button } from '../button';
|
|
11
12
|
|
|
12
13
|
defineOptions({
|
|
@@ -17,8 +18,10 @@ defineOptions({
|
|
|
17
18
|
const props = withDefaults(defineProps<DialogContentProps & {
|
|
18
19
|
class?: HTMLAttributes['class'];
|
|
19
20
|
showCloseButton?: boolean;
|
|
21
|
+
size?: DialogSize;
|
|
20
22
|
}>(), {
|
|
21
|
-
showCloseButton: false
|
|
23
|
+
showCloseButton: false,
|
|
24
|
+
size: 'sm'
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
const { t } = useI18n();
|
|
@@ -26,7 +29,7 @@ const { t } = useI18n();
|
|
|
26
29
|
// Emits
|
|
27
30
|
const emits = defineEmits<DialogContentEmits>();
|
|
28
31
|
|
|
29
|
-
const delegatedProps = reactiveOmit(props, 'class');
|
|
32
|
+
const delegatedProps = reactiveOmit(props, 'class', 'size');
|
|
30
33
|
|
|
31
34
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
32
35
|
</script>
|
|
@@ -35,7 +38,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
35
38
|
<DialogPortal>
|
|
36
39
|
<DialogOverlay class="uv-overlay-motion fixed inset-0 isolate z-50 grid place-items-center overflow-y-auto bg-black/40 backdrop-blur-[3px] dark:bg-black/60">
|
|
37
40
|
<DialogContent :class="cn(
|
|
38
|
-
'uv-modal-motion relative z-50 my-6 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded border border-border bg-card p-6 text-card-foreground shadow-elevated-xl outline-none
|
|
41
|
+
'uv-modal-motion relative z-50 my-6 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded border border-border bg-card p-6 text-card-foreground shadow-elevated-xl outline-none',
|
|
42
|
+
dialogSizeClasses[props.size],
|
|
39
43
|
props.class
|
|
40
44
|
)
|
|
41
45
|
" v-bind="{ ...$attrs, ...forwarded }" @pointer-down-outside="
|
|
@@ -7,4 +7,14 @@ export { default as DialogHeader } from './DialogHeader.vue';
|
|
|
7
7
|
export { default as DialogOverlay } from './DialogOverlay.vue';
|
|
8
8
|
export { default as DialogScrollContent } from './DialogScrollContent.vue';
|
|
9
9
|
export { default as DialogTitle } from './DialogTitle.vue';
|
|
10
|
-
export { default as DialogTrigger } from './DialogTrigger.vue';
|
|
10
|
+
export { default as DialogTrigger } from './DialogTrigger.vue';
|
|
11
|
+
|
|
12
|
+
export type DialogSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
13
|
+
|
|
14
|
+
// Max width per dialog size (sm is the compact default)
|
|
15
|
+
export const dialogSizeClasses: Record<DialogSize, string> = {
|
|
16
|
+
sm: 'sm:max-w-106.25',
|
|
17
|
+
md: 'sm:max-w-md',
|
|
18
|
+
lg: 'sm:max-w-xl',
|
|
19
|
+
xl: 'sm:max-w-2xl'
|
|
20
|
+
};
|
|
@@ -221,7 +221,7 @@ onBeforeUnmount(() => clearTimeout(rejectTimer));
|
|
|
221
221
|
<div class="pb-2">
|
|
222
222
|
<!-- File row -->
|
|
223
223
|
<div data-slot="file-dropzone-item" class="flex items-center gap-3 rounded border border-border bg-card py-2 pr-2 pl-3 shadow-elevated-sm">
|
|
224
|
-
<span class="flex size-9 shrink-0 items-center justify-center rounded
|
|
224
|
+
<span class="flex size-9 shrink-0 items-center justify-center rounded bg-surface text-muted-foreground">
|
|
225
225
|
<HugeiconsIcon :icon="getFileIcon(file)" class="size-4.5" />
|
|
226
226
|
</span>
|
|
227
227
|
<div class="flex min-w-0 flex-1 flex-col">
|