@de-kit/st-ui 0.1.11 → 0.1.14
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 +2 -2
- package/dist/components/birthday-picker/birthday-picker.cjs +245 -0
- package/dist/components/birthday-picker/birthday-picker.cjs.map +1 -0
- package/dist/components/birthday-picker/birthday-picker.d.cts +24 -0
- package/dist/components/birthday-picker/birthday-picker.d.ts +24 -0
- package/dist/components/birthday-picker/birthday-picker.js +245 -0
- package/dist/components/birthday-picker/birthday-picker.js.map +1 -0
- package/dist/components/birthday-picker/birthday-picker.scss +84 -0
- package/dist/components/birthday-picker/index.d.cts +2 -0
- package/dist/components/birthday-picker/index.d.ts +2 -0
- package/dist/components/birthday-picker/wheel-column.cjs +114 -0
- package/dist/components/birthday-picker/wheel-column.cjs.map +1 -0
- package/dist/components/birthday-picker/wheel-column.js +114 -0
- package/dist/components/birthday-picker/wheel-column.js.map +1 -0
- package/dist/components/birthday-picker/wheel-column.scss +61 -0
- package/dist/components/button/button.scss +2 -2
- package/dist/components/field/field.cjs +4 -3
- package/dist/components/field/field.cjs.map +1 -1
- package/dist/components/field/field.d.cts +1 -1
- package/dist/components/field/field.d.ts +1 -1
- package/dist/components/field/field.js +4 -3
- package/dist/components/field/field.js.map +1 -1
- package/dist/components/form/form.cjs +124 -0
- package/dist/components/form/form.cjs.map +1 -0
- package/dist/components/form/form.d.cts +31 -0
- package/dist/components/form/form.d.ts +31 -0
- package/dist/components/form/form.js +121 -0
- package/dist/components/form/form.js.map +1 -0
- package/dist/components/form/form.scss +29 -0
- package/dist/components/form/index.d.cts +2 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/phone-input/index.d.cts +2 -0
- package/dist/components/phone-input/index.d.ts +2 -0
- package/dist/components/phone-input/phone-input.cjs +105 -0
- package/dist/components/phone-input/phone-input.cjs.map +1 -0
- package/dist/components/phone-input/phone-input.d.cts +29 -0
- package/dist/components/phone-input/phone-input.d.ts +29 -0
- package/dist/components/phone-input/phone-input.js +103 -0
- package/dist/components/phone-input/phone-input.js.map +1 -0
- package/dist/icons/icon-gallery-next.cjs +1 -1
- package/dist/icons/icon-gallery-next.cjs.map +1 -1
- package/dist/icons/icon-gallery-next.js +1 -1
- package/dist/icons/icon-gallery-next.js.map +1 -1
- package/dist/icons/icon-gallery-prev.cjs +1 -1
- package/dist/icons/icon-gallery-prev.cjs.map +1 -1
- package/dist/icons/icon-gallery-prev.js +1 -1
- package/dist/icons/icon-gallery-prev.js.map +1 -1
- package/dist/icons/icon-label-ozon.cjs +4 -4
- package/dist/icons/icon-label-ozon.cjs.map +1 -1
- package/dist/icons/icon-label-ozon.js +4 -4
- package/dist/icons/icon-label-ozon.js.map +1 -1
- package/dist/icons/icon-label-yandex.cjs +1 -1
- package/dist/icons/icon-label-yandex.cjs.map +1 -1
- package/dist/icons/icon-label-yandex.js +1 -1
- package/dist/icons/icon-label-yandex.js.map +1 -1
- package/dist/icons/index.cjs +2 -2
- package/dist/icons/index.js +2 -2
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +4 -1
- package/dist/styles/vars.scss +2 -2
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ import { IconClose, LogoVisa } from '@de-kit/st-ui/icons';
|
|
|
27
27
|
<IconClose width={16} height={16} aria-label="Закрыть" />
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
`icon_*` и `control_*` — 20×20 и `currentColor`
|
|
30
|
+
`icon_*` и `control_*` — 20×20 и `currentColor`. У стрелок галереи `viewBox` 20×40, рисуются тоже 20×20. Размер меняется пропом `width`/`height` или CSS, он перекрывает дефолт. `icon_label-*` сохраняют заливку. `logo_*` и `illustration_*` — размер из SVG, у иллюстраций чёрный stroke заменён на `currentColor`. Новый svg кладётся в `src/icons/svg`, дальше `pnpm build:icons` (его же зовут `dev` и `build`).
|
|
31
31
|
|
|
32
32
|
`global.scss` — база приложения (body, заголовки, таблицы, `.wrapper`). В чужой странице его не подключать. Шрифт Inter библиотека не везёт, его по-прежнему грузит фронтенд.
|
|
33
33
|
|
|
@@ -117,7 +117,7 @@ import { IconClose, LogoVisa } from '@de-kit/st-ui/icons';
|
|
|
117
117
|
|
|
118
118
|
## Поле
|
|
119
119
|
|
|
120
|
-
`Field` оборачивает один контрол: ставит
|
|
120
|
+
`Field` оборачивает один контрол: ставит подсказку и ошибку и связывает их через `aria-describedby`, `aria-errormessage` и `role="alert"`. `label` необязателен: без него `<label>` не рисуется, а текст `required` — «Обязательное поле».
|
|
121
121
|
|
|
122
122
|
```tsx
|
|
123
123
|
<Field label="Имя" hint="Нельзя изменить после проверки">
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
const require_cn = require("../../lib/cn.cjs");
|
|
2
|
+
const require_button = require("../button/button.cjs");
|
|
3
|
+
const require_input = require("../input/input.cjs");
|
|
4
|
+
const require_icon_calendar = require("../../icons/icon-calendar.cjs");
|
|
5
|
+
const require_icon_correct = require("../../icons/icon-correct.cjs");
|
|
6
|
+
const require_wheel_column = require("./wheel-column.cjs");
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let react_dom = require("react-dom");
|
|
10
|
+
require("@/components/input/input.scss");
|
|
11
|
+
require("./birthday-picker.scss");
|
|
12
|
+
// src/components/birthday-picker/birthday-picker.tsx
|
|
13
|
+
const MONTH_NAMES = [
|
|
14
|
+
"январь",
|
|
15
|
+
"февраль",
|
|
16
|
+
"март",
|
|
17
|
+
"апрель",
|
|
18
|
+
"май",
|
|
19
|
+
"июнь",
|
|
20
|
+
"июль",
|
|
21
|
+
"август",
|
|
22
|
+
"сентябрь",
|
|
23
|
+
"октябрь",
|
|
24
|
+
"ноябрь",
|
|
25
|
+
"декабрь"
|
|
26
|
+
];
|
|
27
|
+
const MONTH_NAMES_GENITIVE = [
|
|
28
|
+
"января",
|
|
29
|
+
"февраля",
|
|
30
|
+
"марта",
|
|
31
|
+
"апреля",
|
|
32
|
+
"мая",
|
|
33
|
+
"июня",
|
|
34
|
+
"июля",
|
|
35
|
+
"августа",
|
|
36
|
+
"сентября",
|
|
37
|
+
"октября",
|
|
38
|
+
"ноября",
|
|
39
|
+
"декабря"
|
|
40
|
+
];
|
|
41
|
+
const daysInMonth = (year, month) => new Date(year, month, 0).getDate();
|
|
42
|
+
const partsOf = (date) => ({
|
|
43
|
+
day: date.getDate(),
|
|
44
|
+
month: date.getMonth() + 1,
|
|
45
|
+
year: date.getFullYear()
|
|
46
|
+
});
|
|
47
|
+
const toDate = ({ day, month, year }) => new Date(year, month - 1, day);
|
|
48
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
49
|
+
const toIsoDate = (date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
50
|
+
const formatBirthday = (date) => `${date.getDate()} ${MONTH_NAMES_GENITIVE[date.getMonth()]} ${date.getFullYear()}`;
|
|
51
|
+
/** Сегодня минус `minAge` лет. 29 февраля в невисокосном году уходит на 28-е. */
|
|
52
|
+
const maxBirthDate = (minAge, today) => {
|
|
53
|
+
const year = today.getFullYear() - minAge;
|
|
54
|
+
const month = today.getMonth();
|
|
55
|
+
const day = Math.min(today.getDate(), daysInMonth(year, month + 1));
|
|
56
|
+
return new Date(year, month, day);
|
|
57
|
+
};
|
|
58
|
+
/** Подрезает день под длину месяца и не даёт выбрать дату позже максимальной. */
|
|
59
|
+
const clampParts = (parts, max) => {
|
|
60
|
+
const next = {
|
|
61
|
+
...parts,
|
|
62
|
+
day: Math.min(parts.day, daysInMonth(parts.year, parts.month))
|
|
63
|
+
};
|
|
64
|
+
return toDate(next) > max ? partsOf(max) : next;
|
|
65
|
+
};
|
|
66
|
+
const range = (from, to) => from > to ? [] : Array.from({ length: to - from + 1 }, (_, i) => from + i);
|
|
67
|
+
/**
|
|
68
|
+
* Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.
|
|
69
|
+
* Поле только для чтения с клавиатуры, поэтому `required` из `Field` и
|
|
70
|
+
* нативная проверка формы работают, а телефонная клавиатура не открывается.
|
|
71
|
+
*/
|
|
72
|
+
const BirthdayPicker = ({ value, onChange, minYear = 1930, maxYear, minAge = 18, disabled = false, name, placeholder, error, className }) => {
|
|
73
|
+
const [today] = (0, react.useState)(() => /* @__PURE__ */ new Date());
|
|
74
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
75
|
+
const maxBirth = maxBirthDate(minAge, today);
|
|
76
|
+
const lastYear = Math.min(maxYear ?? today.getFullYear(), maxBirth.getFullYear());
|
|
77
|
+
const [draft, setDraft] = (0, react.useState)(() => clampParts(partsOf(value ?? maxBirth), maxBirth));
|
|
78
|
+
const triggerRef = (0, react.useRef)(null);
|
|
79
|
+
const sheetRef = (0, react.useRef)(null);
|
|
80
|
+
const titleId = (0, react.useId)();
|
|
81
|
+
const years = range(Math.min(minYear, draft.year), lastYear);
|
|
82
|
+
const days = range(1, daysInMonth(draft.year, draft.month));
|
|
83
|
+
(0, react.useEffect)(() => {
|
|
84
|
+
if (!open) return;
|
|
85
|
+
const { position, top, width } = document.body.style;
|
|
86
|
+
const scrollY = window.scrollY;
|
|
87
|
+
document.body.style.position = "fixed";
|
|
88
|
+
document.body.style.top = `-${scrollY}px`;
|
|
89
|
+
document.body.style.width = "100%";
|
|
90
|
+
sheetRef.current?.querySelector("[role=\"spinbutton\"]")?.focus();
|
|
91
|
+
const handleKeyDown = (event) => {
|
|
92
|
+
if (event.key !== "Escape") return;
|
|
93
|
+
setOpen(false);
|
|
94
|
+
triggerRef.current?.focus();
|
|
95
|
+
};
|
|
96
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
97
|
+
return () => {
|
|
98
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
99
|
+
document.body.style.position = position;
|
|
100
|
+
document.body.style.top = top;
|
|
101
|
+
document.body.style.width = width;
|
|
102
|
+
window.scrollTo(0, scrollY);
|
|
103
|
+
};
|
|
104
|
+
}, [open]);
|
|
105
|
+
const openSheet = () => {
|
|
106
|
+
if (disabled) return;
|
|
107
|
+
setDraft(clampParts(partsOf(value ?? maxBirth), maxBirth));
|
|
108
|
+
setOpen(true);
|
|
109
|
+
};
|
|
110
|
+
const close = () => {
|
|
111
|
+
setOpen(false);
|
|
112
|
+
triggerRef.current?.focus();
|
|
113
|
+
};
|
|
114
|
+
const updateDraft = (patch) => {
|
|
115
|
+
setDraft((prev) => clampParts({
|
|
116
|
+
...prev,
|
|
117
|
+
...patch
|
|
118
|
+
}, maxBirth));
|
|
119
|
+
};
|
|
120
|
+
const apply = () => {
|
|
121
|
+
onChange?.(toDate(draft));
|
|
122
|
+
close();
|
|
123
|
+
};
|
|
124
|
+
const handleTriggerKeyDown = (event) => {
|
|
125
|
+
if (event.key === "Tab") return;
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") openSheet();
|
|
128
|
+
};
|
|
129
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
130
|
+
className: require_cn.cn("birthday-picker", className),
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_input.Input, {
|
|
133
|
+
ref: triggerRef,
|
|
134
|
+
className: "birthday-picker__trigger",
|
|
135
|
+
value: value ? formatBirthday(value) : "",
|
|
136
|
+
onChange: () => {},
|
|
137
|
+
onClick: openSheet,
|
|
138
|
+
onKeyDown: handleTriggerKeyDown,
|
|
139
|
+
onPaste: (event) => event.preventDefault(),
|
|
140
|
+
inputMode: "none",
|
|
141
|
+
autoComplete: "off",
|
|
142
|
+
"aria-haspopup": "dialog",
|
|
143
|
+
"aria-expanded": open,
|
|
144
|
+
placeholder: placeholder ?? `Возраст от ${minAge} лет`,
|
|
145
|
+
disabled,
|
|
146
|
+
error
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icon_calendar.IconCalendar, {
|
|
149
|
+
className: "birthday-picker__icon",
|
|
150
|
+
"aria-hidden": true
|
|
151
|
+
}),
|
|
152
|
+
name ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
153
|
+
type: "hidden",
|
|
154
|
+
name,
|
|
155
|
+
value: value ? toIsoDate(value) : ""
|
|
156
|
+
}) : null,
|
|
157
|
+
open ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
158
|
+
className: "birthday-picker__layer",
|
|
159
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
160
|
+
type: "button",
|
|
161
|
+
className: "birthday-picker__mask",
|
|
162
|
+
"aria-label": "Закрыть",
|
|
163
|
+
onClick: close
|
|
164
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
165
|
+
ref: sheetRef,
|
|
166
|
+
open: true,
|
|
167
|
+
"aria-modal": "true",
|
|
168
|
+
"aria-labelledby": titleId,
|
|
169
|
+
className: "birthday-picker__sheet",
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
172
|
+
id: titleId,
|
|
173
|
+
className: "birthday-picker__title",
|
|
174
|
+
children: "Дата рождения"
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
177
|
+
className: "birthday-picker__columns",
|
|
178
|
+
children: [
|
|
179
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
180
|
+
className: "birthday-picker__column",
|
|
181
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
182
|
+
className: "birthday-picker__caption",
|
|
183
|
+
children: "День"
|
|
184
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_wheel_column.WheelColumn, {
|
|
185
|
+
label: "День",
|
|
186
|
+
items: days.map((day) => ({
|
|
187
|
+
value: day,
|
|
188
|
+
label: String(day)
|
|
189
|
+
})),
|
|
190
|
+
value: draft.day,
|
|
191
|
+
onChange: (day) => updateDraft({ day })
|
|
192
|
+
})]
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
195
|
+
className: "birthday-picker__column",
|
|
196
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
197
|
+
className: "birthday-picker__caption",
|
|
198
|
+
children: "Месяц"
|
|
199
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_wheel_column.WheelColumn, {
|
|
200
|
+
label: "Месяц",
|
|
201
|
+
items: MONTH_NAMES.map((month, index) => ({
|
|
202
|
+
value: index + 1,
|
|
203
|
+
label: month
|
|
204
|
+
})),
|
|
205
|
+
value: draft.month,
|
|
206
|
+
onChange: (month) => updateDraft({ month })
|
|
207
|
+
})]
|
|
208
|
+
}),
|
|
209
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
210
|
+
className: "birthday-picker__column",
|
|
211
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
212
|
+
className: "birthday-picker__caption",
|
|
213
|
+
children: "Год"
|
|
214
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_wheel_column.WheelColumn, {
|
|
215
|
+
label: "Год",
|
|
216
|
+
items: years.map((year) => ({
|
|
217
|
+
value: year,
|
|
218
|
+
label: String(year)
|
|
219
|
+
})),
|
|
220
|
+
value: draft.year,
|
|
221
|
+
onChange: (year) => updateDraft({ year })
|
|
222
|
+
})]
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
}),
|
|
226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
227
|
+
className: "birthday-picker__actions",
|
|
228
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_button.Button, {
|
|
229
|
+
onClick: apply,
|
|
230
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icon_correct.IconCorrect, {}), "Применить"]
|
|
231
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
232
|
+
variant: "link",
|
|
233
|
+
onClick: close,
|
|
234
|
+
children: "Отмена"
|
|
235
|
+
})]
|
|
236
|
+
})
|
|
237
|
+
]
|
|
238
|
+
})]
|
|
239
|
+
}), document.body) : null
|
|
240
|
+
]
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
exports.BirthdayPicker = BirthdayPicker;
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=birthday-picker.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"birthday-picker.cjs","names":["useState","useRef","useId","cn","Input","IconCalendar","createPortal","WheelColumn","Button","IconCorrect"],"sources":["../../../src/components/birthday-picker/birthday-picker.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type KeyboardEvent } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { Button } from '@/components/button';\nimport { Input } from '@/components/input';\nimport '@/components/input/input.scss';\nimport { IconCalendar } from '@/icons/icon-calendar';\nimport { IconCorrect } from '@/icons/icon-correct';\nimport { cn } from '@/lib/cn';\nimport { WheelColumn } from './wheel-column';\nimport './birthday-picker.scss';\n\nconst MONTH_NAMES = [\n 'январь',\n 'февраль',\n 'март',\n 'апрель',\n 'май',\n 'июнь',\n 'июль',\n 'август',\n 'сентябрь',\n 'октябрь',\n 'ноябрь',\n 'декабрь',\n];\n\n// В дате пишется родительный падеж: «15 марта 1990».\nconst MONTH_NAMES_GENITIVE = [\n 'января',\n 'февраля',\n 'марта',\n 'апреля',\n 'мая',\n 'июня',\n 'июля',\n 'августа',\n 'сентября',\n 'октября',\n 'ноября',\n 'декабря',\n];\n\n/** Месяц здесь 1–12, как в календаре, а не индекс массива. */\ninterface BirthdayParts {\n day: number;\n month: number;\n year: number;\n}\n\nconst daysInMonth = (year: number, month: number) =>\n new Date(year, month, 0).getDate();\n\nconst partsOf = (date: Date): BirthdayParts => ({\n day: date.getDate(),\n month: date.getMonth() + 1,\n year: date.getFullYear(),\n});\n\nconst toDate = ({ day, month, year }: BirthdayParts) =>\n new Date(year, month - 1, day);\n\nconst pad = (value: number) => String(value).padStart(2, '0');\n\nconst toIsoDate = (date: Date) =>\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n\nconst formatBirthday = (date: Date) =>\n `${date.getDate()} ${MONTH_NAMES_GENITIVE[date.getMonth()]} ${date.getFullYear()}`;\n\n/** Сегодня минус `minAge` лет. 29 февраля в невисокосном году уходит на 28-е. */\nconst maxBirthDate = (minAge: number, today: Date) => {\n const year = today.getFullYear() - minAge;\n const month = today.getMonth();\n const day = Math.min(today.getDate(), daysInMonth(year, month + 1));\n return new Date(year, month, day);\n};\n\n/** Подрезает день под длину месяца и не даёт выбрать дату позже максимальной. */\nconst clampParts = (parts: BirthdayParts, max: Date): BirthdayParts => {\n const next = {\n ...parts,\n day: Math.min(parts.day, daysInMonth(parts.year, parts.month)),\n };\n\n return toDate(next) > max ? partsOf(max) : next;\n};\n\nconst range = (from: number, to: number) =>\n from > to ? [] : Array.from({ length: to - from + 1 }, (_, i) => from + i);\n\nexport interface BirthdayPickerProps {\n value?: Date | null;\n onChange?: (date: Date) => void;\n /** Самый ранний год в списке. */\n minYear?: number;\n /** Самый поздний год в списке. По умолчанию — текущий. */\n maxYear?: number;\n /** Минимальный возраст: позже этой даты выбрать нельзя. */\n minAge?: number;\n disabled?: boolean;\n /** Имя для FormData: уходит дата в формате `YYYY-MM-DD`. */\n name?: string;\n placeholder?: string;\n error?: boolean;\n className?: string;\n}\n\n/**\n * Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.\n * Поле только для чтения с клавиатуры, поэтому `required` из `Field` и\n * нативная проверка формы работают, а телефонная клавиатура не открывается.\n */\nexport const BirthdayPicker = ({\n value,\n onChange,\n minYear = 1930,\n maxYear,\n minAge = 18,\n disabled = false,\n name,\n placeholder,\n error,\n className,\n}: BirthdayPickerProps) => {\n const [today] = useState(() => new Date());\n const [open, setOpen] = useState(false);\n const maxBirth = maxBirthDate(minAge, today);\n const lastYear = Math.min(\n maxYear ?? today.getFullYear(),\n maxBirth.getFullYear(),\n );\n const [draft, setDraft] = useState<BirthdayParts>(() =>\n clampParts(partsOf(value ?? maxBirth), maxBirth),\n );\n const triggerRef = useRef<HTMLInputElement>(null);\n const sheetRef = useRef<HTMLDialogElement>(null);\n const titleId = useId();\n\n const years = range(Math.min(minYear, draft.year), lastYear);\n const days = range(1, daysInMonth(draft.year, draft.month));\n\n // iOS: без position: fixed фон прокручивается под открытым листом.\n useEffect(() => {\n if (!open) return;\n\n const { position, top, width } = document.body.style;\n const scrollY = window.scrollY;\n document.body.style.position = 'fixed';\n document.body.style.top = `-${scrollY}px`;\n document.body.style.width = '100%';\n sheetRef.current\n ?.querySelector<HTMLElement>('[role=\"spinbutton\"]')\n ?.focus();\n\n const handleKeyDown = (event: globalThis.KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n\n setOpen(false);\n triggerRef.current?.focus();\n };\n document.addEventListener('keydown', handleKeyDown);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.body.style.position = position;\n document.body.style.top = top;\n document.body.style.width = width;\n window.scrollTo(0, scrollY);\n };\n }, [open]);\n\n const openSheet = () => {\n if (disabled) return;\n\n setDraft(clampParts(partsOf(value ?? maxBirth), maxBirth));\n setOpen(true);\n };\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus();\n };\n\n const updateDraft = (patch: Partial<BirthdayParts>) => {\n setDraft(prev => clampParts({ ...prev, ...patch }, maxBirth));\n };\n\n const apply = () => {\n onChange?.(toDate(draft));\n close();\n };\n\n // Текст не вводится: поле только выбирает дату через лист.\n const handleTriggerKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Tab') return;\n\n event.preventDefault();\n if (\n event.key === 'Enter' ||\n event.key === ' ' ||\n event.key === 'ArrowDown'\n ) {\n openSheet();\n }\n };\n\n return (\n <div className={cn('birthday-picker', className)}>\n <Input\n ref={triggerRef}\n className=\"birthday-picker__trigger\"\n value={value ? formatBirthday(value) : ''}\n onChange={() => {}}\n onClick={openSheet}\n onKeyDown={handleTriggerKeyDown}\n onPaste={event => event.preventDefault()}\n inputMode=\"none\"\n autoComplete=\"off\"\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n placeholder={placeholder ?? `Возраст от ${minAge} лет`}\n disabled={disabled}\n error={error}\n />\n <IconCalendar className=\"birthday-picker__icon\" aria-hidden />\n {name ? (\n <input\n type=\"hidden\"\n name={name}\n value={value ? toIsoDate(value) : ''}\n />\n ) : null}\n\n {open\n ? createPortal(\n <div className=\"birthday-picker__layer\">\n <button\n type=\"button\"\n className=\"birthday-picker__mask\"\n aria-label=\"Закрыть\"\n onClick={close}\n />\n <dialog\n ref={sheetRef}\n open\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className=\"birthday-picker__sheet\"\n >\n <h2 id={titleId} className=\"birthday-picker__title\">\n Дата рождения\n </h2>\n <div className=\"birthday-picker__columns\">\n <div className=\"birthday-picker__column\">\n <span className=\"birthday-picker__caption\">День</span>\n <WheelColumn\n label=\"День\"\n items={days.map(day => ({\n value: day,\n label: String(day),\n }))}\n value={draft.day}\n onChange={day => updateDraft({ day })}\n />\n </div>\n <div className=\"birthday-picker__column\">\n <span className=\"birthday-picker__caption\">Месяц</span>\n <WheelColumn\n label=\"Месяц\"\n items={MONTH_NAMES.map((month, index) => ({\n value: index + 1,\n label: month,\n }))}\n value={draft.month}\n onChange={month => updateDraft({ month })}\n />\n </div>\n <div className=\"birthday-picker__column\">\n <span className=\"birthday-picker__caption\">Год</span>\n <WheelColumn\n label=\"Год\"\n items={years.map(year => ({\n value: year,\n label: String(year),\n }))}\n value={draft.year}\n onChange={year => updateDraft({ year })}\n />\n </div>\n </div>\n <div className=\"birthday-picker__actions\">\n <Button onClick={apply}>\n <IconCorrect />\n Применить\n </Button>\n <Button variant=\"link\" onClick={close}>\n Отмена\n </Button>\n </div>\n </dialog>\n </div>,\n document.body,\n )\n : null}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "@/components/input/input.scss";
|
|
2
|
+
export interface BirthdayPickerProps {
|
|
3
|
+
value?: Date | null;
|
|
4
|
+
onChange?: (date: Date) => void;
|
|
5
|
+
/** Самый ранний год в списке. */
|
|
6
|
+
minYear?: number;
|
|
7
|
+
/** Самый поздний год в списке. По умолчанию — текущий. */
|
|
8
|
+
maxYear?: number;
|
|
9
|
+
/** Минимальный возраст: позже этой даты выбрать нельзя. */
|
|
10
|
+
minAge?: number;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** Имя для FormData: уходит дата в формате `YYYY-MM-DD`. */
|
|
13
|
+
name?: string;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
error?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.
|
|
20
|
+
* Поле только для чтения с клавиатуры, поэтому `required` из `Field` и
|
|
21
|
+
* нативная проверка формы работают, а телефонная клавиатура не открывается.
|
|
22
|
+
*/
|
|
23
|
+
export declare const BirthdayPicker: ({ value, onChange, minYear, maxYear, minAge, disabled, name, placeholder, error, className }: BirthdayPickerProps) => import("react").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=birthday-picker.d.cts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "@/components/input/input.scss";
|
|
2
|
+
export interface BirthdayPickerProps {
|
|
3
|
+
value?: Date | null;
|
|
4
|
+
onChange?: (date: Date) => void;
|
|
5
|
+
/** Самый ранний год в списке. */
|
|
6
|
+
minYear?: number;
|
|
7
|
+
/** Самый поздний год в списке. По умолчанию — текущий. */
|
|
8
|
+
maxYear?: number;
|
|
9
|
+
/** Минимальный возраст: позже этой даты выбрать нельзя. */
|
|
10
|
+
minAge?: number;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** Имя для FormData: уходит дата в формате `YYYY-MM-DD`. */
|
|
13
|
+
name?: string;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
error?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.
|
|
20
|
+
* Поле только для чтения с клавиатуры, поэтому `required` из `Field` и
|
|
21
|
+
* нативная проверка формы работают, а телефонная клавиатура не открывается.
|
|
22
|
+
*/
|
|
23
|
+
export declare const BirthdayPicker: ({ value, onChange, minYear, maxYear, minAge, disabled, name, placeholder, error, className }: BirthdayPickerProps) => import("react").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=birthday-picker.d.ts.map
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { Button } from "../button/button.js";
|
|
3
|
+
import { Input } from "../input/input.js";
|
|
4
|
+
import { IconCalendar } from "../../icons/icon-calendar.js";
|
|
5
|
+
import { IconCorrect } from "../../icons/icon-correct.js";
|
|
6
|
+
import { WheelColumn } from "./wheel-column.js";
|
|
7
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { createPortal } from "react-dom";
|
|
10
|
+
import "@/components/input/input.scss";
|
|
11
|
+
import "./birthday-picker.scss";
|
|
12
|
+
// src/components/birthday-picker/birthday-picker.tsx
|
|
13
|
+
const MONTH_NAMES = [
|
|
14
|
+
"январь",
|
|
15
|
+
"февраль",
|
|
16
|
+
"март",
|
|
17
|
+
"апрель",
|
|
18
|
+
"май",
|
|
19
|
+
"июнь",
|
|
20
|
+
"июль",
|
|
21
|
+
"август",
|
|
22
|
+
"сентябрь",
|
|
23
|
+
"октябрь",
|
|
24
|
+
"ноябрь",
|
|
25
|
+
"декабрь"
|
|
26
|
+
];
|
|
27
|
+
const MONTH_NAMES_GENITIVE = [
|
|
28
|
+
"января",
|
|
29
|
+
"февраля",
|
|
30
|
+
"марта",
|
|
31
|
+
"апреля",
|
|
32
|
+
"мая",
|
|
33
|
+
"июня",
|
|
34
|
+
"июля",
|
|
35
|
+
"августа",
|
|
36
|
+
"сентября",
|
|
37
|
+
"октября",
|
|
38
|
+
"ноября",
|
|
39
|
+
"декабря"
|
|
40
|
+
];
|
|
41
|
+
const daysInMonth = (year, month) => new Date(year, month, 0).getDate();
|
|
42
|
+
const partsOf = (date) => ({
|
|
43
|
+
day: date.getDate(),
|
|
44
|
+
month: date.getMonth() + 1,
|
|
45
|
+
year: date.getFullYear()
|
|
46
|
+
});
|
|
47
|
+
const toDate = ({ day, month, year }) => new Date(year, month - 1, day);
|
|
48
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
49
|
+
const toIsoDate = (date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
50
|
+
const formatBirthday = (date) => `${date.getDate()} ${MONTH_NAMES_GENITIVE[date.getMonth()]} ${date.getFullYear()}`;
|
|
51
|
+
/** Сегодня минус `minAge` лет. 29 февраля в невисокосном году уходит на 28-е. */
|
|
52
|
+
const maxBirthDate = (minAge, today) => {
|
|
53
|
+
const year = today.getFullYear() - minAge;
|
|
54
|
+
const month = today.getMonth();
|
|
55
|
+
const day = Math.min(today.getDate(), daysInMonth(year, month + 1));
|
|
56
|
+
return new Date(year, month, day);
|
|
57
|
+
};
|
|
58
|
+
/** Подрезает день под длину месяца и не даёт выбрать дату позже максимальной. */
|
|
59
|
+
const clampParts = (parts, max) => {
|
|
60
|
+
const next = {
|
|
61
|
+
...parts,
|
|
62
|
+
day: Math.min(parts.day, daysInMonth(parts.year, parts.month))
|
|
63
|
+
};
|
|
64
|
+
return toDate(next) > max ? partsOf(max) : next;
|
|
65
|
+
};
|
|
66
|
+
const range = (from, to) => from > to ? [] : Array.from({ length: to - from + 1 }, (_, i) => from + i);
|
|
67
|
+
/**
|
|
68
|
+
* Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.
|
|
69
|
+
* Поле только для чтения с клавиатуры, поэтому `required` из `Field` и
|
|
70
|
+
* нативная проверка формы работают, а телефонная клавиатура не открывается.
|
|
71
|
+
*/
|
|
72
|
+
const BirthdayPicker = ({ value, onChange, minYear = 1930, maxYear, minAge = 18, disabled = false, name, placeholder, error, className }) => {
|
|
73
|
+
const [today] = useState(() => /* @__PURE__ */ new Date());
|
|
74
|
+
const [open, setOpen] = useState(false);
|
|
75
|
+
const maxBirth = maxBirthDate(minAge, today);
|
|
76
|
+
const lastYear = Math.min(maxYear ?? today.getFullYear(), maxBirth.getFullYear());
|
|
77
|
+
const [draft, setDraft] = useState(() => clampParts(partsOf(value ?? maxBirth), maxBirth));
|
|
78
|
+
const triggerRef = useRef(null);
|
|
79
|
+
const sheetRef = useRef(null);
|
|
80
|
+
const titleId = useId();
|
|
81
|
+
const years = range(Math.min(minYear, draft.year), lastYear);
|
|
82
|
+
const days = range(1, daysInMonth(draft.year, draft.month));
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!open) return;
|
|
85
|
+
const { position, top, width } = document.body.style;
|
|
86
|
+
const scrollY = window.scrollY;
|
|
87
|
+
document.body.style.position = "fixed";
|
|
88
|
+
document.body.style.top = `-${scrollY}px`;
|
|
89
|
+
document.body.style.width = "100%";
|
|
90
|
+
sheetRef.current?.querySelector("[role=\"spinbutton\"]")?.focus();
|
|
91
|
+
const handleKeyDown = (event) => {
|
|
92
|
+
if (event.key !== "Escape") return;
|
|
93
|
+
setOpen(false);
|
|
94
|
+
triggerRef.current?.focus();
|
|
95
|
+
};
|
|
96
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
97
|
+
return () => {
|
|
98
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
99
|
+
document.body.style.position = position;
|
|
100
|
+
document.body.style.top = top;
|
|
101
|
+
document.body.style.width = width;
|
|
102
|
+
window.scrollTo(0, scrollY);
|
|
103
|
+
};
|
|
104
|
+
}, [open]);
|
|
105
|
+
const openSheet = () => {
|
|
106
|
+
if (disabled) return;
|
|
107
|
+
setDraft(clampParts(partsOf(value ?? maxBirth), maxBirth));
|
|
108
|
+
setOpen(true);
|
|
109
|
+
};
|
|
110
|
+
const close = () => {
|
|
111
|
+
setOpen(false);
|
|
112
|
+
triggerRef.current?.focus();
|
|
113
|
+
};
|
|
114
|
+
const updateDraft = (patch) => {
|
|
115
|
+
setDraft((prev) => clampParts({
|
|
116
|
+
...prev,
|
|
117
|
+
...patch
|
|
118
|
+
}, maxBirth));
|
|
119
|
+
};
|
|
120
|
+
const apply = () => {
|
|
121
|
+
onChange?.(toDate(draft));
|
|
122
|
+
close();
|
|
123
|
+
};
|
|
124
|
+
const handleTriggerKeyDown = (event) => {
|
|
125
|
+
if (event.key === "Tab") return;
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") openSheet();
|
|
128
|
+
};
|
|
129
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
130
|
+
className: cn("birthday-picker", className),
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ jsx(Input, {
|
|
133
|
+
ref: triggerRef,
|
|
134
|
+
className: "birthday-picker__trigger",
|
|
135
|
+
value: value ? formatBirthday(value) : "",
|
|
136
|
+
onChange: () => {},
|
|
137
|
+
onClick: openSheet,
|
|
138
|
+
onKeyDown: handleTriggerKeyDown,
|
|
139
|
+
onPaste: (event) => event.preventDefault(),
|
|
140
|
+
inputMode: "none",
|
|
141
|
+
autoComplete: "off",
|
|
142
|
+
"aria-haspopup": "dialog",
|
|
143
|
+
"aria-expanded": open,
|
|
144
|
+
placeholder: placeholder ?? `Возраст от ${minAge} лет`,
|
|
145
|
+
disabled,
|
|
146
|
+
error
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsx(IconCalendar, {
|
|
149
|
+
className: "birthday-picker__icon",
|
|
150
|
+
"aria-hidden": true
|
|
151
|
+
}),
|
|
152
|
+
name ? /* @__PURE__ */ jsx("input", {
|
|
153
|
+
type: "hidden",
|
|
154
|
+
name,
|
|
155
|
+
value: value ? toIsoDate(value) : ""
|
|
156
|
+
}) : null,
|
|
157
|
+
open ? createPortal(/* @__PURE__ */ jsxs("div", {
|
|
158
|
+
className: "birthday-picker__layer",
|
|
159
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
160
|
+
type: "button",
|
|
161
|
+
className: "birthday-picker__mask",
|
|
162
|
+
"aria-label": "Закрыть",
|
|
163
|
+
onClick: close
|
|
164
|
+
}), /* @__PURE__ */ jsxs("dialog", {
|
|
165
|
+
ref: sheetRef,
|
|
166
|
+
open: true,
|
|
167
|
+
"aria-modal": "true",
|
|
168
|
+
"aria-labelledby": titleId,
|
|
169
|
+
className: "birthday-picker__sheet",
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ jsx("h2", {
|
|
172
|
+
id: titleId,
|
|
173
|
+
className: "birthday-picker__title",
|
|
174
|
+
children: "Дата рождения"
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ jsxs("div", {
|
|
177
|
+
className: "birthday-picker__columns",
|
|
178
|
+
children: [
|
|
179
|
+
/* @__PURE__ */ jsxs("div", {
|
|
180
|
+
className: "birthday-picker__column",
|
|
181
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
182
|
+
className: "birthday-picker__caption",
|
|
183
|
+
children: "День"
|
|
184
|
+
}), /* @__PURE__ */ jsx(WheelColumn, {
|
|
185
|
+
label: "День",
|
|
186
|
+
items: days.map((day) => ({
|
|
187
|
+
value: day,
|
|
188
|
+
label: String(day)
|
|
189
|
+
})),
|
|
190
|
+
value: draft.day,
|
|
191
|
+
onChange: (day) => updateDraft({ day })
|
|
192
|
+
})]
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ jsxs("div", {
|
|
195
|
+
className: "birthday-picker__column",
|
|
196
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
197
|
+
className: "birthday-picker__caption",
|
|
198
|
+
children: "Месяц"
|
|
199
|
+
}), /* @__PURE__ */ jsx(WheelColumn, {
|
|
200
|
+
label: "Месяц",
|
|
201
|
+
items: MONTH_NAMES.map((month, index) => ({
|
|
202
|
+
value: index + 1,
|
|
203
|
+
label: month
|
|
204
|
+
})),
|
|
205
|
+
value: draft.month,
|
|
206
|
+
onChange: (month) => updateDraft({ month })
|
|
207
|
+
})]
|
|
208
|
+
}),
|
|
209
|
+
/* @__PURE__ */ jsxs("div", {
|
|
210
|
+
className: "birthday-picker__column",
|
|
211
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
212
|
+
className: "birthday-picker__caption",
|
|
213
|
+
children: "Год"
|
|
214
|
+
}), /* @__PURE__ */ jsx(WheelColumn, {
|
|
215
|
+
label: "Год",
|
|
216
|
+
items: years.map((year) => ({
|
|
217
|
+
value: year,
|
|
218
|
+
label: String(year)
|
|
219
|
+
})),
|
|
220
|
+
value: draft.year,
|
|
221
|
+
onChange: (year) => updateDraft({ year })
|
|
222
|
+
})]
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
}),
|
|
226
|
+
/* @__PURE__ */ jsxs("div", {
|
|
227
|
+
className: "birthday-picker__actions",
|
|
228
|
+
children: [/* @__PURE__ */ jsxs(Button, {
|
|
229
|
+
onClick: apply,
|
|
230
|
+
children: [/* @__PURE__ */ jsx(IconCorrect, {}), "Применить"]
|
|
231
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
232
|
+
variant: "link",
|
|
233
|
+
onClick: close,
|
|
234
|
+
children: "Отмена"
|
|
235
|
+
})]
|
|
236
|
+
})
|
|
237
|
+
]
|
|
238
|
+
})]
|
|
239
|
+
}), document.body) : null
|
|
240
|
+
]
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
export { BirthdayPicker };
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=birthday-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"birthday-picker.js","names":[],"sources":["../../../src/components/birthday-picker/birthday-picker.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type KeyboardEvent } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { Button } from '@/components/button';\nimport { Input } from '@/components/input';\nimport '@/components/input/input.scss';\nimport { IconCalendar } from '@/icons/icon-calendar';\nimport { IconCorrect } from '@/icons/icon-correct';\nimport { cn } from '@/lib/cn';\nimport { WheelColumn } from './wheel-column';\nimport './birthday-picker.scss';\n\nconst MONTH_NAMES = [\n 'январь',\n 'февраль',\n 'март',\n 'апрель',\n 'май',\n 'июнь',\n 'июль',\n 'август',\n 'сентябрь',\n 'октябрь',\n 'ноябрь',\n 'декабрь',\n];\n\n// В дате пишется родительный падеж: «15 марта 1990».\nconst MONTH_NAMES_GENITIVE = [\n 'января',\n 'февраля',\n 'марта',\n 'апреля',\n 'мая',\n 'июня',\n 'июля',\n 'августа',\n 'сентября',\n 'октября',\n 'ноября',\n 'декабря',\n];\n\n/** Месяц здесь 1–12, как в календаре, а не индекс массива. */\ninterface BirthdayParts {\n day: number;\n month: number;\n year: number;\n}\n\nconst daysInMonth = (year: number, month: number) =>\n new Date(year, month, 0).getDate();\n\nconst partsOf = (date: Date): BirthdayParts => ({\n day: date.getDate(),\n month: date.getMonth() + 1,\n year: date.getFullYear(),\n});\n\nconst toDate = ({ day, month, year }: BirthdayParts) =>\n new Date(year, month - 1, day);\n\nconst pad = (value: number) => String(value).padStart(2, '0');\n\nconst toIsoDate = (date: Date) =>\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n\nconst formatBirthday = (date: Date) =>\n `${date.getDate()} ${MONTH_NAMES_GENITIVE[date.getMonth()]} ${date.getFullYear()}`;\n\n/** Сегодня минус `minAge` лет. 29 февраля в невисокосном году уходит на 28-е. */\nconst maxBirthDate = (minAge: number, today: Date) => {\n const year = today.getFullYear() - minAge;\n const month = today.getMonth();\n const day = Math.min(today.getDate(), daysInMonth(year, month + 1));\n return new Date(year, month, day);\n};\n\n/** Подрезает день под длину месяца и не даёт выбрать дату позже максимальной. */\nconst clampParts = (parts: BirthdayParts, max: Date): BirthdayParts => {\n const next = {\n ...parts,\n day: Math.min(parts.day, daysInMonth(parts.year, parts.month)),\n };\n\n return toDate(next) > max ? partsOf(max) : next;\n};\n\nconst range = (from: number, to: number) =>\n from > to ? [] : Array.from({ length: to - from + 1 }, (_, i) => from + i);\n\nexport interface BirthdayPickerProps {\n value?: Date | null;\n onChange?: (date: Date) => void;\n /** Самый ранний год в списке. */\n minYear?: number;\n /** Самый поздний год в списке. По умолчанию — текущий. */\n maxYear?: number;\n /** Минимальный возраст: позже этой даты выбрать нельзя. */\n minAge?: number;\n disabled?: boolean;\n /** Имя для FormData: уходит дата в формате `YYYY-MM-DD`. */\n name?: string;\n placeholder?: string;\n error?: boolean;\n className?: string;\n}\n\n/**\n * Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.\n * Поле только для чтения с клавиатуры, поэтому `required` из `Field` и\n * нативная проверка формы работают, а телефонная клавиатура не открывается.\n */\nexport const BirthdayPicker = ({\n value,\n onChange,\n minYear = 1930,\n maxYear,\n minAge = 18,\n disabled = false,\n name,\n placeholder,\n error,\n className,\n}: BirthdayPickerProps) => {\n const [today] = useState(() => new Date());\n const [open, setOpen] = useState(false);\n const maxBirth = maxBirthDate(minAge, today);\n const lastYear = Math.min(\n maxYear ?? today.getFullYear(),\n maxBirth.getFullYear(),\n );\n const [draft, setDraft] = useState<BirthdayParts>(() =>\n clampParts(partsOf(value ?? maxBirth), maxBirth),\n );\n const triggerRef = useRef<HTMLInputElement>(null);\n const sheetRef = useRef<HTMLDialogElement>(null);\n const titleId = useId();\n\n const years = range(Math.min(minYear, draft.year), lastYear);\n const days = range(1, daysInMonth(draft.year, draft.month));\n\n // iOS: без position: fixed фон прокручивается под открытым листом.\n useEffect(() => {\n if (!open) return;\n\n const { position, top, width } = document.body.style;\n const scrollY = window.scrollY;\n document.body.style.position = 'fixed';\n document.body.style.top = `-${scrollY}px`;\n document.body.style.width = '100%';\n sheetRef.current\n ?.querySelector<HTMLElement>('[role=\"spinbutton\"]')\n ?.focus();\n\n const handleKeyDown = (event: globalThis.KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n\n setOpen(false);\n triggerRef.current?.focus();\n };\n document.addEventListener('keydown', handleKeyDown);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.body.style.position = position;\n document.body.style.top = top;\n document.body.style.width = width;\n window.scrollTo(0, scrollY);\n };\n }, [open]);\n\n const openSheet = () => {\n if (disabled) return;\n\n setDraft(clampParts(partsOf(value ?? maxBirth), maxBirth));\n setOpen(true);\n };\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus();\n };\n\n const updateDraft = (patch: Partial<BirthdayParts>) => {\n setDraft(prev => clampParts({ ...prev, ...patch }, maxBirth));\n };\n\n const apply = () => {\n onChange?.(toDate(draft));\n close();\n };\n\n // Текст не вводится: поле только выбирает дату через лист.\n const handleTriggerKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Tab') return;\n\n event.preventDefault();\n if (\n event.key === 'Enter' ||\n event.key === ' ' ||\n event.key === 'ArrowDown'\n ) {\n openSheet();\n }\n };\n\n return (\n <div className={cn('birthday-picker', className)}>\n <Input\n ref={triggerRef}\n className=\"birthday-picker__trigger\"\n value={value ? formatBirthday(value) : ''}\n onChange={() => {}}\n onClick={openSheet}\n onKeyDown={handleTriggerKeyDown}\n onPaste={event => event.preventDefault()}\n inputMode=\"none\"\n autoComplete=\"off\"\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n placeholder={placeholder ?? `Возраст от ${minAge} лет`}\n disabled={disabled}\n error={error}\n />\n <IconCalendar className=\"birthday-picker__icon\" aria-hidden />\n {name ? (\n <input\n type=\"hidden\"\n name={name}\n value={value ? toIsoDate(value) : ''}\n />\n ) : null}\n\n {open\n ? createPortal(\n <div className=\"birthday-picker__layer\">\n <button\n type=\"button\"\n className=\"birthday-picker__mask\"\n aria-label=\"Закрыть\"\n onClick={close}\n />\n <dialog\n ref={sheetRef}\n open\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className=\"birthday-picker__sheet\"\n >\n <h2 id={titleId} className=\"birthday-picker__title\">\n Дата рождения\n </h2>\n <div className=\"birthday-picker__columns\">\n <div className=\"birthday-picker__column\">\n <span className=\"birthday-picker__caption\">День</span>\n <WheelColumn\n label=\"День\"\n items={days.map(day => ({\n value: day,\n label: String(day),\n }))}\n value={draft.day}\n onChange={day => updateDraft({ day })}\n />\n </div>\n <div className=\"birthday-picker__column\">\n <span className=\"birthday-picker__caption\">Месяц</span>\n <WheelColumn\n label=\"Месяц\"\n items={MONTH_NAMES.map((month, index) => ({\n value: index + 1,\n label: month,\n }))}\n value={draft.month}\n onChange={month => updateDraft({ month })}\n />\n </div>\n <div className=\"birthday-picker__column\">\n <span className=\"birthday-picker__caption\">Год</span>\n <WheelColumn\n label=\"Год\"\n items={years.map(year => ({\n value: year,\n label: String(year),\n }))}\n value={draft.year}\n onChange={year => updateDraft({ year })}\n />\n </div>\n </div>\n <div className=\"birthday-picker__actions\">\n <Button onClick={apply}>\n <IconCorrect />\n Применить\n </Button>\n <Button variant=\"link\" onClick={close}>\n Отмена\n </Button>\n </div>\n </dialog>\n </div>,\n document.body,\n )\n : null}\n </div>\n );\n};\n"],"mappings":"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"}
|