@polymarbot/nuxt-layer-shadcn-ui 0.4.2 → 0.5.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/app/components/ui/DatePicker/index.vue +2 -1
- package/app/components/ui/DateRangePicker/index.stories.ts +16 -16
- package/app/components/ui/DateRangePicker/index.vue +8 -11
- package/app/components/ui/DateRangePicker/types.ts +4 -4
- package/app/components/ui/Dropdown/ItemContent.vue +23 -0
- package/app/components/ui/Dropdown/ItemIcon.vue +41 -0
- package/app/components/ui/Dropdown/MenuItems.vue +160 -0
- package/app/components/ui/Dropdown/SlotRenderer.vue +18 -0
- package/app/components/ui/Dropdown/en.json +3 -0
- package/app/components/ui/Dropdown/index.stories.ts +91 -1
- package/app/components/ui/Dropdown/index.vue +43 -159
- package/app/components/ui/Dropdown/types.ts +47 -5
- package/app/components/ui/Progress/index.stories.ts +87 -0
- package/app/components/ui/Progress/index.vue +10 -0
- package/app/components/ui/Progress/types.ts +3 -0
- package/app/components/ui/Surface/index.stories.ts +3 -0
- package/i18n/messages/ar.json +3 -0
- package/i18n/messages/de.json +3 -0
- package/i18n/messages/en.json +3 -0
- package/i18n/messages/es.json +3 -0
- package/i18n/messages/fr.json +3 -0
- package/i18n/messages/hi.json +3 -0
- package/i18n/messages/id.json +3 -0
- package/i18n/messages/it.json +3 -0
- package/i18n/messages/ja.json +3 -0
- package/i18n/messages/ko.json +3 -0
- package/i18n/messages/nl.json +3 -0
- package/i18n/messages/pl.json +3 -0
- package/i18n/messages/pt.json +3 -0
- package/i18n/messages/ru.json +3 -0
- package/i18n/messages/th.json +3 -0
- package/i18n/messages/tr.json +3 -0
- package/i18n/messages/vi.json +3 -0
- package/i18n/messages/zh-CN.json +3 -0
- package/i18n/messages/zh-TW.json +3 -0
- package/nuxt.config.ts +9 -3
- package/package.json +2 -2
|
@@ -79,6 +79,7 @@ const timeConfig = computed(() => {
|
|
|
79
79
|
:yearPicker="type === 'year'"
|
|
80
80
|
:autoApply="autoApply"
|
|
81
81
|
:inputAttrs="{ clearable: false }"
|
|
82
|
+
:teleport="true"
|
|
82
83
|
textInput
|
|
83
84
|
>
|
|
84
85
|
<template #dp-input="{ value, onInput, onEnter, onTab, onClear, onBlur, onFocus, openMenu }">
|
|
@@ -98,7 +99,7 @@ const timeConfig = computed(() => {
|
|
|
98
99
|
<template #prefix>
|
|
99
100
|
<Icon
|
|
100
101
|
name="calendar-days"
|
|
101
|
-
class="
|
|
102
|
+
class="text-muted-foreground cursor-pointer"
|
|
102
103
|
@click="openMenu"
|
|
103
104
|
/>
|
|
104
105
|
</template>
|
|
@@ -20,7 +20,7 @@ const meta = {
|
|
|
20
20
|
class: { control: 'text' },
|
|
21
21
|
},
|
|
22
22
|
args: {
|
|
23
|
-
modelValue:
|
|
23
|
+
modelValue: [ null, null ],
|
|
24
24
|
showTime: false,
|
|
25
25
|
disabled: false,
|
|
26
26
|
readonly: false,
|
|
@@ -36,7 +36,7 @@ const meta = {
|
|
|
36
36
|
render: args => ({
|
|
37
37
|
components: { DateRangePicker },
|
|
38
38
|
setup () {
|
|
39
|
-
const range = ref<DateRangePickerValue>(
|
|
39
|
+
const range = ref<DateRangePickerValue>([ null, null ])
|
|
40
40
|
return { args, range }
|
|
41
41
|
},
|
|
42
42
|
template: `
|
|
@@ -67,7 +67,7 @@ export const WithTime: Story = {
|
|
|
67
67
|
render: () => ({
|
|
68
68
|
components: { DateRangePicker },
|
|
69
69
|
setup () {
|
|
70
|
-
const withTime = ref<DateRangePickerValue>(
|
|
70
|
+
const withTime = ref<DateRangePickerValue>([ null, null ])
|
|
71
71
|
return { withTime }
|
|
72
72
|
},
|
|
73
73
|
template: `
|
|
@@ -91,7 +91,7 @@ export const MaxSpanDays: Story = {
|
|
|
91
91
|
render: () => ({
|
|
92
92
|
components: { DateRangePicker },
|
|
93
93
|
setup () {
|
|
94
|
-
const maxSpan = ref<DateRangePickerValue>(
|
|
94
|
+
const maxSpan = ref<DateRangePickerValue>([ null, null ])
|
|
95
95
|
return { maxSpan }
|
|
96
96
|
},
|
|
97
97
|
template: `
|
|
@@ -115,10 +115,10 @@ export const Preselected: Story = {
|
|
|
115
115
|
render: () => ({
|
|
116
116
|
components: { DateRangePicker },
|
|
117
117
|
setup () {
|
|
118
|
-
const preselected = ref<DateRangePickerValue>(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
const preselected = ref<DateRangePickerValue>([
|
|
119
|
+
new Date(2025, 5, 1),
|
|
120
|
+
new Date(2025, 5, 15),
|
|
121
|
+
])
|
|
122
122
|
return { preselected }
|
|
123
123
|
},
|
|
124
124
|
template: `
|
|
@@ -142,10 +142,10 @@ export const Disabled: Story = {
|
|
|
142
142
|
render: () => ({
|
|
143
143
|
components: { DateRangePicker },
|
|
144
144
|
setup () {
|
|
145
|
-
const range = ref<DateRangePickerValue>(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
const range = ref<DateRangePickerValue>([
|
|
146
|
+
new Date(2025, 5, 1),
|
|
147
|
+
new Date(2025, 5, 15),
|
|
148
|
+
])
|
|
149
149
|
return { range }
|
|
150
150
|
},
|
|
151
151
|
template: `
|
|
@@ -168,10 +168,10 @@ export const Readonly: Story = {
|
|
|
168
168
|
render: () => ({
|
|
169
169
|
components: { DateRangePicker },
|
|
170
170
|
setup () {
|
|
171
|
-
const range = ref<DateRangePickerValue>(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
const range = ref<DateRangePickerValue>([
|
|
172
|
+
new Date(2025, 5, 1),
|
|
173
|
+
new Date(2025, 5, 15),
|
|
174
|
+
])
|
|
175
175
|
return { range }
|
|
176
176
|
},
|
|
177
177
|
template: `
|
|
@@ -4,7 +4,7 @@ import type { DateRangePickerProps, DateRangePickerValue } from './types'
|
|
|
4
4
|
defineOptions({ inheritAttrs: false })
|
|
5
5
|
|
|
6
6
|
const props = withDefaults(defineProps<DateRangePickerProps>(), {
|
|
7
|
-
modelValue: () =>
|
|
7
|
+
modelValue: () => [ null, null ],
|
|
8
8
|
showTime: false,
|
|
9
9
|
disabled: false,
|
|
10
10
|
readonly: false,
|
|
@@ -24,19 +24,16 @@ const emit = defineEmits<{
|
|
|
24
24
|
|
|
25
25
|
const T = useTranslations('components.ui.DateRangePicker')
|
|
26
26
|
|
|
27
|
-
const startDate = ref<Date | string | null>(props.modelValue?.
|
|
28
|
-
const endDate = ref<Date | string | null>(props.modelValue?.
|
|
27
|
+
const startDate = ref<Date | string | null>(props.modelValue?.[0] ?? null)
|
|
28
|
+
const endDate = ref<Date | string | null>(props.modelValue?.[1] ?? null)
|
|
29
29
|
|
|
30
30
|
watch(() => props.modelValue, val => {
|
|
31
|
-
startDate.value = val?.
|
|
32
|
-
endDate.value = val?.
|
|
31
|
+
startDate.value = val?.[0] ?? null
|
|
32
|
+
endDate.value = val?.[1] ?? null
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
function emitRange () {
|
|
36
|
-
emit('update:modelValue',
|
|
37
|
-
start: startDate.value,
|
|
38
|
-
end: endDate.value,
|
|
39
|
-
})
|
|
36
|
+
emit('update:modelValue', [ startDate.value, endDate.value ])
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
function handleStartUpdate (value: Date | string | null) {
|
|
@@ -104,7 +101,7 @@ const endMaxDate = computed(() => {
|
|
|
104
101
|
</script>
|
|
105
102
|
|
|
106
103
|
<template>
|
|
107
|
-
<div :class="cn('flex items-center
|
|
104
|
+
<div :class="cn('gap-2 flex items-center', props.class)">
|
|
108
105
|
<DatePicker
|
|
109
106
|
:modelValue="startDate"
|
|
110
107
|
:showTime="showTime"
|
|
@@ -118,7 +115,7 @@ const endMaxDate = computed(() => {
|
|
|
118
115
|
v-bind="$attrs"
|
|
119
116
|
@update:modelValue="handleStartUpdate"
|
|
120
117
|
/>
|
|
121
|
-
<span class="
|
|
118
|
+
<span class="text-muted-foreground shrink-0">
|
|
122
119
|
{{ T('to') }}
|
|
123
120
|
</span>
|
|
124
121
|
<DatePicker
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { DatePickerTimeConfig } from '../DatePicker/types'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
start: Date | string | null
|
|
5
|
-
end: Date | string | null
|
|
6
|
-
|
|
3
|
+
export type DateRangePickerValue = [
|
|
4
|
+
start: Date | string | null,
|
|
5
|
+
end: Date | string | null,
|
|
6
|
+
]
|
|
7
7
|
|
|
8
8
|
export interface DateRangePickerProps {
|
|
9
9
|
modelValue?: DateRangePickerValue
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import ItemIcon from './ItemIcon.vue'
|
|
3
|
+
import type { DropdownActionItem } from './types'
|
|
4
|
+
|
|
5
|
+
defineProps<{
|
|
6
|
+
item: DropdownActionItem
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<ItemIcon
|
|
12
|
+
:icon="item.icon"
|
|
13
|
+
:iconColor="item.iconColor"
|
|
14
|
+
/>
|
|
15
|
+
<span class="flex-1">
|
|
16
|
+
{{ item.label }}
|
|
17
|
+
</span>
|
|
18
|
+
<Icon
|
|
19
|
+
v-if="item.active"
|
|
20
|
+
name="check"
|
|
21
|
+
class="size-4"
|
|
22
|
+
/>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { cva } from 'class-variance-authority'
|
|
3
|
+
import type { DropdownActionItem } from './types'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
/** Icon name (lucide kebab-case) or a Vue component. */
|
|
7
|
+
icon: DropdownActionItem['icon']
|
|
8
|
+
/** Override icon color independently of the surrounding item color. */
|
|
9
|
+
iconColor?: DropdownActionItem['iconColor']
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const iconColorVariants = cva('', {
|
|
13
|
+
variants: {
|
|
14
|
+
color: {
|
|
15
|
+
default: '',
|
|
16
|
+
primary: 'text-primary',
|
|
17
|
+
success: 'text-success',
|
|
18
|
+
info: 'text-info',
|
|
19
|
+
help: 'text-help',
|
|
20
|
+
warn: 'text-warn',
|
|
21
|
+
danger: 'text-danger',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: { color: 'default' },
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const colorClass = computed(() => iconColorVariants({ color: props.iconColor }))
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<Icon
|
|
32
|
+
v-if="typeof icon === 'string'"
|
|
33
|
+
:name="icon"
|
|
34
|
+
:class="colorClass"
|
|
35
|
+
/>
|
|
36
|
+
<component
|
|
37
|
+
:is="icon"
|
|
38
|
+
v-else-if="icon"
|
|
39
|
+
:class="cn('size-4', colorClass)"
|
|
40
|
+
/>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
DropdownMenuItem,
|
|
4
|
+
DropdownMenuLabel,
|
|
5
|
+
DropdownMenuSeparator,
|
|
6
|
+
DropdownMenuSub,
|
|
7
|
+
DropdownMenuSubContent,
|
|
8
|
+
DropdownMenuSubTrigger,
|
|
9
|
+
} from '../../shadcn/dropdown-menu'
|
|
10
|
+
import { cva } from 'class-variance-authority'
|
|
11
|
+
import ItemContent from './ItemContent.vue'
|
|
12
|
+
import SlotRenderer from './SlotRenderer.vue'
|
|
13
|
+
import type {
|
|
14
|
+
DropdownActionItem,
|
|
15
|
+
DropdownCustomActionItem,
|
|
16
|
+
DropdownItem,
|
|
17
|
+
} from './types'
|
|
18
|
+
|
|
19
|
+
const actionColorVariants = cva('', {
|
|
20
|
+
variants: {
|
|
21
|
+
color: {
|
|
22
|
+
default: '',
|
|
23
|
+
primary: `
|
|
24
|
+
text-primary
|
|
25
|
+
focus:bg-primary/10 focus:text-primary
|
|
26
|
+
data-[state=open]:bg-primary/10 data-[state=open]:text-primary
|
|
27
|
+
`,
|
|
28
|
+
success: `
|
|
29
|
+
text-success
|
|
30
|
+
focus:bg-success/10 focus:text-success
|
|
31
|
+
data-[state=open]:bg-success/10 data-[state=open]:text-success
|
|
32
|
+
`,
|
|
33
|
+
info: `
|
|
34
|
+
text-info
|
|
35
|
+
focus:bg-info/10 focus:text-info
|
|
36
|
+
data-[state=open]:bg-info/10 data-[state=open]:text-info
|
|
37
|
+
`,
|
|
38
|
+
help: `
|
|
39
|
+
text-help
|
|
40
|
+
focus:bg-help/10 focus:text-help
|
|
41
|
+
data-[state=open]:bg-help/10 data-[state=open]:text-help
|
|
42
|
+
`,
|
|
43
|
+
warn: `
|
|
44
|
+
text-warn
|
|
45
|
+
focus:bg-warn/10 focus:text-warn
|
|
46
|
+
data-[state=open]:bg-warn/10 data-[state=open]:text-warn
|
|
47
|
+
`,
|
|
48
|
+
danger: `
|
|
49
|
+
text-danger
|
|
50
|
+
focus:bg-danger/10 focus:text-danger
|
|
51
|
+
data-[state=open]:bg-danger/10 data-[state=open]:text-danger
|
|
52
|
+
`,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
defaultVariants: { color: 'default' },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
defineProps<{
|
|
59
|
+
menus: DropdownItem[]
|
|
60
|
+
}>()
|
|
61
|
+
|
|
62
|
+
const ctx = inject(dropdownContextKey)
|
|
63
|
+
|
|
64
|
+
const handleItemAction = (
|
|
65
|
+
item: DropdownActionItem | DropdownCustomActionItem,
|
|
66
|
+
event?: Event,
|
|
67
|
+
) => {
|
|
68
|
+
if (item.disabled) {
|
|
69
|
+
event?.preventDefault()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
item.command?.()
|
|
73
|
+
ctx?.hide()
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<template
|
|
79
|
+
v-for="(menu, index) in menus"
|
|
80
|
+
:key="index"
|
|
81
|
+
>
|
|
82
|
+
<!-- Built-in: separator -->
|
|
83
|
+
<DropdownMenuSeparator v-if="menu.type === 'separator'" />
|
|
84
|
+
<!-- Built-in: group label -->
|
|
85
|
+
<DropdownMenuLabel
|
|
86
|
+
v-else-if="menu.type === 'label'"
|
|
87
|
+
class="text-xs font-normal text-muted-foreground"
|
|
88
|
+
>
|
|
89
|
+
{{ menu.label }}
|
|
90
|
+
</DropdownMenuLabel>
|
|
91
|
+
<!-- Custom label: content via named slot -->
|
|
92
|
+
<DropdownMenuLabel
|
|
93
|
+
v-else-if="menu.type === 'custom-label'"
|
|
94
|
+
:class="cn('p-0 font-normal', menu.class)"
|
|
95
|
+
>
|
|
96
|
+
<SlotRenderer
|
|
97
|
+
:slotName="menu.slot"
|
|
98
|
+
:item="menu"
|
|
99
|
+
/>
|
|
100
|
+
</DropdownMenuLabel>
|
|
101
|
+
<!-- Custom action: content via named slot -->
|
|
102
|
+
<DropdownMenuItem
|
|
103
|
+
v-else-if="menu.type === 'custom-action'"
|
|
104
|
+
:disabled="menu.disabled"
|
|
105
|
+
:class="cn(actionColorVariants({ color: menu.color }), menu.class)"
|
|
106
|
+
@click="handleItemAction(menu, $event)"
|
|
107
|
+
>
|
|
108
|
+
<SlotRenderer
|
|
109
|
+
:slotName="menu.slot"
|
|
110
|
+
:item="menu"
|
|
111
|
+
/>
|
|
112
|
+
<Icon
|
|
113
|
+
v-if="menu.active"
|
|
114
|
+
name="check"
|
|
115
|
+
class="size-4 ml-auto"
|
|
116
|
+
/>
|
|
117
|
+
</DropdownMenuItem>
|
|
118
|
+
<!-- Action with sub-menu -->
|
|
119
|
+
<DropdownMenuSub v-else-if="menu.subMenus?.length">
|
|
120
|
+
<DropdownMenuSubTrigger
|
|
121
|
+
:disabled="menu.disabled"
|
|
122
|
+
:class="cn(actionColorVariants({ color: menu.color }), menu.class)"
|
|
123
|
+
>
|
|
124
|
+
<ItemContent :item="menu" />
|
|
125
|
+
</DropdownMenuSubTrigger>
|
|
126
|
+
<DropdownMenuSubContent :style="ctx?.contentStyle.value">
|
|
127
|
+
<MenuItems :menus="menu.subMenus" />
|
|
128
|
+
</DropdownMenuSubContent>
|
|
129
|
+
</DropdownMenuSub>
|
|
130
|
+
<!-- Built-in: action (default). When `href` is set, asChild merges
|
|
131
|
+
our props (incl. @click) onto the WebLink, so the click handler
|
|
132
|
+
only needs to live here. -->
|
|
133
|
+
<DropdownMenuItem
|
|
134
|
+
v-else
|
|
135
|
+
:disabled="menu.disabled"
|
|
136
|
+
:asChild="!!menu.href"
|
|
137
|
+
:class="cn(actionColorVariants({ color: menu.color }), menu.class)"
|
|
138
|
+
@click="handleItemAction(menu, $event)"
|
|
139
|
+
>
|
|
140
|
+
<WebLink
|
|
141
|
+
v-if="menu.href"
|
|
142
|
+
unstyled
|
|
143
|
+
:href="menu.href"
|
|
144
|
+
:target="menu.target"
|
|
145
|
+
class="gap-2 flex w-full items-center"
|
|
146
|
+
>
|
|
147
|
+
<ItemContent :item="menu" />
|
|
148
|
+
<Icon
|
|
149
|
+
v-if="isUrl(menu.href)"
|
|
150
|
+
name="external-link"
|
|
151
|
+
class="size-3.5 text-muted-foreground"
|
|
152
|
+
/>
|
|
153
|
+
</WebLink>
|
|
154
|
+
<ItemContent
|
|
155
|
+
v-else
|
|
156
|
+
:item="menu"
|
|
157
|
+
/>
|
|
158
|
+
</DropdownMenuItem>
|
|
159
|
+
</template>
|
|
160
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
/** Name of the slot on the root Dropdown to render. */
|
|
4
|
+
slotName: string
|
|
5
|
+
/** Forwarded to the slot scope as `{ item }`. */
|
|
6
|
+
item: object
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const ctx = inject(dropdownContextKey)
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<component
|
|
14
|
+
:is="ctx.slots[slotName]"
|
|
15
|
+
v-if="ctx?.slots[slotName]"
|
|
16
|
+
:item="item"
|
|
17
|
+
/>
|
|
18
|
+
</template>
|
|
@@ -23,7 +23,7 @@ const accountMenus: DropdownItem[] = [
|
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
const linkMenus: DropdownItem[] = [
|
|
26
|
-
{ label: 'Documentation', icon: 'book-open', href: 'https://example.com/docs', target: '_blank' },
|
|
26
|
+
{ label: 'Documentation', icon: 'book-open', href: 'https://example.com/docs', target: '_blank', active: true },
|
|
27
27
|
{ label: 'Support', icon: 'life-buoy', href: 'https://example.com/support', target: '_blank' },
|
|
28
28
|
]
|
|
29
29
|
|
|
@@ -56,6 +56,53 @@ const customMenus: DropdownItem[] = [
|
|
|
56
56
|
},
|
|
57
57
|
]
|
|
58
58
|
|
|
59
|
+
const iconColorMenus: DropdownItem[] = [
|
|
60
|
+
{ label: 'Default item', icon: 'circle' },
|
|
61
|
+
{ label: 'Primary icon only', icon: 'star', iconColor: 'primary' },
|
|
62
|
+
{ label: 'Success icon only', icon: 'circle-check', iconColor: 'success' },
|
|
63
|
+
{ label: 'Warn icon only', icon: 'triangle-alert', iconColor: 'warn' },
|
|
64
|
+
{ label: 'Danger icon only', icon: 'shield-alert', iconColor: 'danger' },
|
|
65
|
+
{ type: 'separator' },
|
|
66
|
+
{ label: 'Both danger', icon: 'trash-2', color: 'danger', iconColor: 'danger' },
|
|
67
|
+
{ label: 'Danger label, info icon', icon: 'info', color: 'danger', iconColor: 'info' },
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
const subMenus: DropdownItem[] = [
|
|
71
|
+
{ label: 'New File', icon: 'file-plus' },
|
|
72
|
+
{ label: 'New Folder', icon: 'folder-plus' },
|
|
73
|
+
{ type: 'separator' },
|
|
74
|
+
{
|
|
75
|
+
label: 'Share',
|
|
76
|
+
icon: 'share-2',
|
|
77
|
+
subMenus: [
|
|
78
|
+
{ label: 'Email link', icon: 'mail' },
|
|
79
|
+
{ label: 'Copy link', icon: 'link' },
|
|
80
|
+
{ type: 'separator' },
|
|
81
|
+
{
|
|
82
|
+
label: 'Social',
|
|
83
|
+
icon: 'globe',
|
|
84
|
+
subMenus: [
|
|
85
|
+
{ label: 'Twitter', icon: 'twitter' },
|
|
86
|
+
{ label: 'Facebook', icon: 'facebook' },
|
|
87
|
+
{ label: 'LinkedIn', icon: 'linkedin' },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'Move to',
|
|
94
|
+
icon: 'folder-symlink',
|
|
95
|
+
active: true,
|
|
96
|
+
subMenus: [
|
|
97
|
+
{ label: 'Documents', icon: 'folder' },
|
|
98
|
+
{ label: 'Downloads', icon: 'folder', active: true },
|
|
99
|
+
{ label: 'Trash', icon: 'trash-2', color: 'danger', iconColor: 'danger' },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{ type: 'separator' },
|
|
103
|
+
{ label: 'Delete', icon: 'trash-2', color: 'danger' },
|
|
104
|
+
]
|
|
105
|
+
|
|
59
106
|
const meta = {
|
|
60
107
|
title: 'UI/Dropdown',
|
|
61
108
|
component: Dropdown,
|
|
@@ -65,6 +112,7 @@ const meta = {
|
|
|
65
112
|
side: { control: 'select', options: sides },
|
|
66
113
|
align: { control: 'select', options: aligns },
|
|
67
114
|
sideOffset: { control: 'number' },
|
|
115
|
+
minWidth: { control: 'text' },
|
|
68
116
|
},
|
|
69
117
|
args: {
|
|
70
118
|
menus: basicMenus,
|
|
@@ -72,6 +120,7 @@ const meta = {
|
|
|
72
120
|
side: undefined,
|
|
73
121
|
align: undefined,
|
|
74
122
|
sideOffset: undefined,
|
|
123
|
+
minWidth: undefined,
|
|
75
124
|
},
|
|
76
125
|
render: args => ({
|
|
77
126
|
components: { Dropdown, Button },
|
|
@@ -123,6 +172,47 @@ export const WithGroups: Story = {
|
|
|
123
172
|
},
|
|
124
173
|
}
|
|
125
174
|
|
|
175
|
+
export const WithIconColor: Story = {
|
|
176
|
+
parameters: noControls,
|
|
177
|
+
args: {
|
|
178
|
+
menus: iconColorMenus,
|
|
179
|
+
trigger: 'click',
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const WithSubMenus: Story = {
|
|
184
|
+
parameters: noControls,
|
|
185
|
+
args: {
|
|
186
|
+
menus: subMenus,
|
|
187
|
+
trigger: 'click',
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export const WithSubMenusHover: Story = {
|
|
192
|
+
parameters: noControls,
|
|
193
|
+
args: {
|
|
194
|
+
menus: subMenus,
|
|
195
|
+
trigger: 'hover',
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const WithMinWidth: Story = {
|
|
200
|
+
parameters: noControls,
|
|
201
|
+
args: {
|
|
202
|
+
menus: subMenus,
|
|
203
|
+
trigger: 'click',
|
|
204
|
+
minWidth: 240,
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const EmptyMenus: Story = {
|
|
209
|
+
parameters: noControls,
|
|
210
|
+
args: {
|
|
211
|
+
menus: [],
|
|
212
|
+
trigger: 'click',
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
|
|
126
216
|
export const CustomSlots: Story = {
|
|
127
217
|
parameters: {
|
|
128
218
|
...noControls,
|
|
@@ -2,50 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DropdownMenu,
|
|
4
4
|
DropdownMenuContent,
|
|
5
|
-
DropdownMenuItem,
|
|
6
|
-
DropdownMenuLabel,
|
|
7
|
-
DropdownMenuSeparator,
|
|
8
5
|
DropdownMenuTrigger,
|
|
9
6
|
} from '../../shadcn/dropdown-menu'
|
|
10
|
-
import
|
|
11
|
-
import type {
|
|
12
|
-
DropdownActionItem,
|
|
13
|
-
DropdownCustomActionItem,
|
|
14
|
-
DropdownProps,
|
|
15
|
-
} from './types'
|
|
16
|
-
|
|
17
|
-
const actionColorVariants = cva('', {
|
|
18
|
-
variants: {
|
|
19
|
-
color: {
|
|
20
|
-
default: '',
|
|
21
|
-
primary: `
|
|
22
|
-
text-primary
|
|
23
|
-
focus:bg-primary/10 focus:text-primary
|
|
24
|
-
`,
|
|
25
|
-
success: `
|
|
26
|
-
text-success
|
|
27
|
-
focus:bg-success/10 focus:text-success
|
|
28
|
-
`,
|
|
29
|
-
info: `
|
|
30
|
-
text-info
|
|
31
|
-
focus:bg-info/10 focus:text-info
|
|
32
|
-
`,
|
|
33
|
-
help: `
|
|
34
|
-
text-help
|
|
35
|
-
focus:bg-help/10 focus:text-help
|
|
36
|
-
`,
|
|
37
|
-
warn: `
|
|
38
|
-
text-warn
|
|
39
|
-
focus:bg-warn/10 focus:text-warn
|
|
40
|
-
`,
|
|
41
|
-
danger: `
|
|
42
|
-
text-danger
|
|
43
|
-
focus:bg-danger/10 focus:text-danger
|
|
44
|
-
`,
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
defaultVariants: { color: 'default' },
|
|
48
|
-
})
|
|
7
|
+
import MenuItems from './MenuItems.vue'
|
|
8
|
+
import type { DropdownProps } from './types'
|
|
49
9
|
|
|
50
10
|
defineOptions({ inheritAttrs: false })
|
|
51
11
|
|
|
@@ -53,14 +13,23 @@ const props = withDefaults(defineProps<DropdownProps>(), {
|
|
|
53
13
|
menus: () => [],
|
|
54
14
|
trigger: 'hover',
|
|
55
15
|
class: undefined,
|
|
16
|
+
minWidth: undefined,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const contentStyle = computed<{ minWidth?: string } | undefined>(() => {
|
|
20
|
+
if (props.minWidth == null) return undefined
|
|
21
|
+
const value = typeof props.minWidth === 'number' ? `${props.minWidth}px` : props.minWidth
|
|
22
|
+
return { minWidth: value }
|
|
56
23
|
})
|
|
57
24
|
|
|
58
|
-
defineSlots<{
|
|
59
|
-
default?: () =>
|
|
60
|
-
popup?: (props: { hide: () => void }) =>
|
|
61
|
-
|
|
25
|
+
const slots = defineSlots<{
|
|
26
|
+
default?: () => any
|
|
27
|
+
popup?: (props: { hide: () => void }) => any
|
|
28
|
+
empty?: () => any
|
|
29
|
+
[key: string]: ((props?: any) => any) | undefined
|
|
62
30
|
}>()
|
|
63
31
|
|
|
32
|
+
const T = useTranslations('components.ui.Dropdown')
|
|
64
33
|
const { isMobile } = useDevice()
|
|
65
34
|
|
|
66
35
|
// Force click trigger on mobile devices for better touch experience
|
|
@@ -107,17 +76,11 @@ const handleMenuLeave = () => {
|
|
|
107
76
|
}
|
|
108
77
|
}
|
|
109
78
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
event?.preventDefault()
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
item.command?.()
|
|
119
|
-
hide()
|
|
120
|
-
}
|
|
79
|
+
provide(dropdownContextKey, {
|
|
80
|
+
hide,
|
|
81
|
+
slots,
|
|
82
|
+
contentStyle,
|
|
83
|
+
})
|
|
121
84
|
|
|
122
85
|
onBeforeUnmount(() => {
|
|
123
86
|
clearHideTimeout()
|
|
@@ -139,6 +102,7 @@ onBeforeUnmount(() => {
|
|
|
139
102
|
<DropdownMenuContent
|
|
140
103
|
v-bind="$attrs"
|
|
141
104
|
:class="props.class"
|
|
105
|
+
:style="contentStyle"
|
|
142
106
|
@mouseenter="handleMenuEnter"
|
|
143
107
|
@mouseleave="handleMenuLeave"
|
|
144
108
|
>
|
|
@@ -151,109 +115,29 @@ onBeforeUnmount(() => {
|
|
|
151
115
|
</template>
|
|
152
116
|
|
|
153
117
|
<!-- Default menu dropdown -->
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
118
|
+
<MenuItems
|
|
119
|
+
v-else-if="menus.length"
|
|
120
|
+
:menus="menus"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<!-- Empty placeholder. Default content is wrapped; #empty slot is not. -->
|
|
124
|
+
<slot
|
|
125
|
+
v-else
|
|
126
|
+
name="empty"
|
|
127
|
+
>
|
|
128
|
+
<div
|
|
129
|
+
class="
|
|
130
|
+
gap-2 px-2 py-4 text-sm text-muted-foreground flex flex-col
|
|
131
|
+
items-center
|
|
132
|
+
"
|
|
158
133
|
>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
{{ item.label }}
|
|
167
|
-
</DropdownMenuLabel>
|
|
168
|
-
<!-- Custom label: content via named slot -->
|
|
169
|
-
<DropdownMenuLabel
|
|
170
|
-
v-else-if="item.type === 'custom-label'"
|
|
171
|
-
:class="cn('p-0 font-normal', item.class)"
|
|
172
|
-
>
|
|
173
|
-
<slot
|
|
174
|
-
:name="item.slot"
|
|
175
|
-
:item="item"
|
|
176
|
-
/>
|
|
177
|
-
</DropdownMenuLabel>
|
|
178
|
-
<!-- Custom action: content via named slot -->
|
|
179
|
-
<DropdownMenuItem
|
|
180
|
-
v-else-if="item.type === 'custom-action'"
|
|
181
|
-
:disabled="item.disabled"
|
|
182
|
-
:class="cn(actionColorVariants({ color: item.color }), item.class)"
|
|
183
|
-
@click="handleItemAction(item, $event)"
|
|
184
|
-
>
|
|
185
|
-
<slot
|
|
186
|
-
:name="item.slot"
|
|
187
|
-
:item="item"
|
|
188
|
-
/>
|
|
189
|
-
<Icon
|
|
190
|
-
v-if="item.active"
|
|
191
|
-
name="check"
|
|
192
|
-
class="size-4 ml-auto"
|
|
193
|
-
/>
|
|
194
|
-
</DropdownMenuItem>
|
|
195
|
-
<!-- Built-in: action (default) -->
|
|
196
|
-
<DropdownMenuItem
|
|
197
|
-
v-else
|
|
198
|
-
:disabled="item.disabled"
|
|
199
|
-
:asChild="!!item.href"
|
|
200
|
-
:class="cn(actionColorVariants({ color: item.color }), item.class)"
|
|
201
|
-
@click="!item.href && handleItemAction(item, $event)"
|
|
202
|
-
>
|
|
203
|
-
<template v-if="item.href">
|
|
204
|
-
<WebLink
|
|
205
|
-
unstyled
|
|
206
|
-
:href="item.href"
|
|
207
|
-
:target="item.target"
|
|
208
|
-
class="gap-2 flex w-full items-center"
|
|
209
|
-
@click="handleItemAction(item, $event)"
|
|
210
|
-
>
|
|
211
|
-
<Icon
|
|
212
|
-
v-if="typeof item.icon === 'string'"
|
|
213
|
-
:name="item.icon"
|
|
214
|
-
/>
|
|
215
|
-
<component
|
|
216
|
-
:is="item.icon"
|
|
217
|
-
v-else-if="item.icon"
|
|
218
|
-
class="size-4"
|
|
219
|
-
/>
|
|
220
|
-
<span class="flex-1">
|
|
221
|
-
{{ item.label }}
|
|
222
|
-
</span>
|
|
223
|
-
<Icon
|
|
224
|
-
v-if="isUrl(item.href)"
|
|
225
|
-
name="external-link"
|
|
226
|
-
class="size-3.5 text-muted-foreground"
|
|
227
|
-
/>
|
|
228
|
-
<Icon
|
|
229
|
-
v-if="item.active"
|
|
230
|
-
name="check"
|
|
231
|
-
class="size-4"
|
|
232
|
-
/>
|
|
233
|
-
</WebLink>
|
|
234
|
-
</template>
|
|
235
|
-
<template v-else>
|
|
236
|
-
<Icon
|
|
237
|
-
v-if="typeof item.icon === 'string'"
|
|
238
|
-
:name="item.icon"
|
|
239
|
-
/>
|
|
240
|
-
<component
|
|
241
|
-
:is="item.icon"
|
|
242
|
-
v-else-if="item.icon"
|
|
243
|
-
class="size-4"
|
|
244
|
-
/>
|
|
245
|
-
<span class="flex-1">
|
|
246
|
-
{{ item.label }}
|
|
247
|
-
</span>
|
|
248
|
-
<Icon
|
|
249
|
-
v-if="item.active"
|
|
250
|
-
name="check"
|
|
251
|
-
class="size-4"
|
|
252
|
-
/>
|
|
253
|
-
</template>
|
|
254
|
-
</DropdownMenuItem>
|
|
255
|
-
</template>
|
|
256
|
-
</template>
|
|
134
|
+
<Icon
|
|
135
|
+
name="inbox"
|
|
136
|
+
class="size-6"
|
|
137
|
+
/>
|
|
138
|
+
<span>{{ T('empty') }}</span>
|
|
139
|
+
</div>
|
|
140
|
+
</slot>
|
|
257
141
|
</DropdownMenuContent>
|
|
258
142
|
</DropdownMenu>
|
|
259
143
|
</template>
|
|
@@ -1,29 +1,45 @@
|
|
|
1
1
|
import type { DropdownMenuContentProps } from 'reka-ui'
|
|
2
|
-
import type { Component } from 'vue'
|
|
2
|
+
import type { Component, ComputedRef, InjectionKey, Slots } from 'vue'
|
|
3
3
|
|
|
4
|
+
/** Semantic color, matches project-wide color scheme. */
|
|
4
5
|
export type DropdownItemColor = 'default' | 'primary' | 'success' | 'info' | 'help' | 'warn' | 'danger'
|
|
5
6
|
|
|
6
7
|
export interface DropdownActionItem {
|
|
7
|
-
/** Defaults to 'action' when omitted. */
|
|
8
|
+
/** Item kind. Defaults to 'action' when omitted. */
|
|
8
9
|
type?: 'action'
|
|
9
|
-
/**
|
|
10
|
+
/** Foreground color of the whole item (label + focus background). */
|
|
10
11
|
color?: DropdownItemColor
|
|
12
|
+
/** Override icon color independently of `color`. */
|
|
13
|
+
iconColor?: DropdownItemColor
|
|
14
|
+
/** Display text shown in the item. */
|
|
11
15
|
label?: string
|
|
16
|
+
/** Icon name (lucide kebab-case) or a Vue component. */
|
|
12
17
|
icon?: string | Component
|
|
18
|
+
/** Click handler. Ignored when `subMenus` is set. */
|
|
13
19
|
command?: () => void
|
|
20
|
+
/** Disabled items are non-interactive and visually muted. */
|
|
14
21
|
disabled?: boolean
|
|
22
|
+
/** Renders a trailing check icon to indicate selected/active state. */
|
|
15
23
|
active?: boolean
|
|
24
|
+
/** Extra class merged onto the item element. */
|
|
16
25
|
class?: ClassValue
|
|
26
|
+
/** Render the item as a link. Ignored when `subMenus` is set. */
|
|
17
27
|
href?: string
|
|
28
|
+
/** Anchor target. Only meaningful with `href`. */
|
|
18
29
|
target?: string
|
|
30
|
+
/** Nested sub-menu items. When provided, `command` / `href` are ignored. */
|
|
31
|
+
subMenus?: DropdownItem[]
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
export interface DropdownSeparatorItem {
|
|
35
|
+
/** Item kind. */
|
|
22
36
|
type: 'separator'
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
export interface DropdownLabelItem {
|
|
40
|
+
/** Item kind. */
|
|
26
41
|
type: 'label'
|
|
42
|
+
/** Group header text. */
|
|
27
43
|
label: string
|
|
28
44
|
}
|
|
29
45
|
|
|
@@ -33,14 +49,21 @@ export interface DropdownLabelItem {
|
|
|
33
49
|
* to the slot as `item` for rendering.
|
|
34
50
|
*/
|
|
35
51
|
export interface DropdownCustomActionItem {
|
|
52
|
+
/** Item kind. */
|
|
36
53
|
type: 'custom-action'
|
|
37
|
-
/**
|
|
54
|
+
/** Foreground color of the whole item (label + focus background). */
|
|
38
55
|
color?: DropdownItemColor
|
|
56
|
+
/** Name of the slot that renders this item's content. */
|
|
39
57
|
slot: string
|
|
58
|
+
/** Click handler. */
|
|
40
59
|
command?: () => void
|
|
60
|
+
/** Disabled items are non-interactive and visually muted. */
|
|
41
61
|
disabled?: boolean
|
|
62
|
+
/** Renders a trailing check icon to indicate selected/active state. */
|
|
42
63
|
active?: boolean
|
|
64
|
+
/** Extra class merged onto the item element. */
|
|
43
65
|
class?: ClassValue
|
|
66
|
+
/** Arbitrary extra data forwarded to the slot as `item`. */
|
|
44
67
|
[field: string]: unknown
|
|
45
68
|
}
|
|
46
69
|
|
|
@@ -50,9 +73,13 @@ export interface DropdownCustomActionItem {
|
|
|
50
73
|
* to the slot as `item` for rendering.
|
|
51
74
|
*/
|
|
52
75
|
export interface DropdownCustomLabelItem {
|
|
76
|
+
/** Item kind. */
|
|
53
77
|
type: 'custom-label'
|
|
78
|
+
/** Name of the slot that renders this label's content. */
|
|
54
79
|
slot: string
|
|
80
|
+
/** Extra class merged onto the label element. */
|
|
55
81
|
class?: ClassValue
|
|
82
|
+
/** Arbitrary extra data forwarded to the slot as `item`. */
|
|
56
83
|
[field: string]: unknown
|
|
57
84
|
}
|
|
58
85
|
|
|
@@ -73,10 +100,25 @@ export type DropdownItem
|
|
|
73
100
|
| DropdownCustomLabelItem
|
|
74
101
|
|
|
75
102
|
export interface DropdownProps extends /* @vue-ignore */ DropdownMenuContentProps {
|
|
76
|
-
/** Menu items to display in the dropdown
|
|
103
|
+
/** Menu items to display in the dropdown. Not required when using the `popup` slot. */
|
|
77
104
|
menus?: DropdownItem[]
|
|
78
105
|
/** Trigger mode for showing the dropdown. Defaults to 'hover'. */
|
|
79
106
|
trigger?: 'click' | 'hover'
|
|
80
107
|
/** Extra class for the dropdown content container. */
|
|
81
108
|
class?: ClassValue
|
|
109
|
+
/** Min-width applied to the root content and all sub-menus. Numbers are treated as px. */
|
|
110
|
+
minWidth?: string | number
|
|
82
111
|
}
|
|
112
|
+
|
|
113
|
+
/** Context shared from the root Dropdown to nested MenuItems via provide/inject. */
|
|
114
|
+
export interface DropdownContext {
|
|
115
|
+
/** Closes the entire dropdown (root + any open sub-menus). */
|
|
116
|
+
hide: () => void
|
|
117
|
+
/** The root Dropdown's slots, used to render `custom-label` / `custom-action` items. */
|
|
118
|
+
slots: Slots
|
|
119
|
+
/** Inline style applied to root content and all sub-menus (currently min-width). */
|
|
120
|
+
contentStyle: ComputedRef<{ minWidth?: string } | undefined>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Provide/inject key for the shared DropdownContext. */
|
|
124
|
+
export const dropdownContextKey: InjectionKey<DropdownContext> = Symbol('dropdown-context')
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import Progress from './index.vue'
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'UI/Progress',
|
|
6
|
+
component: Progress,
|
|
7
|
+
argTypes: {
|
|
8
|
+
modelValue: { control: { type: 'number', min: 0, max: 100 }},
|
|
9
|
+
max: { control: 'number' },
|
|
10
|
+
},
|
|
11
|
+
args: {
|
|
12
|
+
modelValue: 50,
|
|
13
|
+
max: 100,
|
|
14
|
+
},
|
|
15
|
+
render: args => ({
|
|
16
|
+
components: { Progress },
|
|
17
|
+
setup: () => ({ args }),
|
|
18
|
+
template: `
|
|
19
|
+
<div class="max-w-sm">
|
|
20
|
+
<Progress v-bind="args" />
|
|
21
|
+
</div>
|
|
22
|
+
`,
|
|
23
|
+
}),
|
|
24
|
+
} satisfies Meta<typeof Progress>
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
type Story = StoryObj<typeof meta>
|
|
28
|
+
|
|
29
|
+
const noControls = { controls: { disable: true }} satisfies Story['parameters']
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {}
|
|
32
|
+
|
|
33
|
+
export const CustomMax: Story = {
|
|
34
|
+
parameters: {
|
|
35
|
+
...noControls,
|
|
36
|
+
docs: {
|
|
37
|
+
source: {
|
|
38
|
+
code: `
|
|
39
|
+
<template>
|
|
40
|
+
<Progress :modelValue="150" :max="200" />
|
|
41
|
+
</template>
|
|
42
|
+
`.trim(),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
render: () => ({
|
|
47
|
+
components: { Progress },
|
|
48
|
+
template: `
|
|
49
|
+
<div class="max-w-sm space-y-2">
|
|
50
|
+
<Progress :modelValue="150" :max="200" />
|
|
51
|
+
<div class="text-sm text-muted-foreground">150 / 200</div>
|
|
52
|
+
</div>
|
|
53
|
+
`,
|
|
54
|
+
}),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Animated: Story = {
|
|
58
|
+
parameters: {
|
|
59
|
+
...noControls,
|
|
60
|
+
docs: {
|
|
61
|
+
source: {
|
|
62
|
+
code: `
|
|
63
|
+
<template>
|
|
64
|
+
<Progress :modelValue="value" />
|
|
65
|
+
</template>
|
|
66
|
+
`.trim(),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
render: () => ({
|
|
71
|
+
components: { Progress },
|
|
72
|
+
setup () {
|
|
73
|
+
const value = ref(0)
|
|
74
|
+
const id = setInterval(() => {
|
|
75
|
+
value.value = (value.value + 5) % 105
|
|
76
|
+
}, 300)
|
|
77
|
+
onUnmounted(() => clearInterval(id))
|
|
78
|
+
return { value }
|
|
79
|
+
},
|
|
80
|
+
template: `
|
|
81
|
+
<div class="max-w-sm space-y-2">
|
|
82
|
+
<Progress :modelValue="value" />
|
|
83
|
+
<div class="text-sm text-muted-foreground">{{ value }}%</div>
|
|
84
|
+
</div>
|
|
85
|
+
`,
|
|
86
|
+
}),
|
|
87
|
+
}
|
package/i18n/messages/ar.json
CHANGED
package/i18n/messages/de.json
CHANGED
package/i18n/messages/en.json
CHANGED
package/i18n/messages/es.json
CHANGED
package/i18n/messages/fr.json
CHANGED
package/i18n/messages/hi.json
CHANGED
package/i18n/messages/id.json
CHANGED
package/i18n/messages/it.json
CHANGED
package/i18n/messages/ja.json
CHANGED
package/i18n/messages/ko.json
CHANGED
package/i18n/messages/nl.json
CHANGED
package/i18n/messages/pl.json
CHANGED
package/i18n/messages/pt.json
CHANGED
package/i18n/messages/ru.json
CHANGED
package/i18n/messages/th.json
CHANGED
package/i18n/messages/tr.json
CHANGED
package/i18n/messages/vi.json
CHANGED
package/i18n/messages/zh-CN.json
CHANGED
package/i18n/messages/zh-TW.json
CHANGED
package/nuxt.config.ts
CHANGED
|
@@ -52,9 +52,15 @@ export default defineNuxtConfig({
|
|
|
52
52
|
css: [ join(currentDir, 'app/assets/styles/globals.css') ],
|
|
53
53
|
|
|
54
54
|
components: [
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
// Auto-import only first-level UI components: `Foo.vue` or `Foo/index.vue`.
|
|
56
|
+
// Files nested inside a component folder (e.g. `Dropdown/MenuItems.vue`)
|
|
57
|
+
// are intentionally skipped — those should be imported explicitly by their
|
|
58
|
+
// owning component. shadcn/* is excluded by the pattern (no nested scan).
|
|
59
|
+
{
|
|
60
|
+
path: join(currentDir, 'app/components/ui'),
|
|
61
|
+
pathPrefix: true,
|
|
62
|
+
pattern: '{*.vue,*/index.vue}',
|
|
63
|
+
},
|
|
58
64
|
],
|
|
59
65
|
|
|
60
66
|
imports: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polymarbot/nuxt-layer-shadcn-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Nuxt layer providing shadcn-vue based UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"vue-i18n": "^11",
|
|
43
43
|
"vue-router": "^4 || ^5"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "715490d2d8fd6d2511c0c57828b17f47238bcd45"
|
|
46
46
|
}
|