@de-kit/st-ui 0.1.13 → 0.1.15
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/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 +23 -0
- package/dist/components/birthday-picker/birthday-picker.d.ts +23 -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/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 +28 -0
- package/dist/components/phone-input/phone-input.d.ts +28 -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-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/package.json +8 -5
|
@@ -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("../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 '../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,23 @@
|
|
|
1
|
+
export interface BirthdayPickerProps {
|
|
2
|
+
value?: Date | null;
|
|
3
|
+
onChange?: (date: Date) => void;
|
|
4
|
+
/** Самый ранний год в списке. */
|
|
5
|
+
minYear?: number;
|
|
6
|
+
/** Самый поздний год в списке. По умолчанию — текущий. */
|
|
7
|
+
maxYear?: number;
|
|
8
|
+
/** Минимальный возраст: позже этой даты выбрать нельзя. */
|
|
9
|
+
minAge?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
/** Имя для FormData: уходит дата в формате `YYYY-MM-DD`. */
|
|
12
|
+
name?: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
error?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.
|
|
19
|
+
* Поле только для чтения с клавиатуры, поэтому `required` из `Field` и
|
|
20
|
+
* нативная проверка формы работают, а телефонная клавиатура не открывается.
|
|
21
|
+
*/
|
|
22
|
+
export declare const BirthdayPicker: ({ value, onChange, minYear, maxYear, minAge, disabled, name, placeholder, error, className }: BirthdayPickerProps) => import("react").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=birthday-picker.d.cts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface BirthdayPickerProps {
|
|
2
|
+
value?: Date | null;
|
|
3
|
+
onChange?: (date: Date) => void;
|
|
4
|
+
/** Самый ранний год в списке. */
|
|
5
|
+
minYear?: number;
|
|
6
|
+
/** Самый поздний год в списке. По умолчанию — текущий. */
|
|
7
|
+
maxYear?: number;
|
|
8
|
+
/** Минимальный возраст: позже этой даты выбрать нельзя. */
|
|
9
|
+
minAge?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
/** Имя для FormData: уходит дата в формате `YYYY-MM-DD`. */
|
|
12
|
+
name?: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
error?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Дата рождения: поле открывает нижний лист с выбором дня, месяца и года.
|
|
19
|
+
* Поле только для чтения с клавиатуры, поэтому `required` из `Field` и
|
|
20
|
+
* нативная проверка формы работают, а телефонная клавиатура не открывается.
|
|
21
|
+
*/
|
|
22
|
+
export declare const BirthdayPicker: ({ value, onChange, minYear, maxYear, minAge, disabled, name, placeholder, error, className }: BirthdayPickerProps) => import("react").JSX.Element;
|
|
23
|
+
//# 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 "../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 '../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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"}
|