@polymarbot/nuxt-layer-shadcn-ui 0.4.1 → 0.5.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/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 +7 -4
- package/app/components/ui/Surface/index.vue +33 -33
- package/app/components/ui/Surface/types.ts +1 -1
- 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
|
@@ -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
|
+
}
|
|
@@ -12,13 +12,13 @@ const meta = {
|
|
|
12
12
|
color: { control: 'select', options: colors },
|
|
13
13
|
variant: { control: 'select', options: variants },
|
|
14
14
|
active: { control: 'boolean' },
|
|
15
|
-
|
|
15
|
+
clickable: { control: 'boolean' },
|
|
16
16
|
},
|
|
17
17
|
args: {
|
|
18
18
|
color: 'default',
|
|
19
19
|
variant: 'soft',
|
|
20
20
|
active: false,
|
|
21
|
-
|
|
21
|
+
clickable: false,
|
|
22
22
|
},
|
|
23
23
|
render: args => ({
|
|
24
24
|
components: { Surface },
|
|
@@ -98,6 +98,9 @@ export const VariantColorMatrix: Story = {
|
|
|
98
98
|
parameters: {
|
|
99
99
|
...noControls,
|
|
100
100
|
docs: {
|
|
101
|
+
description: {
|
|
102
|
+
story: 'Surfaces in this matrix are clickable — click any cell to toggle its active state.',
|
|
103
|
+
},
|
|
101
104
|
source: {
|
|
102
105
|
code: `
|
|
103
106
|
<template>
|
|
@@ -110,7 +113,7 @@ export const VariantColorMatrix: Story = {
|
|
|
110
113
|
:key="c"
|
|
111
114
|
:variant="v"
|
|
112
115
|
:color="c"
|
|
113
|
-
|
|
116
|
+
clickable
|
|
114
117
|
:active="selected === keyFor(v, c)"
|
|
115
118
|
class="p-4"
|
|
116
119
|
@click="selected = keyFor(v, c)"
|
|
@@ -147,7 +150,7 @@ const keyFor = (v: string, c: string) => \`\${v}:\${c}\`
|
|
|
147
150
|
:key="c"
|
|
148
151
|
:variant="v"
|
|
149
152
|
:color="c"
|
|
150
|
-
|
|
153
|
+
clickable
|
|
151
154
|
:active="selected === keyFor(v, c)"
|
|
152
155
|
class="p-4"
|
|
153
156
|
@click="selected = keyFor(v, c)"
|
|
@@ -25,7 +25,7 @@ const surfaceVariants = cva(
|
|
|
25
25
|
true: 'ring-[3px]',
|
|
26
26
|
false: '',
|
|
27
27
|
},
|
|
28
|
-
|
|
28
|
+
clickable: {
|
|
29
29
|
true: 'cursor-pointer transition-colors',
|
|
30
30
|
false: '',
|
|
31
31
|
},
|
|
@@ -109,89 +109,89 @@ const surfaceVariants = cva(
|
|
|
109
109
|
{ active: true, color: 'help', class: 'border-help ring-help/50' },
|
|
110
110
|
{ active: true, color: 'warn', class: 'border-warn ring-warn/50' },
|
|
111
111
|
{ active: true, color: 'danger', class: 'border-danger ring-danger/50' },
|
|
112
|
-
//
|
|
113
|
-
{
|
|
112
|
+
// clickable — hover bg, one step up the variant's intensity ladder
|
|
113
|
+
{ clickable: true, variant: 'solid', color: 'default', class: `
|
|
114
114
|
hover:bg-accent/80
|
|
115
115
|
` },
|
|
116
|
-
{
|
|
116
|
+
{ clickable: true, variant: 'solid', color: 'primary', class: `
|
|
117
117
|
hover:bg-primary/90
|
|
118
118
|
` },
|
|
119
|
-
{
|
|
119
|
+
{ clickable: true, variant: 'solid', color: 'success', class: `
|
|
120
120
|
hover:bg-success/90
|
|
121
121
|
` },
|
|
122
|
-
{
|
|
122
|
+
{ clickable: true, variant: 'solid', color: 'info', class: `
|
|
123
123
|
hover:bg-info/90
|
|
124
124
|
` },
|
|
125
|
-
{
|
|
125
|
+
{ clickable: true, variant: 'solid', color: 'help', class: `
|
|
126
126
|
hover:bg-help/90
|
|
127
127
|
` },
|
|
128
|
-
{
|
|
128
|
+
{ clickable: true, variant: 'solid', color: 'warn', class: `
|
|
129
129
|
hover:bg-warn/90
|
|
130
130
|
` },
|
|
131
|
-
{
|
|
131
|
+
{ clickable: true, variant: 'solid', color: 'danger', class: `
|
|
132
132
|
hover:bg-danger/90
|
|
133
133
|
` },
|
|
134
|
-
{
|
|
134
|
+
{ clickable: true, variant: 'soft', color: 'default', class: `
|
|
135
135
|
hover:bg-secondary/70
|
|
136
136
|
` },
|
|
137
|
-
{
|
|
137
|
+
{ clickable: true, variant: 'soft', color: 'primary', class: `
|
|
138
138
|
hover:bg-primary/20
|
|
139
139
|
` },
|
|
140
|
-
{
|
|
140
|
+
{ clickable: true, variant: 'soft', color: 'success', class: `
|
|
141
141
|
hover:bg-success/20
|
|
142
142
|
` },
|
|
143
|
-
{
|
|
143
|
+
{ clickable: true, variant: 'soft', color: 'info', class: `
|
|
144
144
|
hover:bg-info/20
|
|
145
145
|
` },
|
|
146
|
-
{
|
|
146
|
+
{ clickable: true, variant: 'soft', color: 'help', class: `
|
|
147
147
|
hover:bg-help/20
|
|
148
148
|
` },
|
|
149
|
-
{
|
|
149
|
+
{ clickable: true, variant: 'soft', color: 'warn', class: `
|
|
150
150
|
hover:bg-warn/20
|
|
151
151
|
` },
|
|
152
|
-
{
|
|
152
|
+
{ clickable: true, variant: 'soft', color: 'danger', class: `
|
|
153
153
|
hover:bg-danger/20
|
|
154
154
|
` },
|
|
155
|
-
{
|
|
155
|
+
{ clickable: true, variant: 'bordered', color: 'default', class: `
|
|
156
156
|
hover:bg-accent/50
|
|
157
157
|
` },
|
|
158
|
-
{
|
|
158
|
+
{ clickable: true, variant: 'bordered', color: 'primary', class: `
|
|
159
159
|
hover:bg-primary/10
|
|
160
160
|
` },
|
|
161
|
-
{
|
|
161
|
+
{ clickable: true, variant: 'bordered', color: 'success', class: `
|
|
162
162
|
hover:bg-success/10
|
|
163
163
|
` },
|
|
164
|
-
{
|
|
164
|
+
{ clickable: true, variant: 'bordered', color: 'info', class: `
|
|
165
165
|
hover:bg-info/10
|
|
166
166
|
` },
|
|
167
|
-
{
|
|
167
|
+
{ clickable: true, variant: 'bordered', color: 'help', class: `
|
|
168
168
|
hover:bg-help/10
|
|
169
169
|
` },
|
|
170
|
-
{
|
|
170
|
+
{ clickable: true, variant: 'bordered', color: 'warn', class: `
|
|
171
171
|
hover:bg-warn/10
|
|
172
172
|
` },
|
|
173
|
-
{
|
|
173
|
+
{ clickable: true, variant: 'bordered', color: 'danger', class: `
|
|
174
174
|
hover:bg-danger/10
|
|
175
175
|
` },
|
|
176
|
-
{
|
|
176
|
+
{ clickable: true, variant: 'flat', color: 'default', class: `
|
|
177
177
|
hover:bg-secondary/70
|
|
178
178
|
` },
|
|
179
|
-
{
|
|
179
|
+
{ clickable: true, variant: 'flat', color: 'primary', class: `
|
|
180
180
|
hover:bg-primary/20
|
|
181
181
|
` },
|
|
182
|
-
{
|
|
182
|
+
{ clickable: true, variant: 'flat', color: 'success', class: `
|
|
183
183
|
hover:bg-success/20
|
|
184
184
|
` },
|
|
185
|
-
{
|
|
185
|
+
{ clickable: true, variant: 'flat', color: 'info', class: `
|
|
186
186
|
hover:bg-info/20
|
|
187
187
|
` },
|
|
188
|
-
{
|
|
188
|
+
{ clickable: true, variant: 'flat', color: 'help', class: `
|
|
189
189
|
hover:bg-help/20
|
|
190
190
|
` },
|
|
191
|
-
{
|
|
191
|
+
{ clickable: true, variant: 'flat', color: 'warn', class: `
|
|
192
192
|
hover:bg-warn/20
|
|
193
193
|
` },
|
|
194
|
-
{
|
|
194
|
+
{ clickable: true, variant: 'flat', color: 'danger', class: `
|
|
195
195
|
hover:bg-danger/20
|
|
196
196
|
` },
|
|
197
197
|
],
|
|
@@ -199,7 +199,7 @@ const surfaceVariants = cva(
|
|
|
199
199
|
variant: 'soft',
|
|
200
200
|
color: 'default',
|
|
201
201
|
active: false,
|
|
202
|
-
|
|
202
|
+
clickable: false,
|
|
203
203
|
},
|
|
204
204
|
},
|
|
205
205
|
)
|
|
@@ -208,7 +208,7 @@ const props = withDefaults(defineProps<SurfaceProps>(), {
|
|
|
208
208
|
color: 'default',
|
|
209
209
|
variant: 'soft',
|
|
210
210
|
active: false,
|
|
211
|
-
|
|
211
|
+
clickable: false,
|
|
212
212
|
class: undefined,
|
|
213
213
|
})
|
|
214
214
|
|
|
@@ -218,7 +218,7 @@ const mergedClass = computed(() =>
|
|
|
218
218
|
color: props.color,
|
|
219
219
|
variant: props.variant,
|
|
220
220
|
active: props.active,
|
|
221
|
-
|
|
221
|
+
clickable: props.clickable,
|
|
222
222
|
}),
|
|
223
223
|
props.class,
|
|
224
224
|
),
|
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.0",
|
|
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": "b25d6d9d16ad5ba72906936f2c672d65035df9ea"
|
|
46
46
|
}
|