@m3ui-vue/m3ui-vue 0.1.11 → 0.2.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/dist/{MMenuItem-_n5OG5MT.js → MMenuItem-CIDblhtb.js} +45 -33
- package/dist/MMenuItem-CIDblhtb.js.map +1 -0
- package/dist/components/MCarousel.vue.d.ts +36 -0
- package/dist/components/MDataTable.vue.d.ts +17 -0
- package/dist/components/MFab.vue.d.ts +1 -1
- package/dist/components/MFlex.vue.d.ts +1 -1
- package/dist/components/MMaskField.vue.d.ts +24 -0
- package/dist/components/MMultiSelect.vue.d.ts +7 -5
- package/dist/components/MNavigationDrawer.vue.d.ts +9 -3
- package/dist/components/MNumberField.vue.d.ts +27 -0
- package/dist/components/MSelect.vue.d.ts +12 -9
- package/dist/components/MSplitter.vue.d.ts +1 -1
- package/dist/components/MStack.vue.d.ts +1 -1
- package/dist/components/MTable.vue.d.ts +1 -1
- package/dist/components/MTextField.vue.d.ts +2 -5
- package/dist/components/_MDrawerItemList.vue.d.ts +57 -0
- package/dist/index.d.ts +8 -1
- package/dist/m3ui-vue.css +1 -1
- package/dist/m3ui.js +1864 -1162
- package/dist/m3ui.js.map +1 -1
- package/dist/rich-text-editor.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/MCarousel.vue +203 -0
- package/src/components/MDataTable.vue +60 -5
- package/src/components/MMaskField.vue +198 -0
- package/src/components/MMultiSelect.vue +42 -17
- package/src/components/MNavigationDrawer.vue +282 -107
- package/src/components/MNumberField.vue +176 -0
- package/src/components/MSelect.vue +41 -14
- package/src/components/MTextField.vue +19 -10
- package/src/components/_MDrawerItemList.vue +105 -0
- package/src/index.ts +8 -1
- package/dist/MMenuItem-_n5OG5MT.js.map +0 -1
|
@@ -6,13 +6,13 @@ import { useFieldBg } from '../composables/useFieldBg'
|
|
|
6
6
|
|
|
7
7
|
export interface MultiSelectOption {
|
|
8
8
|
label: string
|
|
9
|
-
value:
|
|
9
|
+
value: unknown
|
|
10
10
|
disabled?: boolean
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const props = withDefaults(
|
|
14
14
|
defineProps<{
|
|
15
|
-
modelValue:
|
|
15
|
+
modelValue: unknown[]
|
|
16
16
|
options: MultiSelectOption[]
|
|
17
17
|
label?: string
|
|
18
18
|
placeholder?: string
|
|
@@ -25,6 +25,7 @@ const props = withDefaults(
|
|
|
25
25
|
fieldBg?: string
|
|
26
26
|
searchable?: boolean
|
|
27
27
|
maxChips?: number
|
|
28
|
+
clearable?: boolean
|
|
28
29
|
}>(),
|
|
29
30
|
{
|
|
30
31
|
modelValue: () => [],
|
|
@@ -33,10 +34,11 @@ const props = withDefaults(
|
|
|
33
34
|
required: false,
|
|
34
35
|
searchable: true,
|
|
35
36
|
maxChips: 3,
|
|
37
|
+
clearable: false,
|
|
36
38
|
},
|
|
37
39
|
)
|
|
38
40
|
|
|
39
|
-
const emit = defineEmits<{ 'update:modelValue': [
|
|
41
|
+
const emit = defineEmits<{ 'update:modelValue': [unknown[]] }>()
|
|
40
42
|
|
|
41
43
|
const id = useId()
|
|
42
44
|
const open = ref(false)
|
|
@@ -47,6 +49,16 @@ const dropdownEl = ref<HTMLElement | null>(null)
|
|
|
47
49
|
const searchInput = ref<HTMLInputElement | null>(null)
|
|
48
50
|
const dropPos = ref({ top: '0px', left: '0px', width: '0px' })
|
|
49
51
|
|
|
52
|
+
function eq(a: unknown, b: unknown): boolean {
|
|
53
|
+
if (a === b) return true
|
|
54
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null) return false
|
|
55
|
+
return JSON.stringify(a) === JSON.stringify(b)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function includes(arr: unknown[], val: unknown): boolean {
|
|
59
|
+
return arr.some((v) => eq(v, val))
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
const hasValue = computed(() => props.modelValue.length > 0)
|
|
51
63
|
|
|
52
64
|
const filteredOptions = computed(() => {
|
|
@@ -58,24 +70,24 @@ const filteredOptions = computed(() => {
|
|
|
58
70
|
const visibleChips = computed(() =>
|
|
59
71
|
props.modelValue.slice(0, props.maxChips).map((v) => ({
|
|
60
72
|
value: v,
|
|
61
|
-
label: props.options.find((o) => o.value
|
|
73
|
+
label: props.options.find((o) => eq(o.value, v))?.label ?? String(v),
|
|
62
74
|
})),
|
|
63
75
|
)
|
|
64
76
|
|
|
65
77
|
const overflowCount = computed(() => Math.max(0, props.modelValue.length - props.maxChips))
|
|
66
78
|
|
|
67
|
-
function toggle(value:
|
|
79
|
+
function toggle(value: unknown) {
|
|
68
80
|
const current = props.modelValue
|
|
69
|
-
if (
|
|
70
|
-
emit('update:modelValue', current.filter((v) => v
|
|
81
|
+
if (includes(current, value)) {
|
|
82
|
+
emit('update:modelValue', current.filter((v) => !eq(v, value)))
|
|
71
83
|
} else {
|
|
72
84
|
emit('update:modelValue', [...current, value])
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
function removeChip(value:
|
|
88
|
+
function removeChip(value: unknown, e: Event) {
|
|
77
89
|
e.stopPropagation()
|
|
78
|
-
emit('update:modelValue', props.modelValue.filter((v) => v
|
|
90
|
+
emit('update:modelValue', props.modelValue.filter((v) => !eq(v, value)))
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
function computeDropPos() {
|
|
@@ -164,7 +176,9 @@ const labelClasses = computed(() => {
|
|
|
164
176
|
? '-top-2.5 translate-y-0 text-label-small bg-[var(--field-bg)] px-1 right-auto max-w-[calc(100%-1.5rem)]'
|
|
165
177
|
: 'top-2 translate-y-0 text-label-small'
|
|
166
178
|
|
|
167
|
-
const unFloated =
|
|
179
|
+
const unFloated = props.variant === 'filled'
|
|
180
|
+
? 'top-[53%] -translate-y-1/2 text-body-large'
|
|
181
|
+
: 'top-1/2 -translate-y-1/2 text-body-large'
|
|
168
182
|
const active = open.value || hasValue.value
|
|
169
183
|
|
|
170
184
|
return [
|
|
@@ -188,7 +202,8 @@ const labelClasses = computed(() => {
|
|
|
188
202
|
>
|
|
189
203
|
<div
|
|
190
204
|
v-if="leadingIcon"
|
|
191
|
-
class="pointer-events-none absolute left-3.5
|
|
205
|
+
class="pointer-events-none absolute left-3.5 text-on-surface-variant"
|
|
206
|
+
:class="variant === 'filled' ? 'top-[57%] -translate-y-1/2' : 'top-[55%] -translate-y-1/2'"
|
|
192
207
|
>
|
|
193
208
|
<MIcon :name="leadingIcon" :size="20" />
|
|
194
209
|
</div>
|
|
@@ -208,8 +223,8 @@ const labelClasses = computed(() => {
|
|
|
208
223
|
>
|
|
209
224
|
<template v-if="hasValue">
|
|
210
225
|
<span
|
|
211
|
-
v-for="chip in visibleChips"
|
|
212
|
-
:key="
|
|
226
|
+
v-for="(chip, i) in visibleChips"
|
|
227
|
+
:key="i"
|
|
213
228
|
class="inline-flex items-center gap-1 rounded-full bg-secondary-container px-2 py-0.5 text-label-small text-on-secondary-container"
|
|
214
229
|
>
|
|
215
230
|
{{ chip.label }}
|
|
@@ -237,7 +252,17 @@ const labelClasses = computed(() => {
|
|
|
237
252
|
{{ label }}<span v-if="required" class="text-error"> *</span>
|
|
238
253
|
</label>
|
|
239
254
|
|
|
240
|
-
<
|
|
255
|
+
<button
|
|
256
|
+
v-if="clearable && hasValue && !disabled"
|
|
257
|
+
type="button"
|
|
258
|
+
class="absolute right-9 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-on-surface-variant transition-colors hover:bg-on-surface/8 hover:text-on-surface"
|
|
259
|
+
:class="variant === 'filled' ? 'top-[57%] -translate-y-1/2' : 'top-[55%] -translate-y-1/2'"
|
|
260
|
+
@click.stop="emit('update:modelValue', []); close()"
|
|
261
|
+
>
|
|
262
|
+
<MIcon name="close" :size="18" />
|
|
263
|
+
</button>
|
|
264
|
+
|
|
265
|
+
<div class="pointer-events-none absolute right-2" :class="variant === 'filled' ? 'top-[57%] -translate-y-1/2' : 'top-[55%] -translate-y-1/2'">
|
|
241
266
|
<MIcon
|
|
242
267
|
:name="open ? 'arrow_drop_up' : 'arrow_drop_down'"
|
|
243
268
|
:size="24"
|
|
@@ -282,13 +307,13 @@ const labelClasses = computed(() => {
|
|
|
282
307
|
|
|
283
308
|
<div class="flex flex-col py-1">
|
|
284
309
|
<label
|
|
285
|
-
v-for="opt in filteredOptions"
|
|
286
|
-
:key="
|
|
310
|
+
v-for="(opt, i) in filteredOptions"
|
|
311
|
+
:key="i"
|
|
287
312
|
class="flex cursor-pointer items-center gap-3 px-4 py-2 hover:bg-on-surface/8"
|
|
288
313
|
:class="opt.disabled ? 'cursor-not-allowed opacity-38' : ''"
|
|
289
314
|
>
|
|
290
315
|
<MCheckbox
|
|
291
|
-
:model-value="
|
|
316
|
+
:model-value="includes(modelValue, opt.value)"
|
|
292
317
|
:disabled="opt.disabled"
|
|
293
318
|
@update:model-value="!opt.disabled && toggle(opt.value)"
|
|
294
319
|
/>
|
|
@@ -1,66 +1,125 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, watch } from
|
|
3
|
-
import MIcon from
|
|
2
|
+
import { ref, reactive, watch, provide, computed } from "vue";
|
|
3
|
+
import MIcon from "./MIcon.vue";
|
|
4
|
+
import MDrawerItemList from "./_MDrawerItemList.vue";
|
|
4
5
|
|
|
5
6
|
export interface DrawerItem {
|
|
6
|
-
value: string | number
|
|
7
|
-
label: string
|
|
8
|
-
icon?: string
|
|
9
|
-
badge?: string | number
|
|
10
|
-
disabled?: boolean
|
|
11
|
-
to?: string | Record<string, any
|
|
7
|
+
value: string | number;
|
|
8
|
+
label: string;
|
|
9
|
+
icon?: string;
|
|
10
|
+
badge?: string | number;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
to?: string | Record<string, any>;
|
|
13
|
+
children?: DrawerItem[];
|
|
14
|
+
iconSize?: number;
|
|
15
|
+
labelClass?: string;
|
|
16
|
+
py?: string;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
export interface DrawerSection {
|
|
15
|
-
title?: string
|
|
16
|
-
icon?: string
|
|
17
|
-
items: DrawerItem[]
|
|
18
|
-
collapsible?: boolean
|
|
20
|
+
title?: string;
|
|
21
|
+
icon?: string;
|
|
22
|
+
items: DrawerItem[];
|
|
23
|
+
collapsible?: boolean;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
const props = withDefaults(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const props = withDefaults(
|
|
27
|
+
defineProps<{
|
|
28
|
+
modelValue: boolean;
|
|
29
|
+
selected?: string | number;
|
|
30
|
+
sections: DrawerSection[];
|
|
31
|
+
title?: string;
|
|
32
|
+
modal?: boolean;
|
|
33
|
+
collapsed?: boolean;
|
|
34
|
+
}>(),
|
|
35
|
+
{ modal: true },
|
|
36
|
+
);
|
|
29
37
|
|
|
30
38
|
const emit = defineEmits<{
|
|
31
|
-
|
|
32
|
-
select: [string | number]
|
|
33
|
-
}>()
|
|
39
|
+
"update:modelValue": [boolean];
|
|
40
|
+
select: [string | number];
|
|
41
|
+
}>();
|
|
34
42
|
|
|
35
|
-
const openSections = ref<Record<string, boolean>>({})
|
|
43
|
+
const openSections = ref<Record<string, boolean>>({});
|
|
44
|
+
const openItems = reactive<Record<string | number, boolean>>({});
|
|
36
45
|
|
|
37
46
|
function isSectionOpen(section: DrawerSection, index: number) {
|
|
38
|
-
const key = section.title ?? `__${index}
|
|
39
|
-
return openSections.value[key] !== false
|
|
47
|
+
const key = section.title ?? `__${index}`;
|
|
48
|
+
return openSections.value[key] !== false;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
function toggleSection(section: DrawerSection, index: number) {
|
|
43
|
-
const key = section.title ?? `__${index}
|
|
44
|
-
openSections.value[key] = !isSectionOpen(section, index)
|
|
52
|
+
const key = section.title ?? `__${index}`;
|
|
53
|
+
openSections.value[key] = !isSectionOpen(section, index);
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
function
|
|
56
|
+
function isItemOpen(item: DrawerItem) {
|
|
57
|
+
return openItems[item.value] === true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toggleItem(item: DrawerItem) {
|
|
61
|
+
openItems[item.value] = !openItems[item.value];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function close() {
|
|
65
|
+
emit("update:modelValue", false);
|
|
66
|
+
}
|
|
48
67
|
function select(item: DrawerItem) {
|
|
49
|
-
if (item.disabled) return
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
if (item.disabled) return;
|
|
69
|
+
if (item.children?.length) toggleItem(item);
|
|
70
|
+
emit("select", item.value);
|
|
71
|
+
if (props.modal && !item.children?.length) close();
|
|
52
72
|
}
|
|
53
73
|
|
|
54
|
-
function
|
|
55
|
-
|
|
74
|
+
function onChildSelect(item: DrawerItem) {
|
|
75
|
+
if (item.disabled) return;
|
|
76
|
+
emit("select", item.value);
|
|
77
|
+
if (props.modal && !item.children?.length) close();
|
|
56
78
|
}
|
|
57
79
|
|
|
80
|
+
function onChildToggle(item: DrawerItem) {
|
|
81
|
+
openItems[item.value] = !openItems[item.value];
|
|
82
|
+
}
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
function subEnter(el: Element, done: () => void) {
|
|
85
|
+
const e = el as HTMLElement
|
|
86
|
+
e.style.overflow = 'hidden'
|
|
87
|
+
const h = e.scrollHeight
|
|
88
|
+
e.animate(
|
|
89
|
+
[{ height: '0px' }, { height: h + 'px' }],
|
|
90
|
+
{ duration: 250, easing: 'cubic-bezier(0.2, 0, 0, 1)' },
|
|
91
|
+
).onfinish = () => { e.style.overflow = ''; done() }
|
|
92
|
+
}
|
|
93
|
+
function subLeave(el: Element, done: () => void) {
|
|
94
|
+
const e = el as HTMLElement
|
|
95
|
+
e.style.overflow = 'hidden'
|
|
96
|
+
const h = e.scrollHeight
|
|
97
|
+
e.animate(
|
|
98
|
+
[{ height: h + 'px' }, { height: '0px' }],
|
|
99
|
+
{ duration: 200, easing: 'cubic-bezier(0.4, 0, 1, 1)' },
|
|
100
|
+
).onfinish = done
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const collapsedRef = computed(() => props.collapsed ?? false)
|
|
104
|
+
provide("nd-open-items", openItems);
|
|
105
|
+
provide("nd-toggle-item", onChildToggle);
|
|
106
|
+
provide("nd-select-item", onChildSelect);
|
|
107
|
+
provide("nd-collapsed", collapsedRef);
|
|
108
|
+
|
|
109
|
+
defineExpose({ openItems })
|
|
110
|
+
|
|
111
|
+
function itemTag(item: DrawerItem) {
|
|
112
|
+
return item.to && !item.disabled ? "RouterLink" : "button";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
watch(
|
|
116
|
+
() => props.modelValue,
|
|
117
|
+
(open) => {
|
|
118
|
+
if (props.modal) {
|
|
119
|
+
document.body.style.overflow = open ? "hidden" : "";
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
);
|
|
64
123
|
</script>
|
|
65
124
|
|
|
66
125
|
<template>
|
|
@@ -70,12 +129,16 @@ watch(() => props.modelValue, (open) => {
|
|
|
70
129
|
<div v-if="modelValue" class="fixed inset-0 z-100 flex">
|
|
71
130
|
<div class="nd-scrim absolute inset-0 bg-black/40" @click="close" />
|
|
72
131
|
|
|
73
|
-
<nav
|
|
132
|
+
<nav
|
|
133
|
+
class="nd-panel relative flex h-full w-72 max-w-[85vw] flex-col bg-surface-container shadow-elevation-3"
|
|
134
|
+
>
|
|
74
135
|
<div v-if="$slots.header" class="shrink-0">
|
|
75
136
|
<slot name="header" />
|
|
76
137
|
</div>
|
|
77
138
|
<div v-else-if="title" class="shrink-0 px-5 pt-6 pb-2">
|
|
78
|
-
<h2 class="text-title-small font-medium text-on-surface-variant">
|
|
139
|
+
<h2 class="text-title-small font-medium text-on-surface-variant">
|
|
140
|
+
{{ title }}
|
|
141
|
+
</h2>
|
|
79
142
|
</div>
|
|
80
143
|
|
|
81
144
|
<div class="flex-1 overflow-y-auto px-3 py-2">
|
|
@@ -88,48 +151,88 @@ watch(() => props.modelValue, (open) => {
|
|
|
88
151
|
class="mt-1 flex w-full cursor-pointer items-center gap-3 rounded-xl px-3 py-2.5 text-on-surface-variant transition-colors hover:bg-on-surface/8"
|
|
89
152
|
@click="toggleSection(section, si)"
|
|
90
153
|
>
|
|
91
|
-
<MIcon v-if="section.icon" :name="section.icon" :size="24" class="shrink-0" />
|
|
92
|
-
<span class="flex-1 text-left text-title-small font-medium">{{ section.title }}</span>
|
|
93
154
|
<MIcon
|
|
94
|
-
|
|
155
|
+
v-if="section.icon"
|
|
156
|
+
:name="section.icon"
|
|
157
|
+
:size="24"
|
|
158
|
+
class="shrink-0"
|
|
159
|
+
/>
|
|
160
|
+
<span class="flex-1 text-left text-title-small font-medium">{{
|
|
161
|
+
section.title
|
|
162
|
+
}}</span>
|
|
163
|
+
<MIcon
|
|
164
|
+
:name="
|
|
165
|
+
isSectionOpen(section, si) ? 'expand_less' : 'expand_more'
|
|
166
|
+
"
|
|
95
167
|
:size="18"
|
|
96
168
|
class="shrink-0"
|
|
97
169
|
/>
|
|
98
170
|
</button>
|
|
99
|
-
<p
|
|
171
|
+
<p
|
|
172
|
+
v-else-if="section.title"
|
|
173
|
+
class="px-4 pt-4 pb-2 text-title-small font-medium text-on-surface-variant"
|
|
174
|
+
>
|
|
100
175
|
{{ section.title }}
|
|
101
176
|
</p>
|
|
102
177
|
|
|
103
178
|
<Transition name="nd-section">
|
|
104
|
-
<div
|
|
179
|
+
<div
|
|
180
|
+
v-if="!section.collapsible || isSectionOpen(section, si)"
|
|
181
|
+
class="nd-section-grid"
|
|
182
|
+
>
|
|
105
183
|
<div class="nd-section-body">
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
184
|
+
<template v-for="item in section.items" :key="item.value">
|
|
185
|
+
<component
|
|
186
|
+
:is="itemTag(item)"
|
|
187
|
+
:to="item.to && !item.disabled ? item.to : undefined"
|
|
188
|
+
:type="item.to ? undefined : 'button'"
|
|
189
|
+
class="flex w-full items-center gap-3 rounded-full py-2.5 text-left transition-colors focus-visible:outline-none"
|
|
190
|
+
:class="[
|
|
191
|
+
section.collapsible ? 'pl-8 pr-3' : 'px-4',
|
|
192
|
+
item.disabled
|
|
193
|
+
? 'cursor-not-allowed opacity-[0.38]'
|
|
194
|
+
: item.value === selected
|
|
195
|
+
? 'bg-secondary-container text-on-secondary-container'
|
|
196
|
+
: 'cursor-pointer text-on-surface-variant hover:bg-on-surface/8',
|
|
197
|
+
]"
|
|
198
|
+
:disabled="item.disabled && !item.to"
|
|
199
|
+
@click="select(item)"
|
|
200
|
+
>
|
|
201
|
+
<MIcon v-if="item.icon" :name="item.icon" :size="item.iconSize ?? 24" />
|
|
202
|
+
<span class="flex-1 font-medium" :class="item.labelClass ?? 'text-label-large'">{{
|
|
203
|
+
item.label
|
|
204
|
+
}}</span>
|
|
205
|
+
<MIcon
|
|
206
|
+
v-if="item.children?.length"
|
|
207
|
+
:name="
|
|
208
|
+
isItemOpen(item) ? 'expand_less' : 'expand_more'
|
|
209
|
+
"
|
|
210
|
+
:size="18"
|
|
211
|
+
class="shrink-0 text-on-surface-variant"
|
|
212
|
+
/>
|
|
213
|
+
<span
|
|
214
|
+
v-else-if="item.badge != null"
|
|
215
|
+
class="text-label-medium text-on-surface-variant"
|
|
216
|
+
>
|
|
217
|
+
{{ item.badge }}
|
|
218
|
+
</span>
|
|
219
|
+
</component>
|
|
220
|
+
<Transition
|
|
221
|
+
name="nd-sub"
|
|
222
|
+
:css="false"
|
|
223
|
+
@enter="subEnter"
|
|
224
|
+
@leave="subLeave"
|
|
129
225
|
>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
226
|
+
<div
|
|
227
|
+
v-if="item.children?.length && isItemOpen(item)"
|
|
228
|
+
>
|
|
229
|
+
<MDrawerItemList
|
|
230
|
+
:items="item.children"
|
|
231
|
+
:selected="selected"
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
</Transition>
|
|
235
|
+
</template>
|
|
133
236
|
</div>
|
|
134
237
|
</div>
|
|
135
238
|
</Transition>
|
|
@@ -145,7 +248,11 @@ watch(() => props.modelValue, (open) => {
|
|
|
145
248
|
<nav
|
|
146
249
|
class="nd-inline flex h-full shrink-0 flex-col border-r border-outline-variant bg-surface"
|
|
147
250
|
:class="[
|
|
148
|
-
!modelValue
|
|
251
|
+
!modelValue
|
|
252
|
+
? 'nd-hidden w-0 border-r-0'
|
|
253
|
+
: collapsed
|
|
254
|
+
? 'nd-collapsed w-[72px]'
|
|
255
|
+
: 'w-72',
|
|
149
256
|
]"
|
|
150
257
|
>
|
|
151
258
|
<div v-if="$slots.header" class="shrink-0">
|
|
@@ -153,11 +260,17 @@ watch(() => props.modelValue, (open) => {
|
|
|
153
260
|
</div>
|
|
154
261
|
<div v-else-if="title" class="nd-collapse-h shrink-0 overflow-hidden">
|
|
155
262
|
<div class="px-5 pt-6 pb-2">
|
|
156
|
-
<h2
|
|
263
|
+
<h2
|
|
264
|
+
class="whitespace-nowrap text-title-small font-medium text-on-surface-variant"
|
|
265
|
+
>
|
|
266
|
+
{{ title }}
|
|
267
|
+
</h2>
|
|
157
268
|
</div>
|
|
158
269
|
</div>
|
|
159
270
|
|
|
160
|
-
<div
|
|
271
|
+
<div
|
|
272
|
+
class="flex flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-2"
|
|
273
|
+
>
|
|
161
274
|
<template v-for="(section, si) in sections" :key="si">
|
|
162
275
|
<div v-if="si > 0" class="my-1 border-t border-outline-variant" />
|
|
163
276
|
|
|
@@ -169,8 +282,16 @@ watch(() => props.modelValue, (open) => {
|
|
|
169
282
|
:title="collapsed ? section.title : undefined"
|
|
170
283
|
@click="toggleSection(section, si)"
|
|
171
284
|
>
|
|
172
|
-
<MIcon
|
|
173
|
-
|
|
285
|
+
<MIcon
|
|
286
|
+
v-if="section.icon"
|
|
287
|
+
:name="section.icon"
|
|
288
|
+
:size="24"
|
|
289
|
+
class="shrink-0"
|
|
290
|
+
/>
|
|
291
|
+
<span
|
|
292
|
+
class="nd-label min-w-0 flex-1 text-left text-title-small font-medium"
|
|
293
|
+
>{{ section.title }}</span
|
|
294
|
+
>
|
|
174
295
|
<MIcon
|
|
175
296
|
:name="isSectionOpen(section, si) ? 'expand_less' : 'expand_more'"
|
|
176
297
|
:size="18"
|
|
@@ -180,46 +301,84 @@ watch(() => props.modelValue, (open) => {
|
|
|
180
301
|
|
|
181
302
|
<!-- Static section title -->
|
|
182
303
|
<div v-else-if="section.title" class="nd-collapse-h overflow-hidden">
|
|
183
|
-
<p
|
|
304
|
+
<p
|
|
305
|
+
class="whitespace-nowrap px-4 pt-4 pb-2 text-title-small font-medium text-on-surface-variant"
|
|
306
|
+
>
|
|
184
307
|
{{ section.title }}
|
|
185
308
|
</p>
|
|
186
309
|
</div>
|
|
187
310
|
|
|
188
311
|
<Transition name="nd-section">
|
|
189
|
-
<div
|
|
312
|
+
<div
|
|
313
|
+
v-if="!section.collapsible || isSectionOpen(section, si)"
|
|
314
|
+
class="nd-section-grid"
|
|
315
|
+
>
|
|
190
316
|
<div class="nd-section-body">
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
317
|
+
<template v-for="item in section.items" :key="item.value">
|
|
318
|
+
<component
|
|
319
|
+
:is="itemTag(item)"
|
|
320
|
+
:to="item.to && !item.disabled ? item.to : undefined"
|
|
321
|
+
:type="item.to ? undefined : 'button'"
|
|
322
|
+
:title="collapsed ? item.label : undefined"
|
|
323
|
+
class="flex w-full shrink-0 items-center gap-3 overflow-hidden whitespace-nowrap rounded-full py-2.5 text-left focus-visible:outline-none"
|
|
324
|
+
:class="[
|
|
325
|
+
section.collapsible && !collapsed ? 'pl-8 pr-3' : 'px-3',
|
|
326
|
+
item.disabled
|
|
327
|
+
? 'cursor-not-allowed opacity-[0.38]'
|
|
328
|
+
: item.value === selected
|
|
329
|
+
? 'bg-secondary-container text-on-secondary-container'
|
|
330
|
+
: 'cursor-pointer text-on-surface-variant hover:bg-on-surface/8',
|
|
331
|
+
]"
|
|
332
|
+
:disabled="item.disabled && !item.to"
|
|
333
|
+
@click="select(item)"
|
|
334
|
+
>
|
|
335
|
+
<MIcon
|
|
336
|
+
v-if="item.icon"
|
|
337
|
+
:name="item.icon"
|
|
338
|
+
:size="item.iconSize ?? 24"
|
|
339
|
+
class="shrink-0"
|
|
340
|
+
/>
|
|
341
|
+
<span
|
|
342
|
+
class="nd-label min-w-0 flex-1 font-medium"
|
|
343
|
+
:class="item.labelClass ?? 'text-label-large'"
|
|
344
|
+
>{{ item.label }}</span
|
|
345
|
+
>
|
|
346
|
+
<MIcon
|
|
347
|
+
v-if="item.children?.length"
|
|
348
|
+
:name="isItemOpen(item) ? 'expand_less' : 'expand_more'"
|
|
349
|
+
:size="18"
|
|
350
|
+
class="nd-label shrink-0 text-on-surface-variant"
|
|
351
|
+
/>
|
|
352
|
+
<span
|
|
353
|
+
v-else-if="item.badge != null"
|
|
354
|
+
class="nd-label text-label-medium text-on-surface-variant"
|
|
355
|
+
>
|
|
356
|
+
{{ item.badge }}
|
|
357
|
+
</span>
|
|
358
|
+
</component>
|
|
359
|
+
<div v-if="item.children?.length && isItemOpen(item)" class="nd-children-divider" />
|
|
360
|
+
<Transition
|
|
361
|
+
:css="false"
|
|
362
|
+
@enter="subEnter"
|
|
363
|
+
@leave="subLeave"
|
|
364
|
+
>
|
|
365
|
+
<div
|
|
366
|
+
v-if="item.children?.length && isItemOpen(item)"
|
|
367
|
+
>
|
|
368
|
+
<MDrawerItemList
|
|
369
|
+
:items="item.children"
|
|
370
|
+
:selected="selected"
|
|
371
|
+
/>
|
|
372
|
+
</div>
|
|
373
|
+
</Transition>
|
|
374
|
+
<div v-if="item.children?.length && isItemOpen(item)" class="nd-children-divider" />
|
|
375
|
+
</template>
|
|
216
376
|
</div>
|
|
217
377
|
</div>
|
|
218
378
|
</Transition>
|
|
219
379
|
</template>
|
|
220
380
|
</div>
|
|
221
381
|
</nav>
|
|
222
|
-
|
|
223
382
|
</template>
|
|
224
383
|
</template>
|
|
225
384
|
|
|
@@ -296,7 +455,8 @@ watch(() => props.modelValue, (open) => {
|
|
|
296
455
|
.nd-label {
|
|
297
456
|
transition: opacity 200ms 80ms cubic-bezier(0.2, 0, 0, 1);
|
|
298
457
|
}
|
|
299
|
-
.nd-inline.nd-collapsed .nd-label
|
|
458
|
+
.nd-inline.nd-collapsed .nd-label,
|
|
459
|
+
.nd-inline.nd-collapsed :deep(.nd-label) {
|
|
300
460
|
opacity: 0;
|
|
301
461
|
transition: opacity 100ms cubic-bezier(0.2, 0, 0, 1);
|
|
302
462
|
}
|
|
@@ -310,7 +470,22 @@ watch(() => props.modelValue, (open) => {
|
|
|
310
470
|
max-height: 0;
|
|
311
471
|
}
|
|
312
472
|
|
|
313
|
-
.nd-inline .nd-section-body >
|
|
473
|
+
.nd-inline .nd-section-body > *,
|
|
474
|
+
.nd-inline :deep(.nd-section-body) > * {
|
|
314
475
|
transition: padding 300ms cubic-bezier(0.2, 0, 0, 1);
|
|
315
476
|
}
|
|
477
|
+
|
|
478
|
+
/* Dividers between children groups — only visible in collapsed mode */
|
|
479
|
+
.nd-children-divider {
|
|
480
|
+
height: 0;
|
|
481
|
+
margin: 0 0.75rem;
|
|
482
|
+
border-top: 1px solid transparent;
|
|
483
|
+
transition: border-color 100ms, margin 100ms;
|
|
484
|
+
}
|
|
485
|
+
.nd-inline.nd-collapsed .nd-children-divider {
|
|
486
|
+
border-top-color: var(--color-outline-variant);
|
|
487
|
+
margin: 0.25rem 0.75rem;
|
|
488
|
+
transition: border-color 200ms 300ms, margin 200ms 300ms;
|
|
489
|
+
}
|
|
490
|
+
|
|
316
491
|
</style>
|