# DateField: Руководства

## Паттерны

### Поле даты с календарём в поповере

Когда использовать:

- Обычное поле формы должно позволять и вводить, и выбирать дату.
- Календарь должен появляться по запросу, а не быть видимым постоянно.

Заметки о композиции:

- Оставьте `DateFieldInput` основной поверхностью и дайте ему подпись.
- Смонтируйте `DateFieldCalendar` внутри `PopoverContent` и используйте `DateFieldTriggerIconButton` с `aria-label` в качестве триггера.

Компромиссы:

- Экономит место в раскладке.
- Требует проверки оверлея внутри диалогов, выдвижных панелей и прокручиваемых контейнеров.

```tsx
import {
  CalendarContent,
  CalendarDay,
  CalendarDays,
  CalendarHeader,
  CalendarMonth,
  CalendarMonthLabel,
  CalendarMonths,
  CalendarNextButton,
  CalendarPrevButton,
  CalendarViewButton,
  CalendarViewButtonGroup,
  CalendarWeekday,
  CalendarWeekdays,
  CalendarWeeks,
  CalendarYear,
  CalendarYearLabel,
  CalendarYears,
  DateField,
  DateFieldCalendar,
  DateFieldInput,
  DateFieldTriggerIconButton,
  HelperText,
  Label,
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@vega-ui/react';

export const locales = ['en-US', 'ru-RU', 'ar-EG', 'he-IL'];

export default function TypedDateWithACalendarPopover({ locale = 'en-US' }: { locale?: string }) {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%', maxWidth: 280 }}>
      <Label htmlFor='due-date'>Payment due</Label>
      <DateField locale={locale} format='dd.MM.yyyy' separator='.' defaultValue='2026-03-31'>
        <DateFieldInput id='due-date' placeholder='dd.mm.yyyy' aria-describedby='due-date-hint' />
        <Popover placement='bottom-end'>
          <PopoverTrigger asChild>
            <DateFieldTriggerIconButton aria-label='Choose due date' />
          </PopoverTrigger>
          <PopoverContent>
            <DateFieldCalendar>
              <CalendarHeader>
                <CalendarPrevButton aria-label='Previous month' />
                <CalendarViewButtonGroup>
                  <CalendarViewButton view='month'><CalendarMonthLabel /></CalendarViewButton>
                  <CalendarViewButton view='year'><CalendarYearLabel /></CalendarViewButton>
                </CalendarViewButtonGroup>
                <CalendarNextButton aria-label='Next month' />
              </CalendarHeader>
              <CalendarContent>
                <CalendarDays>
                  <CalendarWeekdays>
                    <CalendarWeekday />
                  </CalendarWeekdays>
                  <CalendarWeeks>
                    <CalendarDay />
                  </CalendarWeeks>
                </CalendarDays>
                <CalendarMonths>
                  <CalendarMonth />
                </CalendarMonths>
                <CalendarYears>
                  <CalendarYear />
                </CalendarYears>
              </CalendarContent>
            </DateFieldCalendar>
          </PopoverContent>
        </Popover>
      </DateField>
      <HelperText id='due-date-hint'>Type the date as dd.mm.yyyy or pick it in the calendar.</HelperText>
    </div>
  );
}
```

### Поле даты в формах с насыщенной валидацией

Когда использовать:

- Дата должна участвовать в валидации при отправке.
- Тексты ошибок и подсказок важнее визуального просмотра календаря.

Заметки о композиции:

- Используйте нативные `required` и `pattern` на `DateFieldInput` для проверки наличия и полноты; `min` и `max` уже соблюдаются при вводе.
- `disabledDates` соблюдается только в календаре, поэтому повторите это правило в валидации при отправке.
- Показывайте ошибку через `error` на корне и `HelperText error`, связанный через `aria-describedby`.

Компромиссы:

