@sberbusiness/triplex-next-mcp-server 0.24.0-beta1

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.
Files changed (140) hide show
  1. package/README.md +124 -0
  2. package/bundle/.gitkeep +0 -0
  3. package/bundle/components/AbstractTree.md +198 -0
  4. package/bundle/components/AccordionBase.md +227 -0
  5. package/bundle/components/Alert.md +317 -0
  6. package/bundle/components/Amount.md +225 -0
  7. package/bundle/components/AmountField.md +289 -0
  8. package/bundle/components/Avatar.md +209 -0
  9. package/bundle/components/Badge.md +277 -0
  10. package/bundle/components/Body.md +167 -0
  11. package/bundle/components/Button.md +230 -0
  12. package/bundle/components/ButtonDropdown.md +159 -0
  13. package/bundle/components/ButtonIcon.md +122 -0
  14. package/bundle/components/Calendar.md +445 -0
  15. package/bundle/components/Card.md +374 -0
  16. package/bundle/components/CarouselExtended.md +250 -0
  17. package/bundle/components/Checkbox.md +126 -0
  18. package/bundle/components/CheckboxTree.md +280 -0
  19. package/bundle/components/CheckboxTreeExtended.md +321 -0
  20. package/bundle/components/CheckboxXGroup.md +68 -0
  21. package/bundle/components/CheckboxYGroup.md +67 -0
  22. package/bundle/components/Chip.md +404 -0
  23. package/bundle/components/ChipGroup.md +179 -0
  24. package/bundle/components/Col.md +221 -0
  25. package/bundle/components/CollapsableTree.md +255 -0
  26. package/bundle/components/Confirm.md +343 -0
  27. package/bundle/components/DateField.md +371 -0
  28. package/bundle/components/DatePickerExtended.md +342 -0
  29. package/bundle/components/DateRange.md +275 -0
  30. package/bundle/components/DesignTokens.md +385 -0
  31. package/bundle/components/Divider.md +196 -0
  32. package/bundle/components/DocumentNumberEdit.md +280 -0
  33. package/bundle/components/Dropdown.md +433 -0
  34. package/bundle/components/Ellipsis.md +208 -0
  35. package/bundle/components/EmptyView.md +99 -0
  36. package/bundle/components/ExpandAnimation.md +281 -0
  37. package/bundle/components/Footer.md +339 -0
  38. package/bundle/components/FormField.md +326 -0
  39. package/bundle/components/FormGroup.md +99 -0
  40. package/bundle/components/Gap.md +113 -0
  41. package/bundle/components/Header.md +280 -0
  42. package/bundle/components/HelpBox.md +154 -0
  43. package/bundle/components/IconWrapper.md +145 -0
  44. package/bundle/components/Island.md +197 -0
  45. package/bundle/components/IslandAccordion.md +230 -0
  46. package/bundle/components/IslandWidget.md +233 -0
  47. package/bundle/components/KeyDownListener.md +157 -0
  48. package/bundle/components/LightBox.md +322 -0
  49. package/bundle/components/Link.md +161 -0
  50. package/bundle/components/List.md +247 -0
  51. package/bundle/components/ListMaster.md +204 -0
  52. package/bundle/components/Loader.md +161 -0
  53. package/bundle/components/LoaderScreen.md +134 -0
  54. package/bundle/components/Marker.md +127 -0
  55. package/bundle/components/MarkerStatus.md +148 -0
  56. package/bundle/components/MediaWidth.md +169 -0
  57. package/bundle/components/MobileView.md +120 -0
  58. package/bundle/components/ModalWindow.md +308 -0
  59. package/bundle/components/MonthYearField.md +168 -0
  60. package/bundle/components/MultiselectField.md +213 -0
  61. package/bundle/components/Notification.md +233 -0
  62. package/bundle/components/NumberField.md +178 -0
  63. package/bundle/components/OrderedList.md +169 -0
  64. package/bundle/components/Overlay.md +237 -0
  65. package/bundle/components/Page.md +246 -0
  66. package/bundle/components/Pagination.md +298 -0
  67. package/bundle/components/Portal.md +119 -0
  68. package/bundle/components/Radio.md +174 -0
  69. package/bundle/components/Row.md +125 -0
  70. package/bundle/components/SMSField.md +160 -0
  71. package/bundle/components/SegmentedControl.md +235 -0
  72. package/bundle/components/SelectExtendedField.md +322 -0
  73. package/bundle/components/SelectField.md +213 -0
  74. package/bundle/components/Skeleton.md +129 -0
  75. package/bundle/components/Slider.md +157 -0
  76. package/bundle/components/SliderExtended.md +210 -0
  77. package/bundle/components/SliderRange.md +160 -0
  78. package/bundle/components/SmallInput.md +101 -0
  79. package/bundle/components/Spoiler.md +121 -0
  80. package/bundle/components/StatusTracker.md +151 -0
  81. package/bundle/components/Step.md +121 -0
  82. package/bundle/components/Stepper.md +163 -0
  83. package/bundle/components/Suggest.md +160 -0
  84. package/bundle/components/SuggestField.md +157 -0
  85. package/bundle/components/SwipeableArea.md +144 -0
  86. package/bundle/components/Table.md +199 -0
  87. package/bundle/components/Tabs.md +152 -0
  88. package/bundle/components/TabsExtended.md +273 -0
  89. package/bundle/components/TabsLine.md +189 -0
  90. package/bundle/components/Tag.md +135 -0
  91. package/bundle/components/TagColor.md +121 -0
  92. package/bundle/components/TagGroup.md +100 -0
  93. package/bundle/components/TextField.md +152 -0
  94. package/bundle/components/TextareaField.md +124 -0
  95. package/bundle/components/ThemeProvider.md +181 -0
  96. package/bundle/components/Tooltip.md +254 -0
  97. package/bundle/components/TopOverlay.md +145 -0
  98. package/bundle/components/TreeView.md +224 -0
  99. package/bundle/components/Triggers.md +121 -0
  100. package/bundle/guides/codestyle.md +205 -0
  101. package/bundle/guides/commits.md +166 -0
  102. package/bundle/guides/context.md +277 -0
  103. package/bundle/guides/stories.md +618 -0
  104. package/bundle/guides/template.md +154 -0
  105. package/bundle/guides/tests.md +301 -0
  106. package/bundle/icons.json +4156 -0
  107. package/bundle/manifest.json +796 -0
  108. package/bundle/release-notes/1.1.0.mdx +26 -0
  109. package/bundle/release-notes/1.1.1.mdx +14 -0
  110. package/bundle/release-notes/1.10.0.mdx +43 -0
  111. package/bundle/release-notes/1.11.0.mdx +19 -0
  112. package/bundle/release-notes/1.12.0.mdx +21 -0
  113. package/bundle/release-notes/1.13.0.mdx +43 -0
  114. package/bundle/release-notes/1.14.0.mdx +29 -0
  115. package/bundle/release-notes/1.15.0.mdx +56 -0
  116. package/bundle/release-notes/1.16.0.mdx +55 -0
  117. package/bundle/release-notes/1.17.0.mdx +15 -0
  118. package/bundle/release-notes/1.18.0.mdx +24 -0
  119. package/bundle/release-notes/1.19.0.mdx +48 -0
  120. package/bundle/release-notes/1.19.1.mdx +9 -0
  121. package/bundle/release-notes/1.2.0.mdx +80 -0
  122. package/bundle/release-notes/1.20.0.mdx +48 -0
  123. package/bundle/release-notes/1.21.0.mdx +66 -0
  124. package/bundle/release-notes/1.22.0.mdx +75 -0
  125. package/bundle/release-notes/1.23.0.mdx +50 -0
  126. package/bundle/release-notes/1.24.0.mdx +59 -0
  127. package/bundle/release-notes/1.25.0.mdx +52 -0
  128. package/bundle/release-notes/1.26.0.mdx +11 -0
  129. package/bundle/release-notes/1.3.0.mdx +21 -0
  130. package/bundle/release-notes/1.4.0.mdx +37 -0
  131. package/bundle/release-notes/1.5.0.mdx +24 -0
  132. package/bundle/release-notes/1.6.0.mdx +37 -0
  133. package/bundle/release-notes/1.6.1.mdx +11 -0
  134. package/bundle/release-notes/1.7.0.mdx +29 -0
  135. package/bundle/release-notes/1.8.0.mdx +38 -0
  136. package/bundle/release-notes/1.9.0.mdx +28 -0
  137. package/dist/index.d.ts +2 -0
  138. package/dist/index.js +324 -0
  139. package/dist/index.js.map +1 -0
  140. package/package.json +51 -0
