@saasmakers/ui 2.0.68 → 2.0.70

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