fix(date-field): polish calendar UX — dropdown nav, today/clear footer, ru weekdays
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 43s
CI / Web (React + Vite) (push) Successful in 34s
Docker Web / Build + push Web (push) Successful in 28s
Docker Web / Deploy Web on stage (push) Successful in 11s

Календарь приведён к виду нативного date-picker macOS:

- captionLayout="dropdown" + startMonth/endMonth — в шапке
  селекты «Апрель» и «2026» (можно прыгнуть на любой месяц/год
  без 12 кликов next).
- Стрелки навигации справа в шапке (absolute right-1 top-1),
  компактные 28×28, hover-bg, без ярко-синего акцента.
- footer prop — две inline-ссылки «Очистить» (сбрасывает) и
  «Сегодня» (ставит сегодняшнюю дату); border-t над ними.
- Сегодня = синяя заливка bg-brand text-white (как на референсе);
  выбранная дата = ring-2 ring-brand; если сегодня = выбранный —
  применяются обе (сначала заливка, потом ring).
- Ширина popover'а w-[340px], ячейки 36×36, weekday и dropdown
  capitalize → «Пн Вт Ср …», «Апрель 2026».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-26 03:22:48 +05:00
parent 22cc0256b9
commit 88e382d9d7

View file

@ -140,24 +140,27 @@ export function DateField({ value, onChange, disabled, required, className, widt
ref={popRef} ref={popRef}
style={{ style={{
position: 'fixed', top: pos.top, left: pos.left, position: 'fixed', top: pos.top, left: pos.left,
// Тонкие настройки react-day-picker v9 через CSS-переменные — // Тонкие настройки react-day-picker v9 через CSS-переменные.
// shadcn-style плотный календарь ~280290px вместо ~700px. // Сегодня = синяя заливка (как у нативного date-picker macOS).
// eslint-disable-next-line @typescript-eslint/no-explicit-any ...({ '--rdp-day-height': '2.25rem', '--rdp-day-width': '2.25rem',
...({ '--rdp-day-height': '2rem', '--rdp-day-width': '2rem',
'--rdp-weekday-padding': '0', '--rdp-nav-height': '1.75rem', '--rdp-weekday-padding': '0', '--rdp-nav-height': '1.75rem',
'--rdp-day_button-height': '2rem', '--rdp-day_button-width': '2rem', '--rdp-day_button-height': '2.25rem', '--rdp-day_button-width': '2.25rem',
'--rdp-day_button-border-radius': '0.375rem', '--rdp-day_button-border-radius': '0.375rem',
'--rdp-accent-color': 'var(--color-brand)', '--rdp-accent-color': 'var(--color-brand)',
'--rdp-accent-background-color': 'var(--color-brand)', '--rdp-accent-background-color': 'transparent',
'--rdp-selected-border': 'none' } as Record<string, string>), '--rdp-selected-border': 'none',
'--rdp-today-color': 'inherit' } as Record<string, string>),
}} }}
className="z-[100] rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-md p-3 text-sm" className="z-[100] w-[340px] rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-md p-3 text-sm"
> >
<DayPicker <DayPicker
mode="single" mode="single"
locale={ru} locale={ru}
weekStartsOn={1} weekStartsOn={1}
ISOWeek ISOWeek
captionLayout="dropdown"
startMonth={new Date(2020, 0)}
endMonth={new Date(2035, 11)}
selected={isoToDate(value)} selected={isoToDate(value)}
defaultMonth={isoToDate(value) ?? new Date()} defaultMonth={isoToDate(value) ?? new Date()}
onSelect={(d) => { onSelect={(d) => {
@ -167,23 +170,47 @@ export function DateField({ value, onChange, disabled, required, className, widt
setDraft(isoToDisplay(iso)) setDraft(isoToDisplay(iso))
setOpen(false) setOpen(false)
}} }}
footer={
<div className="flex items-center justify-between pt-2 mt-2 border-t border-slate-100 dark:border-slate-800">
<button
type="button"
onClick={() => { onChange(''); setDraft(''); setOpen(false) }}
className="text-sm text-[var(--color-brand)] hover:underline"
>
Очистить
</button>
<button
type="button"
onClick={() => {
const iso = dateToIso(new Date())
onChange(iso); setDraft(isoToDisplay(iso)); setOpen(false)
}}
className="text-sm text-[var(--color-brand)] hover:underline"
>
Сегодня
</button>
</div>
}
classNames={{ classNames={{
months: 'flex flex-col gap-2', months: 'flex flex-col',
month: 'space-y-2', month: 'relative space-y-2',
month_caption: 'flex justify-center pt-1 relative items-center h-7', month_caption: 'flex items-center pt-0.5 pl-1 h-8 pr-16',
caption_label: 'text-sm font-medium capitalize', dropdowns: 'flex items-center gap-1.5 text-sm font-semibold',
nav: 'absolute inset-x-0 top-0 flex items-center justify-between px-1 h-7', dropdown_root: 'relative inline-flex items-center',
button_previous: 'h-6 w-6 inline-flex items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800', dropdown: 'appearance-none bg-transparent capitalize cursor-pointer focus:outline-none rounded hover:bg-slate-100 dark:hover:bg-slate-800 px-1.5 py-0.5 pr-5',
button_next: 'h-6 w-6 inline-flex items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800', caption_label: 'capitalize pointer-events-none absolute opacity-0',
nav: 'absolute right-1 top-1 flex items-center gap-0.5',
button_previous: 'h-7 w-7 inline-flex items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800',
button_next: 'h-7 w-7 inline-flex items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800',
chevron: 'w-4 h-4 fill-current', chevron: 'w-4 h-4 fill-current',
weekdays: 'flex', weekdays: 'flex',
weekday: 'w-8 text-center text-[0.75rem] font-normal text-slate-500 capitalize', weekday: 'w-9 text-center text-[0.75rem] font-normal text-slate-500 capitalize',
weeks: 'mt-1', weeks: 'mt-1',
week: 'flex w-full mt-1', week: 'flex w-full mt-1',
day: 'w-8 h-8 p-0 text-center align-middle', day: 'w-9 h-9 p-0 text-center align-middle',
day_button: 'w-8 h-8 inline-flex items-center justify-center rounded-md text-sm font-normal hover:bg-slate-100 dark:hover:bg-slate-800', day_button: 'w-9 h-9 inline-flex items-center justify-center rounded-md text-sm font-normal hover:bg-slate-100 dark:hover:bg-slate-800 disabled:hover:bg-transparent',
today: '[&_button]:bg-slate-100 dark:[&_button]:bg-slate-800 [&_button]:font-semibold', today: '[&_button]:bg-[var(--color-brand)] [&_button]:text-white [&_button]:font-medium [&_button]:hover:bg-[var(--color-brand)]',
selected: '[&_button]:bg-[var(--color-brand)] [&_button]:text-white [&_button]:hover:bg-[var(--color-brand)]', selected: '[&_button]:ring-2 [&_button]:ring-[var(--color-brand)] [&_button]:ring-offset-1',
outside: 'text-slate-300 dark:text-slate-600 opacity-60', outside: 'text-slate-300 dark:text-slate-600 opacity-60',
disabled: 'text-slate-300 opacity-50', disabled: 'text-slate-300 opacity-50',
}} }}