@sberbusiness/triplex-next-mcp-server 1.45.0-beta → 1.48.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bundle/components/AmountField.md +222 -323
- package/bundle/components/Avatar.md +138 -146
- package/bundle/components/CardAction.md +615 -0
- package/bundle/components/CardStatic.md +402 -0
- package/bundle/components/CheckboxTree.md +157 -215
- package/bundle/components/CheckboxTreeExtended.md +7 -7
- package/bundle/components/Chip.md +7 -6
- package/bundle/components/ChipGroup.md +1 -1
- package/bundle/components/ChipMonthYearPicker.md +344 -0
- package/bundle/components/ChipMultiselect.md +1251 -0
- package/bundle/components/ChipOptions.md +329 -0
- package/bundle/components/ChipSort.md +333 -0
- package/bundle/components/ChipSuggest.md +588 -0
- package/bundle/components/CollapsibleTree.md +8 -4
- package/bundle/components/CollapsibleTreeExtended.md +8 -6
- package/bundle/components/Confirm.md +346 -281
- package/bundle/components/DateField.md +222 -303
- package/bundle/components/DatePickerExtended.md +3 -1
- package/bundle/components/DateRange.md +351 -210
- package/bundle/components/FooterPage.md +1 -0
- package/bundle/components/FormField.md +14 -5
- package/bundle/components/FormGroup.md +3 -2
- package/bundle/components/HelpBox.md +7 -1
- package/bundle/components/IslandAccordion.md +443 -152
- package/bundle/components/IslandWidget.md +622 -157
- package/bundle/components/LightBox.md +8 -0
- package/bundle/components/ListMaster.md +2 -2
- package/bundle/components/ListTableItem.md +6 -6
- package/bundle/components/MaskedField.md +2 -1
- package/bundle/components/MonthYearField.md +18 -5
- package/bundle/components/Slider.md +389 -97
- package/bundle/components/SliderRange.md +486 -105
- package/bundle/components/StatusTracker.md +386 -94
- package/bundle/components/Stepper.md +355 -102
- package/bundle/components/StepperExtended.md +633 -0
- package/bundle/components/SuggestField.md +1049 -96
- package/bundle/components/SwipeableArea.md +7 -7
- package/bundle/components/Tabs.md +265 -92
- package/bundle/components/TabsExtended.md +18 -18
- package/bundle/components/TabsLine.md +301 -127
- package/bundle/components/TagColor.md +187 -66
- package/bundle/components/TagGroup.md +184 -48
- package/bundle/components/TextareaField.md +278 -70
- package/bundle/components/TopOverlay.md +17 -1
- package/bundle/components/TreeView.md +16 -7
- package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
- package/bundle/components/UploadZone.md +318 -0
- package/bundle/guides/codestyle.md +16 -1
- package/bundle/guides/commits.md +47 -1
- package/bundle/guides/tests.md +23 -2
- package/bundle/manifest.json +92 -24
- package/bundle/release-notes/1.46.0.mdx +59 -0
- package/bundle/release-notes/1.47.0.mdx +122 -0
- package/bundle/release-notes/1.48.0.mdx +68 -0
- package/bundle/release-notes/1.49.0.mdx +7 -0
- package/bundle/tokens.json +3 -3
- package/dist/index.js +220 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: DateField
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [DatePickerExtended, Calendar, MaskedField, MonthYearField, DateRange, Tooltip, MobileView]
|
|
3
|
+
category: Date components
|
|
4
|
+
related: [DatePickerExtended, MonthYearField, ChipDatePicker, MaskedField, Calendar, Tooltip]
|
|
7
5
|
tokens: []
|
|
8
6
|
stories: stories/DateField/DateField.stories.tsx
|
|
9
7
|
version: "1.0"
|
|
@@ -13,359 +11,280 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
Поле ввода
|
|
17
|
-
(`DatePickerExtended`). Объединяет текстовый ввод с пикером, валидирует
|
|
18
|
-
дату (`moment`), синхронизирует состояние input ↔ calendar ↔ внешний
|
|
19
|
-
`value`, показывает подсказку-tooltip при невалидном вводе на десктопе.
|
|
14
|
+
Поле ввода и выбора даты. Поле ввода с маской (`MaskedField` + `FormFieldMaskedInput`) и иконкой календаря в постфиксе, по клику раскрывающее выпадающий календарь `DatePickerExtended`. Дату можно как ввести с клавиатуры в формате `дд.мм.гггг`, так и выбрать в календаре.
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
в заголовке (`DropdownMobileMaskedInput`), без tooltip'а.
|
|
16
|
+
Используй когда: нужен ввод конкретной даты (с днём), и потребителю важна возможность набрать её с клавиатуры, а не только выбрать мышью.
|
|
23
17
|
|
|
24
|
-
`
|
|
25
|
-
(default `YYYY-MM-DD`), `onChange(value: string)` — реакция на изменение.
|
|
26
|
-
|
|
27
|
-
Используй когда: нужно однократное поле даты в форме с возможностью
|
|
28
|
-
ввести руками **или** выбрать в календаре.
|
|
29
|
-
Не используй когда: нужен диапазон дат — бери `DateRange`; нужен выбор
|
|
30
|
-
только месяца/года — `MonthYearField`.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Файловая структура
|
|
35
|
-
|
|
36
|
-
```text
|
|
37
|
-
src/components/DateField/
|
|
38
|
-
├── DateField.tsx # forwardRef<HTMLDivElement>, root FC
|
|
39
|
-
├── DateFieldTarget.tsx # FC, target для DatePickerExtended (MaskedField + иконка календаря + clear)
|
|
40
|
-
├── DateFieldContext.ts # React.Context с inputFocusedRef, onChange, triggerChangeFromInput
|
|
41
|
-
├── types.ts # IDateFieldProps
|
|
42
|
-
├── utils.ts # DateFieldUtils (getPickerValues, getCalendarDate, isAvailableDate)
|
|
43
|
-
├── constants.ts # inputDateFormat = "DD.MM.YYYY"
|
|
44
|
-
├── index.ts # Barrel
|
|
45
|
-
└── __tests__/DateField.test.tsx
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Особенности:
|
|
49
|
-
- **`displayName = "DatePicker"`** (не `"DateField"`) — известная
|
|
50
|
-
особенность, исторически. Не переименовывать без согласования — может
|
|
51
|
-
сломать snapshot-тесты потребителей.
|
|
52
|
-
- **Нет собственных стилей** — вся визуалка от `DatePickerExtended` +
|
|
53
|
-
`MaskedField`.
|
|
54
|
-
- **Нет собственных токенов** — наследуется от формы и календаря.
|
|
55
|
-
- Есть `__tests__/DateField.test.tsx` — unit-тесты ввода/валидации.
|
|
18
|
+
Не используй когда: нужен выбор только месяца и года (`MonthYearField`), диапазона дат (`DateRange`), компактный chip-триггер вместо полноценного поля (`ChipDatePicker`), или календарь без поля ввода (`DatePickerExtended` / `Calendar` напрямую).
|
|
56
19
|
|
|
57
20
|
---
|
|
58
21
|
|
|
59
22
|
## Варианты и props
|
|
60
23
|
|
|
61
|
-
|
|
24
|
+
`DateField` — обёртка над `DatePickerExtended`, поэтому помимо собственных props принимает все props `DatePickerExtended` (включая props `Calendar`: `limitRange`, `disabledDays`, `markedDays`, `format` и др.), кроме управляемых внутри (`pickedDate`, `onDateChange`, `renderTarget`, `renderDropdownHeaderTarget`), и часть props `MaskedField` (`size`, `status`, `label`). Остальные неперечисленные props пробрасываются в `DatePickerExtended`.
|
|
62
25
|
|
|
63
|
-
|
|
64
|
-
extends Omit<IDatePickerExtendedProps,
|
|
65
|
-
"pickedDate" | "onDateChange" | "renderTarget" | "renderDropdownHeaderTarget">,
|
|
66
|
-
Pick<IMaskedFieldProps, "size" | "status" | "label">
|
|
67
|
-
```
|
|
26
|
+
### Обязательные props
|
|
68
27
|
|
|
69
|
-
|
|
28
|
+
| Prop | Тип | Описание |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `value` | `string` | Значение даты в формате `format`. Пустая строка — поле очищено. Невалидное значение, дата вне `limitRange` или дата из `disabledDays` отображаются как пустое поле. |
|
|
31
|
+
| `onChange` | `(value: string) => void` | Вызывается при фиксации значения. Аргумент — строка даты в формате `format` либо пустая строка при очистке поля. |
|
|
32
|
+
| `invalidDateHint` | `ReactNode` | Текст тултипа, который показывается, когда в поле введена полная (10 символов), но недоступная для выбора дата. |
|
|
70
33
|
|
|
71
|
-
|
|
72
|
-
|---|---|---|---|
|
|
73
|
-
| `value` | `string` | — | **Controlled** — дата в формате `format`. Пустая строка = нет даты. |
|
|
74
|
-
| `onChange` | `(value: string) => void` | — | Вызывается при валидном вводе/выборе. `value` может быть пустой строкой (очистка). |
|
|
75
|
-
| `format` | `string` | `dateFormatYYYYMMDD` (`"YYYY-MM-DD"`) | moment-формат для `value`. **Не путать с `inputDateFormat` = `"DD.MM.YYYY"`** (формат отображения в маске). |
|
|
76
|
-
| `limitRange` | `IDateLimitRange` | `globalLimitRange` | `{ dateFrom, dateTo }` — допустимый диапазон. |
|
|
77
|
-
| `disabledDays` | `string[]` | — | Массив дат в формате `format`, которые нельзя выбрать. |
|
|
78
|
-
| `placeholderMask` | `string` | — | Плейсхолдер-символы для пустых позиций в маске (обычно `"дд.мм.гггг"`). |
|
|
79
|
-
| `invalidDateHint` | `ReactNode` | — | Текст в тултипе при невалидной введённой дате. |
|
|
80
|
-
| `size` | `EComponentSize` | `MD` | Размер MaskedField (SM/MD/LG). |
|
|
81
|
-
| `status` | `EFormFieldStatus` | — | Валидация + disabled. |
|
|
82
|
-
| `label` | `ReactNode` | — | Label над полем. |
|
|
83
|
-
| `aria-label` / `aria-labelledby` | `string` | — | Прокидываются в MaskedInput. |
|
|
84
|
-
| `targetProps` | `DeepPartial<IMaskedFieldProps>` | — | Глубокий частичный override для MaskedField. Позволяет точечно перебить. |
|
|
85
|
-
| `onDropdownOpen` | `() => void` | — | Coincidence callback при открытии календаря. |
|
|
86
|
-
| `onDropdownClose` | `() => void` | — | Callback при закрытии (запускает trigger, если input дирти). |
|
|
87
|
-
| `...IDatePickerExtendedProps` | — | — | Остальные пропсы пикера (минус 4 omitted). |
|
|
88
|
-
|
|
89
|
-
### `DateFieldContext` — внутренний
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
interface IDateFieldContext {
|
|
93
|
-
inputFocusedRef: MutableRefObject<boolean | null>;
|
|
94
|
-
onChange: (value: string) => void;
|
|
95
|
-
triggerChangeFromInput: () => void;
|
|
96
|
-
}
|
|
97
|
-
```
|
|
34
|
+
### Опциональные props
|
|
98
35
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
36
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| `size` | `EComponentSize` | `MD` (в target) | Размер поля: SM / MD / LG. Определяет и размер иконки календаря, и `min-width` поля ввода. |
|
|
39
|
+
| `status` | `EFormFieldStatus` | `default` | Статус поля: default / error / warning / disabled. `disabled` блокирует поле и кнопку календаря. |
|
|
40
|
+
| `label` | `ReactNode` | — | Лейбл поля (проброс в `MaskedField`). |
|
|
41
|
+
| `placeholderMask` | `string` | — | Символы для заполнения пустых редактируемых позиций маски, например `"дд.мм.гггг"`. |
|
|
42
|
+
| `format` | `string` | `dateFormatYYYYMMDD` (`"YYYYMMDD"`) | Формат парсинга/сериализации `value` и аргумента `onChange`. На отображение НЕ влияет — в поле всегда показывается `"DD.MM.YYYY"`. |
|
|
43
|
+
| `limitRange` | `IDateLimitRange` | `globalLimitRange` | Допустимый диапазон дат. Значение вне диапазона трактуется как пустое и не фиксируется через `onChange`. |
|
|
44
|
+
| `disabledDays` | `string[]` | — | Недоступные для выбора дни. **Строки должны быть в формате `format`**, а не в формате отображения. |
|
|
45
|
+
| `onClear` | `MouseEventHandler<HTMLButtonElement>` | — | Если передан — в постфиксе появляется кнопка очистки. Реальную очистку значения выполняет потребитель. |
|
|
46
|
+
| `onDropdownOpen` / `onDropdownClose` | `() => void` | — | Колбэки открытия/закрытия выпадающего календаря. Вызываются после внутренней обработки: при закрытии `onChange` (фиксация введённого значения) может сработать раньше `onDropdownClose`. |
|
|
47
|
+
| `targetProps` | `DeepPartial<IMaskedFieldProps>` | — | Дополнительные props внутреннего `MaskedField` (`postfix`, `description`, `maskedInputProps` и т.д.). Приоритет: верхнеуровневые ключи `targetProps` **переопределяют** заданные компонентом `size` / `status` / `label` / `onClear` (`{...restTargetProps}` разворачивается после них). Значения `maskedInputProps.value`, `mask`, `placeholderMask`, `aria-label`, `aria-labelledby` — тоже лишь **значения по умолчанию**, переданные одноимённые ключи их переопределяют. **Но обработчики не переопределяются, а дополняются:** `onChange`, `onFocus`, `onBlur`, `onKeyDown`, `onMouseDown` из `maskedInputProps` вызываются ПОСЛЕ внутренних, поэтому свой `onBlur` не отключает фиксацию значения. `className` не переопределяется, а мержится через `clsx`. |
|
|
48
|
+
|
|
49
|
+
### Логика фиксации значения
|
|
50
|
+
|
|
51
|
+
Ключевая особенность компонента: ввод с клавиатуры **не** вызывает `onChange` на каждое нажатие. Значение фиксируется, когда пользователь заканчивает редактирование:
|
|
52
|
+
|
|
53
|
+
- **При вводе** обновляется только внутренний стейт (`pickerValues`): строка поля и подсвеченная в календаре дата. Если введены все 10 символов и дата невалидна/недоступна — открывается тултип с `invalidDateHint`.
|
|
54
|
+
- **По blur** поля (при закрытом дропдауне) и **по закрытию дропдауна** (если поле не в фокусе) вызывается `triggerChangeFromInput`:
|
|
55
|
+
- поле очищено, а `value` не пустое → `onChange("")`;
|
|
56
|
+
- введена валидная и доступная дата, отличная от текущего `value` → `onChange(date.format(format))`;
|
|
57
|
+
- иначе поле откатывается к последнему валидному значению (`lastValidPickerValuesRef`).
|
|
58
|
+
- **Выбор даты в календаре** вызывает `onChange` сразу.
|
|
59
|
+
|
|
60
|
+
Компонент контролируемый: пока потребитель не обновит `value` в ответ на `onChange`, зафиксированное значение в поле не изменится.
|
|
102
61
|
|
|
103
|
-
|
|
62
|
+
---
|
|
104
63
|
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
getPickerValues(value, format, limitRange, disabledDays): { inputString, calendarDate }
|
|
108
|
-
getCalendarDate(value, format, limitRange, disabledDays): Moment | null
|
|
109
|
-
isAvailableDate(date, value, limitRange, disabledDays): boolean
|
|
110
|
-
}
|
|
111
|
-
```
|
|
64
|
+
## Дизайн-токены
|
|
112
65
|
|
|
113
|
-
|
|
114
|
-
(`inputString` в `inputDateFormat` + `calendarDate` Moment|null).
|
|
66
|
+
Собственных токенов нет — в `styles/DateFieldTarget.module.less` заданы только `min-width` поля по размерам. Внешний вид наследуется от `MaskedField` / `FormField`, `ButtonIcon`, `FormFieldClear`, `Tooltip` и `Calendar` / `DatePickerExtended`.
|
|
115
67
|
|
|
116
68
|
---
|
|
117
69
|
|
|
118
|
-
##
|
|
70
|
+
## Инварианты
|
|
119
71
|
|
|
120
|
-
|
|
72
|
+
- `forwardRef` на компоненте — не убирать. Ref пробрасывается в `DatePickerExtended` (корневой DOM-элемент — элемент `DatePickerExtended`); та же ссылка используется внутри как `targetRef` тултипа, поэтому `setRef` должен продолжать писать и в `tooltipTargetRef`.
|
|
73
|
+
- Публичный API (`IDateFieldProps`: `value`, `onChange`, `onClear`, `invalidDateHint`, `placeholderMask`, `targetProps` + унаследованные `size`/`status`/`label` и props `DatePickerExtended`) — изменение имён/типов/значений — breaking change.
|
|
74
|
+
- Отображаемый формат ввода зафиксирован константой `inputDateFormat = "DD.MM.YYYY"` (`constants.ts`) и связан с маской `FormFieldMaskedInput.presets.masks.date`. Менять его нужно синхронно с маской — иначе разъедутся длина ввода (`event.target.value.length === inputDateFormat.length`) и парсинг.
|
|
75
|
+
- `disabledDays` сравниваются со строкой даты в формате `format` (`isDayDisabled` делает `includes`) — не менять формат сравнения без обновления документации потребителя.
|
|
76
|
+
- Sync-эффект в `DateField.tsx` (`useEffect` по `[value, format, limitRange, disabledDays]` с `eslint-disable react-hooks/exhaustive-deps`) — намеренная синхронизация derived-стейта `pickerValues` с внешним `value`. `pickerValues` намеренно не в зависимостях: иначе эффект затирал бы промежуточный ввод пользователя. Сравнение идёт по `inputString`.
|
|
77
|
+
- Внутренние символы `DateFieldUtils` (`utils.ts`), `DateFieldContext`, `DateFieldTarget`, `inputDateFormat` не экспортируются через barrel — это приватные детали реализации. **Исключение — тип `IDateFieldTargetProps`:** он лежит в `types.ts`, а `index.ts` делает `export * from "./types"`, поэтому тип уходит в публичный API пакета. Переименование или изменение его полей — breaking change.
|
|
78
|
+
- **`isOpen={tooltipOpenedRef.current}` читает ref во время рендера** (`DateField.tsx`, ветка `MobileView.fallback`). Это работает только потому, что `handleInputChange` в том же вызове делает `setPickerValues` и тем самым провоцирует ре-рендер. Любая «оптимизация» вида «не вызывать `setPickerValues`, если строка не изменилась» тихо сломает показ `invalidDateHint`, при этом тесты на ввод останутся зелёными. Трогать эту пару (ref + `setPickerValues`) только вместе.
|
|
79
|
+
- **`DateFieldUtils` и `inputDateFormat` переиспользует `ChipDatePicker`** (`src/components/Chip/ChipDatePicker/ChipDatePicker.tsx`), который повторяет ту же логику ввода даты. Переименование или изменение сигнатур этих символов ломает `ChipDatePicker` — правь их только синхронно с ним.
|
|
121
80
|
|
|
122
|
-
|
|
123
|
-
`YYYY-MM-DD`. Настраивается через пропс `format`.
|
|
124
|
-
- **Внутренний ввод (маска)** — всегда `"DD.MM.YYYY"`. Захардкожен
|
|
125
|
-
в `constants.ts`.
|
|
81
|
+
---
|
|
126
82
|
|
|
127
|
-
|
|
128
|
-
внутренний API — ISO для backend. Конвертация через `moment(date,
|
|
129
|
-
format)` / `date.format(inputDateFormat)`.
|
|
83
|
+
## Accessibility
|
|
130
84
|
|
|
131
|
-
|
|
85
|
+
- Триггер — поле ввода с маской. Открытие/закрытие календаря по клику (`mousedown`), по клавише `Enter` и по `Space` (с `preventDefault` для `Space`, чтобы не вводить пробел и не скроллить страницу). Обработчики в `DateFieldTarget`.
|
|
86
|
+
- `aria-label` и `aria-labelledby` пробрасываются потребителем на элемент input через одноимённые props `DateField`. Компонент не хардкодит текст — библиотека мультиязычная.
|
|
87
|
+
- Состояние `disabled` (`status === EFormFieldStatus.DISABLED`) блокирует и поле, и кнопку календаря.
|
|
88
|
+
- Тултип с `invalidDateHint` показывается только в desktop-ветке (`MobileView.fallback`); в мобильном представлении ввод идёт через `DropdownMobileMaskedInput` в заголовке дропдауна.
|
|
89
|
+
- ARIA-роль выпадающего календаря (`dialog`) и навигация по нему — на стороне `DatePickerExtended` / `Calendar`.
|
|
90
|
+
- **Известный пробел:** кнопка-иконка календаря (`ButtonIcon` в `DateFieldTarget`) фокусируема и не имеет `aria-label` — скринридер объявит её без имени. В `MonthYearFieldTarget` аналогичная кнопка выведена из таб-обхода (`role="presentation"`, `tabIndex={-1}`). Приведение к общему поведению требует изменения публичного API (новый prop для локализованного лейбла) и вынесено за рамки AI-рефакторинга.
|
|
132
91
|
|
|
133
|
-
|
|
134
|
-
const [pickerValues, setPickerValues] = useState(
|
|
135
|
-
DateFieldUtils.getPickerValues(value, format, limitRange, disabledDays),
|
|
136
|
-
);
|
|
137
|
-
```
|
|
92
|
+
---
|
|
138
93
|
|
|
139
|
-
|
|
140
|
-
Синхронизируется с внешним `value` через `useEffect`:
|
|
141
|
-
|
|
142
|
-
```ts
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
const newPickerValues = DateFieldUtils.getPickerValues(...);
|
|
145
|
-
if (newPickerValues.inputString !== pickerValues.inputString) {
|
|
146
|
-
setPickerValues(newPickerValues);
|
|
147
|
-
}
|
|
148
|
-
lastValidPickerValuesRef.current = newPickerValues;
|
|
149
|
-
}, [value, format, limitRange, disabledDays]);
|
|
150
|
-
```
|
|
94
|
+
## Связанные компоненты
|
|
151
95
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const triggerChangeFromInput = () => {
|
|
162
|
-
if (pickerValues.inputString.length === 0 && value.length !== 0) {
|
|
163
|
-
return onChange(pickerValues.inputString); // очистка
|
|
164
|
-
}
|
|
165
|
-
const date = moment(pickerValues.inputString, inputDateFormat, true);
|
|
166
|
-
if (date.isValid()) {
|
|
167
|
-
const newValue = date.format(format);
|
|
168
|
-
if (newValue === value) return;
|
|
169
|
-
if (DateFieldUtils.isAvailableDate(...)) {
|
|
170
|
-
return onChange(newValue);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Откат к последнему валидному
|
|
174
|
-
if (pickerValues.inputString !== lastValidPickerValuesRef.current.inputString) {
|
|
175
|
-
tooltipOpened.current = false;
|
|
176
|
-
setPickerValues(lastValidPickerValuesRef.current);
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
```
|
|
96
|
+
- `DatePickerExtended` — база выпадающего календаря; `DateField` конфигурирует её через render-props (`renderTarget`, `renderDropdownHeaderTarget`) и наследует её props.
|
|
97
|
+
- `MonthYearField` — альтернатива для выбора только месяца и года; устроен по той же схеме, но поле read-only.
|
|
98
|
+
- `ChipDatePicker` — выбор даты в виде чипа-фильтра, а не поля формы; альтернатива, когда дата задаётся в панели фильтров. Одновременно единственный потребитель, опирающийся на приватные детали `DateField` — импортирует `DateFieldUtils` и `inputDateFormat` (см. «Инварианты»), поэтому правки этих символов проверяй и на нём.
|
|
99
|
+
- `MaskedField` — внутреннее поле-триггер; его props частично входят в публичный API (`size`, `status`, `label`, `targetProps`).
|
|
100
|
+
- `Calendar` — календарь внутри дропдауна; его props (`limitRange`, `disabledDays`, `markedDays`, `format`) входят в публичный API через `DatePickerExtended`.
|
|
101
|
+
- `Tooltip` — показывает `invalidDateHint` при вводе недоступной даты (только desktop-ветка).
|
|
102
|
+
- `DateFieldTarget` (внутренний, не в barrel) — рендерит `MaskedField` с иконкой календаря и кнопкой очистки, обрабатывает клик/клавиатуру/фокус. Описан здесь, отдельного AI.md не имеет. Покрыт `__tests__/DateFieldTarget.test.tsx`.
|
|
103
|
+
- `DateFieldContext` (внутренний, не в barrel) — прокидывает `onChange`, `inputFocusedRef` и `triggerChangeFromInput` в target.
|
|
104
|
+
- `DateFieldUtils` (`utils.ts`, внутренний) — чистые функции парсинга/валидации даты (`getPickerValues`, `getCalendarDate`, `isAvailableDate`). Покрыты `__tests__/utils.test.tsx`.
|
|
180
105
|
|
|
181
|
-
|
|
182
|
-
dropdown'а (если input был расфокусирован и грязный). **Логика "не дать
|
|
183
|
-
пользователю повесить невалидную дату"** — UX-решение.
|
|
106
|
+
---
|
|
184
107
|
|
|
185
|
-
|
|
108
|
+
## Stories
|
|
186
109
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
fallback={
|
|
190
|
-
<Tooltip targetRef={tooltipTargetRef} size={SM} isOpen={tooltipOpened.current}>
|
|
191
|
-
<Tooltip.Body>{invalidDateHint}</Tooltip.Body>
|
|
192
|
-
<Tooltip.Target>{renderDatePickerExtended()}</Tooltip.Target>
|
|
193
|
-
</Tooltip>
|
|
194
|
-
}
|
|
195
|
-
>
|
|
196
|
-
{renderDatePickerExtended()}
|
|
197
|
-
</MobileView>
|
|
198
|
-
```
|
|
110
|
+
Основные истории: `stories/DateField/DateField.stories.tsx`
|
|
111
|
+
Файлы примеров: `stories/DateField/examples/`
|
|
199
112
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
113
|
+
| Story | Example file | Что демонстрирует |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль size / status / label / placeholderMask / invalidDateHint + настройки postfix и description |
|
|
116
|
+
| `Default` | `DefaultExample.tsx` | Минимальное поле с label и маской-плейсхолдером |
|
|
117
|
+
| `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG |
|
|
118
|
+
| `Statuses` | `StatusesExample.tsx` | Статусы default / disabled / error / warning |
|
|
119
|
+
| `Production` | `ProductionExample.tsx` | Production-композиция (`Example: production`): postfix `HelpBox`, description со ссылкой, кнопка очистки и возврат фокуса на input |
|
|
120
|
+
| `VisualTests` | `VisualTestsExample.tsx` | Скриншот-регрессия: заполненные поля всех размеров; `play` кликает по последнему полю, раскрывая календарь |
|
|
203
121
|
|
|
204
|
-
|
|
122
|
+
---
|
|
205
123
|
|
|
206
|
-
|
|
207
|
-
const renderDropdownHeaderTarget = () => (
|
|
208
|
-
<DropdownMobileMaskedInput
|
|
209
|
-
value={pickerValues.inputString}
|
|
210
|
-
mask={DropdownMobileMaskedInput.presets.masks.date}
|
|
211
|
-
placeholderMask={placeholderMask}
|
|
212
|
-
onChange={handleInputChange}
|
|
213
|
-
autoFocus={true}
|
|
214
|
-
/>
|
|
215
|
-
);
|
|
216
|
-
```
|
|
124
|
+
## История изменений
|
|
217
125
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
126
|
+
| Дата | Изменение |
|
|
127
|
+
|---|---|
|
|
128
|
+
| 2026-09-08 | Создан документ. AI-рефакторинг (JSDoc-фиксы, `displayName` у `DateFieldTarget`), unit-тесты для `utils.ts`, поведения поля (ввод, blur, клавиатура, колбэки дропдауна) и `DateFieldTarget` (фокус, mousedown, клавиатура, проброс обработчиков) |
|
|
221
129
|
|
|
222
|
-
|
|
130
|
+
---
|
|
223
131
|
|
|
224
|
-
|
|
225
|
-
Он не прямой потомок `DateField`, поэтому **обмен с корнем идёт через
|
|
226
|
-
`DateFieldContext`** (inputFocusedRef, onChange, triggerChangeFromInput).
|
|
132
|
+
## Примеры использования
|
|
227
133
|
|
|
228
|
-
|
|
229
|
-
MaskedField + calendar icon + clear button + keyboard (`Enter`/`Space`
|
|
230
|
-
открывают dropdown).
|
|
134
|
+
### Default
|
|
231
135
|
|
|
232
|
-
|
|
136
|
+
`stories/DateField/examples/DefaultExample.tsx`
|
|
233
137
|
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
138
|
+
```tsx
|
|
139
|
+
import React, { useState } from "react";
|
|
140
|
+
import { DateField } from "@sberbusiness/triplex-next";
|
|
141
|
+
|
|
142
|
+
export const DefaultExample = () => {
|
|
143
|
+
const [value, setValue] = useState("");
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div style={{ maxWidth: 300 }}>
|
|
147
|
+
<DateField
|
|
148
|
+
value={value}
|
|
149
|
+
label="Label"
|
|
150
|
+
placeholderMask="дд.мм.гггг"
|
|
151
|
+
invalidDateHint="Указана недоступная для выбора дата."
|
|
152
|
+
onChange={setValue}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
239
156
|
};
|
|
240
157
|
```
|
|
241
158
|
|
|
242
|
-
|
|
243
|
-
1. Как target для Tooltip (`tooltipTargetRef`).
|
|
244
|
-
2. Как forwarded ref для потребителя.
|
|
159
|
+
### Sizes
|
|
245
160
|
|
|
246
|
-
|
|
161
|
+
`stories/DateField/examples/SizesExample.tsx`
|
|
247
162
|
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const handleDateChange = (date: moment.Moment) => {
|
|
252
|
-
dropdownClosedByCalendarRef.current = true;
|
|
253
|
-
// ...
|
|
254
|
-
};
|
|
255
|
-
const handleDropdownClose = () => {
|
|
256
|
-
if (dropdownClosedByCalendarRef.current) {
|
|
257
|
-
dropdownClosedByCalendarRef.current = false; // reset
|
|
258
|
-
} else if (!inputFocusedRef.current && dirty) {
|
|
259
|
-
triggerChangeFromInput(); // откат/commit
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
**Защита от двойной обработки**: если dropdown закрылся потому что
|
|
265
|
-
пользователь выбрал дату в календаре, не дергать `triggerChangeFromInput`
|
|
266
|
-
— значение уже применено через `onChange` в `handleDateChange`.
|
|
267
|
-
|
|
268
|
-
### DateFieldTarget: клавиатурная навигация
|
|
163
|
+
```tsx
|
|
164
|
+
import React, { useState } from "react";
|
|
165
|
+
import { DateField, EComponentSize } from "@sberbusiness/triplex-next";
|
|
269
166
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
else if (isKey(event.code, "SPACE")) {
|
|
273
|
-
event.preventDefault();
|
|
274
|
-
setDropdownOpen(!dropdownOpen);
|
|
167
|
+
interface ISizeItemProps {
|
|
168
|
+
size: EComponentSize;
|
|
275
169
|
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
**Enter / Space** открывают/закрывают dropdown. `preventDefault` на
|
|
279
|
-
Space нужен, чтобы не ввести пробел в input (маска бы его съела,
|
|
280
|
-
но всё равно нежелательно).
|
|
281
|
-
|
|
282
|
-
---
|
|
283
170
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
keys без согласования с DatePickerExtended.
|
|
302
|
-
- **Иконка календаря меняется по size** (CalendarStrokeSrvIcon16 для SM,
|
|
303
|
-
CalendarStrokeSrvIcon20 для MD/LG) — map в `sizeToCalendarIconMap`.
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## Accessibility
|
|
308
|
-
|
|
309
|
-
- **`aria-label` / `aria-labelledby`** передаются в MaskedInput. Если
|
|
310
|
-
есть `label` — он уже создаёт `<label>`-связь; label-only обычно
|
|
311
|
-
достаточно.
|
|
312
|
-
- **Клавиатура:**
|
|
313
|
-
- Tab — фокус на input, следующий Tab — на иконку календаря (ButtonIcon).
|
|
314
|
-
- Enter/Space на input — открыть dropdown.
|
|
315
|
-
- Дальнейшая навигация по календарю — см. `DatePickerExtended`/`Calendar`.
|
|
316
|
-
- **Tooltip `invalidDateHint`** — не имеет ARIA-связи с input (не
|
|
317
|
-
`aria-describedby`). Если нужна screen-reader подсказка — передавать
|
|
318
|
-
через `status` + `hint` на `MaskedField` (их стандартный механизм).
|
|
319
|
-
- **FormFieldClear** кнопка — имеет `aria-label` внутри (см. FormField
|
|
320
|
-
документацию).
|
|
171
|
+
const SizeItem = ({ size }: ISizeItemProps) => {
|
|
172
|
+
const [value, setValue] = useState<string>("");
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div style={{ maxWidth: 300 }}>
|
|
176
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{size.toUpperCase()}</div>
|
|
177
|
+
<DateField
|
|
178
|
+
size={size}
|
|
179
|
+
value={value}
|
|
180
|
+
label="Label"
|
|
181
|
+
placeholderMask="дд.мм.гггг"
|
|
182
|
+
invalidDateHint="Указана недоступная для выбора дата."
|
|
183
|
+
onChange={setValue}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
321
188
|
|
|
322
|
-
|
|
189
|
+
const SIZES = Object.values(EComponentSize);
|
|
323
190
|
|
|
324
|
-
|
|
191
|
+
export const SizesExample = () => (
|
|
192
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
193
|
+
{SIZES.map((size) => (
|
|
194
|
+
<SizeItem key={size} size={size} />
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
```
|
|
325
199
|
|
|
326
|
-
|
|
327
|
-
colors, borders, focus-ring) и `DatePickerExtended` (dropdown, calendar).
|
|
200
|
+
### Statuses
|
|
328
201
|
|
|
329
|
-
|
|
202
|
+
`stories/DateField/examples/StatusesExample.tsx`
|
|
330
203
|
|
|
331
|
-
|
|
204
|
+
```tsx
|
|
205
|
+
import React, { useState } from "react";
|
|
206
|
+
import { DateField, EFormFieldStatus } from "@sberbusiness/triplex-next";
|
|
332
207
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
внутри DatePickerExtended.
|
|
337
|
-
- `MaskedField` (`src/components/TextField`) — input с маской. Основа
|
|
338
|
-
`DateFieldTarget`.
|
|
339
|
-
- `MonthYearField` (`src/components/MonthYearField`) — близнец для
|
|
340
|
-
выбора месяца/года.
|
|
341
|
-
- `DateRange` (`src/components/DateRange`) — выбор диапазона дат.
|
|
342
|
-
- `Tooltip` (`src/components/Tooltip`) — подсказка `invalidDateHint`
|
|
343
|
-
на десктопе.
|
|
344
|
-
- `MobileView` (`src/components/MobileView`) — разветвление mobile/desktop.
|
|
345
|
-
- `DropdownMobileMaskedInput` — мобильный input в заголовке Dropdown'а.
|
|
346
|
-
- `FormFieldClear` — кнопка-крестик очистки поля.
|
|
347
|
-
- `ButtonIcon` — кнопка-иконка календаря.
|
|
208
|
+
interface IStatusItemProps {
|
|
209
|
+
status: EFormFieldStatus;
|
|
210
|
+
}
|
|
348
211
|
|
|
349
|
-
|
|
212
|
+
const StatusItem = ({ status }: IStatusItemProps) => {
|
|
213
|
+
const [value, setValue] = useState<string>("");
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div style={{ maxWidth: 300 }}>
|
|
217
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{status.toUpperCase()}</div>
|
|
218
|
+
<DateField
|
|
219
|
+
status={status}
|
|
220
|
+
value={value}
|
|
221
|
+
label="Label"
|
|
222
|
+
placeholderMask="дд.мм.гггг"
|
|
223
|
+
invalidDateHint="Указана недоступная для выбора дата."
|
|
224
|
+
onChange={setValue}
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
350
229
|
|
|
351
|
-
|
|
230
|
+
const STATUSES = Object.values(EFormFieldStatus);
|
|
352
231
|
|
|
353
|
-
|
|
354
|
-
|
|
232
|
+
export const StatusesExample = () => (
|
|
233
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
234
|
+
{STATUSES.map((status) => (
|
|
235
|
+
<StatusItem key={status} status={status} />
|
|
236
|
+
))}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
```
|
|
355
240
|
|
|
356
|
-
|
|
357
|
-
- `Default` — базовое поле
|
|
358
|
-
- `WithLabel` — с label
|
|
359
|
-
- `Sizes` — SM/MD/LG
|
|
360
|
-
- `Statuses` — default/error/success/disabled
|
|
361
|
-
- `LimitRange` — ограниченный диапазон
|
|
362
|
-
- `DisabledDays` — выборочно отключённые даты
|
|
363
|
-
- `Playground` — интерактив
|
|
241
|
+
### Production
|
|
364
242
|
|
|
365
|
-
|
|
243
|
+
`stories/DateField/examples/ProductionExample.tsx`
|
|
366
244
|
|
|
367
|
-
|
|
245
|
+
```tsx
|
|
246
|
+
import React, { useState, useRef } from "react";
|
|
247
|
+
import { DateField, HelpBox, Text, Link, ETooltipSize, ETextSize, EFontType } from "@sberbusiness/triplex-next";
|
|
248
|
+
|
|
249
|
+
export const ProductionExample = () => {
|
|
250
|
+
const [value, setValue] = useState("");
|
|
251
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
252
|
+
|
|
253
|
+
const handleLinkClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const handleClear: React.MouseEventHandler<HTMLButtonElement> = () => {
|
|
258
|
+
setValue("");
|
|
259
|
+
inputRef.current?.focus();
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<div style={{ maxWidth: 300 }}>
|
|
264
|
+
<DateField
|
|
265
|
+
value={value}
|
|
266
|
+
label="Label"
|
|
267
|
+
placeholderMask="дд.мм.гггг"
|
|
268
|
+
invalidDateHint="Указана недоступная для выбора дата."
|
|
269
|
+
onChange={setValue}
|
|
270
|
+
onClear={handleClear}
|
|
271
|
+
targetProps={{
|
|
272
|
+
maskedInputProps: {
|
|
273
|
+
forwardedRef: inputRef,
|
|
274
|
+
},
|
|
275
|
+
postfix: <HelpBox tooltipSize={ETooltipSize.SM}>Helpful details appear here</HelpBox>,
|
|
276
|
+
description: (
|
|
277
|
+
<Text size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
278
|
+
(21) Description{" "}
|
|
279
|
+
<Link href="#" onClick={handleLinkClick}>
|
|
280
|
+
Link text
|
|
281
|
+
</Link>
|
|
282
|
+
</Text>
|
|
283
|
+
),
|
|
284
|
+
}}
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
```
|
|
368
290
|
|
|
369
|
-
| Дата | Изменение |
|
|
370
|
-
|---|---|
|
|
371
|
-
| 2026-04-18 | Создан документ |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: DatePickerExtended
|
|
3
3
|
category: Date components
|
|
4
|
-
related: [Calendar, Dropdown, DateField, MonthYearField, ChipDatePicker]
|
|
4
|
+
related: [Calendar, Dropdown, DateField, MonthYearField, ChipDatePicker, ChipMonthYearPicker]
|
|
5
5
|
tokens: []
|
|
6
6
|
stories: stories/DatePickerExtended/DatePickerExtended.stories.tsx
|
|
7
7
|
version: "1.0"
|
|
@@ -97,6 +97,7 @@ version: "1.0"
|
|
|
97
97
|
- `DateField` — поле ввода даты; передаёт `dropdownTargetRef` и открывает календарь из `DateFieldTarget` через контекст.
|
|
98
98
|
- `MonthYearField` — выбор месяца и года; конфигурирует `pickType={ECalendarPickType.MONTH_YEAR}`.
|
|
99
99
|
- `ChipDatePicker` — чип-фильтр с календарём; открывает дропдаун из `ChipDatePickerTarget` через контекст.
|
|
100
|
+
- `ChipMonthYearPicker` — чип-фильтр с выбором месяца (`pickType` фиксирован в `MONTH_YEAR`); открывает дропдаун из `ChipMonthYearPickerTarget` через контекст.
|
|
100
101
|
- `DatePickerExtendedContext` — контекст открытости дропдауна для целевого элемента. Экспортируется из barrel вместе с типом `IDatePickerExtendedContext`, отдельного AI.md не имеет.
|
|
101
102
|
|
|
102
103
|
Внутренние части (в barrel не экспортируются, отдельного AI.md не имеют):
|
|
@@ -130,6 +131,7 @@ version: "1.0"
|
|
|
130
131
|
|
|
131
132
|
| Дата | Изменение |
|
|
132
133
|
|---|---|
|
|
134
|
+
| 2026-09-07 | В потребители базы добавлен `ChipMonthYearPicker` (чип-фильтр с `pickType` `MONTH_YEAR`) |
|
|
133
135
|
| 2026-08-24 | `DatePickerExtendedContext` и `IDatePickerExtendedContext` экспортируются из barrel — целевой элемент можно написать снаружи библиотеки. `adaptiveMode` исключён из `IDatePickerExtendedProps` (ломающее изменение, затрагивает также `DateField`, `MonthYearField`, `ChipDatePicker`) |
|
|
134
136
|
| 2026-08-20 | Создан документ. AI-рефакторинг, unit-тесты (`__tests__/DatePickerExtended.test.tsx`), stories по modern pattern. Исправлено: `yesterdayButtonProps` / `tomorrowButtonProps` пробрасываются в `Calendar`, `adaptiveMode` больше не попадает в DOM |
|
|
135
137
|
|