@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
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StepperIndicatorProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { Tick02Icon } from '@hugeicons/core-free-icons';
|
|
5
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
6
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
7
|
+
import { injectStepperItemContext, StepperIndicator } from 'reka-ui';
|
|
8
|
+
import { computed } from 'vue';
|
|
9
|
+
import { cn } from '../../../lib/utils';
|
|
10
|
+
|
|
11
|
+
// Props
|
|
12
|
+
const props = defineProps<StepperIndicatorProps & {
|
|
13
|
+
class?: HTMLAttributes['class'];
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
17
|
+
|
|
18
|
+
// State of the parent step
|
|
19
|
+
const itemContext = injectStepperItemContext();
|
|
20
|
+
const state = computed(() => itemContext.state.value);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<StepperIndicator v-slot="{ step }" data-slot="stepper-indicator" :data-state="state" v-bind="delegatedProps" :class="cn(
|
|
25
|
+
'relative inline-flex size-9 shrink-0 items-center justify-center rounded-full border-2 border-border bg-card text-sm font-bold text-muted-foreground ring-0 ring-primary/15 tabular-nums',
|
|
26
|
+
'[transition:color_150ms,background-color_220ms,border-color_220ms,box-shadow_380ms_var(--ease-spring),transform_380ms_var(--ease-spring)]',
|
|
27
|
+
'group-hover/stepper-trigger:border-border-strong group-active/stepper-trigger:scale-95 group-active/stepper-trigger:duration-75',
|
|
28
|
+
'data-[state=active]:border-primary data-[state=active]:text-primary data-[state=active]:ring-4 data-[state=active]:group-hover/stepper-trigger:border-primary',
|
|
29
|
+
'data-[state=completed]:border-primary data-[state=completed]:bg-primary data-[state=completed]:text-primary-foreground data-[state=completed]:group-hover/stepper-trigger:border-primary',
|
|
30
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
|
31
|
+
props.class
|
|
32
|
+
)">
|
|
33
|
+
<slot :step="step" :state="state">
|
|
34
|
+
<!-- Completed check -->
|
|
35
|
+
<HugeiconsIcon v-if="state === 'completed'" :icon="Tick02Icon" class="size-4 animate-uv-pop" :stroke-width="2.5" />
|
|
36
|
+
|
|
37
|
+
<!-- Step number -->
|
|
38
|
+
<span v-else>{{ step }}</span>
|
|
39
|
+
</slot>
|
|
40
|
+
</StepperIndicator>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StepperItemProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { StepperItem, useForwardProps } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<StepperItemProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<StepperItem v-slot="slotProps" data-slot="stepper-item" v-bind="forwardedProps" :class="cn('group/stepper-item relative flex items-center gap-2 data-disabled:pointer-events-none data-disabled:opacity-50', props.class)">
|
|
19
|
+
<slot v-bind="slotProps" />
|
|
20
|
+
</StepperItem>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StepperSeparatorProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { injectStepperItemContext, injectStepperRootContext, StepperSeparator } from 'reka-ui';
|
|
6
|
+
import { computed } from 'vue';
|
|
7
|
+
import { cn } from '../../../lib/utils';
|
|
8
|
+
|
|
9
|
+
// Props
|
|
10
|
+
const props = defineProps<StepperSeparatorProps & {
|
|
11
|
+
class?: HTMLAttributes['class'];
|
|
12
|
+
}>();
|
|
13
|
+
|
|
14
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
15
|
+
|
|
16
|
+
// Orientation and completion come from the stepper
|
|
17
|
+
const rootContext = injectStepperRootContext();
|
|
18
|
+
const itemContext = injectStepperItemContext();
|
|
19
|
+
const isVertical = computed(() => rootContext.orientation.value === 'vertical');
|
|
20
|
+
const isCompleted = computed(() => itemContext.state.value === 'completed');
|
|
21
|
+
|
|
22
|
+
// Fill grows along the track from its start
|
|
23
|
+
const fillClass = computed(() => {
|
|
24
|
+
if (isVertical.value) return cn('origin-top', isCompleted.value ? 'scale-y-100' : 'scale-y-0');
|
|
25
|
+
return cn('origin-left rtl:origin-right', isCompleted.value ? 'scale-x-100' : 'scale-x-0');
|
|
26
|
+
});
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<StepperSeparator data-slot="stepper-separator" v-bind="delegatedProps" :class="cn('relative shrink-0 overflow-hidden rounded-full bg-border', isVertical ? 'w-0.5' : 'h-0.5', props.class)">
|
|
31
|
+
<!-- Progress fill -->
|
|
32
|
+
<span aria-hidden="true" :class="cn('absolute inset-0 rounded-full bg-primary transition-transform duration-500 ease-out-expo', fillClass)" />
|
|
33
|
+
</StepperSeparator>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StepperTitleProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { StepperTitle } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<StepperTitleProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<StepperTitle data-slot="stepper-title" v-bind="delegatedProps" :class="cn('text-xs font-semibold text-foreground transition-colors duration-150 group-data-[state=inactive]/stepper-item:text-muted-foreground sm:text-sm', props.class)">
|
|
18
|
+
<slot />
|
|
19
|
+
</StepperTitle>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StepperTriggerProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { StepperTrigger } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<StepperTriggerProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<StepperTrigger data-slot="stepper-trigger" v-bind="delegatedProps" :class="cn('group/stepper-trigger flex cursor-pointer items-center gap-3 rounded p-1 text-left outline-none transition-colors duration-150 focus-visible:ring-[3px] focus-visible:ring-ring/45 data-disabled:cursor-not-allowed', props.class)">
|
|
18
|
+
<slot />
|
|
19
|
+
</StepperTrigger>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
2
|
+
|
|
3
|
+
export { default as SimpleStepper } from './SimpleStepper.vue';
|
|
4
|
+
export { default as Stepper } from './Stepper.vue';
|
|
5
|
+
export { default as StepperDescription } from './StepperDescription.vue';
|
|
6
|
+
export { default as StepperIndicator } from './StepperIndicator.vue';
|
|
7
|
+
export { default as StepperItem } from './StepperItem.vue';
|
|
8
|
+
export { default as StepperSeparator } from './StepperSeparator.vue';
|
|
9
|
+
export { default as StepperTitle } from './StepperTitle.vue';
|
|
10
|
+
export { default as StepperTrigger } from './StepperTrigger.vue';
|
|
11
|
+
|
|
12
|
+
// Step definition used by SimpleStepper
|
|
13
|
+
export type StepperStep = {
|
|
14
|
+
title: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
icon?: HugeiconsIconDefinition;
|
|
17
|
+
};
|
|
@@ -19,14 +19,16 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
19
19
|
|
|
20
20
|
<template>
|
|
21
21
|
<SwitchRoot v-slot="slotProps" data-slot="switch" v-bind="forwarded" :class="cn(
|
|
22
|
-
'peer relative inline-flex h-7 w-12 shrink-0 cursor-pointer items-center rounded border border-
|
|
22
|
+
'group/switch peer relative inline-flex h-7 w-12 shrink-0 cursor-pointer items-center rounded border border-input bg-secondary shadow-elevated-sm outline-none',
|
|
23
|
+
'[transition:background-color_200ms,border-color_200ms,box-shadow_250ms] hover:border-border-strong focus-visible:ring-[3px] focus-visible:ring-ring/45 disabled:cursor-not-allowed disabled:opacity-60',
|
|
24
|
+
'data-[state=checked]:border-primary data-[state=checked]:bg-accent data-[state=checked]:shadow-[0_0_0_3px_color-mix(in_oklab,var(--primary)_14%,transparent)]',
|
|
23
25
|
props.class
|
|
24
|
-
)
|
|
25
|
-
">
|
|
26
|
+
)">
|
|
26
27
|
<SwitchThumb data-slot="switch-thumb" :class="cn(
|
|
27
|
-
'pointer-events-none absolute left-1 top-1/2
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
'pointer-events-none absolute left-1 top-1/2 flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-[0.2rem] bg-muted-foreground ring-0',
|
|
29
|
+
'[transition:translate_380ms_var(--ease-spring),width_220ms_var(--ease-spring),background-color_200ms]',
|
|
30
|
+
'group-active/switch:w-5 data-[state=checked]:translate-x-6 data-[state=checked]:bg-primary group-active/switch:data-[state=checked]:translate-x-5'
|
|
31
|
+
)">
|
|
30
32
|
<slot name="thumb" v-bind="slotProps" />
|
|
31
33
|
</SwitchThumb>
|
|
32
34
|
</SwitchRoot>
|
|
@@ -14,7 +14,7 @@ const delegatedProps = reactiveOmit(props, 'class');
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
<TabsContent data-slot="tabs-content" :class="cn('flex-1 outline-none', props.class)" v-bind="delegatedProps">
|
|
17
|
+
<TabsContent data-slot="tabs-content" :class="cn('flex-1 outline-none data-[state=active]:animate-uv-fade-up', props.class)" v-bind="delegatedProps">
|
|
18
18
|
<slot />
|
|
19
19
|
</TabsContent>
|
|
20
20
|
</template>
|
|
@@ -2,23 +2,31 @@
|
|
|
2
2
|
import type { TabsListProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
-
import { TabsList } from 'reka-ui';
|
|
5
|
+
import { TabsIndicator, TabsList } from 'reka-ui';
|
|
6
6
|
import { cn } from '../../../lib/utils';
|
|
7
7
|
|
|
8
8
|
// Props
|
|
9
9
|
const props = defineProps<TabsListProps & {
|
|
10
10
|
class?: HTMLAttributes['class'];
|
|
11
|
+
indicatorClass?: HTMLAttributes['class'];
|
|
11
12
|
}>();
|
|
12
13
|
|
|
13
|
-
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
const delegatedProps = reactiveOmit(props, 'class', 'indicatorClass');
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
17
|
<template>
|
|
17
18
|
<TabsList data-slot="tabs-list" v-bind="delegatedProps" :class="cn(
|
|
18
|
-
'inline-flex h-auto w-fit items-center justify-center gap-1 rounded border border-border bg-card p-1 text-card-foreground
|
|
19
|
+
'group/tabs-list relative isolate inline-flex h-auto w-fit items-center justify-center gap-1 rounded border border-border bg-card p-1 text-card-foreground shadow-elevated-sm aria-[orientation=vertical]:flex-col aria-[orientation=vertical]:items-stretch',
|
|
19
20
|
props.class
|
|
20
|
-
)
|
|
21
|
-
|
|
21
|
+
)">
|
|
22
|
+
<!-- Sliding active indicator -->
|
|
23
|
+
<TabsIndicator data-slot="tabs-indicator" :class="cn(
|
|
24
|
+
'absolute -z-10 rounded border border-primary bg-surface shadow-elevated-sm transition-[translate,width,height] duration-[380ms] ease-spring',
|
|
25
|
+
'group-aria-[orientation=horizontal]/tabs-list:inset-y-1 group-aria-[orientation=horizontal]/tabs-list:left-0 group-aria-[orientation=horizontal]/tabs-list:w-(--reka-tabs-indicator-size) group-aria-[orientation=horizontal]/tabs-list:translate-x-(--reka-tabs-indicator-position)',
|
|
26
|
+
'group-aria-[orientation=vertical]/tabs-list:inset-x-1 group-aria-[orientation=vertical]/tabs-list:top-0 group-aria-[orientation=vertical]/tabs-list:h-(--reka-tabs-indicator-size) group-aria-[orientation=vertical]/tabs-list:translate-y-(--reka-tabs-indicator-position)',
|
|
27
|
+
props.indicatorClass
|
|
28
|
+
)" />
|
|
29
|
+
|
|
22
30
|
<slot />
|
|
23
31
|
</TabsList>
|
|
24
32
|
</template>
|
|
@@ -16,10 +16,12 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
16
16
|
|
|
17
17
|
<template>
|
|
18
18
|
<TabsTrigger data-slot="tabs-trigger" :class="cn(
|
|
19
|
-
'
|
|
19
|
+
'relative inline-flex min-h-9 flex-1 cursor-pointer items-center justify-center gap-1.5 rounded border border-transparent bg-transparent px-4 py-2 text-sm font-semibold whitespace-nowrap text-muted-foreground outline-none select-none',
|
|
20
|
+
'[transition:color_150ms,background-color_150ms,scale_300ms_var(--ease-spring)] hover:text-foreground active:scale-[0.97] focus-visible:ring-[3px] focus-visible:ring-ring/45 disabled:pointer-events-none disabled:opacity-50',
|
|
21
|
+
'data-[state=active]:text-primary data-[state=inactive]:hover:bg-accent/60',
|
|
22
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg]:transition-transform [&_svg]:duration-300 [&_svg]:ease-bounce data-[state=active]:[&_svg]:scale-110',
|
|
20
23
|
props.class
|
|
21
|
-
)
|
|
22
|
-
" v-bind="forwardedProps">
|
|
24
|
+
)" v-bind="forwardedProps">
|
|
23
25
|
<slot />
|
|
24
26
|
</TabsTrigger>
|
|
25
27
|
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends AcceptableInputValue = string">
|
|
2
|
+
import type { AcceptableInputValue, TagsInputRootEmits, TagsInputRootProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { computed } from 'vue';
|
|
5
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
6
|
+
import { TagsInputRoot, useForwardPropsEmits } from 'reka-ui';
|
|
7
|
+
import { useI18n } from 'vue-i18n';
|
|
8
|
+
import { cn } from '../../../lib/utils';
|
|
9
|
+
import TagsInputInput from './TagsInputInput.vue';
|
|
10
|
+
import TagsInputItem from './TagsInputItem.vue';
|
|
11
|
+
|
|
12
|
+
// Props
|
|
13
|
+
const props = defineProps<TagsInputRootProps<T> & {
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
class?: HTMLAttributes['class'];
|
|
16
|
+
inputClass?: HTMLAttributes['class'];
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
// Emits
|
|
20
|
+
const emits = defineEmits<TagsInputRootEmits<T>>();
|
|
21
|
+
|
|
22
|
+
const delegatedProps = reactiveOmit(props, 'class', 'inputClass', 'placeholder');
|
|
23
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
24
|
+
|
|
25
|
+
const { t } = useI18n();
|
|
26
|
+
const resolvedPlaceholder = computed(() => props.placeholder ?? t('uiVintage.tagsInput.placeholder'));
|
|
27
|
+
|
|
28
|
+
// Stable key per tag (falls back to the index when duplicates are allowed)
|
|
29
|
+
const getItemKey = (item: AcceptableInputValue, index: number) => {
|
|
30
|
+
if (props.duplicate) return index;
|
|
31
|
+
return typeof item === 'object' ? JSON.stringify(item) : String(item);
|
|
32
|
+
};
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<TagsInputRoot v-slot="slotProps" data-slot="tags-input" v-bind="forwarded" :class="cn(
|
|
37
|
+
'uv-field flex min-h-13 w-full flex-wrap items-center gap-2 rounded border border-input bg-secondary px-3 py-2 text-xs font-semibold text-foreground shadow-elevated-sm sm:text-sm',
|
|
38
|
+
'data-disabled:cursor-not-allowed data-disabled:opacity-60 data-invalid:animate-uv-shake',
|
|
39
|
+
props.class
|
|
40
|
+
)">
|
|
41
|
+
<slot v-bind="slotProps">
|
|
42
|
+
<!-- Auto-rendered tags -->
|
|
43
|
+
<TagsInputItem v-for="(item, index) in slotProps.modelValue" :key="getItemKey(item, index)" :value="item" />
|
|
44
|
+
|
|
45
|
+
<!-- Text input -->
|
|
46
|
+
<TagsInputInput :placeholder="resolvedPlaceholder" :class="props.inputClass" />
|
|
47
|
+
</slot>
|
|
48
|
+
</TagsInputRoot>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TagsInputInputProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { TagsInputInput, useForwardProps } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<TagsInputInputProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<TagsInputInput data-slot="tags-input-input" v-bind="forwardedProps" :class="cn(
|
|
19
|
+
'h-7 min-w-24 flex-1 bg-transparent px-1 text-xs font-semibold text-foreground outline-none sm:text-sm',
|
|
20
|
+
'placeholder:text-muted-foreground placeholder:opacity-60 disabled:cursor-not-allowed',
|
|
21
|
+
props.class
|
|
22
|
+
)" />
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TagsInputItemProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { TagsInputItem, useForwardProps } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
import TagsInputItemDelete from './TagsInputItemDelete.vue';
|
|
8
|
+
import TagsInputItemText from './TagsInputItemText.vue';
|
|
9
|
+
|
|
10
|
+
// Props
|
|
11
|
+
const props = defineProps<TagsInputItemProps & {
|
|
12
|
+
class?: HTMLAttributes['class'];
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
16
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<TagsInputItem data-slot="tags-input-item" v-bind="forwardedProps" :class="cn(
|
|
21
|
+
'group/tag inline-flex h-7 max-w-full animate-uv-pop items-center gap-1.5 rounded border border-border bg-surface py-1 pr-1 pl-2.5 text-xs font-semibold text-foreground outline-none',
|
|
22
|
+
'transition-[border-color,background-color,box-shadow] duration-150 ease-snappy',
|
|
23
|
+
'data-[state=active]:border-primary data-[state=active]:bg-primary/5 data-[state=active]:ring-[3px] data-[state=active]:ring-primary/15',
|
|
24
|
+
'data-disabled:opacity-60',
|
|
25
|
+
props.class
|
|
26
|
+
)">
|
|
27
|
+
<slot>
|
|
28
|
+
<!-- Tag text -->
|
|
29
|
+
<TagsInputItemText />
|
|
30
|
+
|
|
31
|
+
<!-- Delete button -->
|
|
32
|
+
<TagsInputItemDelete />
|
|
33
|
+
</slot>
|
|
34
|
+
</TagsInputItem>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TagsInputItemDeleteProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
|
6
|
+
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
|
+
import { TagsInputItemDelete, useForwardProps } from 'reka-ui';
|
|
8
|
+
import { cn } from '../../../lib/utils';
|
|
9
|
+
|
|
10
|
+
// Props
|
|
11
|
+
const props = defineProps<TagsInputItemDeleteProps & {
|
|
12
|
+
class?: HTMLAttributes['class'];
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
16
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<TagsInputItemDelete data-slot="tags-input-item-delete" v-bind="forwardedProps" :class="cn(
|
|
21
|
+
'inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm text-muted-foreground outline-none',
|
|
22
|
+
'[transition:rotate_320ms_var(--ease-spring),scale_320ms_var(--ease-spring),background-color_150ms_var(--ease-snappy),color_150ms_var(--ease-snappy)]',
|
|
23
|
+
'hover:rotate-90 hover:bg-accent hover:text-foreground active:scale-[0.8] focus-visible:ring-[3px] focus-visible:ring-ring/45',
|
|
24
|
+
'group-data-[state=active]/tag:text-primary data-disabled:pointer-events-none',
|
|
25
|
+
props.class
|
|
26
|
+
)">
|
|
27
|
+
<slot>
|
|
28
|
+
<HugeiconsIcon :icon="Cancel01Icon" class="size-3" />
|
|
29
|
+
</slot>
|
|
30
|
+
</TagsInputItemDelete>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TagsInputItemTextProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { TagsInputItemText, useForwardProps } from 'reka-ui';
|
|
6
|
+
import { cn } from '../../../lib/utils';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
const props = defineProps<TagsInputItemTextProps & {
|
|
10
|
+
class?: HTMLAttributes['class'];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<TagsInputItemText data-slot="tags-input-item-text" v-bind="forwardedProps" :class="cn('truncate leading-none', props.class)">
|
|
19
|
+
<slot />
|
|
20
|
+
</TagsInputItemText>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as TagsInput } from './TagsInput.vue';
|
|
2
|
+
export { default as TagsInputItem } from './TagsInputItem.vue';
|
|
3
|
+
export { default as TagsInputItemText } from './TagsInputItemText.vue';
|
|
4
|
+
export { default as TagsInputItemDelete } from './TagsInputItemDelete.vue';
|
|
5
|
+
export { default as TagsInputInput } from './TagsInputInput.vue';
|
|
@@ -23,10 +23,9 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
|
|
23
23
|
|
|
24
24
|
<template>
|
|
25
25
|
<textarea v-model="modelValue" data-slot="textarea" :class="cn(
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
26
|
+
'uv-field flex field-sizing-content min-h-30 w-full min-w-0 rounded border border-input bg-secondary px-4 py-3 text-xs font-semibold text-foreground shadow-elevated-sm outline-none sm:text-sm',
|
|
27
|
+
'selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground placeholder:opacity-60',
|
|
28
|
+
'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-60 aria-invalid:animate-uv-shake',
|
|
29
29
|
props.class
|
|
30
|
-
)
|
|
31
|
-
" />
|
|
30
|
+
)" />
|
|
32
31
|
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ToggleEmits, ToggleProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import type { ToggleVariants } from '.';
|
|
5
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
6
|
+
import { Toggle, useForwardPropsEmits } from 'reka-ui';
|
|
7
|
+
import { cn } from '../../../lib/utils';
|
|
8
|
+
import { toggleVariants } from '.';
|
|
9
|
+
|
|
10
|
+
// Props
|
|
11
|
+
const props = withDefaults(defineProps<ToggleProps & {
|
|
12
|
+
variant?: ToggleVariants['variant'];
|
|
13
|
+
size?: ToggleVariants['size'];
|
|
14
|
+
class?: HTMLAttributes['class'];
|
|
15
|
+
}>(), {
|
|
16
|
+
variant: 'default',
|
|
17
|
+
size: 'default',
|
|
18
|
+
disabled: false,
|
|
19
|
+
modelValue: undefined,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Emits
|
|
23
|
+
const emits = defineEmits<ToggleEmits>();
|
|
24
|
+
|
|
25
|
+
const delegatedProps = reactiveOmit(props, 'class', 'variant', 'size');
|
|
26
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<Toggle v-slot="slotProps" data-slot="toggle" :data-variant="props.variant" :data-size="props.size" v-bind="forwarded" :class="cn(toggleVariants({ variant: props.variant, size: props.size }), props.class)">
|
|
31
|
+
<slot v-bind="slotProps" />
|
|
32
|
+
</Toggle>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ToggleGroupRootEmits, ToggleGroupRootProps } from 'reka-ui';
|
|
3
|
+
import type { CSSProperties, HTMLAttributes } from 'vue';
|
|
4
|
+
import type { ToggleGroupSize } from '.';
|
|
5
|
+
import { reactiveOmit, unrefElement, useMutationObserver, useResizeObserver } from '@vueuse/core';
|
|
6
|
+
import { ToggleGroupRoot, useForwardPropsEmits } from 'reka-ui';
|
|
7
|
+
import { computed, nextTick, onMounted, provide, ref, shallowRef, toRef } from 'vue';
|
|
8
|
+
import { cn } from '../../../lib/utils';
|
|
9
|
+
import { toggleGroupContextKey } from '.';
|
|
10
|
+
|
|
11
|
+
// Props
|
|
12
|
+
const props = withDefaults(defineProps<ToggleGroupRootProps & {
|
|
13
|
+
size?: ToggleGroupSize;
|
|
14
|
+
highlight?: boolean;
|
|
15
|
+
class?: HTMLAttributes['class'];
|
|
16
|
+
}>(), {
|
|
17
|
+
size: 'default',
|
|
18
|
+
highlight: false,
|
|
19
|
+
disabled: false,
|
|
20
|
+
rovingFocus: true,
|
|
21
|
+
loop: true,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Emits
|
|
25
|
+
const emits = defineEmits<ToggleGroupRootEmits>();
|
|
26
|
+
|
|
27
|
+
const delegatedProps = reactiveOmit(props, 'class', 'size', 'highlight');
|
|
28
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
29
|
+
|
|
30
|
+
// Single selection gets the sliding indicator (mirrors reka's type inference)
|
|
31
|
+
const isSingle = computed(() => {
|
|
32
|
+
if (props.type) return props.type === 'single';
|
|
33
|
+
return !Array.isArray(props.modelValue ?? props.defaultValue);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Share size and indicator mode with the items
|
|
37
|
+
provide(toggleGroupContextKey, { size: toRef(props, 'size'), indicator: isSingle });
|
|
38
|
+
|
|
39
|
+
// Sliding indicator geometry
|
|
40
|
+
const rootRef = ref();
|
|
41
|
+
const activeItem = shallowRef<HTMLElement | null>(null);
|
|
42
|
+
const rect = ref({ x: 0, y: 0, width: 0, height: 0 });
|
|
43
|
+
const visible = ref(false);
|
|
44
|
+
const animated = ref(false);
|
|
45
|
+
|
|
46
|
+
// Measure the active item (offset* ignores the press scale transform)
|
|
47
|
+
function measure() {
|
|
48
|
+
const root = unrefElement(rootRef) as HTMLElement | null | undefined;
|
|
49
|
+
if (!root || !isSingle.value) return;
|
|
50
|
+
const item = root.querySelector<HTMLElement>('[data-slot="toggle-group-item"][data-state="on"]');
|
|
51
|
+
activeItem.value = item;
|
|
52
|
+
if (!item) {
|
|
53
|
+
visible.value = false;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
rect.value = { x: item.offsetLeft, y: item.offsetTop, width: item.offsetWidth, height: item.offsetHeight };
|
|
57
|
+
visible.value = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Re-measure when selection changes or when the layout moves
|
|
61
|
+
useMutationObserver(rootRef, measure, { subtree: true, attributes: true, attributeFilter: ['data-state'] });
|
|
62
|
+
useResizeObserver(rootRef, measure);
|
|
63
|
+
useResizeObserver(activeItem, measure);
|
|
64
|
+
|
|
65
|
+
// First placement is instant, later moves slide
|
|
66
|
+
onMounted(async () => {
|
|
67
|
+
measure();
|
|
68
|
+
await nextTick();
|
|
69
|
+
requestAnimationFrame(() => {
|
|
70
|
+
animated.value = true;
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const indicatorStyle = computed<CSSProperties>(() => ({
|
|
75
|
+
width: `${rect.value.width}px`,
|
|
76
|
+
height: `${rect.value.height}px`,
|
|
77
|
+
transform: `translate(${rect.value.x}px, ${rect.value.y}px) scale(${visible.value ? 1 : 0.9})`,
|
|
78
|
+
opacity: visible.value ? 1 : 0,
|
|
79
|
+
}));
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<ToggleGroupRoot ref="rootRef" v-slot="slotProps" data-slot="toggle-group" :data-size="props.size" v-bind="forwarded" :class="cn('relative inline-flex w-fit items-center gap-1 rounded border border-border bg-card p-1 shadow-elevated-sm data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch', props.class)">
|
|
84
|
+
<!-- Sliding indicator behind the active item -->
|
|
85
|
+
<span v-if="isSingle" data-slot="toggle-group-indicator" aria-hidden="true" :style="indicatorStyle" :class="cn(
|
|
86
|
+
'pointer-events-none absolute top-0 left-0 z-0 rounded-sm border',
|
|
87
|
+
animated && '[transition:transform_380ms_var(--ease-spring),width_380ms_var(--ease-spring),height_380ms_var(--ease-spring),opacity_180ms_var(--ease-snappy)]',
|
|
88
|
+
props.highlight ? 'border-primary/40 bg-primary/10 shadow-[0_0_0_3px_color-mix(in_oklab,var(--primary)_10%,transparent)]' : 'border-border-strong bg-surface shadow-elevated-sm'
|
|
89
|
+
)" />
|
|
90
|
+
|
|
91
|
+
<slot v-bind="slotProps" />
|
|
92
|
+
</ToggleGroupRoot>
|
|
93
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ToggleGroupItemProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import type { ToggleGroupSize } from '.';
|
|
5
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
6
|
+
import { ToggleGroupItem, useForwardProps } from 'reka-ui';
|
|
7
|
+
import { computed, inject } from 'vue';
|
|
8
|
+
import { cn } from '../../../lib/utils';
|
|
9
|
+
import { toggleGroupContextKey, toggleGroupItemVariants } from '.';
|
|
10
|
+
|
|
11
|
+
// Props
|
|
12
|
+
const props = defineProps<ToggleGroupItemProps & {
|
|
13
|
+
size?: ToggleGroupSize;
|
|
14
|
+
class?: HTMLAttributes['class'];
|
|
15
|
+
}>();
|
|
16
|
+
|
|
17
|
+
const delegatedProps = reactiveOmit(props, 'class', 'size');
|
|
18
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
19
|
+
|
|
20
|
+
// Size and indicator mode come from the group
|
|
21
|
+
const context = inject(toggleGroupContextKey, null);
|
|
22
|
+
const size = computed(() => props.size ?? context?.size.value ?? 'default');
|
|
23
|
+
const hasIndicator = computed(() => context?.indicator.value ?? false);
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<ToggleGroupItem v-slot="slotProps" data-slot="toggle-group-item" :data-size="size" v-bind="forwardedProps" :class="cn(
|
|
28
|
+
toggleGroupItemVariants({ size }),
|
|
29
|
+
!hasIndicator && 'hover:bg-accent data-[state=on]:bg-accent',
|
|
30
|
+
props.class
|
|
31
|
+
)">
|
|
32
|
+
<slot v-bind="slotProps" />
|
|
33
|
+
</ToggleGroupItem>
|
|
34
|
+
</template>
|