@brumaombra/ui-vintage 0.7.13 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +3643 -0
- package/README.md +50 -10
- package/package.json +30 -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/cookie-consent/CookieConsent.vue +82 -0
- package/src/components/cookie-consent/index.ts +3 -0
- package/src/components/cookie-consent/useCookieConsent.ts +55 -0
- 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 +102 -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 +83 -0
- package/src/i18n/en.json +83 -0
- package/src/i18n/es.json +83 -0
- package/src/i18n/fr.json +83 -0
- package/src/i18n/it.json +83 -0
- package/src/i18n/ja.json +83 -0
- package/src/i18n/pt.json +83 -0
- package/src/i18n/ru.json +83 -0
- package/src/i18n/zh.json +83 -0
- package/src/lib/color-tokens.ts +14 -14
- package/src/styles.css +289 -5
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends Record<string, any>">
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { computed } from 'vue';
|
|
4
|
+
import { useI18n } from 'vue-i18n';
|
|
5
|
+
import { ArrowUp01Icon, PackageIcon } from '@hugeicons/core-free-icons';
|
|
6
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
|
+
import { cn } from '../../../lib/utils';
|
|
8
|
+
import { Checkbox } from '../checkbox';
|
|
9
|
+
import { Skeleton } from '../skeleton';
|
|
10
|
+
import type { DataTableColumn, DataTableSort } from '.';
|
|
11
|
+
|
|
12
|
+
// Props
|
|
13
|
+
const props = withDefaults(defineProps<{
|
|
14
|
+
columns: DataTableColumn<T>[];
|
|
15
|
+
rows: T[];
|
|
16
|
+
rowKey?: keyof T | ((row: T) => PropertyKey);
|
|
17
|
+
selectable?: boolean;
|
|
18
|
+
selected?: PropertyKey[];
|
|
19
|
+
sort?: DataTableSort | null;
|
|
20
|
+
manualSort?: boolean;
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
loadingRows?: number;
|
|
23
|
+
emptyText?: string;
|
|
24
|
+
rowClickable?: boolean;
|
|
25
|
+
stickyHeader?: boolean;
|
|
26
|
+
class?: HTMLAttributes['class'];
|
|
27
|
+
}>(), {
|
|
28
|
+
rowKey: 'id' as never,
|
|
29
|
+
selectable: false,
|
|
30
|
+
selected: () => [],
|
|
31
|
+
sort: null,
|
|
32
|
+
manualSort: false,
|
|
33
|
+
loading: false,
|
|
34
|
+
loadingRows: 5,
|
|
35
|
+
emptyText: '',
|
|
36
|
+
rowClickable: false,
|
|
37
|
+
stickyHeader: false
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Emits
|
|
41
|
+
const emits = defineEmits<{
|
|
42
|
+
'update:selected': [value: PropertyKey[]];
|
|
43
|
+
'update:sort': [value: DataTableSort | null];
|
|
44
|
+
'row-click': [row: T, event: MouseEvent];
|
|
45
|
+
}>();
|
|
46
|
+
|
|
47
|
+
// Slots
|
|
48
|
+
defineSlots<{
|
|
49
|
+
[key: `cell-${string}`]: (props: { row: T; value: unknown; index: number }) => unknown;
|
|
50
|
+
[key: `header-${string}`]: (props: { column: DataTableColumn<T> }) => unknown;
|
|
51
|
+
empty?: () => unknown;
|
|
52
|
+
}>();
|
|
53
|
+
|
|
54
|
+
const { t } = useI18n();
|
|
55
|
+
const resolvedEmptyText = computed(() => props.emptyText || t('uiVintage.common.empty.description'));
|
|
56
|
+
|
|
57
|
+
// Resolve a stable key for each row
|
|
58
|
+
const getRowKey = (row: T): PropertyKey => {
|
|
59
|
+
return typeof props.rowKey === 'function' ? props.rowKey(row) : row[props.rowKey as keyof T] as PropertyKey;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Read the raw value of a cell
|
|
63
|
+
const getCellValue = (row: T, column: DataTableColumn<T>) => row[column.key];
|
|
64
|
+
|
|
65
|
+
// Format a cell for display when no slot is provided
|
|
66
|
+
const formatCell = (row: T, column: DataTableColumn<T>) => {
|
|
67
|
+
const value = getCellValue(row, column);
|
|
68
|
+
return column.format ? column.format(value, row) : value ?? '';
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Sort rows on the client unless sorting is delegated to the parent
|
|
72
|
+
const sortedRows = computed(() => {
|
|
73
|
+
if (!props.sort || props.manualSort) return props.rows;
|
|
74
|
+
const column = props.columns.find(item => item.key === props.sort?.key);
|
|
75
|
+
if (!column) return props.rows;
|
|
76
|
+
const direction = props.sort.direction === 'asc' ? 1 : -1;
|
|
77
|
+
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
|
78
|
+
|
|
79
|
+
// Compare values with the column accessor (strings use natural ordering)
|
|
80
|
+
return [...props.rows].sort((first, second) => {
|
|
81
|
+
const firstValue = column.sortValue ? column.sortValue(first) : getCellValue(first, column);
|
|
82
|
+
const secondValue = column.sortValue ? column.sortValue(second) : getCellValue(second, column);
|
|
83
|
+
if (firstValue == null) return 1;
|
|
84
|
+
if (secondValue == null) return -1;
|
|
85
|
+
if (typeof firstValue === 'number' && typeof secondValue === 'number') return (firstValue - secondValue) * direction;
|
|
86
|
+
return collator.compare(String(firstValue), String(secondValue)) * direction;
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Cycle a column through ascending, descending, and unsorted
|
|
91
|
+
const toggleSort = (column: DataTableColumn<T>) => {
|
|
92
|
+
if (!column.sortable) return;
|
|
93
|
+
if (props.sort?.key !== column.key) {
|
|
94
|
+
emits('update:sort', { key: column.key, direction: 'asc' });
|
|
95
|
+
} else if (props.sort.direction === 'asc') {
|
|
96
|
+
emits('update:sort', { key: column.key, direction: 'desc' });
|
|
97
|
+
} else {
|
|
98
|
+
emits('update:sort', null);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Selection helpers
|
|
103
|
+
const selectedSet = computed(() => new Set(props.selected));
|
|
104
|
+
const allSelected = computed(() => props.rows.length > 0 && props.rows.every(row => selectedSet.value.has(getRowKey(row))));
|
|
105
|
+
const someSelected = computed(() => props.rows.some(row => selectedSet.value.has(getRowKey(row))));
|
|
106
|
+
const headerCheckboxState = computed(() => allSelected.value ? true : someSelected.value ? 'indeterminate' : false);
|
|
107
|
+
|
|
108
|
+
// Toggle all rows
|
|
109
|
+
const toggleAll = () => {
|
|
110
|
+
emits('update:selected', allSelected.value ? [] : props.rows.map(getRowKey));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Toggle a single row
|
|
114
|
+
const toggleRow = (row: T) => {
|
|
115
|
+
const key = getRowKey(row);
|
|
116
|
+
const next = new Set(props.selected);
|
|
117
|
+
if (next.has(key)) {
|
|
118
|
+
next.delete(key);
|
|
119
|
+
} else {
|
|
120
|
+
next.add(key);
|
|
121
|
+
}
|
|
122
|
+
emits('update:selected', [...next]);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Alignment classes for header and body cells
|
|
126
|
+
const alignClasses = { left: 'text-left', center: 'text-center', right: 'text-right' } as const;
|
|
127
|
+
const justifyClasses = { left: 'justify-start', center: 'justify-center', right: 'justify-end' } as const;
|
|
128
|
+
const columnCount = computed(() => props.columns.length + (props.selectable ? 1 : 0));
|
|
129
|
+
|
|
130
|
+
// Emit row clicks (ignoring clicks on interactive children)
|
|
131
|
+
const handleRowClick = (row: T, event: MouseEvent) => {
|
|
132
|
+
if ((event.target as HTMLElement).closest('button, a, input, [role=checkbox]')) return;
|
|
133
|
+
emits('row-click', row, event);
|
|
134
|
+
};
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<div data-slot="data-table" :class="cn('relative w-full overflow-auto rounded border border-border bg-card shadow-elevated-sm', props.class)">
|
|
139
|
+
<table class="w-full caption-bottom border-collapse text-sm">
|
|
140
|
+
<!-- Header -->
|
|
141
|
+
<thead :class="cn('bg-surface/80 backdrop-blur', props.stickyHeader && 'sticky top-0 z-10')">
|
|
142
|
+
<tr class="border-b border-border">
|
|
143
|
+
<!-- Select all -->
|
|
144
|
+
<th v-if="props.selectable" class="w-12 px-4 py-3">
|
|
145
|
+
<Checkbox :model-value="headerCheckboxState" :aria-label="t('uiVintage.dataTable.selectAll')" @update:model-value="toggleAll" />
|
|
146
|
+
</th>
|
|
147
|
+
|
|
148
|
+
<!-- Column headers -->
|
|
149
|
+
<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)">
|
|
150
|
+
<slot :name="`header-${column.key}`" :column="column">
|
|
151
|
+
<button v-if="column.sortable" type="button" :class="cn('group/sort -mx-1.5 inline-flex cursor-pointer items-center gap-1.5 rounded-sm 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)">
|
|
152
|
+
{{ column.label }}
|
|
153
|
+
<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')" />
|
|
154
|
+
</button>
|
|
155
|
+
<template v-else>
|
|
156
|
+
{{ column.label }}
|
|
157
|
+
</template>
|
|
158
|
+
</slot>
|
|
159
|
+
</th>
|
|
160
|
+
</tr>
|
|
161
|
+
</thead>
|
|
162
|
+
|
|
163
|
+
<!-- Loading rows -->
|
|
164
|
+
<tbody v-if="props.loading">
|
|
165
|
+
<tr v-for="index in props.loadingRows" :key="`loading-${index}`" class="border-b border-border last:border-b-0">
|
|
166
|
+
<td v-if="props.selectable" class="px-4 py-3">
|
|
167
|
+
<Skeleton class="size-5" />
|
|
168
|
+
</td>
|
|
169
|
+
<td v-for="column in props.columns" :key="column.key" class="px-4 py-3">
|
|
170
|
+
<Skeleton class="h-4" :style="{ width: `${45 + ((index * 17 + column.key.length * 11) % 45)}%` }" />
|
|
171
|
+
</td>
|
|
172
|
+
</tr>
|
|
173
|
+
</tbody>
|
|
174
|
+
|
|
175
|
+
<!-- Empty state -->
|
|
176
|
+
<tbody v-else-if="sortedRows.length === 0">
|
|
177
|
+
<tr>
|
|
178
|
+
<td :colspan="columnCount" class="px-4 py-12">
|
|
179
|
+
<slot name="empty">
|
|
180
|
+
<div class="flex flex-col items-center gap-2 text-center text-muted-foreground">
|
|
181
|
+
<HugeiconsIcon :icon="PackageIcon" class="size-8 animate-uv-float opacity-40" />
|
|
182
|
+
<span class="text-xs sm:text-sm">{{ resolvedEmptyText }}</span>
|
|
183
|
+
</div>
|
|
184
|
+
</slot>
|
|
185
|
+
</td>
|
|
186
|
+
</tr>
|
|
187
|
+
</tbody>
|
|
188
|
+
|
|
189
|
+
<!-- Rows (animated when sorted, added, or removed) -->
|
|
190
|
+
<TransitionGroup v-else tag="tbody" name="uv-data-table-row">
|
|
191
|
+
<tr v-for="(row, index) in sortedRows" :key="getRowKey(row)" :data-state="selectedSet.has(getRowKey(row)) ? 'selected' : undefined" :class="cn('border-b border-border transition-colors duration-150 last:border-b-0 hover:bg-surface/70 data-[state=selected]:bg-primary/5', props.rowClickable && 'cursor-pointer')" @click="handleRowClick(row, $event)">
|
|
192
|
+
<!-- Row selection -->
|
|
193
|
+
<td v-if="props.selectable" class="px-4 py-3">
|
|
194
|
+
<Checkbox :model-value="selectedSet.has(getRowKey(row))" :aria-label="t('uiVintage.dataTable.selectRow')" @update:model-value="toggleRow(row)" />
|
|
195
|
+
</td>
|
|
196
|
+
|
|
197
|
+
<!-- Cells -->
|
|
198
|
+
<td v-for="column in props.columns" :key="column.key" :class="cn('px-4 py-3 align-middle text-xs whitespace-nowrap md:text-sm', alignClasses[column.align ?? 'left'], column.class)">
|
|
199
|
+
<slot :name="`cell-${column.key}`" :row="row" :value="getCellValue(row, column)" :index="index">
|
|
200
|
+
{{ formatCell(row, column) }}
|
|
201
|
+
</slot>
|
|
202
|
+
</td>
|
|
203
|
+
</tr>
|
|
204
|
+
</TransitionGroup>
|
|
205
|
+
</table>
|
|
206
|
+
</div>
|
|
207
|
+
</template>
|
|
208
|
+
|
|
209
|
+
<style>
|
|
210
|
+
.uv-data-table-row-move {
|
|
211
|
+
transition: transform 0.45s var(--ease-spring);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.uv-data-table-row-enter-active {
|
|
215
|
+
transition: opacity 0.3s var(--ease-out-expo), transform 0.4s var(--ease-spring);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.uv-data-table-row-leave-active {
|
|
219
|
+
transition: opacity 0.18s var(--ease-snappy);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.uv-data-table-row-enter-from {
|
|
223
|
+
opacity: 0;
|
|
224
|
+
transform: translateY(-6px);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.uv-data-table-row-leave-to {
|
|
228
|
+
opacity: 0;
|
|
229
|
+
}
|
|
230
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { default as DataTable } from './DataTable.vue';
|
|
2
|
+
|
|
3
|
+
export type DataTableSortDirection = 'asc' | 'desc';
|
|
4
|
+
|
|
5
|
+
export interface DataTableSort {
|
|
6
|
+
key: string;
|
|
7
|
+
direction: DataTableSortDirection;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DataTableColumn<T> {
|
|
11
|
+
key: string;
|
|
12
|
+
label: string;
|
|
13
|
+
sortable?: boolean;
|
|
14
|
+
align?: 'left' | 'center' | 'right';
|
|
15
|
+
width?: string;
|
|
16
|
+
class?: string;
|
|
17
|
+
headerClass?: string;
|
|
18
|
+
format?: (value: unknown, row: T) => string;
|
|
19
|
+
sortValue?: (row: T) => string | number | null | undefined;
|
|
20
|
+
}
|
|
@@ -5,6 +5,7 @@ import { Calendar03Icon } from '@hugeicons/core-free-icons';
|
|
|
5
5
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
6
6
|
import type { DateValue } from 'reka-ui';
|
|
7
7
|
import { computed, ref } from 'vue';
|
|
8
|
+
import { useI18n } from 'vue-i18n';
|
|
8
9
|
import { cn } from '../../../lib/utils';
|
|
9
10
|
import { Button } from '../button';
|
|
10
11
|
import { Calendar } from '../calendar';
|
|
@@ -21,7 +22,6 @@ const props = withDefaults(defineProps<{
|
|
|
21
22
|
class?: HTMLAttributes['class'];
|
|
22
23
|
contentClass?: HTMLAttributes['class'];
|
|
23
24
|
}>(), {
|
|
24
|
-
placeholder: 'Pick a date',
|
|
25
25
|
disabled: false
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -30,6 +30,8 @@ const emits = defineEmits<{
|
|
|
30
30
|
(e: 'update:modelValue', payload: DateValue | undefined): void;
|
|
31
31
|
}>();
|
|
32
32
|
|
|
33
|
+
const { t, locale } = useI18n();
|
|
34
|
+
const resolvedPlaceholder = computed(() => props.placeholder || t('uiVintage.datePicker.placeholder'));
|
|
33
35
|
const internalDate = ref<DateValue | undefined>(props.defaultValue);
|
|
34
36
|
const isControlled = computed(() => props.modelValue !== undefined);
|
|
35
37
|
|
|
@@ -38,7 +40,6 @@ const date = computed({
|
|
|
38
40
|
(isControlled.value ? props.modelValue : internalDate.value) as any,
|
|
39
41
|
set: (value: DateValue | undefined) => {
|
|
40
42
|
if (!isControlled.value) internalDate.value = value;
|
|
41
|
-
|
|
42
43
|
emits('update:modelValue', value);
|
|
43
44
|
},
|
|
44
45
|
});
|
|
@@ -47,28 +48,23 @@ const defaultPlaceholder = computed(
|
|
|
47
48
|
() => (date.value ?? today(getLocalTimeZone())) as any,
|
|
48
49
|
);
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
// Format the selected date in the active locale
|
|
52
|
+
const formatter = computed(() => new DateFormatter(locale.value || 'en-US', {
|
|
51
53
|
dateStyle: 'long',
|
|
52
|
-
});
|
|
54
|
+
}));
|
|
53
55
|
</script>
|
|
54
56
|
|
|
55
57
|
<template>
|
|
56
58
|
<Popover v-slot="{ close }">
|
|
59
|
+
<!-- Date picker trigger -->
|
|
57
60
|
<PopoverTrigger as-child>
|
|
58
|
-
<Button variant="secondary" :disabled="disabled" :class="cn(
|
|
59
|
-
'h-13 w-60 justify-start text-left font-normal enabled:active:scale-100',
|
|
60
|
-
!date && 'text-muted-foreground',
|
|
61
|
-
props.class,
|
|
62
|
-
)
|
|
63
|
-
">
|
|
61
|
+
<Button variant="secondary" :disabled="disabled" :class="cn('h-13 w-60 justify-start text-left font-normal enabled:active:scale-100', !date && 'text-muted-foreground', props.class,)">
|
|
64
62
|
<HugeiconsIcon :icon="Calendar03Icon" class="size-4" />
|
|
65
|
-
{{
|
|
66
|
-
date
|
|
67
|
-
? formatter.format(date.toDate(getLocalTimeZone()))
|
|
68
|
-
: placeholder
|
|
69
|
-
}}
|
|
63
|
+
{{ date ? formatter.format(date.toDate(getLocalTimeZone())) : resolvedPlaceholder }}
|
|
70
64
|
</Button>
|
|
71
65
|
</PopoverTrigger>
|
|
66
|
+
|
|
67
|
+
<!-- Date picker content -->
|
|
72
68
|
<PopoverContent :class="cn('w-auto p-0', props.contentClass)" align="start">
|
|
73
69
|
<Calendar v-model="date" :default-placeholder="defaultPlaceholder" :min-value="props.minValue" :max-value="props.maxValue" layout="month-and-year" initial-focus @update:model-value="close" />
|
|
74
70
|
</PopoverContent>
|
|
@@ -25,8 +25,7 @@ const props = withDefaults(defineProps<{
|
|
|
25
25
|
minDate: null,
|
|
26
26
|
maxDate: null,
|
|
27
27
|
step: 60,
|
|
28
|
-
disabled: false
|
|
29
|
-
datePlaceholder: 'Pick a date'
|
|
28
|
+
disabled: false
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
// Emits
|
|
@@ -158,11 +157,7 @@ const maxTimeValue = computed(() => {
|
|
|
158
157
|
</script>
|
|
159
158
|
|
|
160
159
|
<template>
|
|
161
|
-
<div :class="cn(
|
|
162
|
-
'grid grid-cols-1 gap-3 md:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)]',
|
|
163
|
-
props.class,
|
|
164
|
-
)
|
|
165
|
-
">
|
|
160
|
+
<div :class="cn('grid grid-cols-1 gap-3 md:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)]', props.class,)">
|
|
166
161
|
<div>
|
|
167
162
|
<DatePicker v-model="dateValue" :disabled="props.disabled" :placeholder="props.datePlaceholder" :min-value="minDateValue" :max-value="maxDateValue" :class="cn('w-full', props.datePickerClass)" :content-class="props.datePickerContentClass" />
|
|
168
163
|
</div>
|
|
@@ -3,6 +3,9 @@ import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
5
|
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
|
7
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
8
|
+
import { useI18n } from 'vue-i18n';
|
|
6
9
|
import { cn } from '../../../lib/utils';
|
|
7
10
|
import { Button } from '../button';
|
|
8
11
|
import DialogOverlay from './DialogOverlay.vue';
|
|
@@ -19,6 +22,8 @@ const props = withDefaults(defineProps<DialogContentProps & {
|
|
|
19
22
|
showCloseButton: false
|
|
20
23
|
});
|
|
21
24
|
|
|
25
|
+
const { t } = useI18n();
|
|
26
|
+
|
|
22
27
|
// Emits
|
|
23
28
|
const emits = defineEmits<DialogContentEmits>();
|
|
24
29
|
|
|
@@ -31,16 +36,16 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
31
36
|
<DialogPortal>
|
|
32
37
|
<DialogOverlay />
|
|
33
38
|
<DialogContent data-slot="dialog-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn(
|
|
34
|
-
'bg-card text-card-foreground
|
|
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 sm:max-w-106.25',
|
|
35
40
|
props.class
|
|
36
41
|
)
|
|
37
42
|
">
|
|
38
43
|
<slot />
|
|
39
44
|
|
|
40
45
|
<DialogClose v-if="props.showCloseButton" data-slot="dialog-close" as-child>
|
|
41
|
-
<Button variant="ghost" size="icon-sm" class="absolute top-
|
|
42
|
-
<
|
|
43
|
-
<span class="sr-only">
|
|
46
|
+
<Button variant="ghost" size="icon-sm" class="group/close absolute top-3 right-3 size-8 p-0.5">
|
|
47
|
+
<HugeiconsIcon :icon="Cancel01Icon" aria-hidden="true" class="transition-transform duration-300 ease-spring group-hover/close:rotate-90" />
|
|
48
|
+
<span class="sr-only">{{ t('uiVintage.buttons.close') }}</span>
|
|
44
49
|
</Button>
|
|
45
50
|
</DialogClose>
|
|
46
51
|
</DialogContent>
|
|
@@ -13,7 +13,7 @@ const delegatedProps = reactiveOmit(props, 'class');
|
|
|
13
13
|
|
|
14
14
|
<template>
|
|
15
15
|
<DialogOverlay data-slot="dialog-overlay" v-bind="delegatedProps" :class="cn(
|
|
16
|
-
'fixed inset-0 isolate z-50 bg-black/
|
|
16
|
+
'uv-overlay-motion fixed inset-0 isolate z-50 bg-black/40 backdrop-blur-[3px] dark:bg-black/60',
|
|
17
17
|
props.class
|
|
18
18
|
)
|
|
19
19
|
">
|
|
@@ -3,6 +3,9 @@ import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
5
|
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
|
7
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
8
|
+
import { useI18n } from 'vue-i18n';
|
|
6
9
|
import { cn } from '../../../lib/utils';
|
|
7
10
|
import { Button } from '../button';
|
|
8
11
|
|
|
@@ -18,6 +21,8 @@ const props = withDefaults(defineProps<DialogContentProps & {
|
|
|
18
21
|
showCloseButton: false
|
|
19
22
|
});
|
|
20
23
|
|
|
24
|
+
const { t } = useI18n();
|
|
25
|
+
|
|
21
26
|
// Emits
|
|
22
27
|
const emits = defineEmits<DialogContentEmits>();
|
|
23
28
|
|
|
@@ -28,9 +33,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
28
33
|
|
|
29
34
|
<template>
|
|
30
35
|
<DialogPortal>
|
|
31
|
-
<DialogOverlay class="fixed inset-0 isolate z-50 grid place-items-center overflow-y-auto bg-black/
|
|
36
|
+
<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">
|
|
32
37
|
<DialogContent :class="cn(
|
|
33
|
-
'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 outline-none
|
|
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 sm:max-w-106.25',
|
|
34
39
|
props.class
|
|
35
40
|
)
|
|
36
41
|
" v-bind="{ ...$attrs, ...forwarded }" @pointer-down-outside="
|
|
@@ -48,9 +53,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
48
53
|
<slot />
|
|
49
54
|
|
|
50
55
|
<DialogClose v-if="props.showCloseButton" data-slot="dialog-close" as-child>
|
|
51
|
-
<Button variant="ghost" size="icon-sm" class="absolute top-
|
|
52
|
-
<
|
|
53
|
-
<span class="sr-only">
|
|
56
|
+
<Button variant="ghost" size="icon-sm" class="group/close absolute top-3 right-3 size-8 p-0.5">
|
|
57
|
+
<HugeiconsIcon :icon="Cancel01Icon" aria-hidden="true" class="transition-transform duration-300 ease-spring group-hover/close:rotate-90" />
|
|
58
|
+
<span class="sr-only">{{ t('uiVintage.buttons.close') }}</span>
|
|
54
59
|
</Button>
|
|
55
60
|
</DialogClose>
|
|
56
61
|
</DialogContent>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuRootEmits, DropdownMenuRootProps } from 'reka-ui';
|
|
3
|
+
import { DropdownMenuRoot, useForwardPropsEmits } from 'reka-ui';
|
|
4
|
+
|
|
5
|
+
// Props
|
|
6
|
+
const props = defineProps<DropdownMenuRootProps>();
|
|
7
|
+
|
|
8
|
+
// Emits
|
|
9
|
+
const emits = defineEmits<DropdownMenuRootEmits>();
|
|
10
|
+
|
|
11
|
+
const forwarded = useForwardPropsEmits(props, emits);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<DropdownMenuRoot v-slot="slotProps" data-slot="dropdown-menu" v-bind="forwarded">
|
|
16
|
+
<slot v-bind="slotProps" />
|
|
17
|
+
</DropdownMenuRoot>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { Tick02Icon } from '@hugeicons/core-free-icons';
|
|
6
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
|
+
import { DropdownMenuCheckboxItem, DropdownMenuItemIndicator, useForwardPropsEmits } from 'reka-ui';
|
|
8
|
+
import { cn } from '../../../lib/utils';
|
|
9
|
+
import { dropdownMenuItemClass } from './classes';
|
|
10
|
+
|
|
11
|
+
// Props
|
|
12
|
+
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>();
|
|
13
|
+
|
|
14
|
+
// Emits
|
|
15
|
+
const emits = defineEmits<DropdownMenuCheckboxItemEmits>();
|
|
16
|
+
|
|
17
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
18
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<DropdownMenuCheckboxItem data-slot="dropdown-menu-checkbox-item" v-bind="forwarded" :class="cn(dropdownMenuItemClass, 'pl-9 data-[state=checked]:text-foreground', props.class)">
|
|
23
|
+
<!-- Check indicator (pops in when checked) -->
|
|
24
|
+
<span class="pointer-events-none absolute left-3 flex size-4 items-center justify-center text-primary">
|
|
25
|
+
<DropdownMenuItemIndicator>
|
|
26
|
+
<slot name="indicator-icon">
|
|
27
|
+
<HugeiconsIcon :icon="Tick02Icon" class="size-4 animate-uv-pop" :stroke-width="2.5" />
|
|
28
|
+
</slot>
|
|
29
|
+
</DropdownMenuItemIndicator>
|
|
30
|
+
</span>
|
|
31
|
+
|
|
32
|
+
<slot />
|
|
33
|
+
</DropdownMenuCheckboxItem>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuContentEmits, DropdownMenuContentProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { DropdownMenuContent, DropdownMenuPortal, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
import { dropdownMenuStaggerClass, dropdownMenuSurfaceClass } from './classes';
|
|
8
|
+
|
|
9
|
+
defineOptions({
|
|
10
|
+
inheritAttrs: false
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Props
|
|
14
|
+
const props = withDefaults(defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(), {
|
|
15
|
+
align: 'start',
|
|
16
|
+
sideOffset: 6,
|
|
17
|
+
collisionPadding: 8
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Emits
|
|
21
|
+
const emits = defineEmits<DropdownMenuContentEmits>();
|
|
22
|
+
|
|
23
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
24
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<DropdownMenuPortal>
|
|
29
|
+
<DropdownMenuContent data-slot="dropdown-menu-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn(
|
|
30
|
+
dropdownMenuSurfaceClass,
|
|
31
|
+
'max-h-(--reka-dropdown-menu-content-available-height)',
|
|
32
|
+
dropdownMenuStaggerClass,
|
|
33
|
+
props.class
|
|
34
|
+
)
|
|
35
|
+
">
|
|
36
|
+
<slot />
|
|
37
|
+
</DropdownMenuContent>
|
|
38
|
+
</DropdownMenuPortal>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuGroupProps } from 'reka-ui';
|
|
3
|
+
import { DropdownMenuGroup } from 'reka-ui';
|
|
4
|
+
|
|
5
|
+
// Props
|
|
6
|
+
const props = defineProps<DropdownMenuGroupProps>();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<DropdownMenuGroup data-slot="dropdown-menu-group" v-bind="props">
|
|
11
|
+
<slot />
|
|
12
|
+
</DropdownMenuGroup>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuItemEmits, DropdownMenuItemProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { DropdownMenuItem, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
import { dropdownMenuItemClass } from './classes';
|
|
8
|
+
|
|
9
|
+
// Props
|
|
10
|
+
const props = withDefaults(defineProps<DropdownMenuItemProps & {
|
|
11
|
+
class?: HTMLAttributes['class'];
|
|
12
|
+
inset?: boolean;
|
|
13
|
+
variant?: 'default' | 'destructive';
|
|
14
|
+
}>(), {
|
|
15
|
+
variant: 'default'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Emits
|
|
19
|
+
const emits = defineEmits<DropdownMenuItemEmits>();
|
|
20
|
+
|
|
21
|
+
const delegatedProps = reactiveOmit(props, 'class', 'inset', 'variant');
|
|
22
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<DropdownMenuItem data-slot="dropdown-menu-item" :data-inset="props.inset ? '' : undefined" :data-variant="props.variant" v-bind="forwarded" :class="cn(
|
|
27
|
+
dropdownMenuItemClass,
|
|
28
|
+
props.variant === 'destructive' && 'text-destructive data-highlighted:bg-destructive/10 data-highlighted:text-destructive',
|
|
29
|
+
props.class
|
|
30
|
+
)
|
|
31
|
+
">
|
|
32
|
+
<slot />
|
|
33
|
+
</DropdownMenuItem>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuLabelProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { DropdownMenuLabel, useForwardProps } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<DropdownMenuLabelProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
inset?: boolean;
|
|
12
|
+
}>();
|
|
13
|
+
|
|
14
|
+
const delegatedProps = reactiveOmit(props, 'class', 'inset');
|
|
15
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<DropdownMenuLabel data-slot="dropdown-menu-label" :data-inset="props.inset ? '' : undefined" v-bind="forwardedProps" :class="cn(
|
|
20
|
+
'px-3 pt-2 pb-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground select-none data-inset:pl-9',
|
|
21
|
+
props.class
|
|
22
|
+
)
|
|
23
|
+
">
|
|
24
|
+
<slot />
|
|
25
|
+
</DropdownMenuLabel>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuPortalProps } from 'reka-ui';
|
|
3
|
+
import { DropdownMenuPortal } from 'reka-ui';
|
|
4
|
+
|
|
5
|
+
// Props
|
|
6
|
+
const props = defineProps<DropdownMenuPortalProps>();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<DropdownMenuPortal v-bind="props">
|
|
11
|
+
<slot />
|
|
12
|
+
</DropdownMenuPortal>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from 'reka-ui';
|
|
3
|
+
import { DropdownMenuRadioGroup, useForwardPropsEmits } from 'reka-ui';
|
|
4
|
+
|
|
5
|
+
// Props
|
|
6
|
+
const props = defineProps<DropdownMenuRadioGroupProps>();
|
|
7
|
+
|
|
8
|
+
// Emits
|
|
9
|
+
const emits = defineEmits<DropdownMenuRadioGroupEmits>();
|
|
10
|
+
|
|
11
|
+
const forwarded = useForwardPropsEmits(props, emits);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<DropdownMenuRadioGroup data-slot="dropdown-menu-radio-group" v-bind="forwarded">
|
|
16
|
+
<slot />
|
|
17
|
+
</DropdownMenuRadioGroup>
|
|
18
|
+
</template>
|