@m3ui-vue/m3ui-vue 0.1.11 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{MMenuItem-_n5OG5MT.js → MMenuItem-DpoEsH91.js} +215 -122
- package/dist/MMenuItem-DpoEsH91.js.map +1 -0
- package/dist/components/MAlert.vue.d.ts +1 -0
- package/dist/components/MButton.vue.d.ts +1 -1
- package/dist/components/MCalendar.vue.d.ts +3 -0
- package/dist/components/MCarousel.vue.d.ts +36 -0
- package/dist/components/MChip.vue.d.ts +3 -3
- package/dist/components/MCommandPalette.vue.d.ts +3 -2
- package/dist/components/MConfirmDialog.vue.d.ts +0 -2
- package/dist/components/MDataTable.vue.d.ts +25 -2
- package/dist/components/MDatePicker.vue.d.ts +2 -0
- package/dist/components/MDateRangePicker.vue.d.ts +5 -0
- package/dist/components/MDialog.vue.d.ts +1 -0
- package/dist/components/MFab.vue.d.ts +1 -1
- package/dist/components/MFileUpload.vue.d.ts +15 -1
- package/dist/components/MFlex.vue.d.ts +1 -1
- package/dist/components/MInfiniteScroll.vue.d.ts +7 -5
- package/dist/components/MMaskField.vue.d.ts +24 -0
- package/dist/components/MMultiSelect.vue.d.ts +20 -6
- package/dist/components/MNavigationDrawer.vue.d.ts +9 -3
- package/dist/components/MNumberField.vue.d.ts +27 -0
- package/dist/components/MRichTextEditor.vue.d.ts +28 -1
- package/dist/components/MScheduler.vue.d.ts +5 -0
- package/dist/components/MSelect.vue.d.ts +12 -9
- package/dist/components/MSnackbar.vue.d.ts +4 -1
- package/dist/components/MSpinner.vue.d.ts +1 -0
- package/dist/components/MSplitter.vue.d.ts +1 -1
- package/dist/components/MSpotlightSearch.vue.d.ts +3 -2
- package/dist/components/MStack.vue.d.ts +1 -1
- package/dist/components/MTable.vue.d.ts +3 -2
- package/dist/components/MTextField.vue.d.ts +2 -5
- package/dist/components/MTimePicker.vue.d.ts +1 -0
- package/dist/components/MTour.vue.d.ts +3 -0
- package/dist/components/MTransferList.vue.d.ts +19 -3
- package/dist/components/MTree.vue.d.ts +1 -1
- package/dist/components/_MDrawerItemList.vue.d.ts +57 -0
- package/dist/composables/useLocale.d.ts +74 -0
- package/dist/index.d.ts +10 -1
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/es.d.ts +2 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/index.d.ts +8 -0
- package/dist/locales/ja.d.ts +2 -0
- package/dist/locales/ko.d.ts +2 -0
- package/dist/locales/pt.d.ts +2 -0
- package/dist/locales/zh.d.ts +2 -0
- package/dist/m3ui-vue.css +1 -1
- package/dist/m3ui.js +3278 -2489
- package/dist/m3ui.js.map +1 -1
- package/dist/plugin.d.ts +3 -1
- package/dist/rich-text-editor.d.ts +1 -0
- package/dist/rich-text-editor.js +141 -113
- package/dist/rich-text-editor.js.map +1 -1
- package/dist/styles/palettes.css +660 -0
- package/dist/styles.css +1 -1
- package/package.json +6 -2
- package/src/components/MAlert.vue +5 -1
- package/src/components/MCalendar.vue +14 -5
- package/src/components/MCarousel.vue +203 -0
- package/src/components/MCommandPalette.vue +11 -7
- package/src/components/MConfirmDialog.vue +6 -5
- package/src/components/MDataTable.vue +78 -14
- package/src/components/MDatePicker.vue +11 -4
- package/src/components/MDateRangePicker.vue +15 -5
- package/src/components/MDialog.vue +5 -1
- package/src/components/MFileUpload.vue +22 -10
- package/src/components/MInfiniteScroll.vue +14 -9
- package/src/components/MMaskField.vue +198 -0
- package/src/components/MMultiSelect.vue +53 -24
- package/src/components/MNavigationDrawer.vue +282 -107
- package/src/components/MNumberField.vue +176 -0
- package/src/components/MRichTextEditor.vue +95 -34
- package/src/components/MScheduler.vue +12 -4
- package/src/components/MSelect.vue +41 -14
- package/src/components/MSnackbar.vue +8 -1
- package/src/components/MSpinner.vue +5 -1
- package/src/components/MSpotlightSearch.vue +11 -7
- package/src/components/MTable.vue +8 -4
- package/src/components/MTextField.vue +19 -10
- package/src/components/MTimePicker.vue +5 -1
- package/src/components/MTour.vue +12 -9
- package/src/components/MTransferList.vue +30 -15
- package/src/components/_MDrawerItemList.vue +105 -0
- package/src/composables/useColorPalette.ts +30 -20
- package/src/composables/useLocale.ts +181 -0
- package/src/index.ts +10 -1
- package/src/locales/de.ts +93 -0
- package/src/locales/es.ts +93 -0
- package/src/locales/fr.ts +93 -0
- package/src/locales/index.ts +8 -0
- package/src/locales/ja.ts +93 -0
- package/src/locales/ko.ts +93 -0
- package/src/locales/pt.ts +93 -0
- package/src/locales/zh.ts +93 -0
- package/src/plugin.ts +7 -1
- package/src/rich-text-editor.ts +1 -0
- package/src/styles/palettes.css +660 -0
- package/dist/MMenuItem-_n5OG5MT.js.map +0 -1
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
|
3
3
|
import MIcon from './MIcon.vue'
|
|
4
4
|
import MSpinner from './MSpinner.vue'
|
|
5
|
+
import { useLocale } from '../composables/useLocale'
|
|
6
|
+
|
|
7
|
+
const locale = useLocale()
|
|
5
8
|
|
|
6
9
|
export interface SpotlightResult {
|
|
7
10
|
id: string | number
|
|
@@ -20,12 +23,13 @@ const props = withDefaults(
|
|
|
20
23
|
noResultsText?: string
|
|
21
24
|
hotkey?: string
|
|
22
25
|
debounce?: number
|
|
26
|
+
navigateHint?: string
|
|
27
|
+
openHint?: string
|
|
28
|
+
closeHint?: string
|
|
23
29
|
}>(),
|
|
24
30
|
{
|
|
25
31
|
results: () => [],
|
|
26
|
-
placeholder: 'Buscar...',
|
|
27
32
|
loading: false,
|
|
28
|
-
noResultsText: 'No se encontraron resultados',
|
|
29
33
|
hotkey: '/',
|
|
30
34
|
debounce: 0,
|
|
31
35
|
},
|
|
@@ -148,7 +152,7 @@ onBeforeUnmount(() => {
|
|
|
148
152
|
ref="inputRef"
|
|
149
153
|
v-model="query"
|
|
150
154
|
type="text"
|
|
151
|
-
:placeholder="placeholder"
|
|
155
|
+
:placeholder="placeholder ?? locale.search"
|
|
152
156
|
class="h-14 flex-1 bg-transparent text-title-medium text-on-surface outline-none placeholder:text-on-surface-variant/50"
|
|
153
157
|
@keydown="onKeydown"
|
|
154
158
|
/>
|
|
@@ -198,20 +202,20 @@ onBeforeUnmount(() => {
|
|
|
198
202
|
</template>
|
|
199
203
|
<div v-else-if="!loading" class="flex flex-col items-center gap-2 py-10">
|
|
200
204
|
<MIcon name="search_off" :size="40" class="text-on-surface-variant/40" />
|
|
201
|
-
<p class="text-body-medium text-on-surface-variant">{{ noResultsText }}</p>
|
|
205
|
+
<p class="text-body-medium text-on-surface-variant">{{ noResultsText ?? locale.noResults }}</p>
|
|
202
206
|
</div>
|
|
203
207
|
</div>
|
|
204
208
|
|
|
205
209
|
<!-- Hints -->
|
|
206
210
|
<div class="flex items-center gap-4 border-t border-outline-variant px-5 py-2">
|
|
207
211
|
<span class="flex items-center gap-1 text-label-small text-on-surface-variant">
|
|
208
|
-
<kbd class="rounded bg-surface-container px-1 py-0.5">↑↓</kbd>
|
|
212
|
+
<kbd class="rounded bg-surface-container px-1 py-0.5">↑↓</kbd> {{ navigateHint ?? locale.navigateHint }}
|
|
209
213
|
</span>
|
|
210
214
|
<span class="flex items-center gap-1 text-label-small text-on-surface-variant">
|
|
211
|
-
<kbd class="rounded bg-surface-container px-1 py-0.5">↵</kbd>
|
|
215
|
+
<kbd class="rounded bg-surface-container px-1 py-0.5">↵</kbd> {{ openHint ?? locale.openHint }}
|
|
212
216
|
</span>
|
|
213
217
|
<span class="flex items-center gap-1 text-label-small text-on-surface-variant">
|
|
214
|
-
<kbd class="rounded bg-surface-container px-1 py-0.5">esc</kbd>
|
|
218
|
+
<kbd class="rounded bg-surface-container px-1 py-0.5">esc</kbd> {{ closeHint ?? locale.closeHint }}
|
|
215
219
|
</span>
|
|
216
220
|
</div>
|
|
217
221
|
</div>
|
|
@@ -3,6 +3,9 @@ import { computed, onMounted, ref, watch } from 'vue'
|
|
|
3
3
|
import MCheckbox from './MCheckbox.vue'
|
|
4
4
|
import MIcon from './MIcon.vue'
|
|
5
5
|
import MPagination from './MPagination.vue'
|
|
6
|
+
import { useLocale } from '../composables/useLocale'
|
|
7
|
+
|
|
8
|
+
const locale = useLocale()
|
|
6
9
|
|
|
7
10
|
export interface TableColumn {
|
|
8
11
|
key: string
|
|
@@ -38,10 +41,11 @@ const props = withDefaults(
|
|
|
38
41
|
serverSide?: boolean
|
|
39
42
|
total?: number
|
|
40
43
|
page?: number
|
|
44
|
+
searchPlaceholder?: string
|
|
45
|
+
selectedText?: string
|
|
41
46
|
}>(),
|
|
42
47
|
{
|
|
43
48
|
loading: false,
|
|
44
|
-
emptyText: 'Sin resultados',
|
|
45
49
|
rowKey: 'id',
|
|
46
50
|
selectable: false,
|
|
47
51
|
modelValue: () => [],
|
|
@@ -228,7 +232,7 @@ const hasActions = computed(() => !!slots['row-actions'])
|
|
|
228
232
|
<input
|
|
229
233
|
v-model="search"
|
|
230
234
|
type="text"
|
|
231
|
-
placeholder="
|
|
235
|
+
:placeholder="searchPlaceholder ?? locale.search"
|
|
232
236
|
class="w-full bg-transparent text-body-medium text-on-surface outline-none placeholder:text-on-surface-variant"
|
|
233
237
|
/>
|
|
234
238
|
<button
|
|
@@ -254,7 +258,7 @@ const hasActions = computed(() => !!slots['row-actions'])
|
|
|
254
258
|
v-if="selectable && selected.length > 0"
|
|
255
259
|
class="rounded-full bg-primary/12 px-3 py-1 text-label-small font-medium text-primary"
|
|
256
260
|
>
|
|
257
|
-
{{ selected.length }}
|
|
261
|
+
{{ selected.length }} {{ selectedText ?? locale.selectedCount }}{{ selected.length !== 1 ? 's' : '' }}
|
|
258
262
|
</span>
|
|
259
263
|
</Transition>
|
|
260
264
|
</div>
|
|
@@ -346,7 +350,7 @@ const hasActions = computed(() => !!slots['row-actions'])
|
|
|
346
350
|
>
|
|
347
351
|
<slot name="empty">
|
|
348
352
|
<MIcon name="search_off" :size="36" class="mb-2 text-on-surface-variant opacity-30" />
|
|
349
|
-
<p class="text-body-medium text-on-surface-variant">{{ emptyText }}</p>
|
|
353
|
+
<p class="text-body-medium text-on-surface-variant">{{ emptyText ?? locale.noResults }}</p>
|
|
350
354
|
</slot>
|
|
351
355
|
</td>
|
|
352
356
|
</tr>
|
|
@@ -17,17 +17,14 @@ const props = withDefaults(
|
|
|
17
17
|
rows?: number;
|
|
18
18
|
autocomplete?: string;
|
|
19
19
|
leadingIcon?: string;
|
|
20
|
-
|
|
21
|
-
* Background color behind the label in outlined variant.
|
|
22
|
-
* Defaults to the page surface color. Pass e.g. 'var(--color-surface-container-low)'
|
|
23
|
-
* when the input is inside a card.
|
|
24
|
-
*/
|
|
20
|
+
clearable?: boolean;
|
|
25
21
|
fieldBg?: string;
|
|
26
22
|
}>(),
|
|
27
23
|
{
|
|
28
24
|
type: "text",
|
|
29
25
|
variant: "filled",
|
|
30
26
|
rows: 3,
|
|
27
|
+
clearable: false,
|
|
31
28
|
},
|
|
32
29
|
);
|
|
33
30
|
|
|
@@ -39,8 +36,10 @@ const slots = useSlots();
|
|
|
39
36
|
const fieldBgEl = ref<HTMLElement | null>(null);
|
|
40
37
|
const { resolvedFieldBg } = useFieldBg(fieldBgEl, () => props.fieldBg);
|
|
41
38
|
|
|
39
|
+
const showClear = computed(() => props.clearable && String(props.modelValue).length > 0 && !props.disabled)
|
|
40
|
+
|
|
42
41
|
const inputClasses = computed(() => {
|
|
43
|
-
const hasTrailing = !!slots.trailing;
|
|
42
|
+
const hasTrailing = !!slots.trailing || props.clearable;
|
|
44
43
|
const pl = props.leadingIcon ? "pl-12" : "pl-4";
|
|
45
44
|
const pr = hasTrailing ? "pr-12" : "pr-4";
|
|
46
45
|
const size = props.multiline ? "resize-y min-h-[56px]" : "h-14";
|
|
@@ -81,11 +80,12 @@ const labelClasses = computed(() => {
|
|
|
81
80
|
? "left-3"
|
|
82
81
|
: "left-4";
|
|
83
82
|
|
|
83
|
+
const unfloatedTop = props.variant === 'filled' ? 'top-[53%]' : 'top-1/2'
|
|
84
84
|
const base = [
|
|
85
85
|
"pointer-events-none absolute truncate transition-all duration-200",
|
|
86
86
|
left,
|
|
87
87
|
"right-4",
|
|
88
|
-
|
|
88
|
+
`${unfloatedTop} -translate-y-1/2 text-body-large`,
|
|
89
89
|
];
|
|
90
90
|
|
|
91
91
|
if (props.variant === "outlined") {
|
|
@@ -133,10 +133,10 @@ function onInput(event: Event) {
|
|
|
133
133
|
:class="variant === 'outlined' ? 'mt-2' : ''"
|
|
134
134
|
:style="variant === 'outlined' ? { '--field-bg': resolvedFieldBg } : undefined"
|
|
135
135
|
>
|
|
136
|
-
<!-- Leading icon: centered in the 48px left zone (left-3.5 → center at 24px) -->
|
|
137
136
|
<div
|
|
138
137
|
v-if="leadingIcon"
|
|
139
|
-
class="pointer-events-none absolute left-3.5
|
|
138
|
+
class="pointer-events-none absolute left-3.5 text-on-surface-variant"
|
|
139
|
+
:class="variant === 'filled' ? 'top-[57%] -translate-y-1/2' : 'top-[55%] -translate-y-1/2'"
|
|
140
140
|
>
|
|
141
141
|
<MIcon :name="leadingIcon" :size="20" />
|
|
142
142
|
</div>
|
|
@@ -169,9 +169,18 @@ function onInput(event: Event) {
|
|
|
169
169
|
{{ label }}<span v-if="required" class="text-error"> *</span>
|
|
170
170
|
</label>
|
|
171
171
|
|
|
172
|
-
<div v-if="$slots.trailing" class="absolute top-1/2
|
|
172
|
+
<div v-if="$slots.trailing" class="absolute right-2" :class="variant === 'filled' ? 'top-[57%] -translate-y-1/2' : 'top-[55%] -translate-y-1/2'">
|
|
173
173
|
<slot name="trailing" />
|
|
174
174
|
</div>
|
|
175
|
+
<button
|
|
176
|
+
v-else-if="showClear"
|
|
177
|
+
type="button"
|
|
178
|
+
class="absolute right-3 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"
|
|
179
|
+
:class="variant === 'filled' ? 'top-[57%] -translate-y-1/2' : 'top-[55%] -translate-y-1/2'"
|
|
180
|
+
@click="emit('update:modelValue', '')"
|
|
181
|
+
>
|
|
182
|
+
<MIcon name="close" :size="18" />
|
|
183
|
+
</button>
|
|
175
184
|
</div>
|
|
176
185
|
|
|
177
186
|
<p v-if="error" class="px-4 text-body-small text-error">{{ error }}</p>
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
3
3
|
import MIcon from './MIcon.vue'
|
|
4
4
|
import { useFieldBg } from '../composables/useFieldBg'
|
|
5
|
+
import { useLocale } from '../composables/useLocale'
|
|
5
6
|
|
|
6
7
|
const props = withDefaults(defineProps<{
|
|
7
8
|
modelValue: string | null
|
|
8
9
|
label?: string
|
|
10
|
+
placeholder?: string
|
|
9
11
|
disabled?: boolean
|
|
10
12
|
error?: string
|
|
11
13
|
hint?: string
|
|
@@ -14,6 +16,8 @@ const props = withDefaults(defineProps<{
|
|
|
14
16
|
fieldBg?: string
|
|
15
17
|
}>(), { minuteStep: 5, use24h: true })
|
|
16
18
|
|
|
19
|
+
const locale = useLocale()
|
|
20
|
+
|
|
17
21
|
const emit = defineEmits<{ 'update:modelValue': [string | null] }>()
|
|
18
22
|
|
|
19
23
|
const open = ref(false)
|
|
@@ -129,7 +133,7 @@ onUnmounted(() => {
|
|
|
129
133
|
>
|
|
130
134
|
<MIcon name="schedule" :size="20" class="shrink-0 text-on-surface-variant" />
|
|
131
135
|
<span v-if="displayValue" class="flex-1 font-mono text-on-surface">{{ displayValue }}</span>
|
|
132
|
-
<span v-else class="flex-1 text-on-surface-variant">{{ label ||
|
|
136
|
+
<span v-else class="flex-1 text-on-surface-variant">{{ label || placeholder || locale.selectTime }}</span>
|
|
133
137
|
<MIcon
|
|
134
138
|
v-if="modelValue"
|
|
135
139
|
name="close"
|
package/src/components/MTour.vue
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { ref, computed, watch, nextTick, onBeforeUnmount } from 'vue'
|
|
3
3
|
import MButton from './MButton.vue'
|
|
4
4
|
import MIcon from './MIcon.vue'
|
|
5
|
+
import { useLocale } from '../composables/useLocale'
|
|
5
6
|
|
|
6
7
|
export interface TourStep {
|
|
7
8
|
target: string
|
|
@@ -10,13 +11,15 @@ export interface TourStep {
|
|
|
10
11
|
placement?: 'top' | 'bottom' | 'left' | 'right'
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const props =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
modelValue: boolean
|
|
16
|
+
steps: TourStep[]
|
|
17
|
+
prevLabel?: string
|
|
18
|
+
nextLabel?: string
|
|
19
|
+
finishLabel?: string
|
|
20
|
+
}>()
|
|
21
|
+
|
|
22
|
+
const locale = useLocale()
|
|
20
23
|
|
|
21
24
|
const emit = defineEmits<{
|
|
22
25
|
'update:modelValue': [boolean]
|
|
@@ -237,11 +240,11 @@ onBeforeUnmount(() => { stopListeners(); clearHighlight() })
|
|
|
237
240
|
variant="text"
|
|
238
241
|
@click="goPrev"
|
|
239
242
|
>
|
|
240
|
-
|
|
243
|
+
{{ prevLabel ?? locale.previous }}
|
|
241
244
|
</MButton>
|
|
242
245
|
<span v-else />
|
|
243
246
|
<MButton @click="goNext">
|
|
244
|
-
{{ isLast ?
|
|
247
|
+
{{ isLast ? (finishLabel ?? locale.finish) : (nextLabel ?? locale.next) }}
|
|
245
248
|
</MButton>
|
|
246
249
|
</div>
|
|
247
250
|
</div>
|
|
@@ -3,6 +3,9 @@ import { computed, ref } from 'vue'
|
|
|
3
3
|
import MIcon from './MIcon.vue'
|
|
4
4
|
import MIconButton from './MIconButton.vue'
|
|
5
5
|
import MCheckbox from './MCheckbox.vue'
|
|
6
|
+
import { useLocale } from '../composables/useLocale'
|
|
7
|
+
|
|
8
|
+
const locale = useLocale()
|
|
6
9
|
|
|
7
10
|
export interface TransferItem {
|
|
8
11
|
value: string | number
|
|
@@ -16,7 +19,15 @@ const props = withDefaults(defineProps<{
|
|
|
16
19
|
sourceTitle?: string
|
|
17
20
|
targetTitle?: string
|
|
18
21
|
filterable?: boolean
|
|
19
|
-
|
|
22
|
+
searchPlaceholder?: string
|
|
23
|
+
emptyText?: string
|
|
24
|
+
moveAllRightLabel?: string
|
|
25
|
+
moveRightLabel?: string
|
|
26
|
+
moveLeftLabel?: string
|
|
27
|
+
moveAllLeftLabel?: string
|
|
28
|
+
}>(), {
|
|
29
|
+
filterable: false,
|
|
30
|
+
})
|
|
20
31
|
|
|
21
32
|
const emit = defineEmits<{ 'update:modelValue': [(string | number)[]] }>()
|
|
22
33
|
|
|
@@ -83,14 +94,14 @@ function moveAllLeft() {
|
|
|
83
94
|
<!-- Source list -->
|
|
84
95
|
<div class="flex min-w-0 flex-1 flex-col overflow-hidden rounded-lg border border-outline-variant">
|
|
85
96
|
<div class="flex items-center justify-between border-b border-outline-variant bg-surface-container px-3 py-2">
|
|
86
|
-
<span class="text-label-large font-medium text-on-surface">{{ sourceTitle }}</span>
|
|
97
|
+
<span class="text-label-large font-medium text-on-surface">{{ sourceTitle ?? locale.available }}</span>
|
|
87
98
|
<span class="text-label-small text-on-surface-variant">{{ sourceItems.length }}</span>
|
|
88
99
|
</div>
|
|
89
100
|
<div v-if="filterable" class="border-b border-outline-variant px-3 py-2">
|
|
90
101
|
<input
|
|
91
102
|
v-model="sourceSearch"
|
|
92
103
|
type="text"
|
|
93
|
-
placeholder="
|
|
104
|
+
:placeholder="searchPlaceholder ?? locale.search"
|
|
94
105
|
class="w-full bg-transparent text-body-medium text-on-surface outline-none placeholder:text-on-surface-variant/50"
|
|
95
106
|
/>
|
|
96
107
|
</div>
|
|
@@ -106,9 +117,11 @@ function moveAllLeft() {
|
|
|
106
117
|
<MIcon v-if="item.icon" :name="item.icon" :size="18" class="shrink-0 text-on-surface-variant" />
|
|
107
118
|
<span class="flex-1 truncate text-body-medium text-on-surface">{{ item.label }}</span>
|
|
108
119
|
</button>
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
<slot v-if="!sourceItems.length" name="source-empty">
|
|
121
|
+
<p class="px-3 py-4 text-center text-body-small text-on-surface-variant">
|
|
122
|
+
{{ emptyText ?? locale.noItems }}
|
|
123
|
+
</p>
|
|
124
|
+
</slot>
|
|
112
125
|
</div>
|
|
113
126
|
</div>
|
|
114
127
|
|
|
@@ -116,14 +129,14 @@ function moveAllLeft() {
|
|
|
116
129
|
<div class="flex flex-col items-center justify-center gap-1">
|
|
117
130
|
<MIconButton
|
|
118
131
|
icon="keyboard_double_arrow_right"
|
|
119
|
-
label="
|
|
132
|
+
:label="moveAllRightLabel ?? locale.moveAllRight"
|
|
120
133
|
:size="36"
|
|
121
134
|
:disabled="!sourceItems.length"
|
|
122
135
|
@click="moveAllRight"
|
|
123
136
|
/>
|
|
124
137
|
<MIconButton
|
|
125
138
|
icon="chevron_right"
|
|
126
|
-
label="
|
|
139
|
+
:label="moveRightLabel ?? locale.moveRight"
|
|
127
140
|
variant="tonal"
|
|
128
141
|
:size="36"
|
|
129
142
|
:disabled="!checkedSource.size"
|
|
@@ -131,7 +144,7 @@ function moveAllLeft() {
|
|
|
131
144
|
/>
|
|
132
145
|
<MIconButton
|
|
133
146
|
icon="chevron_left"
|
|
134
|
-
label="
|
|
147
|
+
:label="moveLeftLabel ?? locale.moveLeft"
|
|
135
148
|
variant="tonal"
|
|
136
149
|
:size="36"
|
|
137
150
|
:disabled="!checkedTarget.size"
|
|
@@ -139,7 +152,7 @@ function moveAllLeft() {
|
|
|
139
152
|
/>
|
|
140
153
|
<MIconButton
|
|
141
154
|
icon="keyboard_double_arrow_left"
|
|
142
|
-
label="
|
|
155
|
+
:label="moveAllLeftLabel ?? locale.moveAllLeft"
|
|
143
156
|
:size="36"
|
|
144
157
|
:disabled="!targetItems.length"
|
|
145
158
|
@click="moveAllLeft"
|
|
@@ -149,14 +162,14 @@ function moveAllLeft() {
|
|
|
149
162
|
<!-- Target list -->
|
|
150
163
|
<div class="flex min-w-0 flex-1 flex-col overflow-hidden rounded-lg border border-outline-variant">
|
|
151
164
|
<div class="flex items-center justify-between border-b border-outline-variant bg-surface-container px-3 py-2">
|
|
152
|
-
<span class="text-label-large font-medium text-on-surface">{{ targetTitle }}</span>
|
|
165
|
+
<span class="text-label-large font-medium text-on-surface">{{ targetTitle ?? locale.selected }}</span>
|
|
153
166
|
<span class="text-label-small text-on-surface-variant">{{ targetItems.length }}</span>
|
|
154
167
|
</div>
|
|
155
168
|
<div v-if="filterable" class="border-b border-outline-variant px-3 py-2">
|
|
156
169
|
<input
|
|
157
170
|
v-model="targetSearch"
|
|
158
171
|
type="text"
|
|
159
|
-
placeholder="
|
|
172
|
+
:placeholder="searchPlaceholder ?? locale.search"
|
|
160
173
|
class="w-full bg-transparent text-body-medium text-on-surface outline-none placeholder:text-on-surface-variant/50"
|
|
161
174
|
/>
|
|
162
175
|
</div>
|
|
@@ -172,9 +185,11 @@ function moveAllLeft() {
|
|
|
172
185
|
<MIcon v-if="item.icon" :name="item.icon" :size="18" class="shrink-0 text-on-surface-variant" />
|
|
173
186
|
<span class="flex-1 truncate text-body-medium text-on-surface">{{ item.label }}</span>
|
|
174
187
|
</button>
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
<slot v-if="!targetItems.length" name="target-empty">
|
|
189
|
+
<p class="px-3 py-4 text-center text-body-small text-on-surface-variant">
|
|
190
|
+
{{ emptyText ?? locale.noItems }}
|
|
191
|
+
</p>
|
|
192
|
+
</slot>
|
|
178
193
|
</div>
|
|
179
194
|
</div>
|
|
180
195
|
</div>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { defineComponent, inject, type PropType, type Ref } from 'vue'
|
|
3
|
+
import type { DrawerItem } from './MNavigationDrawer.vue'
|
|
4
|
+
import MIcon from './MIcon.vue'
|
|
5
|
+
|
|
6
|
+
export default defineComponent({
|
|
7
|
+
name: 'MDrawerItemList',
|
|
8
|
+
components: { MIcon },
|
|
9
|
+
props: {
|
|
10
|
+
items: { type: Array as PropType<DrawerItem[]>, required: true },
|
|
11
|
+
selected: { type: [String, Number] as PropType<string | number>, default: undefined },
|
|
12
|
+
depth: { type: Number, default: 0 },
|
|
13
|
+
},
|
|
14
|
+
setup() {
|
|
15
|
+
const openItems = inject<Record<string | number, boolean>>('nd-open-items')!
|
|
16
|
+
const toggleItem = inject<(item: DrawerItem) => void>('nd-toggle-item')!
|
|
17
|
+
const selectItem = inject<(item: DrawerItem) => void>('nd-select-item')!
|
|
18
|
+
const collapsed = inject<Ref<boolean>>('nd-collapsed')!
|
|
19
|
+
return { openItems, toggleItem, selectItem, collapsed }
|
|
20
|
+
},
|
|
21
|
+
methods: {
|
|
22
|
+
itemTag(item: DrawerItem) {
|
|
23
|
+
return item.to && !item.disabled ? 'RouterLink' : 'button'
|
|
24
|
+
},
|
|
25
|
+
onClick(item: DrawerItem) {
|
|
26
|
+
if (item.children?.length) this.toggleItem(item)
|
|
27
|
+
this.selectItem(item)
|
|
28
|
+
},
|
|
29
|
+
itemStyle(item: DrawerItem): Record<string, string> {
|
|
30
|
+
const transition = 'padding 300ms cubic-bezier(0.2, 0, 0, 1)'
|
|
31
|
+
if (this.collapsed) {
|
|
32
|
+
const pad = `${(48 - this.iSize(item)) / 2}px`
|
|
33
|
+
return { paddingLeft: pad, paddingRight: pad, transition }
|
|
34
|
+
}
|
|
35
|
+
return { paddingLeft: `${this.depth * 1.25 + 3}rem`, paddingRight: '0.75rem', transition }
|
|
36
|
+
},
|
|
37
|
+
iSize(item: DrawerItem): number {
|
|
38
|
+
return item.iconSize ?? 20
|
|
39
|
+
},
|
|
40
|
+
lClass(item: DrawerItem): string {
|
|
41
|
+
return item.labelClass ?? 'text-label-medium'
|
|
42
|
+
},
|
|
43
|
+
onEnter(el: Element, done: () => void) {
|
|
44
|
+
const e = el as HTMLElement
|
|
45
|
+
e.style.overflow = 'hidden'
|
|
46
|
+
const h = e.scrollHeight
|
|
47
|
+
e.animate(
|
|
48
|
+
[{ height: '0px' }, { height: h + 'px' }],
|
|
49
|
+
{ duration: 250, easing: 'cubic-bezier(0.2, 0, 0, 1)' },
|
|
50
|
+
).onfinish = () => { e.style.overflow = ''; done() }
|
|
51
|
+
},
|
|
52
|
+
onLeave(el: Element, done: () => void) {
|
|
53
|
+
const e = el as HTMLElement
|
|
54
|
+
e.style.overflow = 'hidden'
|
|
55
|
+
const h = e.scrollHeight
|
|
56
|
+
e.animate(
|
|
57
|
+
[{ height: h + 'px' }, { height: '0px' }],
|
|
58
|
+
{ duration: 200, easing: 'cubic-bezier(0.4, 0, 1, 1)' },
|
|
59
|
+
).onfinish = done
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<template v-for="item in items" :key="item.value">
|
|
67
|
+
<component
|
|
68
|
+
:is="itemTag(item)"
|
|
69
|
+
:to="item.to && !item.disabled ? item.to : undefined"
|
|
70
|
+
:type="item.to ? undefined : 'button'"
|
|
71
|
+
:title="collapsed ? item.label : undefined"
|
|
72
|
+
class="flex w-full shrink-0 items-center gap-2.5 overflow-hidden whitespace-nowrap rounded-full text-left transition-colors focus-visible:outline-none"
|
|
73
|
+
:class="[
|
|
74
|
+
item.py ?? 'py-1.5',
|
|
75
|
+
item.disabled
|
|
76
|
+
? 'cursor-not-allowed opacity-[0.38]'
|
|
77
|
+
: item.value === selected
|
|
78
|
+
? 'bg-secondary-container text-on-secondary-container'
|
|
79
|
+
: 'cursor-pointer text-on-surface-variant hover:bg-on-surface/8',
|
|
80
|
+
]"
|
|
81
|
+
:style="itemStyle(item)"
|
|
82
|
+
:disabled="item.disabled && !item.to"
|
|
83
|
+
@click="onClick(item)"
|
|
84
|
+
>
|
|
85
|
+
<MIcon v-if="item.icon" :name="item.icon" :size="iSize(item)" class="shrink-0" />
|
|
86
|
+
<span class="nd-label min-w-0 flex-1" :class="lClass(item)">{{ item.label }}</span>
|
|
87
|
+
<MIcon
|
|
88
|
+
v-if="item.children?.length"
|
|
89
|
+
:name="openItems[item.value] ? 'expand_less' : 'expand_more'"
|
|
90
|
+
:size="16"
|
|
91
|
+
class="nd-label shrink-0 text-on-surface-variant"
|
|
92
|
+
/>
|
|
93
|
+
</component>
|
|
94
|
+
|
|
95
|
+
<Transition :css="false" @enter="onEnter" @leave="onLeave">
|
|
96
|
+
<div v-if="item.children?.length && openItems[item.value]">
|
|
97
|
+
<MDrawerItemList
|
|
98
|
+
:items="item.children"
|
|
99
|
+
:selected="selected"
|
|
100
|
+
:depth="depth + 1"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</Transition>
|
|
104
|
+
</template>
|
|
105
|
+
</template>
|
|
@@ -7,26 +7,36 @@ export interface Palette {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const palettes: Palette[] = [
|
|
10
|
-
{ id: 'purple', label: '
|
|
11
|
-
{ id: 'indigo', label: '
|
|
12
|
-
{ id: 'navy', label: '
|
|
13
|
-
{ id: 'blue', label: '
|
|
14
|
-
{ id: 'cyan', label: '
|
|
15
|
-
{ id: 'teal', label: 'Teal',
|
|
16
|
-
{ id: 'green', label: '
|
|
17
|
-
{ id: 'lime', label: '
|
|
18
|
-
{ id: 'olive', label: '
|
|
19
|
-
{ id: 'amber', label: '
|
|
20
|
-
{ id: 'sand', label: '
|
|
21
|
-
{ id: 'orange', label: '
|
|
22
|
-
{ id: 'deep-orange', label: '
|
|
23
|
-
{ id: 'brown', label: '
|
|
24
|
-
{ id: 'red', label: '
|
|
25
|
-
{ id: 'coral', label: 'Coral',
|
|
26
|
-
{ id: 'crimson', label: '
|
|
27
|
-
{ id: 'pink', label: '
|
|
28
|
-
{ id: 'violet', label: '
|
|
29
|
-
{ id: 'slate', label: '
|
|
10
|
+
{ id: 'purple', label: 'Purple', seed: '#6750A4' },
|
|
11
|
+
{ id: 'indigo', label: 'Indigo', seed: '#4355B9' },
|
|
12
|
+
{ id: 'navy', label: 'Navy', seed: '#354BA0' },
|
|
13
|
+
{ id: 'blue', label: 'Blue', seed: '#005AC1' },
|
|
14
|
+
{ id: 'cyan', label: 'Cyan', seed: '#006874' },
|
|
15
|
+
{ id: 'teal', label: 'Teal', seed: '#006B5F' },
|
|
16
|
+
{ id: 'green', label: 'Green', seed: '#386A20' },
|
|
17
|
+
{ id: 'lime', label: 'Lime', seed: '#4C6706' },
|
|
18
|
+
{ id: 'olive', label: 'Olive', seed: '#636118' },
|
|
19
|
+
{ id: 'amber', label: 'Amber', seed: '#785900' },
|
|
20
|
+
{ id: 'sand', label: 'Sand', seed: '#715C2E' },
|
|
21
|
+
{ id: 'orange', label: 'Orange', seed: '#8B5000' },
|
|
22
|
+
{ id: 'deep-orange', label: 'Deep Orange', seed: '#96480A' },
|
|
23
|
+
{ id: 'brown', label: 'Brown', seed: '#6E4C32' },
|
|
24
|
+
{ id: 'red', label: 'Red', seed: '#B82000' },
|
|
25
|
+
{ id: 'coral', label: 'Coral', seed: '#A03530' },
|
|
26
|
+
{ id: 'crimson', label: 'Crimson', seed: '#9C4068' },
|
|
27
|
+
{ id: 'pink', label: 'Pink', seed: '#9C4057' },
|
|
28
|
+
{ id: 'violet', label: 'Violet', seed: '#7C39A4' },
|
|
29
|
+
{ id: 'slate', label: 'Slate', seed: '#4A6269' },
|
|
30
|
+
{ id: 'graphite', label: 'Graphite', seed: '#5C5C5C' },
|
|
31
|
+
{ id: 'charcoal', label: 'Charcoal', seed: '#3C3C3C' },
|
|
32
|
+
{ id: 'steel', label: 'Steel', seed: '#5A6A72' },
|
|
33
|
+
{ id: 'ash', label: 'Ash', seed: '#6B6560' },
|
|
34
|
+
{ id: 'iron', label: 'Iron', seed: '#505864' },
|
|
35
|
+
{ id: 'fog', label: 'Fog', seed: '#625E6E' },
|
|
36
|
+
{ id: 'pewter', label: 'Pewter', seed: '#5A6360' },
|
|
37
|
+
{ id: 'smoke', label: 'Smoke', seed: '#4D5C66' },
|
|
38
|
+
{ id: 'stone', label: 'Stone', seed: '#68635A' },
|
|
39
|
+
{ id: 'zinc', label: 'Zinc', seed: '#585B5E' },
|
|
30
40
|
]
|
|
31
41
|
|
|
32
42
|
const current = ref(localStorage.getItem('m3-palette') ?? 'purple')
|