TimePickerDropdown
Обёртка над TimePicker с Dropdown: произвольный триггер (children), управление открытием, позиционирование и опциональное закрытие после Apply. Подходит для полей «время открытия», фильтров и компактных форм.
Когда использовать
- Нужна кнопка или кастомный триггер, а панель времени показывается по клику или ховеру.
- Требуется связать открытие с другим UI (например, контролируемый
open).
Когда не нужен: для постоянно видимого времени в форме без попапа используйте TimePicker.
- ✅ Задавать
placementиtriggerв зависимости от layout и UX‑требований. - ❌ Смешивать управляемый
openс лишними внешними кнопками, которые дублируют открытие — проще контролировать только черезonOpenChange.
Анатомия
Триггер и открытие
trigger — click | hover | focus и др. (см. пропсы Dropdown в таблице). closeOnApply закрывает панель после подтверждения, если сценарий это предполагает.
Позиционирование
placement и fallbackPlacements наследуются из @cloud-ru/ds-dropdown — подбирайте, чтобы панель не обрезалась у края экрана.
Визуал времени
Макет барабана времени совпадает с TimePicker; см. узел Figma ниже.
Установка
pnpm add @cloud-ru/ds-calendar @cloud-ru/ds-button
import { Button } from '@cloud-ru/ds-button'
import { TimePickerDropdown, SIZE } from '@cloud-ru/ds-calendar'
Примеры использования
Клик по кнопке
import { Button } from '@cloud-ru/ds-button';
import { SIZE, TimePickerDropdown, TimeValue } from '@cloud-ru/ds-calendar';
import { useState } from 'react';
export function TimePickerDropdownBasic() {
const [value, setValue] = useState<TimeValue | undefined>({ hours: 10, minutes: 5, seconds: 0 });
return (
<TimePickerDropdown
closeOnApply
fitToContainer={false}
placement='bottom-start'
size={SIZE.M}
trigger='click'
value={value}
onChangeValue={v => setValue(v)}
>
<Button label='Выбрать время' />
</TimePickerDropdown>
);
}Контролируемое open
import { Button } from '@cloud-ru/ds-button';
import { SIZE, TimePickerDropdown, TimeValue } from '@cloud-ru/ds-calendar';
import { useState } from 'react';
export function TimePickerDropdownControlled() {
const [open, setOpen] = useState(false);
const [value, setValue] = useState<TimeValue | undefined>({ hours: 14, minutes: 0, seconds: 0 });
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<TimePickerDropdown
fitToContainer={false}
open={open}
placement='bottom-start'
showSeconds={false}
size={SIZE.S}
trigger='click'
value={value}
onChangeValue={v => setValue(v)}
onOpenChange={setOpen}
>
<Button label='Время (controlled open)' />
</TimePickerDropdown>
<span style={{ fontSize: 14, opacity: 0.8 }}>Панель времени: {open ? 'открыта' : 'закрыта'}</span>
</div>
);
}Разные placement
import { Button } from '@cloud-ru/ds-button';
import { SIZE, TimePickerDropdown, TimeValue } from '@cloud-ru/ds-calendar';
import { useState } from 'react';
export function TimePickerDropdownPlacement() {
const [value, setValue] = useState<TimeValue | undefined>({ hours: 7, minutes: 30, seconds: 0 });
return (
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'flex-start' }}>
<TimePickerDropdown
closeOnApply
fitToContainer={false}
placement='bottom-start'
size={SIZE.M}
trigger='click'
value={value}
onChangeValue={v => setValue(v)}
>
<Button label='bottom-start' />
</TimePickerDropdown>
<TimePickerDropdown
closeOnApply
fitToContainer={false}
placement='top-end'
size={SIZE.M}
trigger='click'
value={value}
onChangeValue={v => setValue(v)}
>
<Button label='top-end' />
</TimePickerDropdown>
</div>
);
}Props
Types
TimePickerDropdownProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | ReactNode | — | no | Контент триггера открытия dropdown |
className | string | — | no | CSS-класс контейнера |
closeOnApply | boolean | — | no | Закрыть dropdown после нажатия кнопки Apply |
closeOnEscapeKey | boolean | true | no | Закрывать ли по нажатию на кнопку `Esc` |
closeOnPopstate | boolean | — | no | Закрывать ли поповер при переходе по и стории браузера |
data-test-id | string | — | no | |
defaultValue | TimeValue | — | no | Значение по-умолчанию для uncontrolled. |
disableSpanWrapper | boolean | — | no | Отключает для `isValidElement` внешнюю обертку триггера <br/> Пригодится для элементов с `position: absolute` <br/> Работает для триггеров, которые умеют отдать свою DOM-ноду: нативные элементы, `forwardRef`-компоненты и компоненты, помеченные `withInnerRefSupport` из `@cloud-ru/ds-utils`. Остальные всё равно получают `<span>` — без ноды поповеру не от чего считать позицию; в dev-режиме об этом печатается предупреждение. |
fallbackPlacements | Placement[] | — | no | Цепочка расположений которая будет применяться к поповеру от первого к последнему если при текущем он не влезает. |
fitToContainer | boolean | true | no | Отключает предустановленный размер, заставляя компонент подстраиваться к размеру контейнра: (width: 100%, height: 100%). |
hoverDelayClose | number | — | no | Задержка закрытия по ховеру |
hoverDelayOpen | number | — | no | Задержка открытия по ховеру |
navigationStartRef | RefObject<{ focus(): void; }> | — | no | Ссылка на управление первым элементом навигации |
onApply | (() => void) | — | no | Колбек по нажатию Apply |
onChangeValue | ((value?: TimeValue) => void) | — | no | Колбек выбора значения |
onCurrent | (() => void) | — | no | Колбек по нажатию Current |
onFocusLeave | ((direction: FocusDirection) => void) | — | no | Колбек потери фокуса. Вызывается со значением `next`, когда фокус покидает компонент, передвигаясь вперед, по клавише `tab`. Со значением `prev` - по клавише стрелки вверх или `shift + tab`. |
onOpenChange | ((isOpen: boolean) => void) | — | no | Колбек отображения компонента. Срабатывает при изменении состояния open. |
open | boolean | — | no | Управляет состоянием показан/не показан. |
outsideClick | boolean | OutsideClickHandler | — | no | Закрывать ли при клике вне поповера |
placement | "bottom" | "bottom-end" | "bottom-start" | "left" | "left-end" | "left-start" | "right" | "right-end" | "right-start" | "top" | "top-end" | "top-start" | top | no | Положение поповера относительно своего триггера (children). |
showSeconds | boolean | — | no | Показывать ли секунды |
size | "l" | "m" | "s" | m | no | Размер |
today | number | Date | — | no | Дата сегодняшнего дня |
trigger | "click" | "clickAndFocusVisible" | "focus" | "focusVisible" | "hover" | "hoverAndFocus" | "hoverAndFocusVisible" | — | no | Условие отображения поповера: <br/> - `click` - открывать по клику <br/> - `hover` - открывать по ховеру <br/> - `focusVisible` - открывать по focus-visible <br/> - `focus` - открывать по фокусу <br/> - `hoverAndFocusVisible` - открывать по ховеру и focus-visible <br/> - `hoverAndFocus` - открывать по ховеру и фокусу <br/> - `clickAndFocusVisible` - открывать по клику и focus-visible |
triggerClassName | string | — | no | CSS-класс триггера |
triggerClickByKeys | boolean | true | no | Вызывается ли попоповер по нажатию клавиш Enter/Space (при trigger = `click`) |
triggerRef | ForwardedRef<HTMLElement | ReferenceType | null> | — | no | Ref ссылка на триггер |
value | TimeValue | — | no | Выбранное значение. |
Unions
Types
TimePickerDropdownProps
TimeValue
Unions
Size
Related props
OutsideClickHandler
Placement
Trigger
Адаптивность
TimePickerDropdown — адаптивный компонент с переключением поверхности (surface-swap). Раскладку он берёт из AdaptiveProvider (контекст @cloud-ru/ds-adaptive); встраиваемый TimePicker не адаптируется. Публичный API единый для обеих платформ:
- desktop (по умолчанию) — барабан времени в popover над триггером (
@cloud-ru/ds-dropdown). - mobile — барабан времени в
BottomSheetиз@cloud-ru/ds-bottom-sheetс кнопками «Сейчас» / «Применить».
Верстайте под desktop и поставьте один <AdaptiveProvider> в корне приложения — mobile-поверхность включается автоматически (desktop-first). Пропа layoutType у компонента нет: источник раскладки — только контекст.
Как форсировать платформу
Форс — только контекстом, не пропом:
- Поддерево — вложенный провайдер:
import { AdaptiveProvider } from '@cloud-ru/ds-adaptive' <AdaptiveProvider layoutType='mobile'> <TimePickerDropdown>…</TimePickerDropdown> </AdaptiveProvider> - Отдельный компонент —
withLayoutType(module-scope, сахар над провайдером):import { withLayoutType } from '@cloud-ru/ds-adaptive' import { TimePickerDropdown } from '@cloud-ru/ds-calendar' const MobileTimePickerDropdown = withLayoutType(TimePickerDropdown, 'mobile')
Платформенные пропы
Пропы позиционирования и открытия popover’а (placement, fallbackPlacements, trigger, closeOnApply и др.) применяются только на desktop; на mobile BottomSheet имеет своё позиционирование снизу и игнорирует их.
| Пропы | desktop | mobile |
|---|---|---|
placement, fallbackPlacements, trigger, closeOnApply | используется | игнорируется |
value, defaultValue, onChangeValue, showSeconds, open, onOpenChange | используется | используется |
Подробнее о модели адаптивности — Adaptive.
Storybook
Figma
Смотри также
- TimePicker
- Dropdown — базовый выпадающий контейнер.