@m3ui-vue/m3ui-vue 0.2.0 → 0.2.2

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.
Files changed (92) hide show
  1. package/dist/{MMenuItem-CIDblhtb.js → MMenuItem-DpoEsH91.js} +195 -114
  2. package/dist/MMenuItem-DpoEsH91.js.map +1 -0
  3. package/dist/components/MAlert.vue.d.ts +1 -0
  4. package/dist/components/MButton.vue.d.ts +1 -1
  5. package/dist/components/MCalendar.vue.d.ts +3 -0
  6. package/dist/components/MChip.vue.d.ts +3 -3
  7. package/dist/components/MCommandPalette.vue.d.ts +3 -2
  8. package/dist/components/MConfirmDialog.vue.d.ts +0 -2
  9. package/dist/components/MDataTable.vue.d.ts +8 -2
  10. package/dist/components/MDatePicker.vue.d.ts +2 -0
  11. package/dist/components/MDateRangePicker.vue.d.ts +5 -0
  12. package/dist/components/MDialog.vue.d.ts +1 -0
  13. package/dist/components/MFileUpload.vue.d.ts +15 -1
  14. package/dist/components/MInfiniteScroll.vue.d.ts +7 -5
  15. package/dist/components/MMultiSelect.vue.d.ts +13 -1
  16. package/dist/components/MRichTextEditor.vue.d.ts +28 -1
  17. package/dist/components/MScheduler.vue.d.ts +5 -0
  18. package/dist/components/MSnackbar.vue.d.ts +4 -1
  19. package/dist/components/MSpinner.vue.d.ts +1 -0
  20. package/dist/components/MSpotlightSearch.vue.d.ts +3 -2
  21. package/dist/components/MTable.vue.d.ts +2 -1
  22. package/dist/components/MTimePicker.vue.d.ts +1 -0
  23. package/dist/components/MTour.vue.d.ts +3 -0
  24. package/dist/components/MTransferList.vue.d.ts +19 -3
  25. package/dist/components/MTree.vue.d.ts +1 -1
  26. package/dist/composables/useLocale.d.ts +74 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/locales/de.d.ts +2 -0
  29. package/dist/locales/es.d.ts +2 -0
  30. package/dist/locales/fr.d.ts +2 -0
  31. package/dist/locales/index.d.ts +8 -0
  32. package/dist/locales/index.js +489 -0
  33. package/dist/locales/index.js.map +1 -0
  34. package/dist/locales/ja.d.ts +2 -0
  35. package/dist/locales/ko.d.ts +2 -0
  36. package/dist/locales/pt.d.ts +2 -0
  37. package/dist/locales/zh.d.ts +2 -0
  38. package/dist/m3ui-vue.css +1 -1
  39. package/dist/m3ui.js +2734 -2647
  40. package/dist/m3ui.js.map +1 -1
  41. package/dist/plugin.d.ts +3 -1
  42. package/dist/rich-text-editor.d.ts +1 -0
  43. package/dist/rich-text-editor.js +141 -113
  44. package/dist/rich-text-editor.js.map +1 -1
  45. package/dist/styles/palettes.css +660 -0
  46. package/dist/styles.css +1 -1
  47. package/package.json +11 -2
  48. package/src/__tests__/MAlert.test.ts +59 -0
  49. package/src/__tests__/MConfirmDialog.test.ts +51 -0
  50. package/src/__tests__/MDialog.test.ts +58 -0
  51. package/src/__tests__/MSpinner.test.ts +37 -0
  52. package/src/__tests__/localePresets.test.ts +45 -0
  53. package/src/__tests__/plugin.test.ts +27 -0
  54. package/src/__tests__/setup.ts +11 -0
  55. package/src/__tests__/useColorPalette.test.ts +71 -0
  56. package/src/__tests__/useLocale.test.ts +50 -0
  57. package/src/__tests__/useTheme.test.ts +61 -0
  58. package/src/components/MAlert.vue +5 -1
  59. package/src/components/MCalendar.vue +14 -5
  60. package/src/components/MCommandPalette.vue +11 -7
  61. package/src/components/MConfirmDialog.vue +6 -5
  62. package/src/components/MDataTable.vue +18 -9
  63. package/src/components/MDatePicker.vue +11 -4
  64. package/src/components/MDateRangePicker.vue +15 -5
  65. package/src/components/MDialog.vue +5 -1
  66. package/src/components/MFileUpload.vue +22 -10
  67. package/src/components/MInfiniteScroll.vue +14 -9
  68. package/src/components/MMultiSelect.vue +11 -7
  69. package/src/components/MRichTextEditor.vue +95 -34
  70. package/src/components/MScheduler.vue +12 -4
  71. package/src/components/MSnackbar.vue +8 -1
  72. package/src/components/MSpinner.vue +5 -1
  73. package/src/components/MSpotlightSearch.vue +11 -7
  74. package/src/components/MTable.vue +8 -4
  75. package/src/components/MTimePicker.vue +5 -1
  76. package/src/components/MTour.vue +12 -9
  77. package/src/components/MTransferList.vue +30 -15
  78. package/src/composables/useColorPalette.ts +30 -20
  79. package/src/composables/useLocale.ts +181 -0
  80. package/src/index.ts +2 -0
  81. package/src/locales/de.ts +93 -0
  82. package/src/locales/es.ts +93 -0
  83. package/src/locales/fr.ts +93 -0
  84. package/src/locales/index.ts +8 -0
  85. package/src/locales/ja.ts +93 -0
  86. package/src/locales/ko.ts +93 -0
  87. package/src/locales/pt.ts +93 -0
  88. package/src/locales/zh.ts +93 -0
  89. package/src/plugin.ts +7 -1
  90. package/src/rich-text-editor.ts +1 -0
  91. package/src/styles/palettes.css +660 -0
  92. package/dist/MMenuItem-CIDblhtb.js.map +0 -1