- Нативная валидация ограничений без дополнительных библиотек.
- `name` на input отправляет введённый текст; чтобы отправить ISO-день, копируйте `onChangeValue` в скрытый input, как в разделе [Форма/интеграция](https://vega-ui.dev/ru/components/date-field/#формаинтеграция).

```tsx
import { FormEvent, useState } from 'react';
import {
  Button,
  CalendarContent,
  CalendarDay,
  CalendarDays,
  CalendarHeader,
  CalendarMonth,
  CalendarMonthLabel,
  CalendarMonths,
  CalendarNextButton,
  CalendarPrevButton,
  CalendarViewButton,
  CalendarViewButtonGroup,
  CalendarWeekday,
  CalendarWeekdays,
  CalendarWeeks,
  CalendarYear,
  CalendarYearLabel,
  CalendarYears,
  DateField,
  DateFieldCalendar,
  DateFieldInput,
  DateFieldTriggerIconButton,
  HelperText,
  Label,
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@vega-ui/react';

const HINT = 'Enter the full date as dd.mm.yyyy.';

export default function RenewalForm() {
  const [error, setError] = useState('');
  const invalid = error !== '';

  const onInvalid = (event: FormEvent<HTMLInputElement>) => {
    if (event.currentTarget.validity.valueMissing) {
      setError('Enter the renewal date.');
      return;
    }

    setError('The date is incomplete. Use dd.mm.yyyy.');
  };

  return (
    <form onSubmit={(event) => event.preventDefault()} style={{ display: 'grid', gap: 'var(--spacing-12)', width: '100%', maxWidth: 280 }}>
      <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
        <Label htmlFor='renewal-date'>Renewal date</Label>
        <DateField format='dd.MM.yyyy' separator='.' error={invalid}>
          <DateFieldInput
            id='renewal-date'
            name='renewalDate'
            required
            pattern='\d{2}\.\d{2}\.\d{4}'
            placeholder='dd.mm.yyyy'
            aria-invalid={invalid}
            aria-describedby='renewal-date-hint'
            onInvalid={onInvalid}
            onInput={() => setError('')}
          />
          <Popover placement='bottom-end'>
            <PopoverTrigger asChild>
              <DateFieldTriggerIconButton aria-label='Choose renewal date' />
            </PopoverTrigger>
            <PopoverContent>
              <DateFieldCalendar>
                <CalendarHeader>
                  <CalendarPrevButton aria-label='Previous month' />
                  <CalendarViewButtonGroup>
                    <CalendarViewButton view='month'><CalendarMonthLabel /></CalendarViewButton>
                    <CalendarViewButton view='year'><CalendarYearLabel /></CalendarViewButton>
                  </CalendarViewButtonGroup>
                  <CalendarNextButton aria-label='Next month' />
                </CalendarHeader>
                <CalendarContent>
                  <CalendarDays>
                    <CalendarWeekdays>
                      <CalendarWeekday />
                    </CalendarWeekdays>
                    <CalendarWeeks>
                      <CalendarDay />
                    </CalendarWeeks>
                  </CalendarDays>
                  <CalendarMonths>
                    <CalendarMonth />
                  </CalendarMonths>
                  <CalendarYears>
                    <CalendarYear />
                  </CalendarYears>
                </CalendarContent>
              </DateFieldCalendar>
            </PopoverContent>
          </Popover>
        </DateField>
        <HelperText id='renewal-date-hint' error={invalid}>{invalid ? error : HINT}</HelperText>
      </div>
      <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 'var(--spacing-4)' }}>
        <Button type='submit'>Save</Button>
      </div>
    </form>
  );
}
```

### Поле даты в модальных сценариях

Когда использовать:

- Сценарии планирования или проверки находятся внутри `Dialog`, `Drawer` или `Sheet`.
- Календарь по-прежнему должен открываться по запросу.

Заметки о композиции:

- Проверьте фокус, закрытие и наложение слоёв вместе с родительским оверлеем.
- Календарь должен быть достаточно компактным для родительского контейнера.

Компромиссы:

- Сохраняет существующий модальный сценарий.
- Усложняет координацию оверлеев.

```tsx
import { CSSProperties } from 'react';
import { XIcon } from '@vega-ui/icons';
import {
  Button,
  CalendarContent,
  CalendarDay,
  CalendarDays,
  CalendarHeader,
  CalendarMonth,
  CalendarMonthLabel,
  CalendarMonths,
  CalendarNextButton,
  CalendarPrevButton,
  CalendarViewButton,
  CalendarViewButtonGroup,
  CalendarWeekday,
  CalendarWeekdays,
  CalendarWeeks,
  CalendarYear,
  CalendarYearLabel,
  CalendarYears,
  DateField,
  DateFieldCalendar,
  DateFieldInput,
  DateFieldTriggerIconButton,
  Dialog,
  DialogClose,
  DialogContent,
  DialogTrigger,
  Heading,
  Icon,
  IconButton,
  Label,
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@vega-ui/react';

export default function DateFieldInsideADialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Schedule review</Button>
      </DialogTrigger>
      <DialogContent aria-labelledby='schedule-review-title' style={{ minWidth: 320 }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
          <Heading id='schedule-review-title' as='h2' size={4} fontWeight={600}>Schedule review</Heading>
          <DialogClose asChild>
            <IconButton
              aria-label='Close'
              variant='secondary'
              appearance='ghost'
              style={{ flexShrink: 0, '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5 } as CSSProperties}
            >
              <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
            </IconButton>
          </DialogClose>
        </div>
        <form method='dialog' style={{ display: 'grid', gap: 'var(--spacing-12)', marginBlockStart: 'var(--spacing-8)' }}>
          <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
            <Label htmlFor='review-date'>Review date</Label>
            <DateField format='dd.MM.yyyy' separator='.' min='2026-01-01'>
              <DateFieldInput id='review-date' name='reviewDate' required placeholder='dd.mm.yyyy' />
              <Popover placement='bottom-end'>
                <PopoverTrigger asChild>
                  <DateFieldTriggerIconButton aria-label='Choose review date' />
                </PopoverTrigger>
                <PopoverContent>
                  <DateFieldCalendar>
                    <CalendarHeader>
                      <CalendarPrevButton aria-label='Previous month' />
                      <CalendarViewButtonGroup>
                        <CalendarViewButton view='month'><CalendarMonthLabel /></CalendarViewButton>
                        <CalendarViewButton view='year'><CalendarYearLabel /></CalendarViewButton>
                      </CalendarViewButtonGroup>
                      <CalendarNextButton aria-label='Next month' />
                    </CalendarHeader>
                    <CalendarContent>
                      <CalendarDays>
                        <CalendarWeekdays>
                          <CalendarWeekday />
                        </CalendarWeekdays>
                        <CalendarWeeks>
                          <CalendarDay />
                        </CalendarWeeks>
                      </CalendarDays>
                      <CalendarMonths>
                        <CalendarMonth />
                      </CalendarMonths>
                      <CalendarYears>
                        <CalendarYear />
                      </CalendarYears>
                    </CalendarContent>
                  </DateFieldCalendar>
                </PopoverContent>
              </Popover>
            </DateField>
          </div>
          <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 'var(--spacing-4)' }}>
            <Button type='submit'>Schedule</Button>
          </div>
        </form>
      </DialogContent>
    </Dialog>
  );
}
```

### Поле даты в негригорианском календаре

Когда использовать:

- Пользователи вводят и читают даты в персидском, еврейском, японском, буддийском, исламском или другом календаре.

Заметки о композиции:

- Создайте календарь один раз с помощью `createCalendarSystem` вне компонента (см. [Даты и календарные системы](https://vega-ui.dev/ru/docs/i18n/#адаптер-календарной-системы)) и передайте его в проп `calendar` корня вместе с `locale`.
- Ввод проверяется в этом календаре, например 31-й день 6-го персидского месяца или 13-й еврейский месяц; `min` и `max` остаются ISO-строками `YYYY-MM-DD`.

Компромиссы:

- Нативная длина месяцев и високосные месяцы без изменения хранимого значения.
- Длина месяцев и эры берутся из данных `Intl` среды выполнения.

```tsx
import { useState } from 'react';
import { createCalendarSystem } from '@vega-ui/i18n';
import {
  CalendarContent,
  CalendarDay,
  CalendarDays,
  CalendarHeader,
  CalendarMonth,
  CalendarMonthLabel,
  CalendarMonths,
  CalendarNextButton,
  CalendarPrevButton,
  CalendarViewButton,
  CalendarViewButtonGroup,
  CalendarWeekday,
  CalendarWeekdays,
  CalendarWeeks,
  CalendarYear,
  CalendarYearLabel,
  CalendarYears,
  DateField,
  DateFieldCalendar,
  DateFieldInput,
  DateFieldTriggerIconButton,
  Label,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Text,
} from '@vega-ui/react';

const persianCalendar = createCalendarSystem('persian');

export default function DateFieldInANonGregorianCalendar() {
  const [date, setDate] = useState('2026-03-21');

  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%', maxWidth: 280 }}>
      <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
        <Label htmlFor='persian-date'>تاریخ تحویل</Label>
        <DateField
          format='yyyy/MM/dd'
          separator='/'
          calendar={persianCalendar}
          locale='fa-IR'
          min='2026-03-21'
          value={date}
          onChangeValue={setDate}
        >
          <DateFieldInput id='persian-date' placeholder='yyyy/mm/dd' />
          <Popover placement='bottom-end'>
            <PopoverTrigger asChild>
              <DateFieldTriggerIconButton aria-label='انتخاب تاریخ' />
            </PopoverTrigger>
            <PopoverContent>
              <DateFieldCalendar>
                <CalendarHeader>
                  <CalendarPrevButton aria-label='ماه قبل' />
                  <CalendarViewButtonGroup>
                    <CalendarViewButton view='month'><CalendarMonthLabel /></CalendarViewButton>
                    <CalendarViewButton view='year'><CalendarYearLabel /></CalendarViewButton>
                  </CalendarViewButtonGroup>
                  <CalendarNextButton aria-label='ماه بعد' />
                </CalendarHeader>
                <CalendarContent>
                  <CalendarDays>
                    <CalendarWeekdays>
                      <CalendarWeekday />
                    </CalendarWeekdays>
                    <CalendarWeeks>
                      <CalendarDay />
                    </CalendarWeeks>
                  </CalendarDays>
                  <CalendarMonths>
                    <CalendarMonth />
                  </CalendarMonths>
                  <CalendarYears>
                    <CalendarYear />
                  </CalendarYears>
                </CalendarContent>
              </DateFieldCalendar>
            </PopoverContent>
          </Popover>
        </DateField>
      </div>
      <Text size={2} style={{ color: 'var(--label-secondary)' }}>
        Stored value: {date ? <code>{date}</code> : 'no complete date'}
      </Text>
    </div>
  );
}
```

## Сравнение

### Быстрое правило выбора

Используйте `DateField`, когда значение — одна календарная дата, и пользователи должны иметь возможность ввести её, выбрать или и то и другое.

### `DateField` или `DateRangeField`

Используйте `DateField`, когда хранится одна дата.

Используйте `DateRangeField`, когда начальная и конечная даты должны оставаться связанными в одном элементе управления.

Главный компромисс: `DateField` проще подписать и провалидировать, а `DateRangeField` уменьшает ошибки согласования двух связанных значений.

### `DateField` или `DateTimeField`

Используйте `DateField`, когда время не важно для хранимого значения.

Используйте `DateTimeField`, когда итоговое значение определяют дата и время вместе.

Главный компромисс: `DateField` проще вводить и проверять, а `DateTimeField` фиксирует полную временну́ю метку.

### `DateField` или `Calendar`

Используйте `DateField`, когда пользователям нужен ввод с клавиатуры и поле в духе формы.

Используйте `Calendar`, когда интерфейс в первую очередь про визуальный выбор даты без ввода текста в поле.

Главный компромисс: `DateField` лучше вписывается в формы, а `Calendar` сильнее при постоянно видимом выборе даты.

### Выбирайте этот компонент, когда

- Значение — одна дата.
- Пользователи могут вводить дату напрямую.
- Элемент управления находится в строке формы или в панели настроек.
- Календарь — необязательная поддержка, а не вся модель взаимодействия.

### Не выбирайте этот компонент, когда

- Функции нужна пара из начальной и конечной даты.
- Хранимое значение должно включать время.
- Интерфейс должен показывать полностью видимый календарь вместо поля ввода.

## Решение проблем

### Кнопка календаря ничего не открывает

#### Симптом

Иконка-кнопка триггера рендерится, но по клику календарь не появляется.

#### Вероятная причина

`DateFieldTriggerIconButton` отрендерен без окружающего `Popover` или без `DateFieldCalendar` внутри оверлея.

#### Как проверить

- Проверьте, обёрнут ли триггер в `PopoverTrigger`.
- Проверьте, содержит ли `PopoverContent` компонент `DateFieldCalendar`.

#### Исправление

Скомпонуйте триггер и календарь через `Popover` внутри `DateField`.

```tsx
import {
  CalendarContent,
  CalendarDay,
  CalendarDays,
  CalendarHeader,
  CalendarMonth,
  CalendarMonthLabel,
  CalendarMonths,
  CalendarNextButton,
  CalendarPrevButton,
  CalendarViewButton,
  CalendarViewButtonGroup,
  CalendarWeekday,
  CalendarWeekdays,
  CalendarWeeks,
  CalendarYear,
  CalendarYearLabel,
  CalendarYears,
  DateField,
  DateFieldCalendar,
  DateFieldInput,
  DateFieldTriggerIconButton,
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@vega-ui/react';

export default function MinimalComposition() {
  return (
    <DateField format='dd.MM.yyyy' separator='.'>
      <DateFieldInput aria-label='Due date' />
      <Popover placement='bottom-end'>
        <PopoverTrigger asChild>
          <DateFieldTriggerIconButton aria-label='Choose due date' />
        </PopoverTrigger>
        <PopoverContent>
          <DateFieldCalendar>
            <CalendarHeader>
              <CalendarPrevButton aria-label='Previous month' />
              <CalendarViewButtonGroup>
                <CalendarViewButton view='month'><CalendarMonthLabel /></CalendarViewButton>
                <CalendarViewButton view='year'><CalendarYearLabel /></CalendarViewButton>
              </CalendarViewButtonGroup>
              <CalendarNextButton aria-label='Next month' />
            </CalendarHeader>
            <CalendarContent>
              <CalendarDays>
                <CalendarWeekdays>
                  <CalendarWeekday />
                </CalendarWeekdays>
                <CalendarWeeks>
                  <CalendarDay />
                </CalendarWeeks>
              </CalendarDays>
              <CalendarMonths>
                <CalendarMonth />
              </CalendarMonths>
              <CalendarYears>
                <CalendarYear />
              </CalendarYears>
            </CalendarContent>
          </DateFieldCalendar>
        </PopoverContent>
      </Popover>
    </DateField>
  );
}
```

### Введённое значение выглядит неправильно

#### Симптом

Формат, который видят пользователи, не совпадает с поведением разбора.

#### Вероятная причина

`format` или `separator` не совпадает с форматом даты в подписях, подсказках и сообщениях валидации.

#### Как проверить

- Сравните заданный `format` с видимой подсказкой формы.
- Введите типичное значение и проверьте, как оно интерпретируется.

#### Исправление

Согласуйте видимые подсказки, правила разбора и тексты валидации вокруг одного и того же формата.

### Отправляемое значение — не ISO-дата

#### Симптом

Форма отправляет `15.01.2026` вместо `2026-01-15`.

#### Вероятная причина

`DateFieldInput` — нативный input, и с `name` отправляется введённый в него текст.

#### Как проверить

- Прочитайте поле из `FormData` в обработчике отправки.

#### Исправление

Берите ISO-день из `onChangeValue` на корне, а не текст. Чтобы отправить его с формой, уберите `name` у input и задайте его скрытому input с этим днём, как в разделе [Форма/интеграция](https://vega-ui.dev/ru/components/date-field/#формаинтеграция).

### Отключённые даты всё ещё можно ввести

#### Симптом

Календарь блокирует некоторые дни, но если ввести один из них вручную, он принимается и отправляется.

#### Вероятная причина

`min` и `max` ограничивают введённые даты, но `disabledDates` действует только на календарь.

#### Как проверить

- Введите отключённый день и отправьте форму.

#### Исправление

Проверяйте то же правило `disabledDates` в валидации при отправке и на сервере.
