Перейти до вмісту
tatetUI

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

НазваТипТиповоОпис
modelValueDateRangenullОбраний період або `null`. Через `v-model`. Об'єкт, а не кортеж: `range.start` у шаблоні читається, `range[0]` — ні. Половинчастого стану тут не буває: поки другий кінець невідомий, модель лишається попередньою.
sizeFieldSize"md"Висота поля. На мобільному кожен розмір вищий за десктопний.
placeholderstring"Оберіть період"
presetsDateRangePreset[]defaultDateRangePresetsПресети ліворуч від календаря. Порожній масив ховає колонку.
displayFormatshortlong"short"Формат періоду в тригері.
placementbottom-startbottom-endtop-starttop-end"bottom-start"Куди відкривати панель відносно поля.
weekStartsOn0 | 11Перший день тижня. `1` — понеділок (uk).
localestring"uk-UA"Локаль підписів.
labelstring
errorstringТекст помилки. Стан помилки вмикає САМА наявність тексту.
hintstringПідказка під полем. Ховається, коли показано помилку.
idstring
namestring
minDateНайраніша доступна дата включно.
maxDateНайпізніша доступна дата включно.
disabledDate(date: Date) => booleanЯкі дати недоступні. Проксується в `UiCalendar`.
todayDateЩо вважати «сьогодні» — для детермінізму прередеру й тестів.
clearablebooleantrueХрестик, що скидає період.
autoClosebooleantrueЗакривати панель, щойно обрано обидва кінці.
disabledboolean
requiredboolean

Події

Назва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.