@@ -2,6 +2,7 @@
2
2
  import { computed, ref } from 'vue'
3
3
  import MIcon from './MIcon.vue'
4
4
  import MIconButton from './MIconButton.vue'
5
+ import { useLocale } from '../composables/useLocale'
5
6
 
6
7
  export interface CalendarEvent {
7
8
  id: string | number
@@ -14,7 +15,15 @@ export interface CalendarEvent {
14
15
  const props = withDefaults(defineProps<{
15
16
  events?: CalendarEvent[]
16
17
  locale?: string
17
- }>(), { events: () => [], locale: 'es-ES' })
18
+ todayLabel?: string
19
+ prevMonthLabel?: string
20
+ nextMonthLabel?: string
21
+ }>(), {
22
+ events: () => [],
23
+ locale: 'es-ES',
24
+ })
25
+
26
+ const localeStrings = useLocale()
18
27
 
19
28
  const emit = defineEmits<{
20
29
  dateClick: [string]
@@ -96,8 +105,8 @@ const eventColor: Record<string, string> = {
96
105
  <!-- Header -->
97
106
  <div class="flex items-center justify-between border-b border-outline-variant bg-surface-container px-4 py-3">
98
107
  <div class="flex items-center gap-1">
99
- <MIconButton icon="chevron_left" label="Mes anterior" :size="36" @click="prevMonth" />
100
- <MIconButton icon="chevron_right" label="Mes siguiente" :size="36" @click="nextMonth" />
108
+ <MIconButton icon="chevron_left" :label="prevMonthLabel ?? localeStrings.previousMonth" :size="36" @click="prevMonth" />
109
+ <MIconButton icon="chevron_right" :label="nextMonthLabel ?? localeStrings.nextMonth" :size="36" @click="nextMonth" />
101
110
  </div>
102
111
  <h3 class="text-title-medium font-medium capitalize text-on-surface">{{ monthLabel }}</h3>
103
112
  <button
@@ -105,7 +114,7 @@ const eventColor: Record<string, string> = {
105
114
  class="cursor-pointer rounded-full border border-outline px-3 py-1 text-label-medium text-on-surface transition-colors hover:bg-on-surface/8"
106
115
  @click="goToday"
107
116
  >
108
- Hoy
117
+ {{ todayLabel ?? localeStrings.today }}
109
118
  </button>
110
119
  </div>
111
120
 
@@ -164,7 +173,7 @@ const eventColor: Record<string, string> = {
164
173
  v-if="day.events.length > 2"
165
174
  class="px-1 text-label-small text-on-surface-variant"
166
175
  >
167
- +{{ day.events.length - 2 }} más
176
+ +{{ day.events.length - 2 }} {{ localeStrings.more }}
168
177
  </span>
169
178
  </div>
170
179
  </div>
@@ -1,6 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
3
3
  import MIcon from './MIcon.vue'
4
+ import { useLocale } from '../composables/useLocale'
5
+
6
+ const locale = useLocale()
4
7
 
5
8
  export interface CommandItem {
6
9
  id: string
@@ -19,10 +22,11 @@ const props = withDefaults(
19
22
  placeholder?: string
20
23
  noResultsText?: string
21
24
  hotkey?: string
25
+ navigateHint?: string
26
+ selectHint?: string
27
+ closeHint?: string
22
28
  }>(),
23
29
  {
24
- placeholder: 'Buscar comando...',
25
- noResultsText: 'Sin resultados',
26
30
  hotkey: 'k',
27
31
  },
28
32
  )
@@ -136,7 +140,7 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onGlobalKeydown))
136
140
  ref="inputRef"
137
141
  v-model="query"
138
142
  type="text"
139
- :placeholder="placeholder"
143
+ :placeholder="placeholder ?? locale.searchCommand"
140
144
  class="h-12 flex-1 bg-transparent text-body-large text-on-surface outline-none placeholder:text-on-surface-variant/50"
141
145
  @keydown="onKeydown"
142
146
  />
@@ -171,20 +175,20 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onGlobalKeydown))
171
175
  </template>
