frappe-ui 0.1.172 → 0.1.174
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/package.json +1 -1
- package/src/components/Charts/NumberChart.vue +4 -4
- package/src/components/Charts/donutChartOptions.ts +4 -4
- package/src/components/Charts/eChartOptions.ts +24 -14
- package/src/components/Dropdown/Dropdown.story.vue +163 -73
- package/src/components/Dropdown/Dropdown.vue +241 -100
- package/src/components/Dropdown/types.ts +2 -1
- package/src/components/TabButtons/TabButtons.vue +2 -0
package/package.json
CHANGED
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
:class="[
|
|
20
20
|
config.negativeIsBetter
|
|
21
21
|
? config.delta >= 0
|
|
22
|
-
? 'text-red-
|
|
23
|
-
: 'text-green-
|
|
22
|
+
? 'text-ink-red-4'
|
|
23
|
+
: 'text-ink-green-3'
|
|
24
24
|
: config.delta >= 0
|
|
25
|
-
? 'text-green-
|
|
26
|
-
: 'text-red-
|
|
25
|
+
? 'text-ink-green-3'
|
|
26
|
+
: 'text-ink-red-4',
|
|
27
27
|
]"
|
|
28
28
|
>
|
|
29
29
|
<span class="">
|
|
@@ -94,7 +94,7 @@ export default function useDonutChartOptions(config: DonutChartConfig) {
|
|
|
94
94
|
},
|
|
95
95
|
textStyle: {
|
|
96
96
|
padding: [0, 0, 0, -5],
|
|
97
|
-
color: '
|
|
97
|
+
color: 'var(--ink-gray-8)',
|
|
98
98
|
},
|
|
99
99
|
icon: 'circle',
|
|
100
100
|
pageIcons: {
|
|
@@ -103,11 +103,11 @@ export default function useDonutChartOptions(config: DonutChartConfig) {
|
|
|
103
103
|
'M 12 27 h -2 c -0.386 0 -0.738 -0.223 -0.904 -0.572 s -0.115 -0.762 0.13 -1.062 L 17.708 15 L 9.226 4.633 c -0.245 -0.299 -0.295 -0.712 -0.13 -1.062 S 9.614 3 10 3 h 2 c 0.3 0 0.584 0.135 0.774 0.367 l 9 11 c 0.301 0.369 0.301 0.898 0 1.267 l -9 11 C 12.584 26.865 12.3 27 12 27 Z',
|
|
104
104
|
],
|
|
105
105
|
},
|
|
106
|
-
pageIconColor: '
|
|
107
|
-
pageInactiveColor: '
|
|
106
|
+
pageIconColor: 'var(--ink-gray-6)',
|
|
107
|
+
pageInactiveColor: 'var(--ink-gray-4)',
|
|
108
108
|
pageIconSize: 10,
|
|
109
109
|
pageTextStyle: {
|
|
110
|
-
color: '
|
|
110
|
+
color: 'var(--ink-gray-6)',
|
|
111
111
|
},
|
|
112
112
|
animationDurationUpdate: 300,
|
|
113
113
|
}
|
|
@@ -110,7 +110,7 @@ export default function useEchartsOptions(config: AxisChartConfig) {
|
|
|
110
110
|
},
|
|
111
111
|
textStyle: {
|
|
112
112
|
padding: [0, 0, 0, -5],
|
|
113
|
-
color: '
|
|
113
|
+
color: 'var(--ink-gray-8)',
|
|
114
114
|
},
|
|
115
115
|
icon: 'circle',
|
|
116
116
|
pageIcons: {
|
|
@@ -119,11 +119,11 @@ export default function useEchartsOptions(config: AxisChartConfig) {
|
|
|
119
119
|
'M 12 27 h -2 c -0.386 0 -0.738 -0.223 -0.904 -0.572 s -0.115 -0.762 0.13 -1.062 L 17.708 15 L 9.226 4.633 c -0.245 -0.299 -0.295 -0.712 -0.13 -1.062 S 9.614 3 10 3 h 2 c 0.3 0 0.584 0.135 0.774 0.367 l 9 11 c 0.301 0.369 0.301 0.898 0 1.267 l -9 11 C 12.584 26.865 12.3 27 12 27 Z',
|
|
120
120
|
],
|
|
121
121
|
},
|
|
122
|
-
pageIconColor: '
|
|
123
|
-
pageInactiveColor: '
|
|
122
|
+
pageIconColor: 'var(--ink-gray-6)',
|
|
123
|
+
pageInactiveColor: 'var(--ink-gray-4)',
|
|
124
124
|
pageIconSize: 10,
|
|
125
125
|
pageTextStyle: {
|
|
126
|
-
color: '
|
|
126
|
+
color: 'var(--ink-gray-6)',
|
|
127
127
|
},
|
|
128
128
|
animationDurationUpdate: 300,
|
|
129
129
|
},
|
|
@@ -142,13 +142,13 @@ export function getTitleOptions(title: string, subtitle?: string) {
|
|
|
142
142
|
fontSize: 14,
|
|
143
143
|
fontWeight: 500,
|
|
144
144
|
lineHeight: 24,
|
|
145
|
-
|
|
145
|
+
color: 'var(--ink-gray-8)',
|
|
146
146
|
},
|
|
147
147
|
subtextStyle: {
|
|
148
148
|
fontSize: 13,
|
|
149
149
|
fontWeight: 400,
|
|
150
150
|
lineHeight: 20,
|
|
151
|
-
|
|
151
|
+
color: 'var(--ink-gray-6)',
|
|
152
152
|
},
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -169,14 +169,17 @@ function getXAxisOptions(config: AxisChartConfig) {
|
|
|
169
169
|
align: 'right',
|
|
170
170
|
verticalAlign: 'bottom',
|
|
171
171
|
padding: [0, 0, 26, 0],
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
backgroundColor: 'var(--surface-white)',
|
|
173
|
+
borderColor: 'var(--surface-white)',
|
|
174
|
+
color: 'var(--ink-gray-8)',
|
|
175
175
|
borderWidth: 4,
|
|
176
176
|
},
|
|
177
177
|
splitLine: {
|
|
178
178
|
show: true,
|
|
179
179
|
width: 1,
|
|
180
|
+
lineStyle: {
|
|
181
|
+
color: 'var(--ink-gray-3)',
|
|
182
|
+
},
|
|
180
183
|
},
|
|
181
184
|
axisLine: {
|
|
182
185
|
show: false,
|
|
@@ -261,14 +264,17 @@ function getYAxisOptions(config: AxisChartConfig) {
|
|
|
261
264
|
align: 'left',
|
|
262
265
|
verticalAlign: 'top',
|
|
263
266
|
padding: [0, 0, 0, -2],
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
backgroundColor: 'var(--surface-white)',
|
|
268
|
+
borderColor: 'var(--surface-white)',
|
|
269
|
+
color: 'var(--ink-gray-8)',
|
|
267
270
|
borderWidth: 4,
|
|
268
271
|
},
|
|
269
272
|
splitLine: {
|
|
270
273
|
show: true,
|
|
271
274
|
width: 1,
|
|
275
|
+
lineStyle: {
|
|
276
|
+
color: 'var(--ink-gray-3)',
|
|
277
|
+
},
|
|
272
278
|
},
|
|
273
279
|
axisLine: {
|
|
274
280
|
show: false,
|
|
@@ -308,13 +314,17 @@ function getYAxisOptions(config: AxisChartConfig) {
|
|
|
308
314
|
align: 'right',
|
|
309
315
|
verticalAlign: 'top',
|
|
310
316
|
padding: [0, 5, 0, 0],
|
|
311
|
-
|
|
312
|
-
|
|
317
|
+
backgroundColor: 'var(--surface-white)',
|
|
318
|
+
borderColor: 'var(--surface-white)',
|
|
319
|
+
color: 'var(--ink-gray-8)',
|
|
313
320
|
},
|
|
314
321
|
nameGap: 6,
|
|
315
322
|
splitLine: {
|
|
316
323
|
show: true,
|
|
317
324
|
width: 1,
|
|
325
|
+
lineStyle: {
|
|
326
|
+
color: 'var(--ink-gray-3)',
|
|
327
|
+
},
|
|
318
328
|
},
|
|
319
329
|
axisLine: {
|
|
320
330
|
show: false,
|
|
@@ -1,98 +1,188 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import Dropdown from './Dropdown.vue'
|
|
4
|
-
import FeatherIcon from '../FeatherIcon.vue'
|
|
2
|
+
import { Dropdown } from './index'
|
|
5
3
|
import { Button } from '../Button'
|
|
6
|
-
</script>
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const actions = [
|
|
6
|
+
{
|
|
7
|
+
label: 'Edit',
|
|
8
|
+
icon: 'edit',
|
|
9
|
+
onClick: () => console.log('Edit clicked'),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
label: 'Delete',
|
|
13
|
+
icon: 'trash-2',
|
|
14
|
+
onClick: () => console.log('Delete clicked'),
|
|
15
|
+
},
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const groupedActions = [
|
|
19
|
+
{
|
|
20
|
+
group: 'Actions',
|
|
21
|
+
items: [
|
|
22
|
+
{
|
|
23
|
+
label: 'Edit',
|
|
24
|
+
icon: 'edit',
|
|
25
|
+
onClick: () => console.log('Edit clicked'),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Duplicate',
|
|
29
|
+
icon: 'copy',
|
|
30
|
+
onClick: () => console.log('Duplicate clicked'),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'More Actions',
|
|
34
|
+
icon: 'more-horizontal',
|
|
35
|
+
submenu: [
|
|
13
36
|
{
|
|
14
|
-
label: '
|
|
15
|
-
|
|
16
|
-
|
|
37
|
+
label: 'Archive',
|
|
38
|
+
icon: 'archive',
|
|
39
|
+
onClick: () => console.log('Archive clicked'),
|
|
17
40
|
},
|
|
18
41
|
{
|
|
19
|
-
label: '
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
label: 'Export',
|
|
43
|
+
icon: 'download',
|
|
44
|
+
submenu: [
|
|
45
|
+
{
|
|
46
|
+
label: 'Export as PDF',
|
|
47
|
+
icon: 'file-text',
|
|
48
|
+
onClick: () => console.log('Export as PDF clicked'),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'Export as CSV',
|
|
52
|
+
icon: 'file',
|
|
53
|
+
onClick: () => console.log('Export as CSV clicked'),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
22
56
|
},
|
|
23
57
|
{
|
|
24
|
-
label: '
|
|
25
|
-
|
|
26
|
-
|
|
58
|
+
label: 'Share',
|
|
59
|
+
icon: 'share',
|
|
60
|
+
onClick: () => console.log('Share clicked'),
|
|
27
61
|
},
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
group: 'Danger',
|
|
68
|
+
items: [
|
|
69
|
+
{
|
|
70
|
+
label: 'Delete',
|
|
71
|
+
icon: 'trash-2',
|
|
72
|
+
onClick: () => console.log('Delete clicked'),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
]
|
|
31
77
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
78
|
+
const submenuActions = [
|
|
79
|
+
{
|
|
80
|
+
label: 'New',
|
|
81
|
+
icon: 'plus',
|
|
82
|
+
submenu: [
|
|
83
|
+
{
|
|
84
|
+
group: 'Documents',
|
|
85
|
+
items: [
|
|
35
86
|
{
|
|
36
|
-
label: '
|
|
37
|
-
|
|
87
|
+
label: 'New Document',
|
|
88
|
+
icon: 'file-plus',
|
|
89
|
+
onClick: () => console.log('New Document clicked'),
|
|
38
90
|
},
|
|
39
91
|
{
|
|
40
|
-
label: '
|
|
41
|
-
|
|
92
|
+
label: 'New Template',
|
|
93
|
+
icon: 'file-text',
|
|
94
|
+
onClick: () => console.log('New Template clicked'),
|
|
42
95
|
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
group: 'Organization',
|
|
100
|
+
items: [
|
|
43
101
|
{
|
|
44
|
-
label: '
|
|
45
|
-
|
|
102
|
+
label: 'New Folder',
|
|
103
|
+
icon: 'folder-plus',
|
|
104
|
+
onClick: () => console.log('New Folder clicked'),
|
|
46
105
|
},
|
|
47
|
-
]"
|
|
48
|
-
:button="{
|
|
49
|
-
label: 'Actions',
|
|
50
|
-
}"
|
|
51
|
-
/>
|
|
52
|
-
</Variant>
|
|
53
|
-
|
|
54
|
-
<Variant title="Custom Button and Groups">
|
|
55
|
-
<Dropdown
|
|
56
|
-
:options="[
|
|
57
106
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
label: 'Edit Title',
|
|
62
|
-
icon: () => h(FeatherIcon, { name: 'edit' }),
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
label: 'Manage Members',
|
|
66
|
-
icon: () => h(FeatherIcon, { name: 'users' }),
|
|
67
|
-
},
|
|
68
|
-
],
|
|
107
|
+
label: 'New Project',
|
|
108
|
+
icon: 'briefcase',
|
|
109
|
+
onClick: () => console.log('New Project clicked'),
|
|
69
110
|
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: 'Edit',
|
|
117
|
+
icon: 'edit',
|
|
118
|
+
onClick: () => console.log('Edit clicked'),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
label: 'Share',
|
|
122
|
+
icon: 'share',
|
|
123
|
+
submenu: [
|
|
124
|
+
{
|
|
125
|
+
label: 'Share with Link',
|
|
126
|
+
icon: 'link',
|
|
127
|
+
onClick: () => console.log('Share with Link clicked'),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
label: 'Share with Email',
|
|
131
|
+
icon: 'mail',
|
|
132
|
+
onClick: () => console.log('Share with Email clicked'),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
group: 'Advanced',
|
|
136
|
+
items: [
|
|
70
137
|
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
label: 'Delete users',
|
|
75
|
-
icon: () => h(FeatherIcon, { name: 'edit' }),
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
label: 'Delete this project',
|
|
79
|
-
icon: () => h(FeatherIcon, { name: 'trash' }),
|
|
80
|
-
},
|
|
81
|
-
],
|
|
138
|
+
label: 'Share Settings',
|
|
139
|
+
icon: 'settings',
|
|
140
|
+
onClick: () => console.log('Share Settings clicked'),
|
|
82
141
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
142
|
+
{
|
|
143
|
+
label: 'Permission Management',
|
|
144
|
+
icon: 'shield',
|
|
145
|
+
onClick: () => console.log('Permission Management clicked'),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
]
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<template>
|
|
155
|
+
<Story title="Dropdown" :layout="{ type: 'grid', width: '200px' }">
|
|
156
|
+
<Variant title="Default">
|
|
157
|
+
<div class="asdf">
|
|
158
|
+
<Dropdown :options="actions" />
|
|
159
|
+
</div>
|
|
160
|
+
</Variant>
|
|
161
|
+
|
|
162
|
+
<Variant title="With Custom Button">
|
|
163
|
+
<Dropdown :options="actions">
|
|
164
|
+
<Button variant="solid">Custom Trigger</Button>
|
|
90
165
|
</Dropdown>
|
|
91
166
|
</Variant>
|
|
92
167
|
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
168
|
+
<Variant title="With Groups">
|
|
169
|
+
<Dropdown :options="groupedActions" />
|
|
170
|
+
</Variant>
|
|
171
|
+
|
|
172
|
+
<Variant title="Right Aligned">
|
|
173
|
+
<Dropdown :options="actions" placement="right" />
|
|
174
|
+
</Variant>
|
|
175
|
+
|
|
176
|
+
<Variant title="Center Aligned">
|
|
177
|
+
<Dropdown :options="actions" placement="center" />
|
|
178
|
+
</Variant>
|
|
179
|
+
|
|
180
|
+
<Variant title="With Submenus">
|
|
181
|
+
<Dropdown :options="submenuActions" />
|
|
182
|
+
</Variant>
|
|
183
|
+
|
|
184
|
+
<Variant title="With Nested Submenus">
|
|
185
|
+
<Dropdown :options="groupedActions" />
|
|
186
|
+
</Variant>
|
|
97
187
|
</Story>
|
|
98
188
|
</template>
|
|
@@ -1,79 +1,159 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
<DropdownMenuRoot v-slot="{ open }">
|
|
3
|
+
<DropdownMenuTrigger as-child>
|
|
4
|
+
<slot v-if="$slots.default" v-bind="{ open }" />
|
|
5
|
+
<Button v-else :active="false" v-bind="button">
|
|
6
|
+
{{ button ? button?.label || null : 'Options' }}
|
|
7
|
+
</Button>
|
|
8
|
+
</DropdownMenuTrigger>
|
|
9
|
+
|
|
10
|
+
<DropdownMenuPortal>
|
|
11
|
+
<DropdownMenuContent
|
|
12
|
+
:class="[
|
|
13
|
+
cssClasses.dropdownContent,
|
|
14
|
+
{
|
|
15
|
+
'origin-top-left': placement == 'left',
|
|
16
|
+
'origin-top-right': placement == 'right',
|
|
17
|
+
'origin-top': placement == 'center',
|
|
18
|
+
},
|
|
19
|
+
]"
|
|
20
|
+
:side="contentSide"
|
|
21
|
+
:align="contentAlign"
|
|
22
|
+
:side-offset="4"
|
|
23
|
+
>
|
|
24
|
+
<div
|
|
25
|
+
v-for="group in groups"
|
|
26
|
+
:key="group.key"
|
|
27
|
+
:class="cssClasses.groupContainer"
|
|
25
28
|
>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
<DropdownMenuLabel
|
|
30
|
+
v-if="group.group && !group.hideLabel"
|
|
31
|
+
:class="cssClasses.groupLabel"
|
|
32
|
+
>
|
|
33
|
+
{{ group.group }}
|
|
34
|
+
</DropdownMenuLabel>
|
|
35
|
+
|
|
36
|
+
<DropdownMenuItem
|
|
37
|
+
v-for="item in group.items"
|
|
38
|
+
:key="item.label"
|
|
39
|
+
as-child
|
|
40
|
+
@select="item.onClick"
|
|
41
|
+
>
|
|
42
|
+
<component
|
|
43
|
+
v-if="item.component"
|
|
44
|
+
:is="item.component"
|
|
45
|
+
:active="false"
|
|
46
|
+
/>
|
|
47
|
+
<DropdownMenuSub v-else-if="item.submenu">
|
|
48
|
+
<DropdownMenuSubTrigger as-child>
|
|
49
|
+
<button :class="cssClasses.submenuTrigger">
|
|
50
|
+
<FeatherIcon
|
|
51
|
+
v-if="item.icon && typeof item.icon === 'string'"
|
|
52
|
+
:name="item.icon"
|
|
53
|
+
:class="cssClasses.itemIcon"
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
/>
|
|
56
|
+
<component
|
|
57
|
+
:class="cssClasses.itemIcon"
|
|
58
|
+
v-else-if="item.icon"
|
|
59
|
+
:is="item.icon"
|
|
60
|
+
/>
|
|
61
|
+
<span :class="cssClasses.itemLabel">
|
|
62
|
+
{{ item.label }}
|
|
63
|
+
</span>
|
|
64
|
+
<FeatherIcon
|
|
65
|
+
name="chevron-right"
|
|
66
|
+
:class="cssClasses.chevronIcon"
|
|
67
|
+
aria-hidden="true"
|
|
68
|
+
/>
|
|
69
|
+
</button>
|
|
70
|
+
</DropdownMenuSubTrigger>
|
|
71
|
+
<DropdownMenuPortal>
|
|
72
|
+
<DropdownMenuSubContent
|
|
73
|
+
:class="cssClasses.dropdownContent"
|
|
74
|
+
:side-offset="4"
|
|
75
|
+
>
|
|
76
|
+
<div
|
|
77
|
+
v-for="submenuGroup in getSubmenuGroups(item.submenu)"
|
|
78
|
+
:key="submenuGroup.key"
|
|
79
|
+
:class="cssClasses.groupContainer"
|
|
80
|
+
>
|
|
81
|
+
<DropdownMenuLabel
|
|
82
|
+
v-if="submenuGroup.group && !submenuGroup.hideLabel"
|
|
83
|
+
:class="cssClasses.groupLabel"
|
|
84
|
+
>
|
|
85
|
+
{{ submenuGroup.group }}
|
|
86
|
+
</DropdownMenuLabel>
|
|
87
|
+
|
|
88
|
+
<DropdownMenuItem
|
|
89
|
+
v-for="subItem in submenuGroup.items"
|
|
90
|
+
:key="subItem.label"
|
|
91
|
+
as-child
|
|
92
|
+
@select="() => handleItemClick(subItem)"
|
|
93
|
+
>
|
|
94
|
+
<component
|
|
95
|
+
v-if="subItem.component"
|
|
96
|
+
:is="subItem.component"
|
|
97
|
+
:active="false"
|
|
98
|
+
/>
|
|
99
|
+
<button v-else :class="cssClasses.itemButton">
|
|
100
|
+
<FeatherIcon
|
|
101
|
+
v-if="
|
|
102
|
+
subItem.icon && typeof subItem.icon === 'string'
|
|
103
|
+
"
|
|
104
|
+
:name="subItem.icon"
|
|
105
|
+
:class="cssClasses.itemIcon"
|
|
106
|
+
aria-hidden="true"
|
|
107
|
+
/>
|
|
108
|
+
<component
|
|
109
|
+
:class="cssClasses.itemIcon"
|
|
110
|
+
v-else-if="subItem.icon"
|
|
111
|
+
:is="subItem.icon"
|
|
112
|
+
/>
|
|
113
|
+
<span :class="cssClasses.itemLabel">
|
|
114
|
+
{{ subItem.label }}
|
|
115
|
+
</span>
|
|
116
|
+
</button>
|
|
117
|
+
</DropdownMenuItem>
|
|
118
|
+
</div>
|
|
119
|
+
</DropdownMenuSubContent>
|
|
120
|
+
</DropdownMenuPortal>
|
|
121
|
+
</DropdownMenuSub>
|
|
122
|
+
<button v-else :class="cssClasses.itemButton">
|
|
123
|
+
<FeatherIcon
|
|
124
|
+
v-if="item.icon && typeof item.icon === 'string'"
|
|
125
|
+
:name="item.icon"
|
|
126
|
+
:class="cssClasses.itemIcon"
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
/>
|
|
38
129
|
<component
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
:
|
|
130
|
+
:class="cssClasses.itemIcon"
|
|
131
|
+
v-else-if="item.icon"
|
|
132
|
+
:is="item.icon"
|
|
42
133
|
/>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
v-if="item.icon && typeof item.icon === 'string'"
|
|
53
|
-
:name="item.icon"
|
|
54
|
-
class="mr-2 h-4 w-4 flex-shrink-0 text-ink-gray-6"
|
|
55
|
-
aria-hidden="true"
|
|
56
|
-
/>
|
|
57
|
-
<component
|
|
58
|
-
class="mr-2 h-4 w-4 flex-shrink-0 text-ink-gray-6"
|
|
59
|
-
v-else-if="item.icon"
|
|
60
|
-
:is="item.icon"
|
|
61
|
-
/>
|
|
62
|
-
<span class="whitespace-nowrap text-ink-gray-7">
|
|
63
|
-
{{ item.label }}
|
|
64
|
-
</span>
|
|
65
|
-
</button>
|
|
66
|
-
</MenuItem>
|
|
67
|
-
</div>
|
|
68
|
-
</MenuItems>
|
|
69
|
-
</template>
|
|
70
|
-
</Popover>
|
|
71
|
-
</Menu>
|
|
134
|
+
<span :class="cssClasses.itemLabel">
|
|
135
|
+
{{ item.label }}
|
|
136
|
+
</span>
|
|
137
|
+
</button>
|
|
138
|
+
</DropdownMenuItem>
|
|
139
|
+
</div>
|
|
140
|
+
</DropdownMenuContent>
|
|
141
|
+
</DropdownMenuPortal>
|
|
142
|
+
</DropdownMenuRoot>
|
|
72
143
|
</template>
|
|
73
144
|
|
|
74
145
|
<script setup lang="ts">
|
|
75
|
-
import {
|
|
76
|
-
|
|
146
|
+
import {
|
|
147
|
+
DropdownMenuRoot,
|
|
148
|
+
DropdownMenuTrigger,
|
|
149
|
+
DropdownMenuPortal,
|
|
150
|
+
DropdownMenuContent,
|
|
151
|
+
DropdownMenuLabel,
|
|
152
|
+
DropdownMenuItem,
|
|
153
|
+
DropdownMenuSub,
|
|
154
|
+
DropdownMenuSubTrigger,
|
|
155
|
+
DropdownMenuSubContent,
|
|
156
|
+
} from 'reka-ui'
|
|
77
157
|
import { Button } from '../Button'
|
|
78
158
|
import FeatherIcon from '../FeatherIcon.vue'
|
|
79
159
|
import { computed } from 'vue'
|
|
@@ -82,6 +162,7 @@ import type {
|
|
|
82
162
|
DropdownProps,
|
|
83
163
|
DropdownOption,
|
|
84
164
|
DropdownGroupOption,
|
|
165
|
+
DropdownOptions,
|
|
85
166
|
} from './types'
|
|
86
167
|
|
|
87
168
|
const router = useRouter()
|
|
@@ -91,36 +172,34 @@ const props = withDefaults(defineProps<DropdownProps>(), {
|
|
|
91
172
|
placement: 'left',
|
|
92
173
|
})
|
|
93
174
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
175
|
+
// Unified click handling for all dropdown items
|
|
176
|
+
const handleItemClick = (item: DropdownOption) => {
|
|
177
|
+
if (item.route) {
|
|
178
|
+
router.push(item.route)
|
|
179
|
+
} else if (item.onClick) {
|
|
180
|
+
item.onClick()
|
|
101
181
|
}
|
|
182
|
+
}
|
|
102
183
|
|
|
184
|
+
const normalizeDropdownItem = (option: DropdownOption) => {
|
|
103
185
|
return {
|
|
104
186
|
label: option.label,
|
|
105
187
|
icon: option.icon,
|
|
106
188
|
component: option.component,
|
|
107
|
-
onClick,
|
|
189
|
+
onClick: () => handleItemClick(option),
|
|
190
|
+
submenu: option.submenu,
|
|
108
191
|
}
|
|
109
192
|
}
|
|
110
193
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
.map((option) => normalizeDropdownItem(option))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const groups = computed(() => {
|
|
194
|
+
// Unified group processing for both main options and submenu options
|
|
195
|
+
const processOptionsIntoGroups = (
|
|
196
|
+
options: DropdownOptions,
|
|
197
|
+
): DropdownGroupOption[] => {
|
|
119
198
|
let groups: DropdownGroupOption[] = []
|
|
120
199
|
let currentGroup: DropdownGroupOption | null = null
|
|
121
200
|
let i = 0
|
|
122
201
|
|
|
123
|
-
for (let option of
|
|
202
|
+
for (let option of options) {
|
|
124
203
|
if (option == null) {
|
|
125
204
|
continue
|
|
126
205
|
}
|
|
@@ -155,23 +234,85 @@ const groups = computed(() => {
|
|
|
155
234
|
}
|
|
156
235
|
|
|
157
236
|
return groups
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const getSubmenuGroups = (submenuOptions: DropdownOptions) => {
|
|
240
|
+
return processOptionsIntoGroups(submenuOptions)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const filterOptions = (options: DropdownOption[]) => {
|
|
244
|
+
return (options || [])
|
|
245
|
+
.filter(Boolean)
|
|
246
|
+
.filter((option) => (option.condition ? option.condition() : true))
|
|
247
|
+
.map((option) => normalizeDropdownItem(option))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Semantic CSS classes for consistent styling
|
|
251
|
+
const cssClasses = {
|
|
252
|
+
// Container classes
|
|
253
|
+
dropdownContent:
|
|
254
|
+
'min-w-40 divide-y divide-outline-gray-modals rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none dropdown-content',
|
|
255
|
+
groupContainer: 'p-1.5',
|
|
256
|
+
|
|
257
|
+
// Label classes
|
|
258
|
+
groupLabel: 'flex h-7 items-center px-2 text-sm font-medium text-ink-gray-6',
|
|
259
|
+
itemLabel: 'whitespace-nowrap text-ink-gray-7',
|
|
260
|
+
|
|
261
|
+
// Icon classes
|
|
262
|
+
itemIcon: 'mr-2 h-4 w-4 flex-shrink-0 text-ink-gray-6',
|
|
263
|
+
chevronIcon: 'ml-auto h-4 w-4 flex-shrink-0 text-ink-gray-6',
|
|
264
|
+
|
|
265
|
+
// Button classes
|
|
266
|
+
itemButton:
|
|
267
|
+
'group flex h-7 w-full items-center rounded px-2 text-base text-ink-gray-6 focus:bg-surface-gray-3 focus:outline-none data-[highlighted]:bg-surface-gray-3',
|
|
268
|
+
submenuTrigger:
|
|
269
|
+
'group flex h-7 w-full items-center rounded px-2 text-base text-ink-gray-6 focus:bg-surface-gray-3 focus:outline-none data-[highlighted]:bg-surface-gray-3 data-[state=open]:bg-surface-gray-3',
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const groups = computed(() => {
|
|
273
|
+
return processOptionsIntoGroups(props.options)
|
|
158
274
|
})
|
|
159
275
|
|
|
160
|
-
const
|
|
161
|
-
return
|
|
162
|
-
enterActiveClass: 'transition duration-100 ease-out',
|
|
163
|
-
enterFromClass: 'transform scale-95 opacity-0',
|
|
164
|
-
enterToClass: 'transform scale-100 opacity-100',
|
|
165
|
-
leaveActiveClass: 'transition duration-75 ease-in',
|
|
166
|
-
leaveFromClass: 'transform scale-100 opacity-100',
|
|
167
|
-
leaveToClass: 'transform scale-95 opacity-0',
|
|
168
|
-
}
|
|
276
|
+
const contentSide = computed(() => {
|
|
277
|
+
return 'bottom' as const
|
|
169
278
|
})
|
|
170
279
|
|
|
171
|
-
const
|
|
172
|
-
if (props.placement === 'left') return '
|
|
173
|
-
if (props.placement === 'right') return '
|
|
174
|
-
if (props.placement === 'center') return '
|
|
175
|
-
return '
|
|
280
|
+
const contentAlign = computed(() => {
|
|
281
|
+
if (props.placement === 'left') return 'start' as const
|
|
282
|
+
if (props.placement === 'right') return 'end' as const
|
|
283
|
+
if (props.placement === 'center') return 'center' as const
|
|
284
|
+
return 'start' as const
|
|
176
285
|
})
|
|
177
286
|
</script>
|
|
287
|
+
|
|
288
|
+
<style scoped>
|
|
289
|
+
@keyframes dropdown-in {
|
|
290
|
+
from {
|
|
291
|
+
opacity: 0;
|
|
292
|
+
transform: scale(0.95);
|
|
293
|
+
}
|
|
294
|
+
to {
|
|
295
|
+
opacity: 1;
|
|
296
|
+
transform: scale(1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
@keyframes dropdown-out {
|
|
301
|
+
from {
|
|
302
|
+
opacity: 1;
|
|
303
|
+
transform: scale(1);
|
|
304
|
+
}
|
|
305
|
+
to {
|
|
306
|
+
opacity: 0;
|
|
307
|
+
transform: scale(0.95);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
:global(.dropdown-content[data-state='open']) {
|
|
312
|
+
animation: dropdown-in 100ms ease-out;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
:global(.dropdown-content[data-state='closed']) {
|
|
316
|
+
animation: dropdown-out 75ms ease-in;
|
|
317
|
+
}
|
|
318
|
+
</style>
|
|
@@ -8,6 +8,7 @@ export type DropdownOption = {
|
|
|
8
8
|
onClick?: () => void
|
|
9
9
|
route?: RouterLinkProps['to']
|
|
10
10
|
condition?: () => boolean
|
|
11
|
+
submenu?: DropdownOptions
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export type DropdownGroupOption = {
|
|
@@ -23,4 +24,4 @@ export interface DropdownProps {
|
|
|
23
24
|
button?: ButtonProps
|
|
24
25
|
options?: DropdownOptions
|
|
25
26
|
placement?: string
|
|
26
|
-
}
|
|
27
|
+
}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
<script>
|
|
40
40
|
import { RadioGroup, RadioGroupLabel, RadioGroupOption } from '@headlessui/vue'
|
|
41
41
|
import FeatherIcon from '../FeatherIcon.vue'
|
|
42
|
+
import Button from '../Button/Button.vue'
|
|
42
43
|
|
|
43
44
|
export default {
|
|
44
45
|
name: 'TabButtons',
|
|
@@ -53,6 +54,7 @@ export default {
|
|
|
53
54
|
},
|
|
54
55
|
emits: ['update:modelValue'],
|
|
55
56
|
components: {
|
|
57
|
+
Button,
|
|
56
58
|
FeatherIcon,
|
|
57
59
|
RadioGroup,
|
|
58
60
|
RadioGroupOption,
|