@@ -0,0 +1,322 @@
1
+ ---
2
+ component: SelectExtendedField
3
+ category: Form Controls
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [SelectField, MultiselectField, Dropdown, FormField, KeyDownListener]
7
+ tokens: []
8
+ stories: stories/SelectExtendedField/SelectExtendedField.stories.tsx
9
+ version: "1.0"
10
+ ---
11
+
12
+ # SelectExtendedField
13
+
14
+ ## Назначение
15
+
16
+ **Базовый компонент** для построения custom-Select'ов: оркестрирует
17
+ открытие/закрытие dropdown'а, click-outside, ESC и (опционально)
18
+ TAB-закрытие, прокидывает `opened/setOpened/targetRef/dropdownRef` в
19
+ **render-prop'ы** для target и dropdown. Сам не диктует ни форму
20
+ target'а (icon vs text vs input), ни содержимое dropdown'а — потребитель
21
+ рендерит их через render-функции.
22
+
23
+ `SelectExtendedField.Target` (`SelectExtendedFieldTarget`) и
24
+ `SelectExtendedField.Dropdown` — типичные реализации; их можно заменить
25
+ своими.
26
+
27
+ Используй когда: строишь нестандартный Select (например, multi-select с
28
+ поиском, color-picker как select, и т.п.). Для типичных — `SelectField`,
29
+ `MultiselectField`.
30
+
31
+ ---
32
+
33
+ ## Файловая структура
34
+
35
+ ```text
36
+ src/components/SelectExtendedField/
37
+ ├── SelectExtendedField.tsx # forwardRef + Object.assign({Target, Dropdown})
38
+ ├── components/
39
+ │ ├── SelectExtendedFieldTarget.tsx # forwardRef; FormField + caret + clear + loader
40
+ │ ├── SelectExtendedFieldDropdown.tsx # FC; <Dropdown> wrapper + .List
41
+ │ └── SelectExtendedFieldDropdownDefault.tsx # FC; готовая раскладка опций (desktop + mobile)
42
+ ├── styles/
43
+ ├── index.ts
44
+ └── __tests__/
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Compound-структура (через render-prop'ы)
50
+
51
+ ```tsx
52
+ <SelectExtendedField
53
+ closeOnTab
54
+ onOpen={...} onClose={...}
55
+ renderTarget={({ opened, setOpened }) => (
56
+ <SelectExtendedField.Target
57
+ opened={opened}
58
+ setOpened={setOpened}
59
+ label={selected?.label}
60
+ placeholder="Выбрать..."
61
+ fieldLabel="Label"
62
+ size={EComponentSize.MD}
63
+ onClear={() => onChange(undefined)}
64
+ />
65
+ )}
66
+ >
67
+ {({ opened, setOpened, targetRef, dropdownRef }) => (
68
+ <SelectExtendedField.Dropdown
69
+ opened={opened}
70
+ setOpened={setOpened}
71
+ targetRef={targetRef}
72
+ forwardedRef={dropdownRef}
73
+ >
74
+ <SelectExtendedField.Dropdown.List dropdownOpened={opened}>
75
+ {options.map(...)}
76
+ </SelectExtendedField.Dropdown.List>
77
+ </SelectExtendedField.Dropdown>
78
+ )}
79
+ </SelectExtendedField>
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Варианты и props
85
+
86
+ ### SelectExtendedField — `ISelectExtendedFieldProps extends Omit<HTMLAttributes<HTMLDivElement>, "children">`
87
+
88
+ | Prop | Тип | Default | Описание |
89
+ |---|---|---|---|
90
+ | `renderTarget` | `(props: ISelectExtendedFieldTargetProvideProps) => ReactNode` | — | Render-prop для target. Получает `{ opened, setOpened }`. |
91
+ | `children` | `(props: ISelectExtendedFieldDropdownProvideProps) => ReactNode` | — | Render-prop для dropdown. Получает `{ opened, setOpened, targetRef, dropdownRef }`. |
92
+ | `onOpen?` | `() => void` | — | Только при реальном переходе `false → true` (skip mount). |
93
+ | `onClose?` | `() => void` | — | Только при `true → false`. |
94
+ | `closeOnTab?` | `boolean` | `false` | Tab закрывает dropdown. |
95
+ | `...HTMLAttributes` | — | — | На корневой `<div>`. |
96
+
97
+ ### SelectExtendedField.Target — `ISelectExtendedFieldTargetProps extends Omit<IFormFieldProps, "prefix" \| "postfix">`
98
+
99
+ | Prop | Тип | Default | Описание |
100
+ |---|---|---|---|
101
+ | `opened` | `boolean` | — | Из render-prop'а. |
102
+ | `setOpened` | `(opened: boolean) => void` | — | Из render-prop'а. |
103
+ | `fieldLabel` | `ReactNode` | — | Label, рендерится через `FormFieldLabel`. |
104
+ | `label?` | `ReactNode` | — | Текущий выбранный (отображается в `FormFieldTarget`). |
105
+ | `placeholder?` | `ReactNode` | — | Когда `label` пуст. |
106
+ | `loading?` | `boolean` | — | Заменяет caret на `LoaderSmall` (BRAND). Блокирует клики и keys. |
107
+ | `prefix?` / `postfix?` | `ReactNode` | — | До/после target. Caret/Loader всегда рендерятся в Postfix. |
108
+ | `onClear?` | `() => void` | — | Если передан — рендерится `FormFieldClear`. |
109
+ | `size` | `EComponentSize` | `MD` | Размер. Маппится на `CaretdownStrokeSrvIcon{16/20/24}` и `LoaderSmall size`. |
110
+ | `status` | `EFormFieldStatus` | — | `DISABLED` блокирует клики/keys. |
111
+ | `tabIndex` | `number` | — | Стандартно. |
112
+
113
+ - `aria-expanded={opened}`, `aria-haspopup="listbox"` — на корне FormField.
114
+ - Открытие по: **click**, **Enter**, **Space**, **ArrowDown**, **ArrowUp** (только когда `!opened`); тогда `preventDefault + stopPropagation`.
115
+
116
+ ### SelectExtendedField.Dropdown — `ISelectExtendedFieldDropdownProps extends Omit<IDropdownProps, "forwardedRef">`
117
+
118
+ | Prop | Тип | Описание |
119
+ |---|---|---|
120
+ | `forwardedRef` | `RefObject<HTMLDivElement>` | Из render-prop'а (dropdownRef). |
121
+ | `targetRef` | `RefObject<HTMLElement>` | Из render-prop'а. |
122
+ | `opened` | `boolean` | — |
123
+ | `children?` | `ReactNode` | — |
124
+ | `...IDropdownProps` | — | width, mobileViewProps, size и т.п. |
125
+
126
+ Slots: **`SelectExtendedField.Dropdown.List`** = `DropdownList` (re-export).
127
+
128
+ ### Provide-types
129
+
130
+ ```ts
131
+ ISelectExtendedFieldTargetProvideProps: { opened, setOpened }
132
+ ISelectExtendedFieldDropdownProvideProps: { opened, setOpened, targetRef, dropdownRef }
133
+ ISelectExtendedFieldDefaultOption: { value: string; label: ReactNode; ...IDropdownListItemProps без active/onSelect/selected/key/className/keyCodesForSelection }
134
+ ```
135
+
136
+ ### SelectExtendedFieldDropdownDefault (отдельная утилита)
137
+
138
+ Готовая раскладка опций для desktop (`Dropdown.List`) **и** mobile (`DropdownMobileHeader/Body/List`). Не часть Object.assign — импортируется отдельно:
139
+
140
+ ```ts
141
+ import { SelectExtendedFieldDropdownDefault } from "@sberbusiness/triplex-next/components/SelectExtendedField";
142
+ ```
143
+
144
+ (используется в `SelectField`).
145
+
146
+ ---
147
+
148
+ ## Ключевые особенности реализации
149
+
150
+ ### Stable `onOpen`/`onClose` через ref
151
+
152
+ ```ts
153
+ const onOpenRef = useRef(onOpen);
154
+ const onCloseRef = useRef(onClose);
155
+
156
+ useLayoutEffect(() => {
157
+ onOpenRef.current = onOpen;
158
+ onCloseRef.current = onClose;
159
+ });
160
+ ```
161
+
162
+ - Колбэки хранятся в ref'ах и обновляются после каждого коммита через `useLayoutEffect` без deps.
163
+ - `useEffect([opened])` читает `onOpenRef.current` — поэтому **эффект не пересоздаётся** при смене ссылок на колбэки.
164
+
165
+ ### Skip mount-вызов `onOpen` / `onClose`
166
+
167
+ ```ts
168
+ if (!isMountedRef.current) {
169
+ isMountedRef.current = true;
170
+ return;
171
+ }
172
+ if (opened) onOpenRef.current?.();
173
+ else onCloseRef.current?.();
174
+ ```
175
+
176
+ - Default `opened = false` → без skip'а `onClose` вызвался бы при mount.
177
+
178
+ ### Click-outside через `document.mousedown`
179
+
180
+ ```ts
181
+ useEffect(() => {
182
+ document.addEventListener("mousedown", listenMouseDown);
183
+ return () => document.removeEventListener("mousedown", listenMouseDown);
184
+ }, [listenMouseDown]);
185
+ ```
186
+
187
+ - Закрывает, если клик вне `targetRef` и `dropdownRef`.
188
+ - **`mousedown`**, не `click` — закрывает раньше, чем сработает `click` на dropdown items.
189
+
190
+ ### ESC — через KeyDownListener (window)
191
+
192
+ ```tsx
193
+ <KeyDownListener onMatch={closeDropdown} eventKeyCode={EVENT_KEY_CODES.ESCAPE}>
194
+ ...
195
+ </KeyDownListener>
196
+ ```
197
+
198
+ - Глобальный listener, см. `KeyDownListener-AI.md`.
199
+
200
+ ### TAB — через onKeyDown в самом контейнере
201
+
202
+ ```ts
203
+ if (closeOnTab && isKey(key, "TAB")) closeDropdown();
204
+ ```
205
+
206
+ - Опционально. Контракт: `closeOnTab` — потребитель решает (по UX).
207
+
208
+ ### Target: открытие по клавишам
209
+
210
+ ```ts
211
+ if (!opened && [SPACE, ENTER, ARROW_DOWN, ARROW_UP].includes(event.keyCode)) {
212
+ event.preventDefault();
213
+ event.stopPropagation(); // чтобы dropdown сразу не среагировал тем же keydown
214
+ setOpened(!opened);
215
+ }
216
+ ```
217
+
218
+ - `event.keyCode` — **deprecated** API (тех-долг).
219
+ - `stopPropagation` — комментарий в коде: предотвращает срабатывание `keydown` в `DropdownListItem` через `document.addEventListener("keydown")`.
220
+
221
+ ### Target: caret + loader через size-map
222
+
223
+ - `CaretdownStrokeSrvIcon{16/20/24}` для `SM/MD/LG`.
224
+ - `LoaderSmall theme=BRAND` тех же размеров.
225
+ - `IconWrapper active={opened} disabled={status===DISABLED}` оборачивает caret — управляет цветом через `@sberbusiness/icons-next` глобальные классы.
226
+
227
+ ### `setRef` мерж forwardRef + targetRef
228
+
229
+ ```ts
230
+ const setRef = (instance) => {
231
+ targetRef.current = instance;
232
+ if (typeof ref === "function") ref(instance); else if (ref) ref.current = instance;
233
+ };
234
+ ```
235
+
236
+ ### Target слоты Postfix
237
+
238
+ ```tsx
239
+ <FormFieldPostfix>
240
+ {onClear && <FormFieldClear onClick={onClear} />}
241
+ {loading ? <LoaderSmall .../> : <IconWrapper .../>}
242
+ {postfix}
243
+ </FormFieldPostfix>
244
+ ```
245
+
246
+ - Порядок: clear → loader/caret → custom postfix.
247
+
248
+ ### Loading блокирует клики и keydown
249
+
250
+ ```ts
251
+ if (loading || status === DISABLED) return; // в handleClick
252
+ if (loading && status === DISABLED) return; // в handleKeyDown — bug? && вместо ||
253
+ ```
254
+
255
+ - В `handleKeyDown` используется `&&` — **похоже на баг**: keys блокируются только если ОБА условия true. Тех-долг, не трогать без задачи.
256
+
257
+ ---
258
+
259
+ ## Инварианты
260
+
261
+ - **`renderTarget` и `children` — render-prop'ы** (функции). Изменение API — breaking.
262
+ - **`onOpen` / `onClose` через ref'ы** — stable callback pattern. Не пересоздавать эффект на смене ссылок.
263
+ - **Skip mount-вызов** колбэков через `isMountedRef`.
264
+ - **Click-outside на `mousedown`** (не `click`) — public behavior contract.
265
+ - **ESC закрывает через `KeyDownListener` (window)** — глобальный listener.
266
+ - **`closeOnTab` default `false`** — UX-выбор.
267
+ - **Target открывает по `Enter / Space / ArrowDown / ArrowUp`** + `preventDefault + stopPropagation`.
268
+ - **Target использует `event.keyCode`** — deprecated, тех-долг.
269
+ - **Target loading: `&&` вместо `||`** в handleKeyDown — известный потенциальный баг.
270
+ - **`aria-haspopup="listbox"`** — публичная семантика.
271
+ - **`SelectExtendedField.Dropdown.List = DropdownList`** — реэкспорт.
272
+ - **`SelectExtendedFieldDropdownDefault`** — не slot, отдельный экспорт.
273
+ - **`Target.size` default `MD`**.
274
+ - **Target caret-icon фиксирован** (`CaretdownStrokeSrvIcon`) — нельзя заменить.
275
+ - **`forwardRef`** на SelectExtendedField и Target; **`FC`** на Dropdown.
276
+
277
+ ---
278
+
279
+ ## Accessibility
280
+
281
+ - `aria-expanded={opened}` + `aria-haspopup="listbox"` на target — корректная семантика combobox-like.
282
+ - ESC закрывает (стандарт).
283
+ - Open by `Enter`/`Space`/`Arrow*` — стандартное keyboard поведение для select.
284
+ - **Target — `<div>` (от FormField)**, не `<button>` — для AT может звучать как regular область. Если нужен strict role — потребитель добавляет через `...rest` (`role="combobox"` и т.п.).
285
+ - `aria-label` / `aria-labelledby` для target — потребитель.
286
+ - `loading` блокирует клики и (по идее) keys — но по факту `keydown` блокировка имеет потенциальный баг (см. выше).
287
+ - `tabIndex` — наследуется от FormField/FormFieldProps.
288
+
289
+ ---
290
+
291
+ ## Дизайн-токены
292
+
293
+ Нет собственных. Цвета/spacing — от `FormField`, `Dropdown`, `Loader`.
294
+
295
+ ---
296
+
297
+ ## Связанные компоненты
298
+
299
+ - `SelectField` — высокоуровневая обёртка для типичного Select.
300
+ - `MultiselectField` — multi-выбор.
301
+ - `Dropdown` — popover.
302
+ - `FormField` (Target/Label/Prefix/Postfix/Clear) — основа target.
303
+ - `KeyDownListener` — ESC.
304
+ - `LoaderSmall` — индикатор загрузки в target.
305
+ - `IconWrapper` — управление цветом caret.
306
+ - `CaretdownStrokeSrvIcon{16/20/24}` — иконка раскрытия.
307
+ - `EComponentSize`, `EFormFieldStatus`.
308
+
309
+ ---
310
+
311
+ ## Stories
312
+
313
+ `stories/SelectExtendedField/SelectExtendedField.stories.tsx`
314
+ Примеры: `stories/SelectExtendedField/examples/`
315
+
316
+ ---
317
+
318
+ ## История изменений
319
+
320
+ | Дата | Изменение |
321
+ |---|---|
322
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,213 @@
1
+ ---
2
+ component: SelectField
3
+ category: Form Controls
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [SelectExtendedField, MultiselectField, Dropdown, FormField]
7
+ tokens: []
8
+ stories: stories/SelectField/SelectField.stories.tsx
9
+ version: "1.0"
10
+ ---
11
+
12
+ # SelectField
13
+
14
+ ## Назначение
15
+
16
+ Готовый Select для одиночного выбора из списка опций. Под капотом —
17
+ `SelectExtendedField` с фиксированной раскладкой target'а и dropdown'а:
18
+ - Target = `SelectExtendedField.Target` (FormField + caret + clear).
19
+ - Dropdown = `SelectExtendedFieldDropdownDefault` (desktop list +
20
+ mobile bottom-sheet) c шириной `EDropdownWidth.TARGET`.
21
+
22
+ Управляет:
23
+ - `activeDescendant` через `DropdownListContext` — для a11y `aria-activedescendant`.
24
+ - `instanceId` через `lodash uniqueId()` — связывает Target ↔ List через `aria-controls`.
25
+ - **`closeOnTab = true`** захардкожен.
26
+
27
+ Используй когда: типичный select из списка опций. Для multi-выбора —
28
+ `MultiselectField`. Для нестандартного target/dropdown —
29
+ `SelectExtendedField` напрямую.
30
+
31
+ ---
32
+
33
+ ## Файловая структура
34
+
35
+ ```text
36
+ src/components/SelectField/
37
+ ├── SelectField.tsx # forwardRef; склейка SelectExtendedField + Default-Dropdown
38
+ ├── index.ts
39
+ └── __tests__/
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Варианты и props
45
+
46
+ ### `ISelectFieldProps`
47
+
48
+ ```ts
49
+ extends Omit<ISelectExtendedFieldProps, "children" | "onChange" | "renderTarget">
50
+ extends Pick<ISelectExtendedFieldTargetProps, "loading" | "status" | "placeholder">
51
+ ```
52
+
53
+ | Prop | Тип | Default | Описание |
54
+ |---|---|---|---|
55
+ | `size` | `EComponentSize` | — (обязателен) | Размер. |
56
+ | `value?` | `ISelectFieldOption` | — | Текущая выбранная опция. |
57
+ | `options` | `ISelectFieldOption[]` | — | Список опций. |
58
+ | `onChange` | `(option: ISelectFieldOption) => void` | — | Колбэк выбора. |
59
+ | `placeholder?` | `ReactNode` | — | На target. |
60
+ | `loading?` | `boolean` | — | Спиннер в target, dropdown НЕ открывается. |
61
+ | `status?` | `EFormFieldStatus` | — | DISABLED блокирует target. |
62
+ | `targetProps?` | `Omit<ISelectExtendedFieldTargetProps, "opened" \| "setOpened" \| "size">` | — | Доп. props для target (spread'ятся **после** дефолтных, но **до** provide-props). |
63
+ | `dropdownListItemClassName?` | `string` | — | className для каждого `DropdownListItem` / mobile-item. |
64
+ | `mobileTitle?` | `ReactNode` | — | Заголовок mobile bottom-sheet. |
65
+ | `aria-labelledby?` | `string` | — | На target. |
66
+ | `children?` | `never` | — | Запрещено. |
67
+ | `...ISelectExtendedFieldProps` | `onOpen`, `onClose`, ... | — | Прокидывается. **`closeOnTab` зашит в `true`**, передача игнорируется. |
68
+
69
+ ### `ISelectFieldOption extends ISelectExtendedFieldDefaultOption`
70
+
71
+ ```ts
72
+ { value: string; label: ReactNode; ...IDropdownListItemProps без active/onSelect/selected/key/className/keyCodesForSelection }
73
+ ```
74
+
75
+ - **`id`** обязателен (часть `IDropdownListItemProps`) — используется как `key` и `selected={option.id === value?.id}`.
76
+
77
+ ---
78
+
79
+ ## Ключевые особенности реализации
80
+
81
+ ### Layered render-prop'ы
82
+
83
+ ```tsx
84
+ <SelectExtendedField renderTarget={renderTarget} closeOnTab={true}>
85
+ {renderDropdown}
86
+ </SelectExtendedField>
87
+ ```
88
+
89
+ - `renderTarget` рендерит `SelectExtendedField.Target` с `value?.label`.
90
+ - `renderDropdown` рендерит `SelectExtendedFieldDropdownDefault` с `width=TARGET`.
91
+
92
+ ### `DropdownListContext` — `activeDescendant` для a11y
93
+
94
+ ```tsx
95
+ <DropdownListContext.Provider value={{ activeDescendant, setActiveDescendant }}>
96
+ <SelectExtendedFieldDropdownDefault ... />
97
+ </DropdownListContext.Provider>
98
+ ```
99
+
100
+ - `DropdownList` items пишут свой `id` в `activeDescendant` при keyboard-навигации.
101
+ - Target читает `activeDescendant` и ставит `aria-activedescendant` — корректная семантика combobox.
102
+
103
+ ### `instanceId = uniqueId()` через `useRef`
104
+
105
+ ```ts
106
+ const instanceId = useRef(uniqueId());
107
+ ```
108
+
109
+ - **SSR-небезопасно** (`uniqueId` глобальный счётчик).
110
+ - Используется как `aria-controls` на target и `id` на List — связывает их.
111
+
112
+ ### Target props order
113
+
114
+ ```tsx
115
+ <SelectExtendedField.Target
116
+ label={value?.label} placeholder={...} loading={...} status={...}
117
+ role="combobox"
118
+ aria-controls={instanceId.current}
119
+ aria-activedescendant={activeDescendant}
120
+ aria-labelledby={ariaLabelledby}
121
+ ref={setRef}
122
+ size={size}
123
+ fieldLabel={undefined}
124
+ {...targetProps} // потребительские targetProps — переопределяют дефолты
125
+ {...props} // provide-props (opened, setOpened) — переопределяют всё
126
+ />
127
+ ```
128
+
129
+ - Порядок: defaults → `targetProps` (потребитель) → `provide-props` (system).
130
+ - **`fieldLabel: undefined`** по умолчанию — потребитель должен передать через `targetProps.fieldLabel`.
131
+
132
+ ### Жёстко зашито
133
+
134
+ - **`closeOnTab: true`**.
135
+ - **`width: EDropdownWidth.TARGET`** — dropdown ровно ширины target'а.
136
+ - **`role="combobox"`** на target.
137
+ - **Default Dropdown layout** (desktop List + mobile bottom-sheet) — нельзя заменить (для нестандартного — использовать `SelectExtendedField`).
138
+
139
+ ### `loading` блокирует открытие dropdown'а
140
+
141
+ В `SelectExtendedFieldDropdownDefault`:
142
+
143
+ ```tsx
144
+ opened={opened && !loading}
145
+ ```
146
+
147
+ - При `loading=true` dropdown не показывается, в target — спиннер.
148
+
149
+ ### `setRef` мерж forwardRef + targetRef
150
+
151
+ Стандартный паттерн.
152
+
153
+ ---
154
+
155
+ ## Инварианты
156
+
157
+ - **`size` обязателен** (нет default).
158
+ - **`closeOnTab=true`** захардкожен.
159
+ - **`width=TARGET`** захардкожен.
160
+ - **`role="combobox"`** на target — публичный a11y-контракт.
161
+ - **`aria-controls`** + **`aria-activedescendant`** связывают target и list через `instanceId` и `DropdownListContext`.
162
+ - **`uniqueId()` через `useRef`** — SSR-небезопасно (тех-долг).
163
+ - **`children: never`** — запрещены.
164
+ - **`option.id`** — обязательный (наследуется от `IDropdownListItemProps`); используется как `key` и `selected`.
165
+ - **`targetProps` мержатся между defaults и provide-props** — порядок важен.
166
+ - **`fieldLabel` по умолчанию `undefined`** — потребитель добавляет через `targetProps.fieldLabel`.
167
+ - **`loading` блокирует Dropdown** (`opened && !loading`).
168
+ - **Default Dropdown layout фиксирован** — для кастомного использовать `SelectExtendedField`.
169
+ - **`forwardRef<HTMLDivElement>`** — на target ref.
170
+
171
+ ---
172
+
173
+ ## Accessibility
174
+
175
+ - `role="combobox"` + `aria-haspopup="listbox"` (от Target) + `aria-expanded` + `aria-controls` + `aria-activedescendant` — каноническая combobox-семантика.
176
+ - `aria-labelledby` — потребитель.
177
+ - ESC закрывает (от SelectExtendedField).
178
+ - `closeOnTab=true` — Tab закрывает (UX-выбор).
179
+ - `loading` — клики/keys блокированы (с known bug в keydown — см. SelectExtendedField-AI).
180
+ - **`uniqueId()` SSR-небезопасно** — может привести к несовпадению `id` между сервером и клиентом.
181
+
182
+ ---
183
+
184
+ ## Дизайн-токены
185
+
186
+ Нет собственных — наследуются от `SelectExtendedField` / `Dropdown` / `FormField`.
187
+
188
+ ---
189
+
190
+ ## Связанные компоненты
191
+
192
+ - `SelectExtendedField` — базовый, для кастомных Select'ов.
193
+ - `MultiselectField` — multi-выбор.
194
+ - `SelectExtendedFieldDropdownDefault` — переиспользуется для default-раскладки.
195
+ - `Dropdown` (+`DropdownList`+`DropdownMobile*`) — popover.
196
+ - `DropdownListContext` — для `activeDescendant`.
197
+ - `EDropdownWidth.TARGET`, `EComponentSize`.
198
+ - `lodash uniqueId`.
199
+
200
+ ---
201
+
202
+ ## Stories
203
+
204
+ `stories/SelectField/SelectField.stories.tsx`
205
+ Примеры: `stories/SelectField/examples/`
206
+
207
+ ---
208
+
209
+ ## История изменений
210
+
211
+ | Дата | Изменение |
212
+ |---|---|
213
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,129 @@
1
+ ---
2
+ component: Skeleton
3
+ category: Feedback
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [Loader, LoaderScreen]
7
+ tokens: [triplex-next-Skeleton-BackgroundColor_Light_Start, triplex-next-Skeleton-BackgroundColor_Light_End, triplex-next-Skeleton-BackgroundColor_Dark_Start, triplex-next-Skeleton-BackgroundColor_Dark_End]
8
+ stories: stories/Skeleton/Skeleton.stories.tsx
9
+ version: "1.0"
10
+ ---
11
+
12
+ # Skeleton
13
+
14
+ ## Назначение
15
+
16
+ Плейсхолдер для загружающегося контента — пульсирующий `<div>` с
17
+ `border-radius: 16px` и `flex-grow: 1`. Поддерживает две темы (`DARK` /
18
+ `LIGHT`) — отличаются фоновыми переменными в @keyframes.
19
+
20
+ Используй когда: показываешь shape будущего контента, чтобы избежать
21
+ layout shift и психологически сократить ощущаемое время загрузки. Для
22
+ точечного индикатора — `Loader`/`LoaderScreen`.
23
+
24
+ ---
25
+
26
+ ## Файловая структура
27
+
28
+ ```text
29
+ src/components/Skeleton/
30
+ ├── Skeleton.tsx # FC; <div> + класс по type
31
+ ├── enums.ts # ESkeletonType (DARK | LIGHT)
32
+ ├── styles/Skeleton.module.less # 2 @keyframes пульсации
33
+ ├── index.ts
34
+ └── __tests__/
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Варианты и props
40
+
41
+ ### `ISkeletonProps extends HTMLAttributes<HTMLDivElement>`
42
+
43
+ | Prop | Тип | Default | Описание |
44
+ |---|---|---|---|
45
+ | `type` | `ESkeletonType` | `DARK` | Тема (DARK/LIGHT). Влияет на анимационные переменные. |
46
+ | `...HTMLAttributes` | — | — | На `<div>`. Размер задаётся через `style`/`className`. |
47
+
48
+ ### Enum
49
+
50
+ - `ESkeletonType.DARK | LIGHT`.
51
+
52
+ ---
53
+
54
+ ## Ключевые особенности реализации
55
+
56
+ ### Анимация — 2s ease-in-out infinite через background-color keyframes
57
+
58
+ ```less
59
+ .skeleton.dark { animation: skeleton-dark-pulse 2s ease-in-out infinite; }
60
+ @keyframes skeleton-dark-pulse {
61
+ 0% { background-color: var(--triplex-next-Skeleton-BackgroundColor_Dark_Start); }
62
+ 50% { background-color: var(--triplex-next-Skeleton-BackgroundColor_Dark_End); }
63
+ 100% { background-color: var(--triplex-next-Skeleton-BackgroundColor_Dark_Start); }
64
+ }
65
+ ```
66
+
67
+ - Пульсация Start ↔ End ↔ Start.
68
+ - **`prefers-reduced-motion`** не учитывается — потенциальный a11y-долг.
69
+
70
+ ### `border-radius: 16px`, `flex-grow: 1`
71
+
72
+ - Скругление **захардкожено** (16px). Для других форм (круги аватаров, узкие линии текста) — потребитель override через className/style.
73
+ - `flex-grow: 1` — растягивается во flex-контейнере. **В блочном родителе** Skeleton сам по себе не имеет ширины — нужна `style.width` или `className` с шириной.
74
+
75
+ ### **`FC, не forwardRef`**
76
+
77
+ - Ref на `<div>` не пробрасывается.
78
+
79
+ ### **Нет `aria-busy`/`role="status"`**
80
+
81
+ - Skeleton — чисто визуальный. AT не озвучивает «загрузка». Если важно — потребитель добавляет `role="status"` / `aria-busy="true"` через `...rest`.
82
+
83
+ ---
84
+
85
+ ## Инварианты
86
+
87
+ - **`type` default `DARK`**.
88
+ - **`border-radius: 16px`** захардкожено — изменение требует override.
89
+ - **`flex-grow: 1`** захардкожено — поведение во flex-контейнере.
90
+ - **Анимация 2s ease-in-out infinite** — захардкожено, не настраивается.
91
+ - **CSS-переменные `--triplex-next-Skeleton-BackgroundColor_{Light|Dark}_{Start|End}`** — публичные токены.
92
+ - **`FC, не forwardRef`**.
93
+ - **`prefers-reduced-motion` не учитывается** — тех-долг.
94
+ - **`data-tx`** — версия пакета.
95
+
96
+ ---
97
+
98
+ ## Accessibility
99
+
100
+ - **Нет `role` / `aria-busy`** по умолчанию — потребитель должен добавить, если нужно.
101
+ - **Нет `prefers-reduced-motion` media-query** — анимация играет всегда. Пользователи с предпочтением reduced motion увидят пульсацию (тех-долг).
102
+
103
+ ---
104
+
105
+ ## Дизайн-токены
106
+
107
+ - `--triplex-next-Skeleton-BackgroundColor_Light_Start` / `_End`.
108
+ - `--triplex-next-Skeleton-BackgroundColor_Dark_Start` / `_End`.
109
+
110
+ ---
111
+
112
+ ## Связанные компоненты
113
+
114
+ - `Loader` / `LoaderScreen` — для активной точечной загрузки.
115
+
116
+ ---
117
+
118
+ ## Stories
119
+
120
+ `stories/Skeleton/Skeleton.stories.tsx`
121
+ Примеры: `stories/Skeleton/examples/`
122
+
123
+ ---
124
+
125
+ ## История изменений
126
+
127
+ | Дата | Изменение |
128
+ |---|---|
129
+ | 2026-04-18 | Создан документ |