@brumaombra/ui-vintage 0.7.13 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +3602 -0
- package/README.md +49 -9
- package/package.json +29 -8
- package/skills/ui-vintage/SKILL.md +42 -0
- package/src/components/blog/BlogFAQSection.vue +2 -2
- package/src/components/blog/BlogHeaderSection.vue +2 -2
- package/src/components/blog/BlogInfoSection.vue +3 -3
- package/src/components/blog/BlogProgressBar.vue +7 -6
- package/src/components/blog/BlogSectionTitle.vue +1 -1
- package/src/components/blog/BlogTagsSection.vue +22 -0
- package/src/components/blog/CategoryCard.vue +1 -1
- package/src/components/blog/HeaderCarousel.vue +6 -6
- package/src/components/blog/PostCard.vue +2 -2
- package/src/components/blog/SocialShareSidebar.vue +1 -1
- package/src/components/blog/TableOfContents.vue +6 -6
- package/src/components/blog/index.ts +1 -0
- package/src/components/busy-indicator/busy-indicator-state.ts +3 -2
- package/src/components/chip/Chip.vue +9 -5
- package/src/components/confirm-dialog/ConfirmDialog.vue +17 -6
- package/src/components/confirm-dialog/confirm-dialog-state.ts +4 -3
- package/src/components/content/BlogList.vue +3 -3
- package/src/components/content/BlogTable.vue +7 -7
- package/src/components/content/CodeBlock.vue +4 -4
- package/src/components/content/Flow.vue +3 -3
- package/src/components/content/ProseHr.vue +2 -2
- package/src/components/content/Terminal.vue +4 -4
- package/src/components/dashboard-shell/DashboardShell.vue +3 -3
- package/src/components/empty-state-card/EmptyStateCard.vue +4 -4
- package/src/components/error-page/ErrorPage.vue +16 -14
- package/src/components/info-card/InfoCard.vue +1 -1
- package/src/components/landing/landing-navbar/LandingNavbar.vue +2 -1
- package/src/components/load-more-button/LoadMoreButton.vue +2 -2
- package/src/components/loading-state-card/LoadingStateCard.vue +4 -4
- package/src/components/message-dialog/MessageDialog.vue +4 -4
- package/src/components/message-toast/MessageToast.vue +222 -50
- package/src/components/message-toast/index.ts +2 -2
- package/src/components/message-toast/message-toast-state.ts +107 -81
- package/src/components/page-header/PageHeader.vue +1 -1
- package/src/components/progress-component/ProgressComponent.vue +4 -4
- package/src/components/single-value-card/SingleValueCard.vue +47 -11
- package/src/components/theme-selector/ThemeSelector.vue +20 -36
- package/src/components/theme-selector/index.ts +3 -1
- package/src/components/theme-selector/theme-transition.ts +86 -0
- package/src/components/ui/accordion/Accordion.vue +41 -26
- package/src/components/ui/alert-dialog/AlertDialogContent.vue +2 -2
- package/src/components/ui/alert-dialog/AlertDialogFooter.vue +1 -1
- package/src/components/ui/animated-number/AnimatedNumber.vue +88 -0
- package/src/components/ui/animated-number/index.ts +1 -0
- package/src/components/ui/avatar/Avatar.vue +66 -0
- package/src/components/ui/avatar/AvatarFallback.vue +31 -0
- package/src/components/ui/avatar/AvatarGroup.vue +75 -0
- package/src/components/ui/avatar/AvatarImage.vue +23 -0
- package/src/components/ui/avatar/index.ts +47 -0
- package/src/components/ui/badge/Badge.vue +13 -2
- package/src/components/ui/breadcrumb/Breadcrumb.vue +4 -1
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +7 -1
- package/src/components/ui/busy/Busy.vue +45 -20
- package/src/components/ui/button/Button.vue +20 -3
- package/src/components/ui/button/index.ts +12 -2
- package/src/components/ui/card/Card.vue +21 -8
- package/src/components/ui/card/CardText.vue +1 -1
- package/src/components/ui/checkbox/Checkbox.vue +48 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/collapsible/Collapsible.vue +22 -0
- package/src/components/ui/collapsible/CollapsibleContent.vue +22 -0
- package/src/components/ui/collapsible/CollapsibleTrigger.vue +19 -0
- package/src/components/ui/collapsible/index.ts +3 -0
- package/src/components/ui/combobox/Combobox.vue +24 -0
- package/src/components/ui/combobox/ComboboxAnchor.vue +39 -0
- package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
- package/src/components/ui/combobox/ComboboxGroup.vue +26 -0
- package/src/components/ui/combobox/ComboboxInput.vue +28 -0
- package/src/components/ui/combobox/ComboboxItem.vue +39 -0
- package/src/components/ui/combobox/ComboboxList.vue +43 -0
- package/src/components/ui/combobox/ComboboxSelect.vue +162 -0
- package/src/components/ui/combobox/ComboboxSeparator.vue +16 -0
- package/src/components/ui/combobox/ComboboxTrigger.vue +28 -0
- package/src/components/ui/combobox/index.ts +11 -0
- package/src/components/ui/combobox/types.ts +16 -0
- package/src/components/ui/command/CommandDialog.vue +18 -16
- package/src/components/ui/command/CommandGroup.vue +1 -1
- package/src/components/ui/command/CommandInput.vue +2 -2
- package/src/components/ui/command/CommandItem.vue +7 -5
- package/src/components/ui/data-table/DataTable.vue +230 -0
- package/src/components/ui/data-table/index.ts +20 -0
- package/src/components/ui/date-picker/DatePicker.vue +11 -15
- package/src/components/ui/date-time-picker/DateTimePicker.vue +2 -7
- package/src/components/ui/dialog/DialogContent.vue +9 -4
- package/src/components/ui/dialog/DialogOverlay.vue +1 -1
- package/src/components/ui/dialog/DialogScrollContent.vue +10 -5
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +18 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +34 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +34 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +26 -0
- package/src/components/ui/dropdown-menu/DropdownMenuPortal.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +18 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +32 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +16 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +15 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +39 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +36 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +15 -0
- package/src/components/ui/dropdown-menu/classes.ts +24 -0
- package/src/components/ui/dropdown-menu/index.ts +15 -0
- package/src/components/ui/field/FieldDescription.vue +1 -1
- package/src/components/ui/field/FieldError.vue +4 -1
- package/src/components/ui/file-dropzone/FileDropzone.vue +250 -0
- package/src/components/ui/file-dropzone/index.ts +35 -0
- package/src/components/ui/hover-card/HoverCard.vue +21 -0
- package/src/components/ui/hover-card/HoverCardContent.vue +33 -0
- package/src/components/ui/hover-card/HoverCardTrigger.vue +13 -0
- package/src/components/ui/hover-card/index.ts +3 -0
- package/src/components/ui/input/Input.vue +5 -5
- package/src/components/ui/kbd/Kbd.vue +21 -0
- package/src/components/ui/kbd/KbdGroup.vue +72 -0
- package/src/components/ui/kbd/index.ts +2 -0
- package/src/components/ui/number-field/NumberField.vue +130 -0
- package/src/components/ui/number-field/index.ts +1 -0
- package/src/components/ui/pagination/Pagination.vue +27 -0
- package/src/components/ui/pagination/PaginationContent.vue +20 -0
- package/src/components/ui/pagination/PaginationEllipsis.vue +31 -0
- package/src/components/ui/pagination/PaginationFirst.vue +32 -0
- package/src/components/ui/pagination/PaginationItem.vue +41 -0
- package/src/components/ui/pagination/PaginationLast.vue +32 -0
- package/src/components/ui/pagination/PaginationNext.vue +32 -0
- package/src/components/ui/pagination/PaginationPrevious.vue +32 -0
- package/src/components/ui/pagination/SimplePagination.vue +49 -0
- package/src/components/ui/pagination/index.ts +9 -0
- package/src/components/ui/pin-input/PinInput.vue +48 -0
- package/src/components/ui/pin-input/PinInputInput.vue +49 -0
- package/src/components/ui/pin-input/context.ts +8 -0
- package/src/components/ui/pin-input/index.ts +2 -0
- package/src/components/ui/popover/PopoverContent.vue +1 -1
- package/src/components/ui/progress/Progress.vue +22 -7
- package/src/components/ui/radio-group/RadioGroup.vue +27 -0
- package/src/components/ui/radio-group/RadioGroupCard.vue +56 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +40 -0
- package/src/components/ui/radio-group/index.ts +3 -0
- package/src/components/ui/select/SelectContent.vue +1 -1
- package/src/components/ui/select/SelectItem.vue +2 -2
- package/src/components/ui/select/SelectItemContent.vue +1 -1
- package/src/components/ui/select/SelectTrigger.vue +6 -6
- package/src/components/ui/select/SelectValueContent.vue +1 -1
- package/src/components/ui/sheet/SheetContent.vue +5 -5
- package/src/components/ui/sheet/SheetOverlay.vue +1 -1
- package/src/components/ui/sidebar/Sidebar.vue +7 -5
- package/src/components/ui/sidebar/SidebarRail.vue +3 -1
- package/src/components/ui/sidebar/SidebarTrigger.vue +3 -1
- package/src/components/ui/skeleton/Skeleton.vue +4 -4
- package/src/components/ui/slider/Slider.vue +8 -6
- package/src/components/ui/spinner/Spinner.vue +47 -0
- package/src/components/ui/spinner/index.ts +1 -0
- package/src/components/ui/stepper/SimpleStepper.vue +56 -0
- package/src/components/ui/stepper/Stepper.vue +30 -0
- package/src/components/ui/stepper/StepperDescription.vue +20 -0
- package/src/components/ui/stepper/StepperIndicator.vue +41 -0
- package/src/components/ui/stepper/StepperItem.vue +21 -0
- package/src/components/ui/stepper/StepperSeparator.vue +34 -0
- package/src/components/ui/stepper/StepperTitle.vue +20 -0
- package/src/components/ui/stepper/StepperTrigger.vue +20 -0
- package/src/components/ui/stepper/index.ts +17 -0
- package/src/components/ui/switch/Switch.vue +8 -6
- package/src/components/ui/tabs/TabsContent.vue +1 -1
- package/src/components/ui/tabs/TabsList.vue +13 -5
- package/src/components/ui/tabs/TabsTrigger.vue +5 -3
- package/src/components/ui/tags-input/TagsInput.vue +49 -0
- package/src/components/ui/tags-input/TagsInputInput.vue +23 -0
- package/src/components/ui/tags-input/TagsInputItem.vue +35 -0
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +31 -0
- package/src/components/ui/tags-input/TagsInputItemText.vue +21 -0
- package/src/components/ui/tags-input/index.ts +5 -0
- package/src/components/ui/textarea/Textarea.vue +4 -5
- package/src/components/ui/toggle-group/Toggle.vue +33 -0
- package/src/components/ui/toggle-group/ToggleGroup.vue +93 -0
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +34 -0
- package/src/components/ui/toggle-group/index.ts +69 -0
- package/src/components/ui/tooltip/TooltipContent.vue +1 -1
- package/src/i18n/de.json +76 -0
- package/src/i18n/en.json +76 -0
- package/src/i18n/es.json +76 -0
- package/src/i18n/fr.json +76 -0
- package/src/i18n/it.json +76 -0
- package/src/i18n/ja.json +76 -0
- package/src/i18n/pt.json +76 -0
- package/src/i18n/ru.json +76 -0
- package/src/i18n/zh.json +76 -0
- package/src/lib/color-tokens.ts +14 -14
- package/src/styles.css +289 -5
|
@@ -26,7 +26,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
26
26
|
<template>
|
|
27
27
|
<PopoverPortal>
|
|
28
28
|
<PopoverContent data-slot="popover-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn(
|
|
29
|
-
'bg-popover text-popover-foreground
|
|
29
|
+
'uv-floating-motion bg-popover text-popover-foreground z-50 w-72 rounded border border-border p-4 shadow-elevated-lg outline-hidden',
|
|
30
30
|
props.class
|
|
31
31
|
)
|
|
32
32
|
">
|
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ProgressRootProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { computed } from 'vue';
|
|
4
5
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
6
|
import { ProgressIndicator, ProgressRoot } from 'reka-ui';
|
|
6
7
|
import { cn } from '../../../lib/utils';
|
|
7
8
|
|
|
8
9
|
// Props
|
|
9
|
-
const props = withDefaults(defineProps<ProgressRootProps & {
|
|
10
|
-
|
|
10
|
+
const props = withDefaults(defineProps<ProgressRootProps & {
|
|
11
|
+
indeterminate?: boolean;
|
|
12
|
+
class?: HTMLAttributes['class'];
|
|
13
|
+
}>(), {
|
|
14
|
+
modelValue: 0,
|
|
15
|
+
indeterminate: false
|
|
11
16
|
});
|
|
12
17
|
|
|
13
|
-
const delegatedProps = reactiveOmit(props, 'class');
|
|
18
|
+
const delegatedProps = reactiveOmit(props, 'class', 'indeterminate');
|
|
19
|
+
|
|
20
|
+
// Resolve the fill percentage against the configured max
|
|
21
|
+
const percentage = computed(() => {
|
|
22
|
+
const max = props.max ?? 100;
|
|
23
|
+
if (!max) return 0;
|
|
24
|
+
return Math.min(100, Math.max(0, ((props.modelValue ?? 0) / max) * 100));
|
|
25
|
+
});
|
|
14
26
|
</script>
|
|
15
27
|
|
|
16
28
|
<template>
|
|
17
29
|
<ProgressRoot data-slot="progress" v-bind="delegatedProps" :class="cn(
|
|
18
|
-
'
|
|
30
|
+
'relative h-2 w-full overflow-hidden rounded-sm bg-muted',
|
|
19
31
|
props.class
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
<
|
|
32
|
+
)">
|
|
33
|
+
<!-- Indeterminate bar -->
|
|
34
|
+
<div v-if="props.indeterminate" data-slot="progress-indicator" class="absolute inset-y-0 left-0 w-full origin-left animate-uv-indeterminate rounded-sm bg-primary" />
|
|
35
|
+
|
|
36
|
+
<!-- Determinate bar -->
|
|
37
|
+
<ProgressIndicator v-else data-slot="progress-indicator" class="relative h-full w-full flex-1 overflow-hidden rounded-sm bg-primary transition-transform duration-700 ease-out-expo after:absolute after:inset-0 after:animate-uv-shimmer after:bg-[linear-gradient(90deg,transparent,rgb(255_255_255/0.35),transparent)]" :style="`transform: translateX(-${100 - percentage}%);`" />
|
|
23
38
|
</ProgressRoot>
|
|
24
39
|
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { RadioGroupRootEmits, RadioGroupRootProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { RadioGroupRoot, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<RadioGroupRootProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
// Emits
|
|
14
|
+
const emits = defineEmits<RadioGroupRootEmits>();
|
|
15
|
+
|
|
16
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
17
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<RadioGroupRoot v-slot="slotProps" data-slot="radio-group" v-bind="forwarded" :class="cn(
|
|
22
|
+
'grid gap-3 aria-[orientation=horizontal]:flex aria-[orientation=horizontal]:flex-wrap aria-[orientation=horizontal]:items-center aria-[orientation=horizontal]:gap-x-5',
|
|
23
|
+
props.class
|
|
24
|
+
)">
|
|
25
|
+
<slot v-bind="slotProps" />
|
|
26
|
+
</RadioGroupRoot>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { AcceptableValue } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
5
|
+
import { RadioGroupIndicator, RadioGroupItem } from 'reka-ui';
|
|
6
|
+
import type { HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
7
|
+
import { cn } from '../../../lib/utils';
|
|
8
|
+
|
|
9
|
+
// Props
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
value: AcceptableValue;
|
|
12
|
+
label: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
icon?: HugeiconsIconDefinition;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
id?: string;
|
|
17
|
+
class?: HTMLAttributes['class'];
|
|
18
|
+
}>();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<RadioGroupItem :id="props.id" :value="props.value" :disabled="props.disabled" data-slot="radio-group-card" :class="cn(
|
|
23
|
+
'group/radio-card relative flex w-full cursor-pointer items-center gap-3 rounded border border-border bg-card px-4 py-3 text-left shadow-elevated-sm outline-none',
|
|
24
|
+
'[transition:scale_320ms_var(--ease-spring),border-color_150ms_var(--ease-snappy),background-color_150ms_var(--ease-snappy),box-shadow_200ms_var(--ease-snappy)]',
|
|
25
|
+
'hover:border-border-strong active:scale-[0.985] focus-visible:ring-[3px] focus-visible:ring-ring/45',
|
|
26
|
+
'data-[state=checked]:border-primary data-[state=checked]:bg-primary/5 data-[state=checked]:shadow-glow data-[state=checked]:hover:border-primary',
|
|
27
|
+
'disabled:cursor-not-allowed disabled:opacity-60 disabled:active:scale-100',
|
|
28
|
+
props.class
|
|
29
|
+
)">
|
|
30
|
+
<!-- Icon -->
|
|
31
|
+
<span v-if="props.icon" class="flex size-9 shrink-0 items-center justify-center rounded border border-border bg-surface text-muted-foreground transition-colors duration-150 group-data-[state=checked]/radio-card:border-primary/40 group-data-[state=checked]/radio-card:bg-primary/10 group-data-[state=checked]/radio-card:text-primary">
|
|
32
|
+
<HugeiconsIcon :icon="props.icon" class="size-4.5" />
|
|
33
|
+
</span>
|
|
34
|
+
|
|
35
|
+
<!-- Label and description -->
|
|
36
|
+
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
37
|
+
<span class="text-xs font-semibold text-foreground sm:text-sm">
|
|
38
|
+
<slot name="label">{{ props.label }}</slot>
|
|
39
|
+
</span>
|
|
40
|
+
<span v-if="props.description || $slots.description" class="text-xs text-muted-foreground">
|
|
41
|
+
<slot name="description">{{ props.description }}</slot>
|
|
42
|
+
</span>
|
|
43
|
+
</span>
|
|
44
|
+
|
|
45
|
+
<!-- Radio circle -->
|
|
46
|
+
<span aria-hidden="true" class="flex size-5 shrink-0 items-center justify-center rounded-full border border-input bg-secondary shadow-elevated-sm transition-colors duration-150 group-data-[state=checked]/radio-card:border-primary">
|
|
47
|
+
<RadioGroupIndicator force-mount data-slot="radio-group-indicator" :class="cn(
|
|
48
|
+
'pointer-events-none flex items-center justify-center opacity-0 scale-0',
|
|
49
|
+
'[transition:scale_140ms_var(--ease-snappy),opacity_100ms_var(--ease-snappy)]',
|
|
50
|
+
'data-[state=checked]:opacity-100 data-[state=checked]:scale-100 data-[state=checked]:[transition:scale_420ms_var(--ease-bounce),opacity_80ms_linear]'
|
|
51
|
+
)">
|
|
52
|
+
<span class="size-2.5 rounded-full bg-primary" />
|
|
53
|
+
</RadioGroupIndicator>
|
|
54
|
+
</span>
|
|
55
|
+
</RadioGroupItem>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { RadioGroupItemEmits, RadioGroupItemProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { RadioGroupIndicator, RadioGroupItem, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<RadioGroupItemProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
// Emits
|
|
14
|
+
const emits = defineEmits<RadioGroupItemEmits>();
|
|
15
|
+
|
|
16
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
17
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<RadioGroupItem v-slot="slotProps" data-slot="radio-group-item" v-bind="forwarded" :class="cn(
|
|
22
|
+
'peer relative inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full border border-input bg-secondary text-primary shadow-elevated-sm outline-none',
|
|
23
|
+
'[transition:scale_320ms_var(--ease-spring),border-color_150ms_var(--ease-snappy),box-shadow_150ms_var(--ease-snappy)]',
|
|
24
|
+
'hover:border-border-strong active:scale-[0.86] focus-visible:ring-[3px] focus-visible:ring-ring/45',
|
|
25
|
+
'data-[state=checked]:border-primary data-[state=checked]:hover:border-primary',
|
|
26
|
+
'disabled:cursor-not-allowed disabled:opacity-60 disabled:active:scale-100 aria-invalid:border-destructive',
|
|
27
|
+
props.class
|
|
28
|
+
)">
|
|
29
|
+
<!-- Indicator (always mounted so the dot can pop in and shrink out) -->
|
|
30
|
+
<RadioGroupIndicator force-mount data-slot="radio-group-indicator" :class="cn(
|
|
31
|
+
'pointer-events-none flex items-center justify-center opacity-0 scale-0',
|
|
32
|
+
'[transition:scale_140ms_var(--ease-snappy),opacity_100ms_var(--ease-snappy)]',
|
|
33
|
+
'data-[state=checked]:opacity-100 data-[state=checked]:scale-100 data-[state=checked]:[transition:scale_420ms_var(--ease-bounce),opacity_80ms_linear]'
|
|
34
|
+
)">
|
|
35
|
+
<slot v-bind="slotProps">
|
|
36
|
+
<span class="size-2.5 rounded-full bg-primary" />
|
|
37
|
+
</slot>
|
|
38
|
+
</RadioGroupIndicator>
|
|
39
|
+
</RadioGroupItem>
|
|
40
|
+
</template>
|
|
@@ -28,7 +28,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
28
28
|
<template>
|
|
29
29
|
<SelectPortal>
|
|
30
30
|
<SelectContent data-slot="select-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn(
|
|
31
|
-
'bg-popover text-popover-foreground
|
|
31
|
+
'uv-floating-motion bg-popover text-popover-foreground relative z-50 max-h-(--reka-select-content-available-height) min-w-32 overflow-x-hidden overflow-y-auto rounded border border-border shadow-elevated-lg max-sm:w-(--reka-select-trigger-width) max-sm:min-w-(--reka-select-trigger-width)',
|
|
32
32
|
position === 'popper' &&
|
|
33
33
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
34
34
|
props.class
|
|
@@ -16,14 +16,14 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
16
16
|
|
|
17
17
|
<template>
|
|
18
18
|
<SelectItem data-slot="select-item" v-bind="forwardedProps" :class="cn(
|
|
19
|
-
'focus:bg-accent focus:text-accent-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-2.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',
|
|
19
|
+
'focus:bg-accent focus:text-accent-foreground data-[state=checked]:text-foreground transition-colors duration-150[&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-2.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',
|
|
20
20
|
props.class
|
|
21
21
|
)
|
|
22
22
|
">
|
|
23
23
|
<span class="absolute right-2 flex size-3.5 items-center justify-center">
|
|
24
24
|
<SelectItemIndicator>
|
|
25
25
|
<slot name="indicator-icon">
|
|
26
|
-
<HugeiconsIcon :icon="Tick02Icon" class="size-4" />
|
|
26
|
+
<HugeiconsIcon :icon="Tick02Icon" class="size-4 animate-uv-pop text-primary" />
|
|
27
27
|
</slot>
|
|
28
28
|
</SelectItemIndicator>
|
|
29
29
|
</span>
|
|
@@ -36,7 +36,7 @@ const props = defineProps<{
|
|
|
36
36
|
|
|
37
37
|
<!-- Description -->
|
|
38
38
|
<span v-if="props.description" data-slot="select-item-content-description" :class="cn(
|
|
39
|
-
'font-regular text-[11px] sm:text-xs leading-4 sm:leading-5 text-
|
|
39
|
+
'font-regular text-[11px] sm:text-xs leading-4 sm:leading-5 text-muted-foreground',
|
|
40
40
|
props.descriptionClass
|
|
41
41
|
)
|
|
42
42
|
">
|
|
@@ -19,15 +19,15 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
19
19
|
|
|
20
20
|
<template>
|
|
21
21
|
<SelectTrigger data-slot="select-trigger" :data-size="size" v-bind="forwardedProps" :class="cn(
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
22
|
+
'group/select-trigger uv-field flex w-fit cursor-pointer items-center justify-between gap-2 rounded border border-input bg-secondary px-4 py-3 text-xs font-semibold whitespace-nowrap text-foreground shadow-elevated-sm outline-none data-placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 data-[size=default]:h-13 data-[size=sm]:h-10 sm:text-sm',
|
|
23
|
+
'*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2',
|
|
24
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground',
|
|
25
|
+
'aria-invalid:animate-uv-shake',
|
|
25
26
|
props.class
|
|
26
|
-
)
|
|
27
|
-
">
|
|
27
|
+
)">
|
|
28
28
|
<slot />
|
|
29
29
|
<SelectIcon as-child>
|
|
30
|
-
<HugeiconsIcon :icon="ArrowDown01Icon" class="size-4 opacity-
|
|
30
|
+
<HugeiconsIcon :icon="ArrowDown01Icon" class="size-4 opacity-60 transition-transform duration-300 ease-spring group-data-[state=open]/select-trigger:rotate-180" />
|
|
31
31
|
</SelectIcon>
|
|
32
32
|
</SelectTrigger>
|
|
33
33
|
</template>
|
|
@@ -44,7 +44,7 @@ const props = defineProps<SelectValueProps & {
|
|
|
44
44
|
|
|
45
45
|
<!-- Description -->
|
|
46
46
|
<span v-if="props.description" data-slot="select-value-content-description" :class="cn(
|
|
47
|
-
'min-w-0 whitespace-normal wrap-break-word text-[11px] leading-4 text-
|
|
47
|
+
'min-w-0 whitespace-normal wrap-break-word text-[11px] leading-4 text-muted-foreground sm:text-xs sm:leading-5 max-sm:[display:-webkit-box] max-sm:overflow-hidden max-sm:[-webkit-box-orient:vertical] max-sm:[-webkit-line-clamp:3]',
|
|
48
48
|
props.descriptionClass
|
|
49
49
|
)
|
|
50
50
|
">
|
|
@@ -30,15 +30,15 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
30
30
|
<DialogPortal>
|
|
31
31
|
<SheetOverlay />
|
|
32
32
|
<DialogContent data-slot="sheet-content" :class="cn(
|
|
33
|
-
'bg-background
|
|
33
|
+
'bg-background fixed z-50 flex flex-col gap-4 shadow-elevated-xl outline-none [animation-fill-mode:both] data-[state=open]:[animation-duration:480ms] data-[state=open]:[animation-timing-function:var(--ease-spring)] data-[state=closed]:[animation-duration:200ms] data-[state=closed]:[animation-timing-function:var(--ease-snappy)]',
|
|
34
34
|
side === 'right' &&
|
|
35
|
-
'data-[state=
|
|
35
|
+
'data-[state=open]:[animation-name:uv-sheet-in-right] data-[state=closed]:[animation-name:uv-sheet-out-right] inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
|
36
36
|
side === 'left' &&
|
|
37
|
-
'data-[state=
|
|
37
|
+
'data-[state=open]:[animation-name:uv-sheet-in-left] data-[state=closed]:[animation-name:uv-sheet-out-left] inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
|
38
38
|
side === 'top' &&
|
|
39
|
-
'data-[state=
|
|
39
|
+
'data-[state=open]:[animation-name:uv-sheet-in-top] data-[state=closed]:[animation-name:uv-sheet-out-top] inset-x-0 top-0 h-auto border-b',
|
|
40
40
|
side === 'bottom' &&
|
|
41
|
-
'data-[state=
|
|
41
|
+
'data-[state=open]:[animation-name:uv-sheet-in-bottom] data-[state=closed]:[animation-name:uv-sheet-out-bottom] inset-x-0 bottom-0 h-auto border-t',
|
|
42
42
|
props.class
|
|
43
43
|
)
|
|
44
44
|
" v-bind="{ ...$attrs, ...forwarded }">
|
|
@@ -13,7 +13,7 @@ const delegatedProps = reactiveOmit(props, 'class');
|
|
|
13
13
|
|
|
14
14
|
<template>
|
|
15
15
|
<DialogOverlay data-slot="sheet-overlay" :class="cn(
|
|
16
|
-
'fixed inset-0 z-50 bg-black/
|
|
16
|
+
'uv-overlay-motion fixed inset-0 z-50 bg-black/40 backdrop-blur-[3px] dark:bg-black/60',
|
|
17
17
|
props.class
|
|
18
18
|
)
|
|
19
19
|
" v-bind="delegatedProps">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { SidebarProps } from '.';
|
|
3
|
+
import { useI18n } from 'vue-i18n';
|
|
3
4
|
import { cn } from '../../../lib/utils';
|
|
4
5
|
import { Sheet, SheetContent } from '../sheet';
|
|
5
6
|
import SheetDescription from '../sheet/SheetDescription.vue';
|
|
@@ -19,6 +20,7 @@ const props = withDefaults(defineProps<SidebarProps>(), {
|
|
|
19
20
|
compact: false
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
const { t } = useI18n();
|
|
22
24
|
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
23
25
|
|
|
24
26
|
function handleMobileSidebarClick(event: MouseEvent) {
|
|
@@ -40,12 +42,12 @@ function handleMobileSidebarClick(event: MouseEvent) {
|
|
|
40
42
|
</div>
|
|
41
43
|
|
|
42
44
|
<Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="setOpenMobile">
|
|
43
|
-
<SheetContent data-sidebar="sidebar" data-slot="sidebar" data-mobile="true" :data-compact="props.compact ? 'true' : 'false'" :side="side" class="group/sidebar-wrapper border-sidebar-border bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden" :style="{
|
|
45
|
+
<SheetContent data-sidebar="sidebar" data-slot="sidebar" data-mobile="true" :data-compact="props.compact ? 'true' : 'false'" :side="side" class="group/sidebar-wrapper border-sidebar-border bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 data-[state=open]:[animation-duration:320ms] data-[state=open]:[animation-timing-function:var(--ease-out-expo)] [&>button]:hidden" :style="{
|
|
44
46
|
'--sidebar-width': SIDEBAR_WIDTH_MOBILE
|
|
45
47
|
}">
|
|
46
48
|
<SheetHeader class="sr-only">
|
|
47
|
-
<SheetTitle>
|
|
48
|
-
<SheetDescription>
|
|
49
|
+
<SheetTitle>{{ t('uiVintage.sidebar.title') }}</SheetTitle>
|
|
50
|
+
<SheetDescription>{{ t('uiVintage.sidebar.description') }}</SheetDescription>
|
|
49
51
|
</SheetHeader>
|
|
50
52
|
<div class="flex h-full w-full flex-col" @click="handleMobileSidebarClick">
|
|
51
53
|
<slot />
|
|
@@ -56,7 +58,7 @@ function handleMobileSidebarClick(event: MouseEvent) {
|
|
|
56
58
|
<div v-else class="group/sidebar-wrapper group peer text-sidebar-foreground hidden md:block" data-slot="sidebar" :data-state="state" :data-collapsible="state === 'collapsed' ? collapsible : ''" :data-variant="variant" :data-side="side">
|
|
57
59
|
<!-- This is what handles the sidebar gap on desktop -->
|
|
58
60
|
<div :class="cn(
|
|
59
|
-
'relative w-(--sidebar-width) bg-transparent transition-[width] duration-
|
|
61
|
+
'relative w-(--sidebar-width) bg-transparent transition-[width] duration-300 ease-out-expo',
|
|
60
62
|
'group-data-[collapsible=offcanvas]:w-0',
|
|
61
63
|
'group-data-[side=right]:rotate-180',
|
|
62
64
|
variant === 'floating' || variant === 'inset'
|
|
@@ -65,7 +67,7 @@ function handleMobileSidebarClick(event: MouseEvent) {
|
|
|
65
67
|
)
|
|
66
68
|
" />
|
|
67
69
|
<div :class="cn(
|
|
68
|
-
'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-
|
|
70
|
+
'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-300 ease-out-expo md:flex',
|
|
69
71
|
side === 'left'
|
|
70
72
|
? 'left-0 group-data-[collapsible=offcanvas]:-left-(--sidebar-width)'
|
|
71
73
|
: 'right-0 group-data-[collapsible=offcanvas]:-right-(--sidebar-width)',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { useI18n } from 'vue-i18n';
|
|
3
4
|
import { cn } from '../../../lib/utils';
|
|
4
5
|
import { useSidebar } from './utils';
|
|
5
6
|
|
|
@@ -8,11 +9,12 @@ const props = defineProps<{
|
|
|
8
9
|
class?: HTMLAttributes['class'];
|
|
9
10
|
}>();
|
|
10
11
|
|
|
12
|
+
const { t } = useI18n();
|
|
11
13
|
const { toggleSidebar } = useSidebar();
|
|
12
14
|
</script>
|
|
13
15
|
|
|
14
16
|
<template>
|
|
15
|
-
<button data-sidebar="rail" data-slot="sidebar-rail" aria-label="
|
|
17
|
+
<button data-sidebar="rail" data-slot="sidebar-rail" :aria-label="t('uiVintage.sidebar.toggle')" :tabindex="-1" :title="t('uiVintage.sidebar.toggle')" :class="cn(
|
|
16
18
|
'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 sm:flex',
|
|
17
19
|
'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
|
|
18
20
|
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
import { PanelLeftIcon } from '@hugeicons/core-free-icons';
|
|
4
4
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
5
|
+
import { useI18n } from 'vue-i18n';
|
|
5
6
|
import { cn } from '../../../lib/utils';
|
|
6
7
|
import { Button } from '../button';
|
|
7
8
|
import { useSidebar } from './utils';
|
|
@@ -11,12 +12,13 @@ const props = defineProps<{
|
|
|
11
12
|
class?: HTMLAttributes['class'];
|
|
12
13
|
}>();
|
|
13
14
|
|
|
15
|
+
const { t } = useI18n();
|
|
14
16
|
const { toggleSidebar } = useSidebar();
|
|
15
17
|
</script>
|
|
16
18
|
|
|
17
19
|
<template>
|
|
18
20
|
<Button data-sidebar="trigger" data-slot="sidebar-trigger" variant="secondary" size="icon" :class="cn(props.class)" @click="toggleSidebar">
|
|
19
21
|
<HugeiconsIcon :icon="PanelLeftIcon" />
|
|
20
|
-
<span class="sr-only">
|
|
22
|
+
<span class="sr-only">{{ t('uiVintage.sidebar.toggle') }}</span>
|
|
21
23
|
</Button>
|
|
22
24
|
</template>
|
|
@@ -10,8 +10,8 @@ const props = defineProps<{
|
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
12
|
<div data-slot="skeleton" :class="cn(
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
'relative overflow-hidden rounded border border-border bg-surface',
|
|
14
|
+
'after:absolute after:inset-0 after:animate-uv-shimmer after:bg-[linear-gradient(90deg,transparent,color-mix(in_oklab,var(--foreground)_6%,transparent),transparent)]',
|
|
15
|
+
props.class
|
|
16
|
+
)" />
|
|
17
17
|
</template>
|
|
@@ -19,14 +19,16 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
19
19
|
|
|
20
20
|
<template>
|
|
21
21
|
<SliderRoot v-slot="{ modelValue }" data-slot="slider" :class="cn(
|
|
22
|
-
'relative flex w-full cursor-pointer touch-none items-center select-none data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
|
|
22
|
+
'group/slider relative flex w-full cursor-pointer touch-none items-center py-2 select-none data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col data-[orientation=vertical]:px-2 data-[orientation=vertical]:py-0',
|
|
23
23
|
props.class
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
<SliderTrack data-slot="slider-track" class="
|
|
27
|
-
|
|
24
|
+
)" v-bind="forwarded">
|
|
25
|
+
<!-- Track -->
|
|
26
|
+
<SliderTrack data-slot="slider-track" class="relative grow overflow-hidden rounded bg-muted transition-[height,width] duration-200 ease-spring data-[orientation=horizontal]:h-2 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-2 group-hover/slider:data-[orientation=horizontal]:h-2.5 group-hover/slider:data-[orientation=vertical]:w-2.5">
|
|
27
|
+
<!-- Filled range -->
|
|
28
|
+
<SliderRange data-slot="slider-range" class="absolute bg-primary bg-[linear-gradient(90deg,transparent,rgb(255_255_255/0.22))] data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full" />
|
|
28
29
|
</SliderTrack>
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
<!-- Thumbs -->
|
|
32
|
+
<SliderThumb v-for="(_, key) in modelValue" :key="key" data-slot="slider-thumb" class="block size-5 shrink-0 cursor-grab rounded border-2 border-primary bg-white shadow-elevated-md outline-none [transition:scale_300ms_var(--ease-spring),box-shadow_200ms] hover:scale-110 hover:shadow-[0_0_0_6px_color-mix(in_oklab,var(--primary)_16%,transparent)] focus-visible:shadow-[0_0_0_6px_color-mix(in_oklab,var(--primary)_24%,transparent)] active:scale-125 active:cursor-grabbing disabled:pointer-events-none disabled:opacity-50" />
|
|
31
33
|
</SliderRoot>
|
|
32
34
|
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { computed } from 'vue';
|
|
4
|
+
import { getUiVintageRuntimeMessage } from '../../../lib/i18n';
|
|
5
|
+
import { cn } from '../../../lib/utils';
|
|
6
|
+
|
|
7
|
+
// Props
|
|
8
|
+
const props = withDefaults(defineProps<{
|
|
9
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
10
|
+
label?: string;
|
|
11
|
+
class?: HTMLAttributes['class'];
|
|
12
|
+
}>(), {
|
|
13
|
+
size: 'md',
|
|
14
|
+
label: ''
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const resolvedLabel = computed(() => props.label || getUiVintageRuntimeMessage('uiVintage.buttons.loading', 'Loading'));
|
|
18
|
+
|
|
19
|
+
// Size classes
|
|
20
|
+
const sizeClasses: Record<NonNullable<typeof props.size>, string> = {
|
|
21
|
+
xs: 'size-3',
|
|
22
|
+
sm: 'size-4',
|
|
23
|
+
md: 'size-5',
|
|
24
|
+
lg: 'size-8',
|
|
25
|
+
xl: 'size-12'
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<span data-slot="spinner" role="status" :aria-label="resolvedLabel" :class="cn('relative inline-flex shrink-0 text-current', sizeClasses[props.size], props.class)">
|
|
31
|
+
<svg viewBox="0 0 24 24" fill="none" class="size-full animate-[spin_0.7s_linear_infinite]" aria-hidden="true">
|
|
32
|
+
<!-- Track -->
|
|
33
|
+
<circle cx="12" cy="12" r="9.5" stroke="currentColor" stroke-width="2.5" class="opacity-20" />
|
|
34
|
+
|
|
35
|
+
<!-- Arc -->
|
|
36
|
+
<circle cx="12" cy="12" r="9.5" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="60" class="origin-center animate-[uv-spinner-dash_1.4s_ease-in-out_infinite]" />
|
|
37
|
+
</svg>
|
|
38
|
+
</span>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
@keyframes uv-spinner-dash {
|
|
43
|
+
0% { stroke-dashoffset: 56; }
|
|
44
|
+
50% { stroke-dashoffset: 16; transform: rotate(135deg); }
|
|
45
|
+
100% { stroke-dashoffset: 56; transform: rotate(450deg); }
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Spinner } from './Spinner.vue';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import type { StepperStep } from '.';
|
|
4
|
+
import { Tick02Icon } from '@hugeicons/core-free-icons';
|
|
5
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
6
|
+
import { computed } from 'vue';
|
|
7
|
+
import { cn } from '../../../lib/utils';
|
|
8
|
+
import Stepper from './Stepper.vue';
|
|
9
|
+
import StepperDescription from './StepperDescription.vue';
|
|
10
|
+
import StepperIndicator from './StepperIndicator.vue';
|
|
11
|
+
import StepperItem from './StepperItem.vue';
|
|
12
|
+
import StepperSeparator from './StepperSeparator.vue';
|
|
13
|
+
import StepperTitle from './StepperTitle.vue';
|
|
14
|
+
import StepperTrigger from './StepperTrigger.vue';
|
|
15
|
+
|
|
16
|
+
// Props
|
|
17
|
+
const props = withDefaults(defineProps<{
|
|
18
|
+
steps: StepperStep[];
|
|
19
|
+
orientation?: 'horizontal' | 'vertical';
|
|
20
|
+
linear?: boolean;
|
|
21
|
+
class?: HTMLAttributes['class'];
|
|
22
|
+
}>(), {
|
|
23
|
+
orientation: 'horizontal',
|
|
24
|
+
linear: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Active step (1-based, v-model)
|
|
28
|
+
const modelValue = defineModel<number>({ default: 1 });
|
|
29
|
+
|
|
30
|
+
const isVertical = computed(() => props.orientation === 'vertical');
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<Stepper v-model="modelValue" :orientation="props.orientation" :linear="props.linear" :class="cn('w-full', isVertical ? 'gap-0' : 'items-start gap-2', props.class)">
|
|
35
|
+
<StepperItem v-for="(step, index) in props.steps" :key="index" :step="index + 1" :class="isVertical ? 'items-start gap-0 pb-8 last:pb-0' : 'flex-1 flex-col items-center gap-0'">
|
|
36
|
+
<!-- Step trigger -->
|
|
37
|
+
<StepperTrigger :class="isVertical ? 'items-start gap-4' : 'flex-col gap-2 text-center'">
|
|
38
|
+
<StepperIndicator v-slot="{ step: stepNumber, state }">
|
|
39
|
+
<!-- Custom icon until the step is completed -->
|
|
40
|
+
<HugeiconsIcon v-if="state !== 'completed' && step.icon" :icon="step.icon" class="size-4" />
|
|
41
|
+
<span v-else-if="state !== 'completed'">{{ stepNumber }}</span>
|
|
42
|
+
<HugeiconsIcon v-else :icon="Tick02Icon" class="size-4 animate-uv-pop" :stroke-width="2.5" />
|
|
43
|
+
</StepperIndicator>
|
|
44
|
+
|
|
45
|
+
<!-- Step text -->
|
|
46
|
+
<div :class="cn('flex flex-col gap-0.5', isVertical ? 'pt-2' : 'items-center px-1')">
|
|
47
|
+
<StepperTitle>{{ step.title }}</StepperTitle>
|
|
48
|
+
<StepperDescription v-if="step.description">{{ step.description }}</StepperDescription>
|
|
49
|
+
</div>
|
|
50
|
+
</StepperTrigger>
|
|
51
|
+
|
|
52
|
+
<!-- Connector to the next step -->
|
|
53
|
+
<StepperSeparator v-if="index < props.steps.length - 1" :class="isVertical ? 'absolute top-12 bottom-1 left-[21px]' : 'absolute top-[21px] right-[calc(-50%+18px)] left-[calc(50%+26px)]'" />
|
|
54
|
+
</StepperItem>
|
|
55
|
+
</Stepper>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StepperRootEmits, StepperRootProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { StepperRoot, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { useI18n } from 'vue-i18n';
|
|
7
|
+
import { cn } from '../../../lib/utils';
|
|
8
|
+
|
|
9
|
+
// Props
|
|
10
|
+
const props = withDefaults(defineProps<StepperRootProps & {
|
|
11
|
+
class?: HTMLAttributes['class'];
|
|
12
|
+
}>(), {
|
|
13
|
+
orientation: 'horizontal',
|
|
14
|
+
linear: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Emits
|
|
18
|
+
const emits = defineEmits<StepperRootEmits>();
|
|
19
|
+
|
|
20
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
21
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
22
|
+
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<StepperRoot v-slot="slotProps" data-slot="stepper" :aria-label="t('uiVintage.stepper.label')" v-bind="forwarded" :class="cn('flex gap-2 data-[orientation=vertical]:flex-col', props.class)">
|
|
28
|
+
<slot v-bind="slotProps" />
|
|
29
|
+
</StepperRoot>
|
|
30
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StepperDescriptionProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { StepperDescription } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<StepperDescriptionProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<StepperDescription data-slot="stepper-description" v-bind="delegatedProps" :class="cn('text-xs text-muted-foreground', props.class)">
|
|
18
|
+
<slot />
|
|
19
|
+
</StepperDescription>
|
|
20
|
+
</template>
|