DateRangePicker
Період із пресетами над таблицею чи звітом — два місяці на десктопі, один на телефоні.
«Період: 01.08 — 17.08» над таблицею чи звітом. Найчастіший фільтр у CRM і єдиний, де користувач майже ніколи не хоче гортати календар: у дев'яти випадках із десяти йому потрібні «останні 30 днів».
Пресети ліворуч, а на телефоні — смугою над сіткою
- Обрано
- 1 серп. 2026 р. — 17 серп. 2026 р.
- Останній пресет
- —
Об'єкт, а не кортеж
v-model — це { start, end } | null. Кортеж [Date, Date] компактніший,
але range[0] у шаблоні не читається, а range.start читається. Конверсія
до кортежа, з яким працює UiCalendar, — один
рядок в обидва боки.
Половинчастого стану не буває: поки другий кінець невідомий, модель
лишається попередньою. Тому v-if="period" завжди чесний, і споживачу не
доводиться відповідати на питання «чи вважати порожнім об'єкт, у якого
заповнено лише start».
Пресети рахуються від «сьогодні»
DateRangePreset.range — це функція від сьогоднішньої дати, а не
готовий кортеж. Пресети, обчислені під час імпорту модуля, назавжди
застигли б на даті збірки; на прередереному сайті «Останні 7 днів» стали б
неправильними вже наступного дня.
Пресет, який не влазить у min/max, зі списку зникає. Запропонувати
«Останні 30 днів», коли min — десять днів тому, означає запропонувати
період, який сам календар відхилить.
Подія presetSelect існує окремо від update:modelValue, бо з самого
значення факт «користувач обрав пресет» не відновити: вручну зведений
однаковий період виглядає так само.
Два місяці — рішенням JS, а не класом
Кількість місяців перемикає matchMedia, а не md:hidden.
Прихований класом другий місяць лишається в DOM. Це 42 зайві gridcell
для скрінрідера і — головне — другий елемент із tabindex="0", який
руйнує roving tabindex першого: Tab починає зупинятись у календарі двічі.
Типове значення — один місяць. Прередер віддає мобільний варіант, і найвужчий клієнт отримує розмітку без стрибка після гідрації.
Нижче md пресети стають горизонтальною смугою над сіткою: колонка
пресетів (≈160px) поруч із місяцем (≈300px) не влазить у 375px екрана.
API
Props
| Назва | Тип | Типово | Опис |
|---|---|---|---|
modelValue | DateRange | null | Обраний період або `null`. Через `v-model`. Об'єкт, а не кортеж: `range.start` у шаблоні читається, `range[0]` — ні. Половинчастого стану тут не буває: поки другий кінець невідомий, модель лишається попередньою. |
size | FieldSize | "md" | Висота поля. На мобільному кожен розмір вищий за десктопний. |
placeholder | string | "Оберіть період" | — |
presets | DateRangePreset[] | defaultDateRangePresets | Пресети ліворуч від календаря. Порожній масив ховає колонку. |
displayFormat | shortlong | "short" | Формат періоду в тригері. |
placement | bottom-startbottom-endtop-starttop-end | "bottom-start" | Куди відкривати панель відносно поля. |
weekStartsOn | 0 | 1 | 1 | Перший день тижня. `1` — понеділок (uk). |
locale | string | "uk-UA" | Локаль підписів. |
label | string | — | — |
error | string | — | Текст помилки. Стан помилки вмикає САМА наявність тексту. |
hint | string | — | Підказка під полем. Ховається, коли показано помилку. |
id | string | — | — |
name | string | — | — |
min | Date | — | Найраніша доступна дата включно. |
max | Date | — | Найпізніша доступна дата включно. |
disabledDate | (date: Date) => boolean | — | Які дати недоступні. Проксується в `UiCalendar`. |
today | Date | — | Що вважати «сьогодні» — для детермінізму прередеру й тестів. |
clearable | boolean | true | Хрестик, що скидає період. |
autoClose | boolean | true | Закривати панель, щойно обрано обидва кінці. |
disabled | boolean | — | — |
required | boolean | — | — |
Події
| Назва | Payload | Опис |
|---|---|---|
update:modelValue | [value: DateRange] | Обраний період або `null`. Використовуйте через `v-model`. |
open | [] | Панель відкрито. |
close | [] | Панель закрито. |
presetSelect | [preset: DateRangePreset] | Обрано готовий пресет. Із самого значення це відновити неможливо - вручну зведений однаковий період виглядає так само. |
Слоти
| Назва | Опис |
|---|---|
trigger | Власний тригер. `text` — уже відформатований період або плейсхолдер. |
preset | Власний рендер пресету. |
footer | Рядок під календарем: «Скасувати», «Застосувати». |
Доступно через ref
| Назва | Тип | Опис |
|---|---|---|
open | () => void | Відкриває панель. |
close | () => void | Закриває панель. |
focus | () => void | Ставить фокус на тригер. |
Коли використовувати
- Фільтр періоду над таблицею, звітом або дашбордом.
- Будь-де, де «останні 30 днів» — типова відповідь.
- Порівняння періодів, якщо потрібні два таких поля поруч.
Коли НЕ використовувати
- Для однієї дати —
UiDatePickerабоUiCalendarу режиміsingle. - Коли календар має бути видимий завжди — беріть
UiCalendarнапряму. - Коли періодів лише кілька фіксованих: тоді це
UiToggleGroupбез календаря взагалі.
Доступність
Тригер — кнопка з aria-haspopup="dialog", aria-expanded і
aria-controls. Панель має role="dialog" і власну назву.
Пастки фокуса тут немає навмисно. Панель немодальна, тієї самої родини,
що UiPopover і
UiMenu; пастка зламала б Tab до наступного поля
форми. Замість неї — закриття по виходу фокуса за межі тригера й панелі,
по кліку зовні та по Escape із поверненням фокуса на тригер.
Z-index береться з getOverlayChildZIndex, тож панель коректно лягає
поверх модалки, якщо фільтр живе всередині неї.
Уся клавіатура сітки — з UiCalendar.