172
176
  </template>
173
177
  <p v-else class="px-4 py-6 text-center text-body-medium text-on-surface-variant">
174
- {{ noResultsText }}
178
+ {{ noResultsText ?? locale.noResults }}
175
179
  </p>
176
180
  </div>
177
181
 
178
182
  <!-- Footer -->
179
183
  <div class="flex items-center gap-4 border-t border-outline-variant px-4 py-2">
180
184
  <span class="flex items-center gap-1 text-label-small text-on-surface-variant">
181
- <kbd class="rounded bg-surface-container px-1 py-0.5">↑↓</kbd> navegar
185
+ <kbd class="rounded bg-surface-container px-1 py-0.5">↑↓</kbd> {{ navigateHint ?? locale.navigateHint }}
182
186
  </span>
183
187
  <span class="flex items-center gap-1 text-label-small text-on-surface-variant">
184
- <kbd class="rounded bg-surface-container px-1 py-0.5">↵</kbd> seleccionar
188
+ <kbd class="rounded bg-surface-container px-1 py-0.5">↵</kbd> {{ selectHint ?? locale.selectHint }}
185
189
  </span>
186
190
  <span class="flex items-center gap-1 text-label-small text-on-surface-variant">
187
- <kbd class="rounded bg-surface-container px-1 py-0.5">esc</kbd> cerrar
191
+ <kbd class="rounded bg-surface-container px-1 py-0.5">esc</kbd> {{ closeHint ?? locale.closeHint }}
188
192
  </span>
189
193
  </div>
190
194
  </div>
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import MDialog from './MDialog.vue'
3
3
  import MButton from './MButton.vue'
4
+ import { useLocale } from '../composables/useLocale'
4
5
 
