@brumaombra/ui-vintage 0.8.2 → 0.9.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/AGENTS.md +41 -0
- package/COMPONENTS.md +89 -18
- package/README.md +296 -262
- package/module.mjs +5 -11
- package/package.json +30 -12
- package/src/components/blog/BlogContentRenderer.vue +1 -1
- package/src/components/blog/BlogFAQSection.vue +1 -1
- package/src/components/blog/BlogHeaderSection.vue +93 -19
- package/src/components/blog/BlogInfoSection.vue +42 -37
- package/src/components/blog/BlogPostHeader.vue +181 -0
- package/src/components/blog/BlogProgressBar.vue +3 -5
- package/src/components/blog/BlogSectionTitle.vue +8 -6
- package/src/components/blog/BlogTagsSection.vue +16 -7
- package/src/components/blog/CategoryCard.vue +19 -23
- package/src/components/blog/HeaderCarousel.vue +17 -7
- package/src/components/blog/PostCard.vue +14 -21
- package/src/components/blog/SocialShareSidebar.vue +32 -15
- package/src/components/blog/TableOfContents.vue +64 -29
- package/src/components/blog/index.ts +1 -0
- package/src/components/card-grid/CardGrid.vue +3 -3
- package/src/components/chip/Chip.vue +1 -1
- package/src/components/confirm-dialog/ConfirmDialog.vue +6 -1
- package/src/components/confirm-dialog/confirm-dialog-state.ts +22 -22
- package/src/components/content/BlogList.vue +29 -52
- package/src/components/content/BlogTable.vue +26 -30
- package/src/components/content/CodeBlock.vue +99 -23
- package/src/components/content/Flow.vue +29 -35
- package/src/components/content/ProseHr.vue +6 -6
- package/src/components/content/ProsePre.vue +9 -4
- package/src/components/content/Terminal.vue +45 -21
- package/src/components/cookie-consent/CookieConsent.vue +1 -1
- package/src/components/dashboard-shell/DashboardShell.vue +24 -7
- package/src/components/empty-state-card/EmptyStateCard.vue +3 -3
- package/src/components/error-page/ErrorPage.vue +4 -4
- package/src/components/info-card/InfoCard.vue +4 -4
- package/src/components/landing/landing-footer/LandingFooter.vue +17 -12
- package/src/components/landing/landing-navbar/LandingNavbar.vue +22 -6
- package/src/components/landing/landing-shell/LandingShell.vue +2 -7
- package/src/components/language-flag/index.ts +1 -0
- package/src/components/language-selector/LanguageSelector.vue +19 -11
- package/src/components/load-more-button/LoadMoreButton.vue +9 -13
- package/src/components/loading-state-card/LoadingStateCard.vue +3 -3
- package/src/components/message-dialog/MessageDialog.vue +14 -12
- package/src/components/message-dialog/message-dialog-state.ts +1 -4
- package/src/components/message-toast/MessageToast.vue +28 -10
- package/src/components/message-toast/message-toast-state.ts +20 -0
- package/src/components/page-header/PageHeader.vue +1 -1
- package/src/components/progress-component/ProgressComponent.vue +7 -3
- package/src/components/single-value-card/SingleValueCard.vue +39 -40
- package/src/components/slider-form-component/SliderFormComponent.vue +1 -1
- package/src/components/stat-strip/StatStrip.vue +36 -0
- package/src/components/stat-strip/index.ts +2 -0
- package/src/components/switch-form-component/SwitchFormComponent.vue +5 -5
- package/src/components/text-link/TextLink.vue +1 -1
- package/src/components/theme-selector/ThemeSelector.vue +6 -3
- package/src/components/theme-selector/theme-transition.ts +15 -3
- package/src/components/ui/accordion/Accordion.vue +19 -19
- package/src/components/ui/alert/AlertTitle.vue +1 -1
- package/src/components/ui/alert/index.ts +1 -1
- package/src/components/ui/avatar/AvatarFallback.vue +2 -1
- package/src/components/ui/avatar/AvatarGroup.vue +13 -3
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +3 -3
- package/src/components/ui/busy/Busy.vue +5 -7
- package/src/components/ui/button/Button.vue +4 -2
- package/src/components/ui/button/index.ts +1 -4
- package/src/components/ui/calendar/Calendar.vue +12 -7
- package/src/components/ui/calendar/CalendarCell.vue +1 -1
- package/src/components/ui/calendar/CalendarCellTrigger.vue +1 -1
- package/src/components/ui/calendar/CalendarHeadCell.vue +1 -1
- package/src/components/ui/calendar/CalendarNextButton.vue +2 -2
- package/src/components/ui/calendar/CalendarPrevButton.vue +2 -2
- package/src/components/ui/card/Card.vue +5 -15
- package/src/components/ui/checkbox/Checkbox.vue +5 -1
- package/src/components/ui/combobox/ComboboxInput.vue +5 -1
- package/src/components/ui/combobox/ComboboxItem.vue +1 -1
- package/src/components/ui/combobox/ComboboxSelect.vue +2 -1
- package/src/components/ui/combobox/ComboboxTrigger.vue +1 -1
- package/src/components/ui/command/CommandGroup.vue +17 -3
- package/src/components/ui/command/CommandInput.vue +2 -2
- package/src/components/ui/command/CommandList.vue +5 -1
- package/src/components/ui/data-table/DataTable.vue +13 -4
- package/src/components/ui/date-picker/DatePicker.vue +8 -2
- package/src/components/ui/date-time-picker/DateTimePicker.vue +5 -1
- package/src/components/ui/dialog/DialogContent.vue +1 -1
- package/src/components/ui/dialog/DialogScrollContent.vue +1 -1
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +1 -1
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +2 -2
- package/src/components/ui/dropdown-menu/classes.ts +2 -0
- package/src/components/ui/field/Field.vue +4 -0
- package/src/components/ui/field/FieldLabel.vue +6 -2
- package/src/components/ui/field/field-context.ts +62 -0
- package/src/components/ui/field/index.ts +1 -0
- package/src/components/ui/file-dropzone/FileDropzone.vue +14 -5
- package/src/components/ui/input/Input.vue +5 -1
- package/src/components/ui/native-select/NativeSelect.vue +7 -3
- package/src/components/ui/number-field/NumberField.vue +7 -2
- package/src/components/ui/pagination/PaginationEllipsis.vue +1 -1
- package/src/components/ui/pagination/PaginationFirst.vue +1 -1
- package/src/components/ui/pagination/PaginationItem.vue +1 -1
- package/src/components/ui/pagination/PaginationLast.vue +1 -1
- package/src/components/ui/pagination/PaginationNext.vue +1 -1
- package/src/components/ui/pagination/PaginationPrevious.vue +1 -1
- package/src/components/ui/pin-input/PinInputInput.vue +2 -2
- package/src/components/ui/progress/Progress.vue +2 -2
- package/src/components/ui/radio-group/RadioGroupCard.vue +1 -1
- package/src/components/ui/select/SelectItem.vue +2 -2
- package/src/components/ui/select/SelectItemContent.vue +1 -1
- package/src/components/ui/select/SelectScrollDownButton.vue +1 -1
- package/src/components/ui/select/SelectScrollUpButton.vue +1 -1
- package/src/components/ui/select/SelectTrigger.vue +6 -2
- package/src/components/ui/sidebar/Sidebar.vue +1 -1
- package/src/components/ui/sidebar/SidebarGroupAction.vue +1 -1
- package/src/components/ui/sidebar/SidebarGroupLabel.vue +1 -1
- package/src/components/ui/sidebar/SidebarInset.vue +1 -1
- package/src/components/ui/sidebar/SidebarMenuAction.vue +1 -1
- package/src/components/ui/sidebar/SidebarMenuBadge.vue +1 -1
- package/src/components/ui/sidebar/SidebarMenuSkeleton.vue +2 -2
- package/src/components/ui/sidebar/SidebarMenuSubButton.vue +1 -1
- package/src/components/ui/sidebar/SidebarProvider.vue +17 -6
- package/src/components/ui/sidebar/SidebarTrigger.vue +1 -1
- package/src/components/ui/sidebar/index.ts +2 -2
- package/src/components/ui/sidebar/utils.ts +1 -1
- package/src/components/ui/slider/Slider.vue +5 -4
- package/src/components/ui/spinner/Spinner.vue +3 -2
- package/src/components/ui/stepper/SimpleStepper.vue +1 -1
- package/src/components/ui/stepper/StepperDescription.vue +5 -3
- package/src/components/ui/stepper/StepperIndicator.vue +3 -3
- package/src/components/ui/stepper/StepperTitle.vue +5 -3
- package/src/components/ui/switch/Switch.vue +6 -2
- package/src/components/ui/tabs/TabsList.vue +1 -1
- package/src/components/ui/tabs/TabsTrigger.vue +1 -1
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +1 -1
- package/src/components/ui/textarea/Textarea.vue +5 -1
- package/src/components/ui/time-picker/TimePicker.vue +2 -2
- package/src/components/ui/toggle-group/ToggleGroup.vue +1 -1
- package/src/components/ui/tooltip/TooltipContent.vue +1 -1
- package/src/i18n/de.json +16 -6
- package/src/i18n/en.json +16 -6
- package/src/i18n/es.json +16 -6
- package/src/i18n/fr.json +16 -6
- package/src/i18n/it.json +16 -6
- package/src/i18n/ja.json +16 -6
- package/src/i18n/pt.json +16 -6
- package/src/i18n/ru.json +16 -6
- package/src/i18n/zh.json +16 -6
- package/src/lib/color-tokens.ts +1 -1
- package/src/lib/i18n.ts +32 -4
- package/src/lib/utils.ts +21 -1
- package/src/runtime/ui-vintage-i18n-plugin.ts +11 -1
- package/src/styles.css +12 -16
- package/skills/ui-vintage/SKILL.md +0 -42
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { DropdownMenuSubTriggerProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
-
import
|
|
5
|
+
import ArrowRight01Icon from '@hugeicons/core-free-icons/ArrowRight01Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { DropdownMenuSubTrigger, useForwardProps } from 'reka-ui';
|
|
8
8
|
import { cn } from '../../../lib/utils';
|
|
@@ -22,7 +22,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|
|
22
22
|
<DropdownMenuSubTrigger data-slot="dropdown-menu-sub-trigger" :data-inset="props.inset ? '' : undefined" v-bind="forwardedProps" :class="cn(
|
|
23
23
|
'group/dropdown-sub-trigger',
|
|
24
24
|
dropdownMenuItemClass,
|
|
25
|
-
'data-[state=open]:bg-accent data-[state=open]:text-foreground',
|
|
25
|
+
'data-[state=open]:bg-accent data-[state=open]:text-foreground data-[state=open]:before:scale-y-100',
|
|
26
26
|
props.class
|
|
27
27
|
)
|
|
28
28
|
">
|
|
@@ -19,6 +19,8 @@ export const dropdownMenuItemClass = [
|
|
|
19
19
|
'relative flex cursor-pointer items-center gap-3 rounded px-3 py-2 text-xs font-semibold text-muted-foreground outline-hidden select-none transition-colors duration-150 sm:text-sm',
|
|
20
20
|
'data-highlighted:bg-accent data-highlighted:text-foreground data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-9',
|
|
21
21
|
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
|
22
|
+
// A primary bar springs onto the highlighted item, like in the command palette
|
|
23
|
+
'before:absolute before:inset-y-1.5 before:left-0 before:w-0.5 before:scale-y-0 before:rounded-full before:bg-primary before:transition-transform before:duration-300 before:ease-spring data-highlighted:before:scale-y-100',
|
|
22
24
|
// Leading icons nudge slightly when the item is highlighted
|
|
23
25
|
'[&>svg]:transition-transform [&>svg]:duration-300 [&>svg]:ease-spring [&[data-highlighted]>svg]:translate-x-0.5 [&[data-highlighted]>svg]:scale-110'
|
|
24
26
|
].join(' ');
|
|
@@ -3,12 +3,16 @@ import type { HTMLAttributes } from 'vue';
|
|
|
3
3
|
import type { FieldVariants } from '.';
|
|
4
4
|
import { cn } from '../../../lib/utils';
|
|
5
5
|
import { fieldVariants } from '.';
|
|
6
|
+
import { provideFieldContext } from './field-context';
|
|
6
7
|
|
|
7
8
|
// Props
|
|
8
9
|
const props = defineProps<{
|
|
9
10
|
class?: HTMLAttributes['class'];
|
|
10
11
|
orientation?: FieldVariants['orientation'];
|
|
11
12
|
}>();
|
|
13
|
+
|
|
14
|
+
// Let FieldLabel point to the control placed in this field
|
|
15
|
+
provideFieldContext();
|
|
12
16
|
</script>
|
|
13
17
|
|
|
14
18
|
<template>
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
import { cn } from '../../../lib/utils';
|
|
4
4
|
import { Label } from '../label';
|
|
5
|
+
import { useFieldLabel } from './field-context';
|
|
5
6
|
|
|
6
7
|
// Props
|
|
7
8
|
const props = defineProps<{
|
|
8
9
|
class?: HTMLAttributes['class'];
|
|
9
10
|
}>();
|
|
11
|
+
|
|
12
|
+
// Point to the control of the surrounding Field (an explicit for attribute still wins)
|
|
13
|
+
const { labelId, target: labelTarget } = useFieldLabel();
|
|
10
14
|
</script>
|
|
11
15
|
|
|
12
16
|
<template>
|
|
13
|
-
<Label data-slot="field-label" :class="cn(
|
|
17
|
+
<Label :id="labelId" data-slot="field-label" :for="labelTarget" :class="cn(
|
|
14
18
|
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
|
|
15
|
-
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded
|
|
19
|
+
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded has-[>[data-slot=field]]:border *:data-[slot=field]:p-4',
|
|
16
20
|
'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
|
|
17
21
|
props.class
|
|
18
22
|
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue';
|
|
2
|
+
import { computed, inject, onBeforeUnmount, provide, ref, useAttrs, useId } from 'vue';
|
|
3
|
+
|
|
4
|
+
interface FieldContext {
|
|
5
|
+
// Id of the control the field label points to (undefined until a control registers)
|
|
6
|
+
controlId: Ref<string | undefined>;
|
|
7
|
+
// Id of the field label, for controls that combine it with their own text
|
|
8
|
+
labelId: Ref<string | undefined>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const fieldContextKey: InjectionKey<FieldContext> = Symbol('uv-field');
|
|
12
|
+
|
|
13
|
+
// Share the label target with the label and the control inside a Field
|
|
14
|
+
export const provideFieldContext = () => {
|
|
15
|
+
const context: FieldContext = { controlId: ref(undefined), labelId: ref(undefined) };
|
|
16
|
+
provide(fieldContextKey, context);
|
|
17
|
+
return context;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Id the FieldLabel should point to, if the field has a control, and the id the label registers for itself
|
|
21
|
+
export const useFieldLabel = () => {
|
|
22
|
+
const field = inject(fieldContextKey, null);
|
|
23
|
+
const labelId = `uv-field-label-${useId()}`;
|
|
24
|
+
if (field && !field.labelId.value) {
|
|
25
|
+
field.labelId.value = labelId;
|
|
26
|
+
onBeforeUnmount(() => {
|
|
27
|
+
if (field.labelId.value === labelId) field.labelId.value = undefined;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
labelId: field ? labelId : undefined,
|
|
32
|
+
target: computed(() => field?.controlId.value)
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Id of the surrounding Field label, if any
|
|
37
|
+
export const useFieldLabelId = () => {
|
|
38
|
+
const field = inject(fieldContextKey, null);
|
|
39
|
+
return computed(() => field?.labelId.value);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Give the first control inside a Field an id its FieldLabel points to (an explicit id on the control wins)
|
|
43
|
+
export const useFieldControlId = (explicitId?: () => string | undefined) => {
|
|
44
|
+
const field = inject(fieldContextKey, null);
|
|
45
|
+
const attrs = useAttrs();
|
|
46
|
+
const generatedId = `uv-field-control-${useId()}`;
|
|
47
|
+
|
|
48
|
+
// Only the first control claims the label, so composite controls (like DateTimePicker) don't fight over it
|
|
49
|
+
if (!field || field.controlId.value) {
|
|
50
|
+
return computed(() => explicitId?.());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const id = (explicitId?.() ?? (attrs.id as string | undefined)) || generatedId;
|
|
54
|
+
field.controlId.value = id;
|
|
55
|
+
|
|
56
|
+
// Release the label when the control goes away
|
|
57
|
+
onBeforeUnmount(() => {
|
|
58
|
+
if (field.controlId.value === id) field.controlId.value = undefined;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return computed(() => id);
|
|
62
|
+
};
|
|
@@ -29,6 +29,7 @@ export const fieldVariants: (props?: FieldVariants & ClassProp) => string = cva(
|
|
|
29
29
|
}
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
+
export { useFieldControlId } from './field-context';
|
|
32
33
|
export { default as Field } from './Field.vue';
|
|
33
34
|
export { default as FieldContent } from './FieldContent.vue';
|
|
34
35
|
export { default as FieldDescription } from './FieldDescription.vue';
|
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
import type { HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
4
4
|
import type { FileRejection } from '.';
|
|
5
|
-
import
|
|
5
|
+
import Cancel01Icon from '@hugeicons/core-free-icons/Cancel01Icon';
|
|
6
|
+
import CloudUploadIcon from '@hugeicons/core-free-icons/CloudUploadIcon';
|
|
7
|
+
import Doc01Icon from '@hugeicons/core-free-icons/Doc01Icon';
|
|
8
|
+
import File01Icon from '@hugeicons/core-free-icons/File01Icon';
|
|
9
|
+
import FileZipIcon from '@hugeicons/core-free-icons/FileZipIcon';
|
|
10
|
+
import Image01Icon from '@hugeicons/core-free-icons/Image01Icon';
|
|
11
|
+
import MusicNote01Icon from '@hugeicons/core-free-icons/MusicNote01Icon';
|
|
12
|
+
import Pdf01Icon from '@hugeicons/core-free-icons/Pdf01Icon';
|
|
13
|
+
import Video01Icon from '@hugeicons/core-free-icons/Video01Icon';
|
|
14
|
+
import Xls01Icon from '@hugeicons/core-free-icons/Xls01Icon';
|
|
6
15
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
16
|
import { computed, onBeforeUnmount, ref } from 'vue';
|
|
8
17
|
import { useI18n } from 'vue-i18n';
|
|
@@ -175,16 +184,16 @@ onBeforeUnmount(() => clearTimeout(rejectTimer));
|
|
|
175
184
|
'group/dropzone relative isolate flex min-h-44 cursor-pointer select-none flex-col items-center justify-center gap-3 rounded border-2 border-dashed border-border bg-surface/50 px-6 py-8 text-center outline-none',
|
|
176
185
|
'[transition:border-color_150ms,background-color_220ms,box-shadow_220ms,transform_420ms_var(--ease-spring)]',
|
|
177
186
|
'hover:border-border-strong hover:bg-surface focus-visible:ring-[3px] focus-visible:ring-ring/45 active:scale-[0.99] active:duration-75',
|
|
178
|
-
'data-dragging:scale-[1.01] data-dragging:border-transparent data-dragging:bg-primary/5 data-dragging:
|
|
187
|
+
'data-dragging:scale-[1.01] data-dragging:border-transparent data-dragging:bg-primary/5 data-dragging:hover:border-transparent data-dragging:hover:bg-primary/5',
|
|
179
188
|
'data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:border-border data-disabled:hover:bg-surface/50 data-disabled:active:scale-100',
|
|
180
189
|
isRejected && 'animate-uv-shake border-destructive/70'
|
|
181
190
|
)" @click="openPicker" @keydown.enter.prevent="openPicker" @keydown.space.prevent="openPicker" @dragenter.prevent="onDragEnter" @dragover.prevent="onDragOver" @dragleave="onDragLeave" @drop.prevent="onDrop">
|
|
182
191
|
<!-- Marching dashed border while dragging -->
|
|
183
192
|
<span aria-hidden="true" class="uv-dropzone-ants pointer-events-none absolute -inset-0.5 rounded opacity-0 transition-opacity duration-150 group-data-dragging/dropzone:opacity-100" />
|
|
184
193
|
|
|
185
|
-
<!-- Upload icon (
|
|
186
|
-
<span aria-hidden="true" class="relative flex size-14 items-center justify-center rounded border border-border bg-card text-muted-foreground shadow-elevated-sm transition-[translate,scale,color,border-color,box-shadow] duration-420 ease-bounce group-hover/dropzone:-translate-y-0.5 group-hover/dropzone:text-foreground group-data-dragging/dropzone:-translate-y-2 group-data-dragging/dropzone:scale-115 group-data-dragging/dropzone:border-primary/50 group-data-dragging/dropzone:text-primary group-data-dragging/dropzone:shadow-
|
|
187
|
-
<HugeiconsIcon :icon="CloudUploadIcon" class="size-6
|
|
194
|
+
<!-- Upload icon (lifts and grows while dragging) -->
|
|
195
|
+
<span aria-hidden="true" class="relative flex size-14 items-center justify-center rounded border border-border bg-card text-muted-foreground shadow-elevated-sm transition-[translate,scale,color,border-color,box-shadow] duration-420 ease-bounce group-hover/dropzone:-translate-y-0.5 group-hover/dropzone:text-foreground group-data-dragging/dropzone:-translate-y-2 group-data-dragging/dropzone:scale-115 group-data-dragging/dropzone:border-primary/50 group-data-dragging/dropzone:text-primary group-data-dragging/dropzone:shadow-elevated-md">
|
|
196
|
+
<HugeiconsIcon :icon="CloudUploadIcon" class="size-6" />
|
|
188
197
|
</span>
|
|
189
198
|
|
|
190
199
|
<!-- Texts -->
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
import { useVModel } from '@vueuse/core';
|
|
4
4
|
import { cn } from '../../../lib/utils';
|
|
5
|
+
import { useFieldControlId } from '../field/field-context';
|
|
5
6
|
|
|
6
7
|
// Props
|
|
7
8
|
const props = defineProps<{
|
|
@@ -19,10 +20,13 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
|
|
19
20
|
passive: true,
|
|
20
21
|
defaultValue: props.defaultValue
|
|
21
22
|
});
|
|
23
|
+
|
|
24
|
+
// Let a surrounding Field label point to this control
|
|
25
|
+
const fieldControlId = useFieldControlId();
|
|
22
26
|
</script>
|
|
23
27
|
|
|
24
28
|
<template>
|
|
25
|
-
<input v-model="modelValue" data-slot="input" :class="cn(
|
|
29
|
+
<input :id="fieldControlId" v-model="modelValue" data-slot="input" :class="cn(
|
|
26
30
|
'uv-field h-13 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
31
|
'selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground placeholder:opacity-60 placeholder:transition-opacity focus-visible:placeholder:opacity-40',
|
|
28
32
|
'file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-semibold file:text-foreground',
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import type { AcceptableValue } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit, useVModel } from '@vueuse/core';
|
|
5
|
-
import
|
|
5
|
+
import ArrowDown01Icon from '@hugeicons/core-free-icons/ArrowDown01Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { cn } from '../../../lib/utils';
|
|
8
|
+
import { useFieldControlId } from '../field/field-context';
|
|
8
9
|
|
|
9
10
|
defineOptions({
|
|
10
11
|
inheritAttrs: false
|
|
@@ -24,12 +25,15 @@ const modelValue = useVModel(props, 'modelValue', emit, {
|
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
const delegatedProps = reactiveOmit(props, 'class');
|
|
28
|
+
|
|
29
|
+
// Let a surrounding Field label point to this control
|
|
30
|
+
const fieldControlId = useFieldControlId();
|
|
27
31
|
</script>
|
|
28
32
|
|
|
29
33
|
<template>
|
|
30
34
|
<div class="group/native-select relative w-fit has-[select:disabled]:opacity-50" data-slot="native-select-wrapper">
|
|
31
|
-
<select v-bind="{ ...$attrs, ...delegatedProps }" v-model="modelValue" data-slot="native-select" :class="cn(
|
|
32
|
-
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded
|
|
35
|
+
<select :id="fieldControlId" v-bind="{ ...$attrs, ...delegatedProps }" v-model="modelValue" data-slot="native-select" :class="cn(
|
|
36
|
+
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded border bg-transparent px-3 py-2 pr-9 text-sm shadow-elevated-sm transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed',
|
|
33
37
|
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
|
34
38
|
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
|
35
39
|
props.class
|
|
@@ -3,11 +3,13 @@ import type { NumberFieldRootEmits, NumberFieldRootProps } from 'reka-ui';
|
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { computed, nextTick, ref, watch } from 'vue';
|
|
5
5
|
import { reactiveOmit } from '@vueuse/core';
|
|
6
|
-
import
|
|
6
|
+
import MinusSignIcon from '@hugeicons/core-free-icons/MinusSignIcon';
|
|
7
|
+
import PlusSignIcon from '@hugeicons/core-free-icons/PlusSignIcon';
|
|
7
8
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
8
9
|
import { NumberFieldDecrement, NumberFieldIncrement, NumberFieldInput, NumberFieldRoot, useForwardPropsEmits } from 'reka-ui';
|
|
9
10
|
import { useI18n } from 'vue-i18n';
|
|
10
11
|
import { cn } from '../../../lib/utils';
|
|
12
|
+
import { useFieldControlId } from '../field/field-context';
|
|
11
13
|
|
|
12
14
|
// Props
|
|
13
15
|
const props = defineProps<NumberFieldRootProps & {
|
|
@@ -77,10 +79,13 @@ const animateChange = async (value: number) => {
|
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
};
|
|
82
|
+
|
|
83
|
+
// Let a surrounding Field label point to this control
|
|
84
|
+
const fieldControlId = useFieldControlId(() => props.id);
|
|
80
85
|
</script>
|
|
81
86
|
|
|
82
87
|
<template>
|
|
83
|
-
<NumberFieldRoot v-slot="slotProps" data-slot="number-field" v-bind="forwarded" :class="cn(
|
|
88
|
+
<NumberFieldRoot v-slot="slotProps" data-slot="number-field" v-bind="forwarded" :id="fieldControlId" :class="cn(
|
|
84
89
|
'uv-field flex h-13 w-full items-center gap-1 rounded border border-input bg-secondary px-1.5 text-foreground shadow-elevated-sm',
|
|
85
90
|
'data-disabled:cursor-not-allowed data-disabled:opacity-60 aria-invalid:animate-uv-shake',
|
|
86
91
|
props.class
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { PaginationEllipsisProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
|
-
import
|
|
4
|
+
import MoreHorizontalIcon from '@hugeicons/core-free-icons/MoreHorizontalIcon';
|
|
5
5
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
6
6
|
import { reactiveOmit } from '@vueuse/core';
|
|
7
7
|
import { PaginationEllipsis } from 'reka-ui';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { PaginationFirstProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import type { ButtonVariants } from '../button';
|
|
5
|
-
import
|
|
5
|
+
import ArrowLeftDoubleIcon from '@hugeicons/core-free-icons/ArrowLeftDoubleIcon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { reactiveOmit } from '@vueuse/core';
|
|
8
8
|
import { PaginationFirst } from 'reka-ui';
|
|
@@ -25,7 +25,7 @@ const { t } = useI18n();
|
|
|
25
25
|
<PaginationListItem data-slot="pagination-item" :aria-label="t('uiVintage.pagination.page', { page: props.value })" v-bind="delegatedProps" :class="cn(
|
|
26
26
|
buttonVariants({ variant: 'secondary', size: props.size }),
|
|
27
27
|
'tabular-nums',
|
|
28
|
-
'data-selected:border-primary data-selected:bg-primary data-selected:text-primary-foreground data-selected:
|
|
28
|
+
'data-selected:border-primary data-selected:bg-primary data-selected:text-primary-foreground data-selected:hover:border-primary data-selected:hover:bg-primary-hover',
|
|
29
29
|
'data-selected:animate-[uv-pagination-pop_380ms_var(--ease-bounce)]',
|
|
30
30
|
props.class
|
|
31
31
|
)">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { PaginationLastProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import type { ButtonVariants } from '../button';
|
|
5
|
-
import
|
|
5
|
+
import ArrowRightDoubleIcon from '@hugeicons/core-free-icons/ArrowRightDoubleIcon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { reactiveOmit } from '@vueuse/core';
|
|
8
8
|
import { PaginationLast } from 'reka-ui';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { PaginationNextProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import type { ButtonVariants } from '../button';
|
|
5
|
-
import
|
|
5
|
+
import ArrowRight01Icon from '@hugeicons/core-free-icons/ArrowRight01Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { reactiveOmit } from '@vueuse/core';
|
|
8
8
|
import { PaginationNext } from 'reka-ui';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { PaginationPrevProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import type { ButtonVariants } from '../button';
|
|
5
|
-
import
|
|
5
|
+
import ArrowLeft01Icon from '@hugeicons/core-free-icons/ArrowLeft01Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { reactiveOmit } from '@vueuse/core';
|
|
8
8
|
import { PaginationPrev } from 'reka-ui';
|
|
@@ -34,7 +34,7 @@ const isInvalid = computed(() => pinInputContext?.invalid.value || undefined);
|
|
|
34
34
|
<span data-slot="pin-input-cell" class="relative inline-flex shrink-0">
|
|
35
35
|
<!-- Native input (text is transparent, the overlay below renders the character) -->
|
|
36
36
|
<PinInputInput data-slot="pin-input-input" v-bind="{ ...$attrs, ...forwardedProps }" :aria-invalid="isInvalid" :data-filled="isFilled ? '' : undefined" :class="cn(
|
|
37
|
-
'uv-field size-12 rounded border border-input bg-secondary text-center text-lg font-
|
|
37
|
+
'uv-field size-12 rounded border border-input bg-secondary text-center text-lg font-semibold text-transparent caret-primary shadow-elevated-sm outline-none sm:size-13',
|
|
38
38
|
'placeholder:text-muted-foreground placeholder:opacity-50 selection:bg-primary/25',
|
|
39
39
|
'data-filled:border-primary/60 data-filled:bg-primary/5',
|
|
40
40
|
'disabled:cursor-not-allowed disabled:opacity-60',
|
|
@@ -42,7 +42,7 @@ const isInvalid = computed(() => pinInputContext?.invalid.value || undefined);
|
|
|
42
42
|
)" />
|
|
43
43
|
|
|
44
44
|
<!-- Animated character (re-keyed on every value so it pops) -->
|
|
45
|
-
<span v-if="isFilled" :key="cellValue" aria-hidden="true" class="pointer-events-none absolute inset-0 flex animate-uv-pop items-center justify-center text-lg font-
|
|
45
|
+
<span v-if="isFilled" :key="cellValue" aria-hidden="true" class="pointer-events-none absolute inset-0 flex animate-uv-pop items-center justify-center text-lg font-semibold text-foreground">
|
|
46
46
|
{{ displayValue }}
|
|
47
47
|
</span>
|
|
48
48
|
</span>
|
|
@@ -26,7 +26,7 @@ const percentage = computed(() => {
|
|
|
26
26
|
</script>
|
|
27
27
|
|
|
28
28
|
<template>
|
|
29
|
-
<ProgressRoot data-slot="progress" v-bind="delegatedProps" :class="cn(
|
|
29
|
+
<ProgressRoot data-slot="progress" v-bind="delegatedProps" :model-value="props.indeterminate ? null : props.modelValue" :class="cn(
|
|
30
30
|
'relative h-2 w-full overflow-hidden rounded-sm bg-muted',
|
|
31
31
|
props.class
|
|
32
32
|
)">
|
|
@@ -34,6 +34,6 @@ const percentage = computed(() => {
|
|
|
34
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
35
|
|
|
36
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
|
|
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" :style="`transform: translateX(-${100 - percentage}%);`" />
|
|
38
38
|
</ProgressRoot>
|
|
39
39
|
</template>
|
|
@@ -23,7 +23,7 @@ const props = defineProps<{
|
|
|
23
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
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
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]:
|
|
26
|
+
'data-[state=checked]:border-primary data-[state=checked]:bg-primary/5 data-[state=checked]:hover:border-primary',
|
|
27
27
|
'disabled:cursor-not-allowed disabled:opacity-60 disabled:active:scale-100',
|
|
28
28
|
props.class
|
|
29
29
|
)">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { SelectItemProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
-
import
|
|
5
|
+
import Tick02Icon from '@hugeicons/core-free-icons/Tick02Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { SelectItem, SelectItemIndicator, SelectItemText, useForwardProps } from 'reka-ui';
|
|
8
8
|
import { cn } from '../../../lib/utils';
|
|
@@ -16,7 +16,7 @@ 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 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',
|
|
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
|
">
|
|
@@ -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-
|
|
39
|
+
'font-normal text-[11px] sm:text-xs leading-4 sm:leading-5 text-muted-foreground',
|
|
40
40
|
props.descriptionClass
|
|
41
41
|
)
|
|
42
42
|
">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { SelectScrollDownButtonProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
-
import
|
|
5
|
+
import ArrowDown01Icon from '@hugeicons/core-free-icons/ArrowDown01Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { SelectScrollDownButton, useForwardProps } from 'reka-ui';
|
|
8
8
|
import { cn } from '../../../lib/utils';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { SelectScrollUpButtonProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
-
import
|
|
5
|
+
import ArrowUp01Icon from '@hugeicons/core-free-icons/ArrowUp01Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { SelectScrollUpButton, useForwardProps } from 'reka-ui';
|
|
8
8
|
import { cn } from '../../../lib/utils';
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import type { SelectTriggerProps } from 'reka-ui';
|
|
3
3
|
import type { HTMLAttributes } from 'vue';
|
|
4
4
|
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
-
import
|
|
5
|
+
import ArrowDown01Icon from '@hugeicons/core-free-icons/ArrowDown01Icon';
|
|
6
6
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
7
|
import { SelectIcon, SelectTrigger, useForwardProps } from 'reka-ui';
|
|
8
8
|
import { cn } from '../../../lib/utils';
|
|
9
|
+
import { useFieldControlId } from '../field/field-context';
|
|
9
10
|
|
|
10
11
|
// Props
|
|
11
12
|
const props = withDefaults(defineProps<SelectTriggerProps & {
|
|
@@ -15,10 +16,13 @@ const props = withDefaults(defineProps<SelectTriggerProps & {
|
|
|
15
16
|
|
|
16
17
|
const delegatedProps = reactiveOmit(props, 'class', 'size');
|
|
17
18
|
const forwardedProps = useForwardProps(delegatedProps);
|
|
19
|
+
|
|
20
|
+
// Let a surrounding Field label point to this control
|
|
21
|
+
const fieldControlId = useFieldControlId();
|
|
18
22
|
</script>
|
|
19
23
|
|
|
20
24
|
<template>
|
|
21
|
-
<SelectTrigger data-slot="select-trigger" :data-size="size" v-bind="forwardedProps" :class="cn(
|
|
25
|
+
<SelectTrigger :id="fieldControlId" data-slot="select-trigger" :data-size="size" v-bind="forwardedProps" :class="cn(
|
|
22
26
|
'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
27
|
'*: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
28
|
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground',
|
|
@@ -78,7 +78,7 @@ function handleMobileSidebarClick(event: MouseEvent) {
|
|
|
78
78
|
props.class
|
|
79
79
|
)
|
|
80
80
|
" v-bind="$attrs">
|
|
81
|
-
<div data-sidebar="sidebar" class="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded
|
|
81
|
+
<div data-sidebar="sidebar" class="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded group-data-[variant=floating]:border group-data-[variant=floating]:shadow-elevated-sm">
|
|
82
82
|
<slot />
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
@@ -10,7 +10,7 @@ const props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>(
|
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
12
|
<Primitive data-slot="sidebar-group-action" data-sidebar="group-action" :as="as" :as-child="asChild" :class="cn(
|
|
13
|
-
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded
|
|
13
|
+
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
14
14
|
'after:absolute after:-inset-2 md:after:hidden',
|
|
15
15
|
'group-data-[collapsible=icon]:hidden',
|
|
16
16
|
props.class
|
|
@@ -10,7 +10,7 @@ const props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>(
|
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
12
|
<Primitive data-slot="sidebar-group-label" data-sidebar="group-label" :as="as" :as-child="asChild" :class="cn(
|
|
13
|
-
'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded
|
|
13
|
+
'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded px-2 text-xs font-semibold outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[compact=true]/sidebar-wrapper:h-7 group-data-[compact=true]/sidebar-wrapper:px-1.5 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
14
14
|
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
|
15
15
|
props.class
|
|
16
16
|
)
|
|
@@ -11,7 +11,7 @@ const props = defineProps<{
|
|
|
11
11
|
<template>
|
|
12
12
|
<main data-slot="sidebar-inset" :class="cn(
|
|
13
13
|
'bg-background relative flex w-full flex-1 flex-col',
|
|
14
|
-
'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded
|
|
14
|
+
'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded md:peer-data-[variant=inset]:shadow-elevated-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',
|
|
15
15
|
props.class
|
|
16
16
|
)
|
|
17
17
|
">
|
|
@@ -15,7 +15,7 @@ const props = withDefaults(defineProps<PrimitiveProps & {
|
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
17
|
<Primitive data-slot="sidebar-menu-action" data-sidebar="menu-action" :class="cn(
|
|
18
|
-
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded
|
|
18
|
+
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
19
19
|
'after:absolute after:-inset-2 md:after:hidden',
|
|
20
20
|
'peer-data-[size=sm]/menu-button:top-1',
|
|
21
21
|
'peer-data-[size=default]/menu-button:top-1.5',
|
|
@@ -10,7 +10,7 @@ const props = defineProps<{
|
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
12
|
<div data-slot="sidebar-menu-badge" data-sidebar="menu-badge" :class="cn(
|
|
13
|
-
'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded
|
|
13
|
+
'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded px-1 text-xs font-semibold tabular-nums select-none',
|
|
14
14
|
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
|
|
15
15
|
'peer-data-[size=sm]/menu-button:top-1',
|
|
16
16
|
'peer-data-[size=default]/menu-button:top-1.5',
|
|
@@ -14,11 +14,11 @@ const width = '72%';
|
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
16
|
<div data-slot="sidebar-menu-skeleton" data-sidebar="menu-skeleton" :class="cn(
|
|
17
|
-
'flex h-8 items-center gap-2 rounded
|
|
17
|
+
'flex h-8 items-center gap-2 rounded px-2 group-data-[compact=true]/sidebar-wrapper:h-7 group-data-[compact=true]/sidebar-wrapper:gap-1.5 group-data-[compact=true]/sidebar-wrapper:px-1.5',
|
|
18
18
|
props.class
|
|
19
19
|
)
|
|
20
20
|
">
|
|
21
|
-
<Skeleton v-if="showIcon" class="size-4 rounded
|
|
21
|
+
<Skeleton v-if="showIcon" class="size-4 rounded group-data-[compact=true]/sidebar-wrapper:size-3.5" data-sidebar="menu-skeleton-icon" />
|
|
22
22
|
|
|
23
23
|
<Skeleton class="h-4 max-w-(--skeleton-width) flex-1" data-sidebar="menu-skeleton-text" :style="{ '--skeleton-width': width }" />
|
|
24
24
|
</div>
|
|
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<PrimitiveProps & {
|
|
|
17
17
|
|
|
18
18
|
<template>
|
|
19
19
|
<Primitive data-slot="sidebar-menu-sub-button" data-sidebar="menu-sub-button" :as="as" :as-child="asChild" :data-size="size" :data-active="isActive" :class="cn(
|
|
20
|
-
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex min-h-7 min-w-0 -translate-x-px items-center gap-2 rounded
|
|
20
|
+
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex min-h-7 min-w-0 -translate-x-px items-center gap-2 rounded px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[compact=true]/sidebar-wrapper:min-h-6 group-data-[compact=true]/sidebar-wrapper:gap-1.5 group-data-[compact=true]/sidebar-wrapper:px-1.5 group-data-[compact=true]/sidebar-wrapper:[&>svg]:size-3.5 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
21
21
|
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
|
22
22
|
size === 'sm' && 'text-xs',
|
|
23
23
|
size === 'md' &&
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes, Ref } from 'vue';
|
|
3
|
-
import {
|
|
3
|
+
import { useEventListener, useMediaQuery, useMounted, useVModel } from '@vueuse/core';
|
|
4
|
+
import { useCookie } from 'nuxt/app';
|
|
4
5
|
import { TooltipProvider } from 'reka-ui';
|
|
5
6
|
import { computed, ref } from 'vue';
|
|
6
7
|
import { cn } from '../../../lib/utils';
|
|
@@ -13,7 +14,7 @@ const props = withDefaults(defineProps<{
|
|
|
13
14
|
compact?: boolean;
|
|
14
15
|
class?: HTMLAttributes['class'];
|
|
15
16
|
}>(), {
|
|
16
|
-
defaultOpen:
|
|
17
|
+
defaultOpen: undefined,
|
|
17
18
|
open: undefined,
|
|
18
19
|
compact: false
|
|
19
20
|
});
|
|
@@ -23,19 +24,29 @@ const emits = defineEmits<{
|
|
|
23
24
|
'update:open': [open: boolean];
|
|
24
25
|
}>();
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
// Remembered open state, read per instance on both server and client so the first render matches
|
|
28
|
+
const sidebarCookie = useCookie<boolean | null>(SIDEBAR_COOKIE_NAME, {
|
|
29
|
+
default: () => null,
|
|
30
|
+
maxAge: SIDEBAR_COOKIE_MAX_AGE,
|
|
31
|
+
path: '/'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// The mobile layout only exists in the browser: render the desktop markup until hydration is done
|
|
35
|
+
const isMounted = useMounted();
|
|
36
|
+
const matchesMobile = useMediaQuery('(max-width: 768px)');
|
|
37
|
+
const isMobile = computed(() => isMounted.value && matchesMobile.value);
|
|
27
38
|
const openMobile = ref(false);
|
|
28
39
|
|
|
29
40
|
const open = useVModel(props, 'open', emits, {
|
|
30
|
-
defaultValue: props.defaultOpen ?? false,
|
|
41
|
+
defaultValue: props.defaultOpen ?? sidebarCookie.value !== false,
|
|
31
42
|
passive: (props.open === undefined) as false
|
|
32
43
|
}) as Ref<boolean>;
|
|
33
44
|
|
|
34
45
|
function setOpen(value: boolean) {
|
|
35
46
|
open.value = value; // emits('update:open', value)
|
|
36
47
|
|
|
37
|
-
//
|
|
38
|
-
|
|
48
|
+
// Keep the sidebar state for the next visit
|
|
49
|
+
sidebarCookie.value = value;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
function setOpenMobile(value: boolean) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
|
-
import
|
|
3
|
+
import PanelLeftIcon from '@hugeicons/core-free-icons/PanelLeftIcon';
|
|
4
4
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
5
5
|
import { useI18n } from 'vue-i18n';
|
|
6
6
|
import { cn } from '../../../lib/utils';
|