@m3ui-vue/m3ui-vue 0.1.10 → 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 +1866 -1161
- 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 +286 -110
- 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();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function onChildToggle(item: DrawerItem) {
|
|
81
|
+
openItems[item.value] = !openItems[item.value];
|
|
82
|
+
}
|
|
83
|
+
|
|
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
|
|
56
101
|
}
|
|
57
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 })
|
|
58
110
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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,11 +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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
132
|
+
<nav
|
|
133
|
+
class="nd-panel relative flex h-full w-72 max-w-[85vw] flex-col bg-surface-container shadow-elevation-3"
|
|
134
|
+
>
|
|
135
|
+
<div v-if="$slots.header" class="shrink-0">
|
|
136
|
+
<slot name="header" />
|
|
137
|
+
</div>
|
|
138
|
+
<div v-else-if="title" class="shrink-0 px-5 pt-6 pb-2">
|
|
139
|
+
<h2 class="text-title-small font-medium text-on-surface-variant">
|
|
140
|
+
{{ title }}
|
|
141
|
+
</h2>
|
|
78
142
|
</div>
|
|
79
143
|
|
|
80
144
|
<div class="flex-1 overflow-y-auto px-3 py-2">
|
|
@@ -87,48 +151,88 @@ watch(() => props.modelValue, (open) => {
|
|
|
87
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"
|
|
88
152
|
@click="toggleSection(section, si)"
|
|
89
153
|
>
|
|
90
|
-
<MIcon v-if="section.icon" :name="section.icon" :size="24" class="shrink-0" />
|
|
91
|
-
<span class="flex-1 text-left text-title-small font-medium">{{ section.title }}</span>
|
|
92
154
|
<MIcon
|
|
93
|
-
|
|
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
|
+
"
|
|
94
167
|
:size="18"
|
|
95
168
|
class="shrink-0"
|
|
96
169
|
/>
|
|
97
170
|
</button>
|
|
98
|
-
<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
|
+
>
|
|
99
175
|
{{ section.title }}
|
|
100
176
|
</p>
|
|
101
177
|
|
|
102
178
|
<Transition name="nd-section">
|
|
103
|
-
<div
|
|
179
|
+
<div
|
|
180
|
+
v-if="!section.collapsible || isSectionOpen(section, si)"
|
|
181
|
+
class="nd-section-grid"
|
|
182
|
+
>
|
|
104
183
|
<div class="nd-section-body">
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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"
|
|
128
225
|
>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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>
|
|
132
236
|
</div>
|
|
133
237
|
</div>
|
|
134
238
|
</Transition>
|
|
@@ -144,7 +248,11 @@ watch(() => props.modelValue, (open) => {
|
|
|
144
248
|
<nav
|
|
145
249
|
class="nd-inline flex h-full shrink-0 flex-col border-r border-outline-variant bg-surface"
|
|
146
250
|
:class="[
|
|
147
|
-
!modelValue
|
|
251
|
+
!modelValue
|
|
252
|
+
? 'nd-hidden w-0 border-r-0'
|
|
253
|
+
: collapsed
|
|
254
|
+
? 'nd-collapsed w-[72px]'
|
|
255
|
+
: 'w-72',
|
|
148
256
|
]"
|
|
149
257
|
>
|
|
150
258
|
<div v-if="$slots.header" class="shrink-0">
|
|
@@ -152,11 +260,17 @@ watch(() => props.modelValue, (open) => {
|
|
|
152
260
|
</div>
|
|
153
261
|
<div v-else-if="title" class="nd-collapse-h shrink-0 overflow-hidden">
|
|
154
262
|
<div class="px-5 pt-6 pb-2">
|
|
155
|
-
<h2
|
|
263
|
+
<h2
|
|
264
|
+
class="whitespace-nowrap text-title-small font-medium text-on-surface-variant"
|
|
265
|
+
>
|
|
266
|
+
{{ title }}
|
|
267
|
+
</h2>
|
|
156
268
|
</div>
|
|
157
269
|
</div>
|
|
158
270
|
|
|
159
|
-
<div
|
|
271
|
+
<div
|
|
272
|
+
class="flex flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-2"
|
|
273
|
+
>
|
|
160
274
|
<template v-for="(section, si) in sections" :key="si">
|
|
161
275
|
<div v-if="si > 0" class="my-1 border-t border-outline-variant" />
|
|
162
276
|
|
|
@@ -168,8 +282,16 @@ watch(() => props.modelValue, (open) => {
|
|
|
168
282
|
:title="collapsed ? section.title : undefined"
|
|
169
283
|
@click="toggleSection(section, si)"
|
|
170
284
|
>
|
|
171
|
-
<MIcon
|
|
172
|
-
|
|
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
|
+
>
|
|
173
295
|
<MIcon
|
|
174
296
|
:name="isSectionOpen(section, si) ? 'expand_less' : 'expand_more'"
|
|
175
297
|
:size="18"
|
|
@@ -179,46 +301,84 @@ watch(() => props.modelValue, (open) => {
|
|
|
179
301
|
|
|
180
302
|
<!-- Static section title -->
|
|
181
303
|
<div v-else-if="section.title" class="nd-collapse-h overflow-hidden">
|
|
182
|
-
<p
|
|
304
|
+
<p
|
|
305
|
+
class="whitespace-nowrap px-4 pt-4 pb-2 text-title-small font-medium text-on-surface-variant"
|
|
306
|
+
>
|
|
183
307
|
{{ section.title }}
|
|
184
308
|
</p>
|
|
185
309
|
</div>
|
|
186
310
|
|
|
187
311
|
<Transition name="nd-section">
|
|
188
|
-
<div
|
|
312
|
+
<div
|
|
313
|
+
v-if="!section.collapsible || isSectionOpen(section, si)"
|
|
314
|
+
class="nd-section-grid"
|
|
315
|
+
>
|
|
189
316
|
<div class="nd-section-body">
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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>
|
|
215
376
|
</div>
|
|
216
377
|
</div>
|
|
217
378
|
</Transition>
|
|
218
379
|
</template>
|
|
219
380
|
</div>
|
|
220
381
|
</nav>
|
|
221
|
-
|
|
222
382
|
</template>
|
|
223
383
|
</template>
|
|
224
384
|
|
|
@@ -295,7 +455,8 @@ watch(() => props.modelValue, (open) => {
|
|
|
295
455
|
.nd-label {
|
|
296
456
|
transition: opacity 200ms 80ms cubic-bezier(0.2, 0, 0, 1);
|
|
297
457
|
}
|
|
298
|
-
.nd-inline.nd-collapsed .nd-label
|
|
458
|
+
.nd-inline.nd-collapsed .nd-label,
|
|
459
|
+
.nd-inline.nd-collapsed :deep(.nd-label) {
|
|
299
460
|
opacity: 0;
|
|
300
461
|
transition: opacity 100ms cubic-bezier(0.2, 0, 0, 1);
|
|
301
462
|
}
|
|
@@ -309,7 +470,22 @@ watch(() => props.modelValue, (open) => {
|
|
|
309
470
|
max-height: 0;
|
|
310
471
|
}
|
|
311
472
|
|
|
312
|
-
.nd-inline .nd-section-body >
|
|
473
|
+
.nd-inline .nd-section-body > *,
|
|
474
|
+
.nd-inline :deep(.nd-section-body) > * {
|
|
313
475
|
transition: padding 300ms cubic-bezier(0.2, 0, 0, 1);
|
|
314
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
|
+
|
|
315
491
|
</style>
|