5
- withDefaults(
6
+ const props = withDefaults(
6
7
  defineProps<{
7
8
  modelValue: boolean
8
9
  title: string
@@ -13,13 +14,13 @@ withDefaults(
13
14
  loading?: boolean
14
15
  }>(),
15
16
  {
16
- confirmLabel: 'Confirmar',
17
- cancelLabel: 'Cancelar',
18
17
  danger: false,
19
18
  loading: false,
20
19
  },
21
20
  )
22
21
 
22
+ const locale = useLocale()
23
+
23
24
  const emit = defineEmits<{ 'update:modelValue': [boolean]; confirm: [] }>()
24
25
  </script>
25
26
 
@@ -33,10 +34,10 @@ const emit = defineEmits<{ 'update:modelValue': [boolean]; confirm: [] }>()
33
34
  <p class="text-body-medium text-on-surface-variant">{{ message }}</p>
34
35
  <template #actions>
35
36
  <MButton variant="text" :disabled="loading" @click="emit('update:modelValue', false)">
36
- {{ cancelLabel }}
37
+ {{ cancelLabel ?? locale.cancel }}
37
38
  </MButton>
38
39
  <MButton :color="danger ? 'error' : 'primary'" :loading="loading" @click="emit('confirm')">
39
- {{ confirmLabel }}
40
+ {{ confirmLabel ?? locale.confirm }}
40
41
  </MButton>
41
42
  </template>
42
43
  </MDialog>
@@ -5,6 +5,9 @@ import MIcon from './MIcon.vue'
5
5
  import MIconButton from './MIconButton.vue'
6
6
  import MPagination from './MPagination.vue'
7
7
  import MChip from './MChip.vue'
8
+ import { useLocale } from '../composables/useLocale'
9
+
10
+ const locale = useLocale()
8
11
 
9
12
  export interface DataTableColumn {
10
13
  key: string
@@ -46,9 +49,15 @@ const props = withDefaults(defineProps<{
46
49
  serverSide?: boolean
47
50
  total?: number
48
51
  page?: number
52
+ searchPlaceholder?: string
53
+ selectedText?: string
54
+ recordsText?: string
55
+ expandLabel?: string
56
+ columnsLabel?: string
57
+ exportLabel?: string
58
+ noGroupText?: string
49
59
  }>(), {
50
60
  loading: false,
51
- emptyText: 'Sin resultados',
52
61
  rowKey: 'id',
53
62
  selectable: false,
54
63
  modelValue: () => [],
@@ -137,7 +146,7 @@ const groupedRows = computed(() => {
137
146
  if (!props.groupBy) return null
138
147
  const map = new Map<string, Record<string, any>[]>()
139
148
  for (const row of processedRows.value) {
140
- const key = String(row[props.groupBy] ?? 'Sin grupo')
149
+ const key = String(row[props.groupBy] ?? (props.noGroupText ?? locale.noGroup))
141
150
  if (!map.has(key)) map.set(key, [])
142
151
  map.get(key)!.push(row)
143
152
  }
@@ -269,7 +278,7 @@ function colStyle(col: DataTableColumn) {
269
278
  >
270
279
  <div v-if="searchable" class="flex min-w-48 flex-1 items-center gap-2 rounded-full border border-outline-variant bg-surface-container px-3 py-1.5 transition-[border-color,box-shadow] duration-150 focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/30">
271
280
  <MIcon name="search" :size="16" class="shrink-0 text-on-surface-variant" />
272
- <input v-model="search" type="text" placeholder="Buscar..." class="w-full bg-transparent text-body-medium text-on-surface outline-none placeholder:text-on-surface-variant" />
281
+ <input v-model="search" type="text" :placeholder="searchPlaceholder ?? locale.search" class="w-full bg-transparent text-body-medium text-on-surface outline-none placeholder:text-on-surface-variant" />
273
282
  <button v-if="search" class="text-on-surface-variant transition-colors hover:text-on-surface" @click="search = ''">
274
283
  <MIcon name="close" :size="14" />
275
284
  </button>
@@ -279,13 +288,13 @@ function colStyle(col: DataTableColumn) {
279
288
 
280
289
  <Transition enter-active-class="transition-[opacity,transform] duration-150" enter-from-class="opacity-0 scale-90" leave-active-class="transition-[opacity,transform] duration-100" leave-to-class="opacity-0 scale-90">
281
290
  <span v-if="selectable && selected.length > 0" class="rounded-full bg-primary/12 px-3 py-1 text-label-small font-medium text-primary">
282
- {{ selected.length }} seleccionado{{ selected.length !== 1 ? 's' : '' }}
291
+ {{ selected.length }} {{ selectedText ?? locale.selectedCount }}{{ selected.length !== 1 ? 's' : '' }}
283
292
  </span>
284
293
  </Transition>
285
294
 
286
295
  <!-- Column toggle -->
287
296
  <div v-if="columnToggle" class="relative">
288
- <MIconButton icon="view_column" label="Columnas" :size="36" @click="showColMenu = !showColMenu" />
297
+ <MIconButton icon="view_column" :label="columnsLabel ?? locale.columns" :size="36" @click="showColMenu = !showColMenu" />
289
298
  <div v-if="showColMenu" class="absolute right-0 top-full z-10 mt-1 min-w-40 rounded-lg bg-surface-container py-2 shadow-elevation-3">
290
299
  <label v-for="col in columns" :key="col.key" class="flex cursor-pointer items-center gap-2 px-3 py-1.5 hover:bg-on-surface/4">
291
300
  <MCheckbox
@@ -297,7 +306,7 @@ function colStyle(col: DataTableColumn) {
297
306
  </div>
298
307
  </div>
299
308
 
300
- <MIconButton v-if="exportable" icon="download" label="Exportar CSV" :size="36" @click="exportCSV" />
309
+ <MIconButton v-if="exportable" icon="download" :label="exportLabel ?? locale.exportCsv" :size="36" @click="exportCSV" />
301
310
  </div>
302
311
 
303
312
  <!-- Table -->
@@ -363,7 +372,7 @@ function colStyle(col: DataTableColumn) {
363
372
  <td :colspan="visibleColumns.length + extraCols" class="border-t border-outline-variant px-4 py-14 text-center">
364
373
  <slot name="empty">
365
374
  <MIcon name="search_off" :size="36" class="mb-2 text-on-surface-variant opacity-30" />
366
- <p class="text-body-medium text-on-surface-variant">{{ emptyText }}</p>
375
+ <p class="text-body-medium text-on-surface-variant">{{ emptyText ?? locale.noResults }}</p>
367
376
  </slot>
368
377
  </td>
369
378
  </tr>
@@ -385,7 +394,7 @@ function colStyle(col: DataTableColumn) {
385
394
  <td v-if="hasExpand" class="px-2" :class="dense ? 'py-1' : 'py-2'" @click.stop>
386
395
  <MIconButton
387
396
  icon="expand_more"
388
- label="Expandir"
397
+ :label="expandLabel ?? locale.expand"
389
398
  :size="28"
390
399
  :class="isExpanded(row) ? 'rotate-180' : ''"
391
400
  class="transition-transform duration-200"
@@ -429,7 +438,7 @@ function colStyle(col: DataTableColumn) {
429
438
  <!-- Footer -->
430
439
  <div class="flex items-center justify-between gap-4 border-t border-outline-variant bg-surface-container-lowest px-4 py-2">
431
440
  <span class="text-label-small text-on-surface-variant">
432
- {{ totalCount }} registro{{ totalCount !== 1 ? 's' : '' }}
441
+ {{ totalCount }} {{ recordsText ?? locale.recordCount }}{{ totalCount !== 1 ? 's' : '' }}
433
442
  </span>
434
443
  <MPagination :page="currentPage" :per-page="perPage" :total="totalCount" @update:page="currentPage = $event" />
435
444
  </div>
@@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
3
3
  import MIcon from './MIcon.vue'
4
4
  import MIconButton from './MIconButton.vue'
5
5
  import { useFieldBg } from '../composables/useFieldBg'
6
+ import { useLocale } from '../composables/useLocale'
6
7
 
7
8
  const props = withDefaults(defineProps<{
8
9
  modelValue: string | null
@@ -15,7 +16,13 @@ const props = withDefaults(defineProps<{
15
16
  hint?: string
16
17
  locale?: string
17
18
  fieldBg?: string
18
- }>(), { locale: 'es-ES' })
19
+ prevMonthLabel?: string
20
+ nextMonthLabel?: string
21
+ }>(), {
22
+ locale: 'es-ES',
23
+ })
24
+
25
+ const localeStrings = useLocale()
19
26
 
20
27
  const emit = defineEmits<{ 'update:modelValue': [string | null] }>()
21
28
 
@@ -173,7 +180,7 @@ onUnmounted(() => {
173
180
  >
174
181
  <MIcon name="calendar_today" :size="20" class="shrink-0 text-on-surface-variant" />
175
182
  <span v-if="displayValue" class="flex-1 text-on-surface">{{ displayValue }}</span>
176
- <span v-else class="flex-1 text-on-surface-variant">{{ placeholder || label || 'Seleccionar fecha' }}</span>
183
+ <span v-else class="flex-1 text-on-surface-variant">{{ placeholder || label || localeStrings.selectDate }}</span>
177
184
  <MIcon
178
185
  v-if="modelValue"
179
186
  name="close"
@@ -210,9 +217,9 @@ onUnmounted(() => {
210
217
  >
211
218
  <!-- Header -->
212
219
  <div class="mb-3 flex items-center justify-between">
213
- <MIconButton icon="chevron_left" label="Mes anterior" :size="36" @click="prevMonth" />
220
+ <MIconButton icon="chevron_left" :label="prevMonthLabel ?? localeStrings.previousMonth" :size="36" @click="prevMonth" />
214
221
  <span class="text-title-small font-medium capitalize text-on-surface">{{ monthLabel }}</span>
215
- <MIconButton icon="chevron_right" label="Mes siguiente" :size="36" @click="nextMonth" />
222
+ <MIconButton icon="chevron_right" :label="nextMonthLabel ?? localeStrings.nextMonth" :size="36" @click="nextMonth" />
216
223
  </div>
217
224
 
218
225
  <!-- Weekday headers -->
@@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
3
3
  import MIcon from './MIcon.vue'
4
4
  import MIconButton from './MIconButton.vue'
5
5
  import { useFieldBg } from '../composables/useFieldBg'
6
+ import { useLocale } from '../composables/useLocale'
6
7
 
7
8
  export interface DateRange {
8
9
  start: string | null
@@ -12,6 +13,7 @@ export interface DateRange {
12
13
  const props = withDefaults(defineProps<{
13
14
  modelValue: DateRange
14
15
  label?: string
16
+ placeholder?: string
15
17
  min?: string
16
18
  max?: string
17
19
  disabled?: boolean
@@ -19,7 +21,15 @@ const props = withDefaults(defineProps<{
19
21
  hint?: string
20
22
  locale?: string
21
23
  fieldBg?: string
22
- }>(), { locale: 'es-ES' })
24
+ prevMonthLabel?: string
25
+ nextMonthLabel?: string
26
+ pickStartText?: string
27
+ pickEndText?: string
28
+ }>(), {
29
+ locale: 'es-ES',
30
+ })
31
+
32
+ const localeStrings = useLocale()
23
33
 
24
34
  const emit = defineEmits<{ 'update:modelValue': [DateRange] }>()
25
35
 
@@ -184,7 +194,7 @@ onUnmounted(() => {
184
194
  >
185
195
  <MIcon name="date_range" :size="20" class="shrink-0 text-on-surface-variant" />
186
196
  <span v-if="displayValue" class="flex-1 text-on-surface">{{ displayValue }}</span>
187
- <span v-else class="flex-1 text-on-surface-variant">{{ label || 'Seleccionar rango' }}</span>
197
+ <span v-else class="flex-1 text-on-surface-variant">{{ label || placeholder || localeStrings.selectRange }}</span>
188
198
  <MIcon
189
199
  v-if="modelValue.start || modelValue.end"
190
200
  name="close"
@@ -219,13 +229,13 @@ onUnmounted(() => {
219
229
  :style="dropPos"
220
230
  >
221
231
  <p class="mb-2 text-center text-label-medium text-on-surface-variant">
222
- {{ picking === 'start' ? 'Selecciona inicio' : 'Selecciona fin' }}
232
+ {{ picking === 'start' ? (pickStartText ?? localeStrings.pickStart) : (pickEndText ?? localeStrings.pickEnd) }}
223
233
  </p>
224
234
 
225
235
  <div class="mb-3 flex items-center justify-between">
226
- <MIconButton icon="chevron_left" label="Anterior" :size="36" @click="prevMonth" />
236
+ <MIconButton icon="chevron_left" :label="prevMonthLabel ?? localeStrings.previousMonth" :size="36" @click="prevMonth" />
227
237
  <span class="text-title-small font-medium capitalize text-on-surface">{{ monthLabel }}</span>
228
- <MIconButton icon="chevron_right" label="Siguiente" :size="36" @click="nextMonth" />
238
+ <MIconButton icon="chevron_right" :label="nextMonthLabel ?? localeStrings.nextMonth" :size="36" @click="nextMonth" />
229
239
  </div>
230
240
 
231
241
  <div class="mb-1 grid grid-cols-7 gap-0.5 text-center">
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { watch } from 'vue'
3
3
  import MIconButton from './MIconButton.vue'
4
+ import { useLocale } from '../composables/useLocale'
4
5
 
5
6
  const props = withDefaults(
6
7
  defineProps<{
@@ -8,6 +9,7 @@ const props = withDefaults(
8
9
  title?: string
9
10
  maxWidth?: string
10
11
  persistent?: boolean
12
+ closeLabel?: string
11
13
  }>(),
12
14
  {
13
15
  maxWidth: 'max-w-md',
@@ -15,6 +17,8 @@ const props = withDefaults(
15
17
  },
16
18
  )
17
19
 
20
+ const locale = useLocale()
21
+
18
22
  const emit = defineEmits<{ 'update:modelValue': [boolean] }>()
19
23
 
20
24
  function close() {
@@ -56,7 +60,7 @@ watch(
56
60
  <h2 class="text-headline-small text-on-surface">
57
61
  <slot name="title">{{ title }}</slot>
58
62
  </h2>
59
- <MIconButton v-if="!persistent" icon="close" label="Cerrar" @click="close" />
63
+ <MIconButton v-if="!persistent" icon="close" :label="closeLabel ?? locale.close" @click="close" />
60
64
  </div>
61
65
  <div class="overflow-y-auto px-6 py-2 text-body-medium text-on-surface-variant">
62
66
  <slot />
@@ -3,6 +3,9 @@ import { ref, computed } from 'vue'
3
3
  import MIcon from './MIcon.vue'
4
4
  import MIconButton from './MIconButton.vue'
5
5
  import MSpinner from './MSpinner.vue'
6
+ import { useLocale } from '../composables/useLocale'
7
+
8
+ const locale = useLocale()
6
9
 
7
10
  export interface UploadFile {
8
11
  file: File
@@ -18,8 +21,15 @@ const props = withDefaults(
18
21
  multiple?: boolean
19
22
  maxSize?: number
20
23
  disabled?: boolean
24
+ dropText?: string
25
+ selectText?: string
26
+ maxSizePrefix?: string
27
+ removeLabel?: string
21
28
  }>(),
22
- { multiple: false, disabled: false },
29
+ {
30
+ multiple: false,
31
+ disabled: false,
32
+ },
23
33
  )
24
34
 
25
35
  const emit = defineEmits<{
@@ -128,14 +138,16 @@ function openPicker() {
128
138
  class="text-on-surface-variant"
129
139
  />
130
140
  <div class="text-center">
131
- <p class="text-body-large text-on-surface">
132
- Arrastra archivos aquí o <span class="font-medium text-primary">selecciona</span>
133
- </p>
134
- <p v-if="accept || maxSize" class="mt-1 text-body-small text-on-surface-variant">
135
- <span v-if="accept">{{ accept }}</span>
136
- <span v-if="accept && maxSize"> · </span>
137
- <span v-if="maxSize">Máx. {{ formatSize(maxSize) }}</span>
138
- </p>
141
+ <slot name="dropzone">
142
+ <p class="text-body-large text-on-surface">
143
+ {{ dropText ?? locale.dropText }} <span class="font-medium text-primary">{{ selectText ?? locale.selectText }}</span>
144
+ </p>
145
+ <p v-if="accept || maxSize" class="mt-1 text-body-small text-on-surface-variant">
146
+ <span v-if="accept">{{ accept }}</span>
147
+ <span v-if="accept && maxSize"> · </span>
148
+ <span v-if="maxSize">{{ maxSizePrefix ?? locale.maxSizePrefix }} {{ formatSize(maxSize) }}</span>
149
+ </p>
150
+ </slot>
139
151
  </div>
140
152
  </div>
141
153
 
@@ -187,7 +199,7 @@ function openPicker() {
187
199
  <MIcon v-else-if="entry.status === 'done'" name="check_circle" :size="20" class="text-success" />
188
200
  <MIcon v-else-if="entry.status === 'error'" name="error" :size="20" class="text-error" />
189
201
 
190
- <MIconButton icon="close" label="Eliminar" :size="32" @click="removeFile(entry)" />
202
+ <MIconButton icon="close" :label="removeLabel ?? locale.remove" :size="32" @click="removeFile(entry)" />
191
203
  </div>
192
204
  </TransitionGroup>
193
205
  </div>
@@ -1,6 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
3
3
  import MSpinner from './MSpinner.vue'
4
+ import { useLocale } from '../composables/useLocale'
5
+
6
+ const locale = useLocale()
4
7
 
5
8
  const props = withDefaults(
6
9
  defineProps<{
@@ -15,8 +18,6 @@ const props = withDefaults(
15
18
  loading: false,
16
19
  disabled: false,
17
20
  threshold: 100,
18
- loadingText: 'Cargando...',
19
- endText: 'No hay más elementos',
20
21
  ended: false,
21
22
  },
22
23
  )
@@ -55,13 +56,17 @@ onBeforeUnmount(() => observer?.disconnect())
55
56
  <slot />
56
57
 
57
58
  <div ref="sentinelRef" class="flex items-center justify-center py-4">
58
- <div v-if="loading" class="flex items-center gap-3">
59
- <MSpinner :size="20" class="text-primary" />
60
- <span class="text-body-medium text-on-surface-variant">{{ loadingText }}</span>
61
- </div>
62
- <p v-else-if="ended" class="text-body-small text-on-surface-variant">
63
- {{ endText }}
64
- </p>
59
+ <slot v-if="loading" name="loading">
60
+ <div class="flex items-center gap-3">
61
+ <MSpinner :size="20" class="text-primary" />
62
+ <span class="text-body-medium text-on-surface-variant">{{ loadingText ?? locale.loadingMore }}</span>
63
+ </div>
64
+ </slot>
65
+ <slot v-else-if="ended" name="end">
66
+ <p class="text-body-small text-on-surface-variant">
67
+ {{ endText ?? locale.noMoreItems }}
68
+ </p>
69
+ </slot>
65
70
  <slot v-else name="idle" />
66
71
  </div>
67
72
  </div>
@@ -3,6 +3,9 @@ import { computed, ref, useId, onMounted, onUnmounted, nextTick } from 'vue'
3
3
  import MIcon from './MIcon.vue'
4
4
  import MCheckbox from './MCheckbox.vue'
5
5
  import { useFieldBg } from '../composables/useFieldBg'
6
+ import { useLocale } from '../composables/useLocale'
7
+
8
+ const locale = useLocale()
6
9
 
7
10
  export interface MultiSelectOption {
8
11
  label: string
@@ -26,6 +29,8 @@ const props = withDefaults(
26
29
  searchable?: boolean
27
30
  maxChips?: number
28
31
  clearable?: boolean
32
+ searchPlaceholder?: string
33
+ noResultsText?: string
29
34
  }>(),
30
35
  {
31
36
  modelValue: () => [],
@@ -299,7 +304,7 @@ const labelClasses = computed(() => {
299
304
  ref="searchInput"
300
305
  v-model="search"
301
306
  type="text"
302
- placeholder="Buscar..."
307
+ :placeholder="searchPlaceholder ?? locale.search"
303
308
  class="w-full bg-transparent text-body-medium text-on-surface outline-none placeholder:text-on-surface-variant"
304
309
  />
305
310
  </div>
@@ -319,12 +324,11 @@ const labelClasses = computed(() => {
319
324
  />
320
325
  <span class="text-body-large text-on-surface">{{ opt.label }}</span>
321
326
  </label>
322
- <p
323
- v-if="filteredOptions.length === 0"
324
- class="px-4 py-3 text-center text-body-small text-on-surface-variant"
325
- >
326
- Sin resultados
327
- </p>
327
+ <slot v-if="filteredOptions.length === 0" name="no-results">
328
+ <p class="px-4 py-3 text-center text-body-small text-on-surface-variant">
329
+ {{ noResultsText ?? locale.noResults }}
330
+ </p>
331
+ </slot>
328
332
  </div>
329
333
  </div>
330
334
  </Transition>