feat(12.2-04): add the datepicker field and stored date and time list cells

- pin @internationalized/date 3.12.4 as a direct admin dependency (approved)
- dateFormat.ts parses and emits date, datetime (local display, UTC emit,
  ignoreTimezone wall clock) and time values without the global Date
- DatepickerField on Reka DatePicker and TimeField with locale segments,
  calendar popover, clear button, min/max and yearRange bounds
- list cells of type date and time render the stored string
- datepicker lang keys in en and pl; rebuilt boardwalk dist
This commit is contained in:
Jakub Zych
2026-10-02 19:45:43 +02:00
parent d66812caef
commit a0c182745e
15 changed files with 797 additions and 17 deletions

View File

@@ -7,6 +7,9 @@ import { t } from '../../app/i18n'
// One cell rendered by column type (D-12): text by default (arrays
// comma-joined, a muted dash when empty), datetime as YYYY-MM-DD HH:mm with
// tabular numbers, switch as the Tak/Nie pills. No per-value icon pills.
// Phase 12.2: a `date` column shows the stored YYYY-MM-DD and a `time`
// column the stored HH:mm, both as strings: a calendar date or a time of day
// has no zone, so it never goes through the global Date (Pitfall 12).
const props = defineProps<{ column: ListColumn; value: unknown }>()
function isEmpty(value: unknown): boolean {
@@ -44,6 +47,24 @@ function datetime(value: unknown): string {
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
}
/** A stored date as-is (YYYY-MM-DD); anything after the day is dropped. */
function storedDate(value: unknown): string {
if (typeof value !== 'string') {
return ''
}
const match = /^\d{4}-\d{2}-\d{2}/.exec(value.trim())
return match ? match[0] : value.trim()
}
/** A stored time of day as HH:mm (seconds dropped). */
function storedTime(value: unknown): string {
if (typeof value !== 'string') {
return ''
}
const match = /(\d{2}):(\d{2})/.exec(value)
return match ? `${match[1]}:${match[2]}` : value.trim()
}
function truthy(value: unknown): boolean {
if (typeof value === 'string') {
return value !== '' && value !== '0' && value.toLowerCase() !== 'false'
@@ -58,10 +79,27 @@ const kind = computed(() => {
if (props.column.type === 'datetime') {
return 'datetime'
}
if (props.column.type === 'date') {
return 'date'
}
if (props.column.type === 'time') {
return 'time'
}
return 'text'
})
const display = computed(() => (kind.value === 'datetime' ? datetime(props.value) : text(props.value)))
const display = computed(() => {
switch (kind.value) {
case 'datetime':
return datetime(props.value)
case 'date':
return storedDate(props.value)
case 'time':
return storedTime(props.value)
default:
return text(props.value)
}
})
</script>
<template>
@@ -81,6 +119,6 @@ const display = computed(() => (kind.value === 'datetime' ? datetime(props.value
>
</template>
<span v-else-if="display === ''" class="text-muted" data-empty>—</span>
<span v-else-if="kind === 'datetime'" class="text-[13px] text-muted tabular-nums">{{ display }}</span>
<span v-else-if="kind === 'datetime' || kind === 'date' || kind === 'time'" class="text-[13px] text-muted tabular-nums">{{ display }}</span>
<template v-else>{{ display }}</template>
</template>