@saasmakers/ui 2.0.68 → 2.0.69

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.
@@ -0,0 +1,755 @@
1
+ <script lang="ts" setup>
2
+ import type { FieldDate, FieldDatePreset } from '../../types/fields'
3
+ import { getRelativeDateMessage } from '../../i18n/relativeDate'
4
+ import { buildCalendarGrid, formatMonthLabel, formatPresetSub, getNextMonday, getNextSaturday, isBeforeDay, isSameDay, resolveLocaleCode, startOfLocalDay } from '../../utils/dateField'
5
+
6
+ const props = withDefaults(defineProps<Omit<FieldDate, 'modelValue'>>(), {
7
+ background: 'white',
8
+ description: '',
9
+ disabled: false,
10
+ hideError: true,
11
+ label: '',
12
+ labelIcon: undefined,
13
+ min: undefined,
14
+ placeholder: '',
15
+ presets: undefined,
16
+ required: false,
17
+ size: 'base',
18
+ validation: undefined,
19
+ weekStartsOn: 1,
20
+ })
21
+
22
+ const emit = defineEmits<{
23
+ close: []
24
+ open: []
25
+ }>()
26
+
27
+ const { locale, t } = useI18n()
28
+
29
+ const localeCode = computed(() => {
30
+ return resolveLocaleCode(locale.value)
31
+ })
32
+
33
+ const id = useId()
34
+ const chipListId = `${id}-presets`
35
+ const modelValue = defineModel<FieldDate['modelValue']>({ default: null })
36
+ const opened = ref(false)
37
+ const calendarOpen = ref(false)
38
+ const panelRef = ref<HTMLElement>()
39
+ const chipListRef = ref<HTMLElement>()
40
+ const viewMonth = ref(startOfLocalDay(new Date()))
41
+ const focusedDayIndex = ref(0)
42
+ const { absoluteLabel, isOverdue, relativeLabel } = useRelativeDate(modelValue)
43
+ const prefersReducedMotion = usePreferredReducedMotion()
44
+
45
+ const panelAnimation = computed(() => {
46
+ if (prefersReducedMotion.value === 'reduce') {
47
+ return {
48
+ animate: { opacity: 1 },
49
+ initial: { opacity: 1 },
50
+ }
51
+ }
52
+
53
+ return fadeIn
54
+ })
55
+
56
+ const triggerIconSize = computed<BaseSize>(() => {
57
+ switch (props.size) {
58
+ case 'lg':
59
+ return '2xl'
60
+ case 'sm':
61
+ return 'lg'
62
+ default:
63
+ return 'xl'
64
+ }
65
+ })
66
+
67
+ const clearIconSize = computed<BaseSize>(() => {
68
+ switch (props.size) {
69
+ case 'lg':
70
+ return 'lg'
71
+ case 'sm':
72
+ return 'base'
73
+ default:
74
+ return 'base'
75
+ }
76
+ })
77
+
78
+ const today = computed(() => {
79
+ return startOfLocalDay(new Date())
80
+ })
81
+
82
+ const computedPresets = computed(() => {
83
+ if (props.presets) {
84
+ return props.presets
85
+ }
86
+
87
+ const currentToday = today.value
88
+ const weekday = currentToday.getDay()
89
+ const tomorrow = new Date(currentToday)
90
+
91
+ tomorrow.setDate(tomorrow.getDate() + 1)
92
+
93
+ const tomorrowDate = startOfLocalDay(tomorrow)
94
+
95
+ const items: FieldDatePreset[] = [
96
+ {
97
+ date: currentToday,
98
+ id: 'today',
99
+ label: getRelativeDateMessage(localeCode.value, 'today'),
100
+ sub: formatPresetSub(currentToday, localeCode.value),
101
+ },
102
+ {
103
+ date: tomorrowDate,
104
+ id: 'tomorrow',
105
+ label: getRelativeDateMessage(localeCode.value, 'tomorrow'),
106
+ sub: formatPresetSub(tomorrowDate, localeCode.value),
107
+ },
108
+ ]
109
+
110
+ if (weekday !== 0 && weekday !== 6) {
111
+ const weekendDate = getNextSaturday(currentToday)
112
+
113
+ items.push({
114
+ date: weekendDate,
115
+ id: 'weekend',
116
+ label: t('weekend'),
117
+ sub: formatPresetSub(weekendDate, localeCode.value),
118
+ })
119
+ }
120
+
121
+ const nextWeekDate = getNextMonday(currentToday)
122
+
123
+ items.push({
124
+ date: nextWeekDate,
125
+ id: 'nextWeek',
126
+ label: t('nextWeek'),
127
+ sub: formatPresetSub(nextWeekDate, localeCode.value),
128
+ })
129
+
130
+ return items
131
+ })
132
+
133
+ const chooseChipIndex = computed(() => {
134
+ return computedPresets.value.length
135
+ })
136
+
137
+ const chipsCount = computed(() => {
138
+ return chooseChipIndex.value + 1
139
+ })
140
+
141
+ const { highlightedIndex, onArrowKey, onHighlight, resetHighlight } = useChipNavigation({
142
+ containerRef: chipListRef,
143
+ count: chipsCount,
144
+ })
145
+
146
+ const calendarCells = computed(() => {
147
+ return buildCalendarGrid({
148
+ viewMonth: viewMonth.value,
149
+ weekStartsOn: props.weekStartsOn,
150
+ })
151
+ })
152
+
153
+ const monthLabel = computed(() => {
154
+ return formatMonthLabel(viewMonth.value, localeCode.value)
155
+ })
156
+
157
+ const weekdayLabels = computed(() => {
158
+ const labels: string[] = []
159
+ const referenceMonday = new Date(2024, 0, 1)
160
+
161
+ for (let index = 0; index < 7; index += 1) {
162
+ const date = new Date(referenceMonday)
163
+
164
+ date.setDate(referenceMonday.getDate() + ((props.weekStartsOn + index - 1 + 7) % 7))
165
+ labels.push(new Intl.DateTimeFormat(localeCode.value, { weekday: 'narrow' }).format(date))
166
+ }
167
+
168
+ return labels
169
+ })
170
+
171
+ watch(opened, (isOpened) => {
172
+ if (isOpened) {
173
+ emit('open')
174
+ resetHighlight(resolveInitialChipHighlight())
175
+
176
+ nextTick(() => {
177
+ chipListRef.value?.focus()
178
+ panelRef.value?.scrollIntoView({
179
+ behavior: prefersReducedMotion.value === 'reduce' ? 'auto' : 'smooth',
180
+ block: 'nearest',
181
+ })
182
+ })
183
+ }
184
+ else {
185
+ calendarOpen.value = false
186
+
187
+ emit('close')
188
+ }
189
+ })
190
+
191
+ watch(modelValue, (value) => {
192
+ if (value) {
193
+ viewMonth.value = startOfLocalDay(new Date(value.getFullYear(), value.getMonth(), 1))
194
+ }
195
+ }, { immediate: true })
196
+
197
+ function closePanel() {
198
+ opened.value = false
199
+ calendarOpen.value = false
200
+ }
201
+
202
+ function onChipEnter() {
203
+ const index = highlightedIndex.value
204
+
205
+ if (index === chooseChipIndex.value) {
206
+ onOpenCalendar()
207
+
208
+ return
209
+ }
210
+
211
+ const preset = computedPresets.value[index]
212
+
213
+ if (preset) {
214
+ onPresetClick(preset)
215
+ }
216
+ }
217
+
218
+ function onChipListKeyDown(event: KeyboardEvent) {
219
+ if (event.key === 'ArrowDown') {
220
+ onArrowKey(event, 'down')
221
+ }
222
+ else if (event.key === 'ArrowUp') {
223
+ onArrowKey(event, 'up')
224
+ }
225
+ else if (event.key === 'ArrowLeft') {
226
+ onArrowKey(event, 'left')
227
+ }
228
+ else if (event.key === 'ArrowRight') {
229
+ onArrowKey(event, 'right')
230
+ }
231
+ else if (event.key === 'Enter') {
232
+ event.preventDefault()
233
+ onChipEnter()
234
+ }
235
+ else if (event.key === 'Escape') {
236
+ event.preventDefault()
237
+ closePanel()
238
+ }
239
+ }
240
+
241
+ function onChipMouseDown(event: MouseEvent) {
242
+ event.preventDefault()
243
+ }
244
+
245
+ function resolveInitialChipHighlight() {
246
+ const selectedIndex = computedPresets.value.findIndex(preset => isPresetSelected(preset))
247
+
248
+ if (selectedIndex >= 0) {
249
+ return selectedIndex
250
+ }
251
+
252
+ if (calendarOpen.value) {
253
+ return chooseChipIndex.value
254
+ }
255
+
256
+ return 0
257
+ }
258
+
259
+ function isDayDisabled(date: Date) {
260
+ return isBeforeDay(date, props.min)
261
+ }
262
+
263
+ function isPresetSelected(preset: FieldDatePreset) {
264
+ return modelValue.value ? isSameDay(preset.date, modelValue.value) : false
265
+ }
266
+
267
+ function onClearClick(event: MouseEvent) {
268
+ event.stopPropagation()
269
+
270
+ modelValue.value = null
271
+
272
+ closePanel()
273
+ }
274
+
275
+ function onContainerClick() {
276
+ if (!props.disabled) {
277
+ opened.value = !opened.value
278
+ }
279
+ }
280
+
281
+ function onContainerKeyDown(event: KeyboardEvent) {
282
+ if (!props.disabled && ['Enter', 'Space'].includes(event.code)) {
283
+ event.preventDefault()
284
+
285
+ opened.value = !opened.value
286
+ }
287
+ }
288
+
289
+ function onDayClick(date: Date) {
290
+ if (!isDayDisabled(date)) {
291
+ selectDate(date)
292
+ }
293
+ }
294
+
295
+ function onCloseCalendar() {
296
+ calendarOpen.value = false
297
+ resetHighlight(chooseChipIndex.value)
298
+
299
+ nextTick(() => {
300
+ chipListRef.value?.focus()
301
+ })
302
+ }
303
+
304
+ function onDayKeyDown(event: KeyboardEvent, date: Date, index: number) {
305
+ if (event.key === 'Escape') {
306
+ event.preventDefault()
307
+ onCloseCalendar()
308
+
309
+ return
310
+ }
311
+
312
+ if (isDayDisabled(date)) {
313
+ return
314
+ }
315
+
316
+ if (event.key === 'Enter' || event.key === ' ') {
317
+ event.preventDefault()
318
+ selectDate(date)
319
+ }
320
+ else if (event.key === 'ArrowLeft') {
321
+ event.preventDefault()
322
+
323
+ focusedDayIndex.value = Math.max(0, index - 1)
324
+ }
325
+ else if (event.key === 'ArrowRight') {
326
+ event.preventDefault()
327
+
328
+ focusedDayIndex.value = Math.min(calendarCells.value.length - 1, index + 1)
329
+ }
330
+ else if (event.key === 'ArrowUp') {
331
+ event.preventDefault()
332
+
333
+ focusedDayIndex.value = Math.max(0, index - 7)
334
+ }
335
+ else if (event.key === 'ArrowDown') {
336
+ event.preventDefault()
337
+
338
+ focusedDayIndex.value = Math.min(calendarCells.value.length - 1, index + 7)
339
+ }
340
+ }
341
+
342
+ function syncFocusedDayIndex() {
343
+ const selectedIndex = calendarCells.value.findIndex(cell => modelValue.value && isSameDay(cell.date, modelValue.value))
344
+
345
+ focusedDayIndex.value = selectedIndex >= 0
346
+ ? selectedIndex
347
+ : calendarCells.value.findIndex(cell => cell.inMonth)
348
+ }
349
+
350
+ function onNextMonth() {
351
+ viewMonth.value = new Date(viewMonth.value.getFullYear(), viewMonth.value.getMonth() + 1, 1)
352
+
353
+ nextTick(() => {
354
+ syncFocusedDayIndex()
355
+ panelRef.value?.querySelector<HTMLElement>('[role="gridcell"][tabindex="0"]')?.focus()
356
+ })
357
+ }
358
+
359
+ function onOpenCalendar() {
360
+ calendarOpen.value = true
361
+ resetHighlight(chooseChipIndex.value)
362
+ syncFocusedDayIndex()
363
+
364
+ nextTick(() => {
365
+ panelRef.value?.scrollIntoView({
366
+ behavior: prefersReducedMotion.value === 'reduce' ? 'auto' : 'smooth',
367
+ block: 'nearest',
368
+ })
369
+
370
+ panelRef.value?.querySelector<HTMLElement>('[role="gridcell"][tabindex="0"]')?.focus()
371
+ })
372
+ }
373
+
374
+ function onPresetClick(preset: FieldDatePreset) {
375
+ selectDate(preset.date)
376
+ }
377
+
378
+ function onPreviousMonth() {
379
+ viewMonth.value = new Date(viewMonth.value.getFullYear(), viewMonth.value.getMonth() - 1, 1)
380
+
381
+ nextTick(() => {
382
+ syncFocusedDayIndex()
383
+ panelRef.value?.querySelector<HTMLElement>('[role="gridcell"][tabindex="0"]')?.focus()
384
+ })
385
+ }
386
+
387
+ function selectDate(date: Date) {
388
+ modelValue.value = startOfLocalDay(date)
389
+
390
+ closePanel()
391
+ }
392
+ </script>
393
+
394
+ <template>
395
+ <div>
396
+ <FieldLabel
397
+ v-if="label"
398
+ :disabled="disabled"
399
+ :for-field="id"
400
+ has-margin-bottom
401
+ :icon="labelIcon"
402
+ :label="label"
403
+ :required="required"
404
+ :size="size"
405
+ />
406
+
407
+ <div
408
+ class="field-uppercase select-none overflow-hidden base-box"
409
+ :class="{
410
+ 'field-disabled': disabled,
411
+ 'base-border-interactive-focus-within': !disabled,
412
+ 'bg-gray-100 dark:bg-gray-900': background === 'gray',
413
+ 'bg-white dark:bg-gray-900': background === 'white',
414
+ }"
415
+ >
416
+ <div
417
+ :id="id"
418
+ class="flex cursor-pointer items-center gap-2 outline-none"
419
+ :class="{
420
+ 'h-10': size === 'sm',
421
+ 'h-12': size === 'base',
422
+ 'h-14': size === 'lg',
423
+ 'pl-3 pr-0': modelValue,
424
+ 'px-3': !modelValue,
425
+ }"
426
+ role="button"
427
+ :aria-disabled="disabled"
428
+ :aria-expanded="opened"
429
+ tabindex="0"
430
+ @click="onContainerClick"
431
+ @keydown="onContainerKeyDown"
432
+ >
433
+ <BaseIcon
434
+ class="shrink-0"
435
+ color="gray"
436
+ :icon="getLayerIcon('calendar')"
437
+ :size="triggerIconSize"
438
+ />
439
+
440
+ <div
441
+ class="min-w-0 flex-1 truncate font-medium tracking-tight uppercase leading-normal"
442
+ :class="{
443
+ 'pr-2': modelValue,
444
+ 'text-xs': size === 'sm',
445
+ 'text-sm': size === 'base',
446
+ 'text-base': size === 'lg',
447
+ }"
448
+ >
449
+ <template v-if="modelValue">
450
+ <span
451
+ :class="{
452
+ 'text-orange-600 dark:text-orange-400': isOverdue,
453
+ 'text-gray-900 dark:text-gray-100': !isOverdue,
454
+ }"
455
+ >{{ relativeLabel }}</span><span class="text-gray-600 font-medium dark:text-gray-400"> · {{ absoluteLabel }}</span>
456
+ </template>
457
+
458
+ <span
459
+ v-else
460
+ class="truncate text-gray-600 dark:text-gray-400"
461
+ >
462
+ {{ placeholder || t('placeholder') }}
463
+ </span>
464
+ </div>
465
+
466
+ <button
467
+ v-if="modelValue"
468
+ :aria-label="t('clear')"
469
+ class="base-control-hover mr-3 inline-flex shrink-0 items-center self-center justify-center rounded-full p-1 text-gray-600 outline-none transition-[transform,colors] hover:scale-105 dark:text-gray-400"
470
+ type="button"
471
+ @click="onClearClick"
472
+ >
473
+ <BaseIcon
474
+ :icon="getLayerIcon('close')"
475
+ :size="clearIconSize"
476
+ />
477
+ </button>
478
+ </div>
479
+
480
+ <Motion
481
+ v-if="opened"
482
+ ref="panelRef"
483
+ :animate="panelAnimation.animate"
484
+ as="div"
485
+ :initial="panelAnimation.initial"
486
+ >
487
+ <div class="border-t base-border px-1.5 pb-1.5 pt-1">
488
+ <div
489
+ v-if="!calendarOpen"
490
+ :id="chipListId"
491
+ ref="chipListRef"
492
+ class="flex flex-wrap gap-1.5 outline-none"
493
+ :class="{
494
+ 'text-xs': size === 'sm',
495
+ 'text-sm': size === 'base',
496
+ 'text-base': size === 'lg',
497
+ }"
498
+ :aria-activedescendant="`${id}-preset-${highlightedIndex}`"
499
+ role="listbox"
500
+ tabindex="0"
501
+ @keydown="onChipListKeyDown"
502
+ >
503
+ <BaseChip
504
+ v-for="(preset, index) in computedPresets"
505
+ :id="`${id}-preset-${index}`"
506
+ :key="preset.id"
507
+ :active="index === highlightedIndex"
508
+ active-highlight="indigo"
509
+ :ariaSelected="index === highlightedIndex"
510
+ as="div"
511
+ hoverable
512
+ max-width
513
+ role="option"
514
+ variant="outline"
515
+ @click="onPresetClick(preset)"
516
+ @mousedown="onChipMouseDown"
517
+ @mouseenter="onHighlight(index)"
518
+ >
519
+ {{ preset.label }}<template v-if="preset.sub"> · {{ preset.sub }}</template>
520
+ </BaseChip>
521
+
522
+ <BaseChip
523
+ :id="`${id}-preset-${chooseChipIndex}`"
524
+ :active="highlightedIndex === chooseChipIndex"
525
+ active-highlight="indigo"
526
+ :ariaSelected="highlightedIndex === chooseChipIndex"
527
+ as="div"
528
+ hoverable
529
+ max-width
530
+ role="option"
531
+ variant="dashed"
532
+ @click="onOpenCalendar"
533
+ @mousedown="onChipMouseDown"
534
+ @mouseenter="onHighlight(chooseChipIndex)"
535
+ >
536
+ {{ t('choose') }}
537
+ </BaseChip>
538
+ </div>
539
+
540
+ <div
541
+ v-if="calendarOpen"
542
+ class="px-0.5"
543
+ role="grid"
544
+ :aria-label="monthLabel"
545
+ @keydown.escape.prevent="onCloseCalendar"
546
+ >
547
+ <div class="mb-0.5 grid grid-cols-7 gap-px">
548
+ <button
549
+ :aria-label="t('prevMonth')"
550
+ class="base-bg-hover min-h-7 min-w-7 inline-flex cursor-pointer items-center justify-center rounded-md outline-none transition-colors"
551
+ type="button"
552
+ @click="onPreviousMonth"
553
+ >
554
+ <BaseIcon
555
+ color="black"
556
+ :icon="getLayerIcon('chevronLeft')"
557
+ size="xs"
558
+ />
559
+ </button>
560
+
561
+ <span class="col-span-5 flex min-h-7 items-center justify-center text-xs text-gray-900 font-bold uppercase dark:text-gray-100">
562
+ {{ monthLabel }}
563
+ </span>
564
+
565
+ <button
566
+ :aria-label="t('nextMonth')"
567
+ class="base-bg-hover min-h-7 min-w-7 inline-flex cursor-pointer items-center justify-center rounded-md outline-none transition-colors"
568
+ type="button"
569
+ @click="onNextMonth"
570
+ >
571
+ <BaseIcon
572
+ color="black"
573
+ :icon="getLayerIcon('chevronRight')"
574
+ size="xs"
575
+ />
576
+ </button>
577
+ </div>
578
+
579
+ <div
580
+ class="grid grid-cols-7 gap-px"
581
+ role="row"
582
+ >
583
+ <div
584
+ v-for="(weekdayLabel, weekdayIndex) in weekdayLabels"
585
+ :key="`weekday-${weekdayIndex}`"
586
+ class="min-h-7 min-w-7 flex items-center justify-center text-xs text-gray-600 font-medium dark:text-gray-400"
587
+ role="columnheader"
588
+ >
589
+ {{ weekdayLabel }}
590
+ </div>
591
+ </div>
592
+
593
+ <div class="grid grid-cols-7 gap-px">
594
+ <button
595
+ v-for="(cell, index) in calendarCells"
596
+ :key="`${cell.date.toISOString()}-${index}`"
597
+ class="relative min-h-7 min-w-7 inline-flex items-center justify-center border border-transparent rounded-md text-xs font-medium outline-none"
598
+ :class="{
599
+ 'cursor-not-allowed text-gray-400 opacity-40 dark:text-gray-600': !cell.inMonth || isDayDisabled(cell.date),
600
+ 'base-bg-hover': cell.inMonth && !isDayDisabled(cell.date) && !(modelValue && isSameDay(cell.date, modelValue)),
601
+ 'text-indigo-600 font-bold dark:text-indigo-400': cell.inMonth && !isDayDisabled(cell.date) && isSameDay(cell.date, today) && !(modelValue && isSameDay(cell.date, modelValue)),
602
+ 'text-gray-600 dark:text-gray-400': cell.inMonth && !isDayDisabled(cell.date) && !isSameDay(cell.date, today) && !(modelValue && isSameDay(cell.date, modelValue)),
603
+ 'border-indigo-600/35 bg-indigo-600/[0.08] text-gray-900 dark:border-indigo-400/35 dark:bg-indigo-400/[0.08] dark:text-gray-100': modelValue && isSameDay(cell.date, modelValue),
604
+ 'ring-2 ring-indigo-600/35 dark:ring-indigo-400/35': index === focusedDayIndex && calendarOpen && !(modelValue && isSameDay(cell.date, modelValue)),
605
+ }"
606
+ :disabled="!cell.inMonth || isDayDisabled(cell.date)"
607
+ role="gridcell"
608
+ :tabindex="index === focusedDayIndex ? 0 : -1"
609
+ @click="onDayClick(cell.date)"
610
+ @keydown="onDayKeyDown($event, cell.date, index)"
611
+ >
612
+ {{ cell.date.getDate() }}
613
+
614
+ <span
615
+ v-if="isSameDay(cell.date, today) && !(modelValue && isSameDay(cell.date, modelValue))"
616
+ class="absolute bottom-0 h-0.5 w-0.5 rounded-full bg-indigo-600 dark:bg-indigo-400"
617
+ />
618
+ </button>
619
+ </div>
620
+ </div>
621
+ </div>
622
+ </Motion>
623
+ </div>
624
+
625
+ <FieldMessage
626
+ :description="description"
627
+ :disabled="disabled"
628
+ :hide-error="hideError"
629
+ :size="size"
630
+ :validation="validation"
631
+ />
632
+ </div>
633
+ </template>
634
+
635
+ <i18n lang="json">
636
+ {
637
+ "de": {
638
+ "choose": "Wählen…",
639
+ "clear": "Datum entfernen",
640
+ "nextMonth": "Nächster Monat",
641
+ "nextWeek": "Nächste Woche",
642
+ "placeholder": "Fälligkeit hinzufügen",
643
+ "prevMonth": "Vorheriger Monat",
644
+ "weekend": "Dieses Wochenende"
645
+ },
646
+ "en": {
647
+ "choose": "Choose…",
648
+ "clear": "Remove date",
649
+ "nextMonth": "Next month",
650
+ "nextWeek": "Next week",
651
+ "placeholder": "Add a due date",
652
+ "prevMonth": "Previous month",
653
+ "weekend": "This weekend"
654
+ },
655
+ "es": {
656
+ "choose": "Elegir…",
657
+ "clear": "Quitar fecha",
658
+ "nextMonth": "Mes siguiente",
659
+ "nextWeek": "Próxima semana",
660
+ "placeholder": "Añadir fecha límite",
661
+ "prevMonth": "Mes anterior",
662
+ "weekend": "Este fin de semana"
663
+ },
664
+ "fr": {
665
+ "choose": "Choisir…",
666
+ "clear": "Retirer la date",
667
+ "nextMonth": "Mois suivant",
668
+ "nextWeek": "Semaine prochaine",
669
+ "placeholder": "Ajouter une échéance",
670
+ "prevMonth": "Mois précédent",
671
+ "weekend": "Ce week-end"
672
+ },
673
+ "id": {
674
+ "choose": "Pilih…",
675
+ "clear": "Hapus tanggal",
676
+ "nextMonth": "Bulan berikutnya",
677
+ "nextWeek": "Minggu depan",
678
+ "placeholder": "Tambahkan tenggat",
679
+ "prevMonth": "Bulan sebelumnya",
680
+ "weekend": "Akhir pekan ini"
681
+ },
682
+ "it": {
683
+ "choose": "Scegli…",
684
+ "clear": "Rimuovi data",
685
+ "nextMonth": "Mese successivo",
686
+ "nextWeek": "Prossima settimana",
687
+ "placeholder": "Aggiungi scadenza",
688
+ "prevMonth": "Mese precedente",
689
+ "weekend": "Questo weekend"
690
+ },
691
+ "ja": {
692
+ "choose": "選択…",
693
+ "clear": "日付を削除",
694
+ "nextMonth": "次の月",
695
+ "nextWeek": "来週",
696
+ "placeholder": "期限を追加",
697
+ "prevMonth": "前の月",
698
+ "weekend": "今週末"
699
+ },
700
+ "ko": {
701
+ "choose": "선택…",
702
+ "clear": "날짜 제거",
703
+ "nextMonth": "다음 달",
704
+ "nextWeek": "다음 주",
705
+ "placeholder": "마감일 추가",
706
+ "prevMonth": "이전 달",
707
+ "weekend": "이번 주말"
708
+ },
709
+ "nl": {
710
+ "choose": "Kiezen…",
711
+ "clear": "Datum verwijderen",
712
+ "nextMonth": "Volgende maand",
713
+ "nextWeek": "Volgende week",
714
+ "placeholder": "Deadline toevoegen",
715
+ "prevMonth": "Vorige maand",
716
+ "weekend": "Dit weekend"
717
+ },
718
+ "pl": {
719
+ "choose": "Wybierz…",
720
+ "clear": "Usuń datę",
721
+ "nextMonth": "Następny miesiąc",
722
+ "nextWeek": "Przyszły tydzień",
723
+ "placeholder": "Dodaj termin",
724
+ "prevMonth": "Poprzedni miesiąc",
725
+ "weekend": "Ten weekend"
726
+ },
727
+ "pt": {
728
+ "choose": "Escolher…",
729
+ "clear": "Remover data",
730
+ "nextMonth": "Mês seguinte",
731
+ "nextWeek": "Próxima semana",
732
+ "placeholder": "Adicionar prazo",
733
+ "prevMonth": "Mês anterior",
734
+ "weekend": "Este fim de semana"
735
+ },
736
+ "pt-BR": {
737
+ "choose": "Escolher…",
738
+ "clear": "Remover data",
739
+ "nextMonth": "Próximo mês",
740
+ "nextWeek": "Próxima semana",
741
+ "placeholder": "Adicionar prazo",
742
+ "prevMonth": "Mês anterior",
743
+ "weekend": "Neste fim de semana"
744
+ },
745
+ "vi": {
746
+ "choose": "Chọn…",
747
+ "clear": "Xóa ngày",
748
+ "nextMonth": "Tháng sau",
749
+ "nextWeek": "Tuần tới",
750
+ "placeholder": "Thêm hạn chót",
751
+ "prevMonth": "Tháng trước",
752
+ "weekend": "Cuối tuần này"
753
+ }
754
+ }
755
+ </i18n>