@de-kit/st-ui 0.1.14 → 0.1.16
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 +1 -1
- package/dist/components/birthday-picker/birthday-picker.cjs.map +1 -1
- package/dist/components/birthday-picker/birthday-picker.d.cts +0 -1
- package/dist/components/birthday-picker/birthday-picker.d.ts +0 -1
- package/dist/components/birthday-picker/birthday-picker.js +1 -1
- package/dist/components/birthday-picker/birthday-picker.js.map +1 -1
- package/dist/components/birthday-picker/birthday-picker.scss +2 -2
- package/dist/components/button/button.scss +1 -1
- package/dist/components/hr/hr.cjs +12 -0
- package/dist/components/hr/hr.cjs.map +1 -0
- package/dist/components/hr/hr.d.cts +7 -0
- package/dist/components/hr/hr.d.ts +7 -0
- package/dist/components/hr/hr.js +12 -0
- package/dist/components/hr/hr.js.map +1 -0
- package/dist/components/hr/hr.scss +8 -0
- package/dist/components/hr/index.d.cts +2 -0
- package/dist/components/hr/index.d.ts +2 -0
- package/dist/components/phone-input/phone-input.cjs +1 -1
- package/dist/components/phone-input/phone-input.cjs.map +1 -1
- package/dist/components/phone-input/phone-input.d.cts +0 -1
- package/dist/components/phone-input/phone-input.d.ts +0 -1
- package/dist/components/phone-input/phone-input.js +1 -1
- package/dist/components/phone-input/phone-input.js.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/styles/global.scss +2 -2
- package/dist/styles/vars.scss +11 -0
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ const require_wheel_column = require("./wheel-column.cjs");
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
let react_dom = require("react-dom");
|
|
10
|
-
require("
|
|
10
|
+
require("../input/input.scss");
|
|
11
11
|
require("./birthday-picker.scss");
|
|
12
12
|
// src/components/birthday-picker/birthday-picker.tsx
|
|
13
13
|
const MONTH_NAMES = [
|
|
@@ -1 +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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"}
|
|
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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"}
|
|
@@ -7,7 +7,7 @@ import { WheelColumn } from "./wheel-column.js";
|
|
|
7
7
|
import { useEffect, useId, useRef, useState } from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { createPortal } from "react-dom";
|
|
10
|
-
import "
|
|
10
|
+
import "../input/input.scss";
|
|
11
11
|
import "./birthday-picker.scss";
|
|
12
12
|
// src/components/birthday-picker/birthday-picker.tsx
|
|
13
13
|
const MONTH_NAMES = [
|
|
@@ -1 +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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"}
|
|
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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"}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
.birthday-picker__trigger {
|
|
6
6
|
cursor: pointer;
|
|
7
|
-
padding-right:
|
|
7
|
+
padding-right: 45px;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
.birthday-picker__icon {
|
|
12
12
|
position: absolute;
|
|
13
13
|
top: 50%;
|
|
14
|
-
right:
|
|
14
|
+
right: var(--space-3);
|
|
15
15
|
transform: translateY(-50%);
|
|
16
16
|
pointer-events: none;
|
|
17
17
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const require_cn = require("../../lib/cn.cjs");
|
|
2
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
3
|
+
require("./hr.scss");
|
|
4
|
+
// src/components/hr/hr.tsx
|
|
5
|
+
/** Горизонтальный разделитель. Отступы задаёт сам компонент, переопределяются через `className`. */
|
|
6
|
+
const HR = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", {
|
|
7
|
+
...props,
|
|
8
|
+
className: require_cn.cn("hr", className)
|
|
9
|
+
});
|
|
10
|
+
exports.HR = HR;
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=hr.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hr.cjs","names":["cn"],"sources":["../../../src/components/hr/hr.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react';\n\nimport { cn } from '@/lib/cn';\nimport './hr.scss';\n\nexport interface HRProps extends Omit<\n HTMLAttributes<HTMLHRElement>,\n 'children'\n> {\n className?: string;\n}\n\n/** Горизонтальный разделитель. Отступы задаёт сам компонент, переопределяются через `className`. */\nexport const HR = ({ className, ...props }: HRProps) => (\n <hr {...props} className={cn('hr', className)} />\n);\n"],"mappings":";;;;;AAaA,MAAa,MAAM,EAAE,WAAW,GAAG,YACjC,iBAAA,GAAA,kBAAA,IAAA,CAAC,MAAD;CAAI,GAAI;CAAO,WAAWA,WAAAA,GAAG,MAAM,SAAS;AAAI,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HTMLAttributes } from "react";
|
|
2
|
+
export interface HRProps extends Omit<HTMLAttributes<HTMLHRElement>, 'children'> {
|
|
3
|
+
className?: string;
|
|
4
|
+
}
|
|
5
|
+
/** Горизонтальный разделитель. Отступы задаёт сам компонент, переопределяются через `className`. */
|
|
6
|
+
export declare const HR: ({ className, ...props }: HRProps) => import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=hr.d.cts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HTMLAttributes } from "react";
|
|
2
|
+
export interface HRProps extends Omit<HTMLAttributes<HTMLHRElement>, 'children'> {
|
|
3
|
+
className?: string;
|
|
4
|
+
}
|
|
5
|
+
/** Горизонтальный разделитель. Отступы задаёт сам компонент, переопределяются через `className`. */
|
|
6
|
+
export declare const HR: ({ className, ...props }: HRProps) => import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=hr.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import "./hr.scss";
|
|
4
|
+
// src/components/hr/hr.tsx
|
|
5
|
+
/** Горизонтальный разделитель. Отступы задаёт сам компонент, переопределяются через `className`. */
|
|
6
|
+
const HR = ({ className, ...props }) => /* @__PURE__ */ jsx("hr", {
|
|
7
|
+
...props,
|
|
8
|
+
className: cn("hr", className)
|
|
9
|
+
});
|
|
10
|
+
export { HR };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=hr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hr.js","names":[],"sources":["../../../src/components/hr/hr.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react';\n\nimport { cn } from '@/lib/cn';\nimport './hr.scss';\n\nexport interface HRProps extends Omit<\n HTMLAttributes<HTMLHRElement>,\n 'children'\n> {\n className?: string;\n}\n\n/** Горизонтальный разделитель. Отступы задаёт сам компонент, переопределяются через `className`. */\nexport const HR = ({ className, ...props }: HRProps) => (\n <hr {...props} className={cn('hr', className)} />\n);\n"],"mappings":";;;;;AAaA,MAAa,MAAM,EAAE,WAAW,GAAG,YACjC,oBAAC,MAAD;CAAI,GAAI;CAAO,WAAW,GAAG,MAAM,SAAS;AAAI,CAAA"}
|
|
@@ -3,7 +3,7 @@ const require_field_context = require("../field/field-context.cjs");
|
|
|
3
3
|
const require_icon_cross = require("../../icons/icon-cross.cjs");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
-
require("
|
|
6
|
+
require("../input/input.scss");
|
|
7
7
|
let react_imask = require("react-imask");
|
|
8
8
|
// src/components/phone-input/phone-input.tsx
|
|
9
9
|
const PHONE_MASK = "+7 (000) 000-00-00";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phone-input.cjs","names":["forwardRef","useFieldControl","useRef","useState","useCallback","IMaskInput","cn","when","IconCross"],"sources":["../../../src/components/phone-input/phone-input.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type InputHTMLAttributes,\n type MouseEvent,\n type Ref,\n} from 'react';\nimport { IMaskInput } from 'react-imask';\n\nimport { useFieldControl } from '@/components/field/field-context';\nimport type { InputSize } from '@/components/input';\nimport { IconCross } from '@/icons/icon-cross';\nimport { cn, when } from '@/lib/cn';\nimport '
|
|
1
|
+
{"version":3,"file":"phone-input.cjs","names":["forwardRef","useFieldControl","useRef","useState","useCallback","IMaskInput","cn","when","IconCross"],"sources":["../../../src/components/phone-input/phone-input.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type InputHTMLAttributes,\n type MouseEvent,\n type Ref,\n} from 'react';\nimport { IMaskInput } from 'react-imask';\n\nimport { useFieldControl } from '@/components/field/field-context';\nimport type { InputSize } from '@/components/input';\nimport { IconCross } from '@/icons/icon-cross';\nimport { cn, when } from '@/lib/cn';\nimport '../input/input.scss';\n\nexport const PHONE_MASK = '+7 (000) 000-00-00';\n\n/**\n * Плейсхолдер в виде маски: цифры маски заменяются на «_», литералы остаются.\n * Это атрибут placeholder, а не значение, поэтому required и FormData не меняются.\n */\nexport const maskPlaceholder = (mask: string) => mask.replace(/[09]/g, '_');\n\nconst assignRef = (\n ref: Ref<HTMLInputElement> | undefined,\n node: HTMLInputElement | null,\n) => {\n if (typeof ref === 'function') {\n ref(node);\n return;\n }\n\n if (ref) ref.current = node;\n};\n\n/**\n * Прямая запись `input.value` обходит react-imask, поэтому значение пишем через\n * сеттер прототипа и шлём `input`: маска сама обновится и вызовет onAccept.\n */\nconst emitInputValue = (input: HTMLInputElement, next: string) => {\n const setter = Object.getOwnPropertyDescriptor(\n HTMLInputElement.prototype,\n 'value',\n )?.set;\n\n setter?.call(input, next);\n input.dispatchEvent(new Event('input', { bubbles: true }));\n};\n\nexport interface PhoneInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'\n> {\n /** Маска из react-imask. По умолчанию — российский номер. */\n mask?: string;\n /** По умолчанию показывает маску вроде «+7 (___) ___-__-__». */\n placeholder?: string;\n /** Маскированное значение, как в `input`. Пустая строка очищает поле. */\n value?: string;\n defaultValue?: string;\n size?: InputSize;\n error?: boolean;\n /** Крестик справа. Появляется, только когда в поле есть значение. */\n clearable?: boolean;\n clearLabel?: string;\n /** Вызывается после очистки крестиком; `onAccept` получит пустые значения. */\n onClear?: () => void;\n /** Вызывается на каждый ввод: `value` — с маской, `unmaskedValue` — только цифры. */\n onAccept?: (value: string, unmaskedValue: string) => void;\n}\n\n/**\n * Телефон на react-imask. Внутри `Field` берёт id, required и ошибку у поля,\n * а в `reportValue` отдаёт цифры, чтобы пустое значение считалось пустым.\n */\nexport const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(\n function PhoneInput(\n {\n mask = PHONE_MASK,\n placeholder = maskPlaceholder(mask),\n value,\n defaultValue,\n size = 'md',\n error,\n clearable = false,\n clearLabel = 'Очистить',\n onClear,\n className,\n id,\n disabled,\n readOnly,\n onAccept,\n 'aria-describedby': ariaDescribedBy,\n ...props\n },\n ref,\n ) {\n const field = useFieldControl();\n const reportValue = field?.reportValue;\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [uncontrolledFilled, setUncontrolledFilled] = useState(\n () => defaultValue !== undefined && defaultValue !== '',\n );\n const isInvalid = Boolean(error) || Boolean(field?.invalid);\n const hasValue = value !== undefined ? value !== '' : uncontrolledFilled;\n const canClear = clearable && !disabled && !readOnly && hasValue;\n const describedBy = [field?.describedBy, ariaDescribedBy]\n .filter(part => Boolean(part))\n .join(' ');\n\n const setRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n assignRef(ref, node);\n },\n [ref],\n );\n\n // Сброс снаружи (value='') не проходит через onAccept, поэтому сообщаем здесь.\n useEffect(() => {\n if (value === '') reportValue?.('');\n }, [value, reportValue]);\n\n const handleAccept = (\n nextValue: string,\n maskRef: { unmaskedValue: string },\n ) => {\n setUncontrolledFilled(maskRef.unmaskedValue !== '');\n reportValue?.(maskRef.unmaskedValue);\n onAccept?.(nextValue, maskRef.unmaskedValue);\n };\n\n const handleClearMouseDown = (event: MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n };\n\n const handleClear = () => {\n const input = inputRef.current;\n if (!input) return;\n\n emitInputValue(input, '');\n onClear?.();\n input.focus();\n };\n\n return (\n <div className=\"input__wrap\">\n <IMaskInput\n {...props}\n type=\"tel\"\n placeholder={placeholder}\n inputMode=\"tel\"\n autoComplete=\"tel\"\n mask={mask}\n inputRef={setRef}\n value={value}\n defaultValue={defaultValue}\n onAccept={handleAccept}\n disabled={disabled}\n readOnly={readOnly}\n {...(field ? { id: field.id } : { id })}\n {...(field?.required\n ? { 'required': true, 'aria-required': true }\n : {})}\n {...(isInvalid ? { 'aria-invalid': true } : {})}\n {...(describedBy ? { 'aria-describedby': describedBy } : {})}\n {...(field?.errorId ? { 'aria-errormessage': field.errorId } : {})}\n className={cn(\n 'input',\n className,\n when(size !== 'md', `input--${size}`),\n when(isInvalid, 'input--error'),\n when(clearable && !disabled && !readOnly, 'input--clearable'),\n )}\n />\n {canClear ? (\n <button\n type=\"button\"\n className=\"input__clear\"\n aria-label={clearLabel}\n onMouseDown={handleClearMouseDown}\n onClick={handleClear}\n >\n <IconCross width={16} height={16} />\n </button>\n ) : null}\n </div>\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { InputSize } from "../input/input.cjs";
|
|
2
2
|
import "../input/index.cjs";
|
|
3
3
|
import { InputHTMLAttributes } from "react";
|
|
4
|
-
import "@/components/input/input.scss";
|
|
5
4
|
export declare const PHONE_MASK = "+7 (000) 000-00-00";
|
|
6
5
|
export interface PhoneInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'> {
|
|
7
6
|
/** Маска из react-imask. По умолчанию — российский номер. */
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { InputSize } from "../input/input.js";
|
|
2
2
|
import "../input/index.js";
|
|
3
3
|
import { InputHTMLAttributes } from "react";
|
|
4
|
-
import "@/components/input/input.scss";
|
|
5
4
|
export declare const PHONE_MASK = "+7 (000) 000-00-00";
|
|
6
5
|
export interface PhoneInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'> {
|
|
7
6
|
/** Маска из react-imask. По умолчанию — российский номер. */
|
|
@@ -3,7 +3,7 @@ import { useFieldControl } from "../field/field-context.js";
|
|
|
3
3
|
import { IconCross } from "../../icons/icon-cross.js";
|
|
4
4
|
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import "
|
|
6
|
+
import "../input/input.scss";
|
|
7
7
|
import { IMaskInput } from "react-imask";
|
|
8
8
|
// src/components/phone-input/phone-input.tsx
|
|
9
9
|
const PHONE_MASK = "+7 (000) 000-00-00";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phone-input.js","names":[],"sources":["../../../src/components/phone-input/phone-input.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type InputHTMLAttributes,\n type MouseEvent,\n type Ref,\n} from 'react';\nimport { IMaskInput } from 'react-imask';\n\nimport { useFieldControl } from '@/components/field/field-context';\nimport type { InputSize } from '@/components/input';\nimport { IconCross } from '@/icons/icon-cross';\nimport { cn, when } from '@/lib/cn';\nimport '
|
|
1
|
+
{"version":3,"file":"phone-input.js","names":[],"sources":["../../../src/components/phone-input/phone-input.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type InputHTMLAttributes,\n type MouseEvent,\n type Ref,\n} from 'react';\nimport { IMaskInput } from 'react-imask';\n\nimport { useFieldControl } from '@/components/field/field-context';\nimport type { InputSize } from '@/components/input';\nimport { IconCross } from '@/icons/icon-cross';\nimport { cn, when } from '@/lib/cn';\nimport '../input/input.scss';\n\nexport const PHONE_MASK = '+7 (000) 000-00-00';\n\n/**\n * Плейсхолдер в виде маски: цифры маски заменяются на «_», литералы остаются.\n * Это атрибут placeholder, а не значение, поэтому required и FormData не меняются.\n */\nexport const maskPlaceholder = (mask: string) => mask.replace(/[09]/g, '_');\n\nconst assignRef = (\n ref: Ref<HTMLInputElement> | undefined,\n node: HTMLInputElement | null,\n) => {\n if (typeof ref === 'function') {\n ref(node);\n return;\n }\n\n if (ref) ref.current = node;\n};\n\n/**\n * Прямая запись `input.value` обходит react-imask, поэтому значение пишем через\n * сеттер прототипа и шлём `input`: маска сама обновится и вызовет onAccept.\n */\nconst emitInputValue = (input: HTMLInputElement, next: string) => {\n const setter = Object.getOwnPropertyDescriptor(\n HTMLInputElement.prototype,\n 'value',\n )?.set;\n\n setter?.call(input, next);\n input.dispatchEvent(new Event('input', { bubbles: true }));\n};\n\nexport interface PhoneInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'\n> {\n /** Маска из react-imask. По умолчанию — российский номер. */\n mask?: string;\n /** По умолчанию показывает маску вроде «+7 (___) ___-__-__». */\n placeholder?: string;\n /** Маскированное значение, как в `input`. Пустая строка очищает поле. */\n value?: string;\n defaultValue?: string;\n size?: InputSize;\n error?: boolean;\n /** Крестик справа. Появляется, только когда в поле есть значение. */\n clearable?: boolean;\n clearLabel?: string;\n /** Вызывается после очистки крестиком; `onAccept` получит пустые значения. */\n onClear?: () => void;\n /** Вызывается на каждый ввод: `value` — с маской, `unmaskedValue` — только цифры. */\n onAccept?: (value: string, unmaskedValue: string) => void;\n}\n\n/**\n * Телефон на react-imask. Внутри `Field` берёт id, required и ошибку у поля,\n * а в `reportValue` отдаёт цифры, чтобы пустое значение считалось пустым.\n */\nexport const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(\n function PhoneInput(\n {\n mask = PHONE_MASK,\n placeholder = maskPlaceholder(mask),\n value,\n defaultValue,\n size = 'md',\n error,\n clearable = false,\n clearLabel = 'Очистить',\n onClear,\n className,\n id,\n disabled,\n readOnly,\n onAccept,\n 'aria-describedby': ariaDescribedBy,\n ...props\n },\n ref,\n ) {\n const field = useFieldControl();\n const reportValue = field?.reportValue;\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [uncontrolledFilled, setUncontrolledFilled] = useState(\n () => defaultValue !== undefined && defaultValue !== '',\n );\n const isInvalid = Boolean(error) || Boolean(field?.invalid);\n const hasValue = value !== undefined ? value !== '' : uncontrolledFilled;\n const canClear = clearable && !disabled && !readOnly && hasValue;\n const describedBy = [field?.describedBy, ariaDescribedBy]\n .filter(part => Boolean(part))\n .join(' ');\n\n const setRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n assignRef(ref, node);\n },\n [ref],\n );\n\n // Сброс снаружи (value='') не проходит через onAccept, поэтому сообщаем здесь.\n useEffect(() => {\n if (value === '') reportValue?.('');\n }, [value, reportValue]);\n\n const handleAccept = (\n nextValue: string,\n maskRef: { unmaskedValue: string },\n ) => {\n setUncontrolledFilled(maskRef.unmaskedValue !== '');\n reportValue?.(maskRef.unmaskedValue);\n onAccept?.(nextValue, maskRef.unmaskedValue);\n };\n\n const handleClearMouseDown = (event: MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n };\n\n const handleClear = () => {\n const input = inputRef.current;\n if (!input) return;\n\n emitInputValue(input, '');\n onClear?.();\n input.focus();\n };\n\n return (\n <div className=\"input__wrap\">\n <IMaskInput\n {...props}\n type=\"tel\"\n placeholder={placeholder}\n inputMode=\"tel\"\n autoComplete=\"tel\"\n mask={mask}\n inputRef={setRef}\n value={value}\n defaultValue={defaultValue}\n onAccept={handleAccept}\n disabled={disabled}\n readOnly={readOnly}\n {...(field ? { id: field.id } : { id })}\n {...(field?.required\n ? { 'required': true, 'aria-required': true }\n : {})}\n {...(isInvalid ? { 'aria-invalid': true } : {})}\n {...(describedBy ? { 'aria-describedby': describedBy } : {})}\n {...(field?.errorId ? { 'aria-errormessage': field.errorId } : {})}\n className={cn(\n 'input',\n className,\n when(size !== 'md', `input--${size}`),\n when(isInvalid, 'input--error'),\n when(clearable && !disabled && !readOnly, 'input--clearable'),\n )}\n />\n {canClear ? (\n <button\n type=\"button\"\n className=\"input__clear\"\n aria-label={clearLabel}\n onMouseDown={handleClearMouseDown}\n onClick={handleClear}\n >\n <IconCross width={16} height={16} />\n </button>\n ) : null}\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;AAkBA,MAAa,aAAa;;;;;AAM1B,MAAa,mBAAmB,SAAiB,KAAK,QAAQ,SAAS,GAAG;AAE1E,MAAM,aACJ,KACA,SACG;CACH,IAAI,OAAO,QAAQ,YAAY;EAC7B,IAAI,IAAI;EACR;CACF;CAEA,IAAI,KAAK,IAAI,UAAU;AACzB;;;;;AAMA,MAAM,kBAAkB,OAAyB,SAAiB;CAMhE,CALe,OAAO,yBACpB,iBAAiB,WACjB,OACF,CAAC,EAAE,IAAA,EAEK,KAAK,OAAO,IAAI;CACxB,MAAM,cAAc,IAAI,MAAM,SAAS,EAAE,SAAS,KAAK,CAAC,CAAC;AAC3D;;;;;AA4BA,MAAa,aAAa,WACxB,SAAS,WACP,EACE,OAAO,YACP,cAAc,gBAAgB,IAAI,GAClC,OACA,cACA,OAAO,MACP,OACA,YAAY,OACZ,aAAa,YACb,SACA,WACA,IACA,UACA,UACA,UACA,oBAAoB,iBACpB,GAAG,SAEL,KACA;CACA,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,cAAc,OAAO;CAC3B,MAAM,WAAW,OAAgC,IAAI;CACrD,MAAM,CAAC,oBAAoB,yBAAyB,eAC5C,iBAAiB,KAAA,KAAa,iBAAiB,EACvD;CACA,MAAM,YAAY,QAAQ,KAAK,KAAK,QAAQ,OAAO,OAAO;CAE1D,MAAM,WAAW,aAAa,CAAC,YAAY,CAAC,aAD3B,UAAU,KAAA,IAAY,UAAU,KAAK;CAEtD,MAAM,cAAc,CAAC,OAAO,aAAa,eAAe,CAAC,CACtD,QAAO,SAAQ,QAAQ,IAAI,CAAC,CAAC,CAC7B,KAAK,GAAG;CAEX,MAAM,SAAS,aACZ,SAAkC;EACjC,SAAS,UAAU;EACnB,UAAU,KAAK,IAAI;CACrB,GACA,CAAC,GAAG,CACN;CAGA,gBAAgB;EACd,IAAI,UAAU,IAAI,cAAc,EAAE;CACpC,GAAG,CAAC,OAAO,WAAW,CAAC;CAEvB,MAAM,gBACJ,WACA,YACG;EACH,sBAAsB,QAAQ,kBAAkB,EAAE;EAClD,cAAc,QAAQ,aAAa;EACnC,WAAW,WAAW,QAAQ,aAAa;CAC7C;CAEA,MAAM,wBAAwB,UAAyC;EACrE,MAAM,eAAe;CACvB;CAEA,MAAM,oBAAoB;EACxB,MAAM,QAAQ,SAAS;EACvB,IAAI,CAAC,OAAO;EAEZ,eAAe,OAAO,EAAE;EACxB,UAAU;EACV,MAAM,MAAM;CACd;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,YAAD;GACE,GAAI;GACJ,MAAK;GACQ;GACb,WAAU;GACV,cAAa;GACP;GACN,UAAU;GACH;GACO;GACd,UAAU;GACA;GACA;GACV,GAAK,QAAQ,EAAE,IAAI,MAAM,GAAG,IAAI,EAAE,GAAG;GACrC,GAAK,OAAO,WACR;IAAE,YAAY;IAAM,iBAAiB;GAAK,IAC1C,CAAC;GACL,GAAK,YAAY,EAAE,gBAAgB,KAAK,IAAI,CAAC;GAC7C,GAAK,cAAc,EAAE,oBAAoB,YAAY,IAAI,CAAC;GAC1D,GAAK,OAAO,UAAU,EAAE,qBAAqB,MAAM,QAAQ,IAAI,CAAC;GAChE,WAAW,GACT,SACA,WACA,KAAK,SAAS,MAAM,UAAU,MAAM,GACpC,KAAK,WAAW,cAAc,GAC9B,KAAK,aAAa,CAAC,YAAY,CAAC,UAAU,kBAAkB,CAC9D;EACD,CAAA,GACA,WACC,oBAAC,UAAD;GACE,MAAK;GACL,WAAU;GACV,cAAY;GACZ,aAAa;GACb,SAAS;GAET,UAAA,oBAAC,WAAD;IAAW,OAAO;IAAI,QAAQ;GAAK,CAAA;EAC7B,CAAA,IACN,IACD;;AAET,CACF"}
|
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ const require_field = require("./components/field/field.cjs");
|
|
|
5
5
|
const require_form = require("./components/form/form.cjs");
|
|
6
6
|
const require_input = require("./components/input/input.cjs");
|
|
7
7
|
const require_birthday_picker = require("./components/birthday-picker/birthday-picker.cjs");
|
|
8
|
+
const require_hr = require("./components/hr/hr.cjs");
|
|
8
9
|
const require_radio = require("./components/radio/radio.cjs");
|
|
9
10
|
const require_phone_input = require("./components/phone-input/phone-input.cjs");
|
|
10
11
|
const require_spinner = require("./components/spinner/spinner.cjs");
|
|
@@ -18,6 +19,7 @@ exports.Form = require_form.Form;
|
|
|
18
19
|
exports.FormActions = require_form.FormActions;
|
|
19
20
|
exports.FormConsent = require_form.FormConsent;
|
|
20
21
|
exports.FormSubmit = require_form.FormSubmit;
|
|
22
|
+
exports.HR = require_hr.HR;
|
|
21
23
|
exports.Input = require_input.Input;
|
|
22
24
|
exports.PHONE_MASK = require_phone_input.PHONE_MASK;
|
|
23
25
|
exports.PhoneInput = require_phone_input.PhoneInput;
|
package/dist/index.d.cts
CHANGED
|
@@ -8,6 +8,8 @@ import { Form, FormActions, FormActionsProps, FormConsent, FormConsentProps, For
|
|
|
8
8
|
import "./components/form/index.cjs";
|
|
9
9
|
import { BirthdayPicker, BirthdayPickerProps } from "./components/birthday-picker/birthday-picker.cjs";
|
|
10
10
|
import "./components/birthday-picker/index.cjs";
|
|
11
|
+
import { HR, HRProps } from "./components/hr/hr.cjs";
|
|
12
|
+
import "./components/hr/index.cjs";
|
|
11
13
|
import { Input, InputProps, InputSize } from "./components/input/input.cjs";
|
|
12
14
|
import "./components/input/index.cjs";
|
|
13
15
|
import { RadioGroup, RadioGroupProps, RadioInput, RadioInputProps } from "./components/radio/radio.cjs";
|
|
@@ -18,4 +20,4 @@ import { Spinner, SpinnerPosition, SpinnerProps } from "./components/spinner/spi
|
|
|
18
20
|
import "./components/spinner/index.cjs";
|
|
19
21
|
import { TextArea, TextAreaProps, TextAreaSize } from "./components/textarea/textarea.cjs";
|
|
20
22
|
import "./components/textarea/index.cjs";
|
|
21
|
-
export { BirthdayPicker, type BirthdayPickerProps, Button, type ButtonAsChildProps, type ButtonComponentProps, type ButtonProps, type ButtonSize, type ButtonVariant, CheckboxInput, type CheckboxInputProps, Field, type FieldHintPlacement, type FieldProps, Form, FormActions, type FormActionsProps, FormConsent, type FormConsentProps, type FormProps, FormSubmit, Input, type InputProps, type InputSize, PHONE_MASK, PhoneInput, type PhoneInputProps, RadioGroup, type RadioGroupProps, RadioInput, type RadioInputProps, Spinner, type SpinnerPosition, type SpinnerProps, TextArea, type TextAreaProps, type TextAreaSize };
|
|
23
|
+
export { BirthdayPicker, type BirthdayPickerProps, Button, type ButtonAsChildProps, type ButtonComponentProps, type ButtonProps, type ButtonSize, type ButtonVariant, CheckboxInput, type CheckboxInputProps, Field, type FieldHintPlacement, type FieldProps, Form, FormActions, type FormActionsProps, FormConsent, type FormConsentProps, type FormProps, FormSubmit, HR, type HRProps, Input, type InputProps, type InputSize, PHONE_MASK, PhoneInput, type PhoneInputProps, RadioGroup, type RadioGroupProps, RadioInput, type RadioInputProps, Spinner, type SpinnerPosition, type SpinnerProps, TextArea, type TextAreaProps, type TextAreaSize };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { Form, FormActions, FormActionsProps, FormConsent, FormConsentProps, For
|
|
|
8
8
|
import "./components/form/index.js";
|
|
9
9
|
import { BirthdayPicker, BirthdayPickerProps } from "./components/birthday-picker/birthday-picker.js";
|
|
10
10
|
import "./components/birthday-picker/index.js";
|
|
11
|
+
import { HR, HRProps } from "./components/hr/hr.js";
|
|
12
|
+
import "./components/hr/index.js";
|
|
11
13
|
import { Input, InputProps, InputSize } from "./components/input/input.js";
|
|
12
14
|
import "./components/input/index.js";
|
|
13
15
|
import { RadioGroup, RadioGroupProps, RadioInput, RadioInputProps } from "./components/radio/radio.js";
|
|
@@ -18,4 +20,4 @@ import { Spinner, SpinnerPosition, SpinnerProps } from "./components/spinner/spi
|
|
|
18
20
|
import "./components/spinner/index.js";
|
|
19
21
|
import { TextArea, TextAreaProps, TextAreaSize } from "./components/textarea/textarea.js";
|
|
20
22
|
import "./components/textarea/index.js";
|
|
21
|
-
export { BirthdayPicker, type BirthdayPickerProps, Button, type ButtonAsChildProps, type ButtonComponentProps, type ButtonProps, type ButtonSize, type ButtonVariant, CheckboxInput, type CheckboxInputProps, Field, type FieldHintPlacement, type FieldProps, Form, FormActions, type FormActionsProps, FormConsent, type FormConsentProps, type FormProps, FormSubmit, Input, type InputProps, type InputSize, PHONE_MASK, PhoneInput, type PhoneInputProps, RadioGroup, type RadioGroupProps, RadioInput, type RadioInputProps, Spinner, type SpinnerPosition, type SpinnerProps, TextArea, type TextAreaProps, type TextAreaSize };
|
|
23
|
+
export { BirthdayPicker, type BirthdayPickerProps, Button, type ButtonAsChildProps, type ButtonComponentProps, type ButtonProps, type ButtonSize, type ButtonVariant, CheckboxInput, type CheckboxInputProps, Field, type FieldHintPlacement, type FieldProps, Form, FormActions, type FormActionsProps, FormConsent, type FormConsentProps, type FormProps, FormSubmit, HR, type HRProps, Input, type InputProps, type InputSize, PHONE_MASK, PhoneInput, type PhoneInputProps, RadioGroup, type RadioGroupProps, RadioInput, type RadioInputProps, Spinner, type SpinnerPosition, type SpinnerProps, TextArea, type TextAreaProps, type TextAreaSize };
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,10 @@ import { Field } from "./components/field/field.js";
|
|
|
4
4
|
import { Form, FormActions, FormConsent, FormSubmit } from "./components/form/form.js";
|
|
5
5
|
import { Input } from "./components/input/input.js";
|
|
6
6
|
import { BirthdayPicker } from "./components/birthday-picker/birthday-picker.js";
|
|
7
|
+
import { HR } from "./components/hr/hr.js";
|
|
7
8
|
import { RadioGroup, RadioInput } from "./components/radio/radio.js";
|
|
8
9
|
import { PHONE_MASK, PhoneInput } from "./components/phone-input/phone-input.js";
|
|
9
10
|
import { Spinner } from "./components/spinner/spinner.js";
|
|
10
11
|
import { TextArea } from "./components/textarea/textarea.js";
|
|
11
12
|
import "./styles/vars.scss";
|
|
12
|
-
export { BirthdayPicker, Button, CheckboxInput, Field, Form, FormActions, FormConsent, FormSubmit, Input, PHONE_MASK, PhoneInput, RadioGroup, RadioInput, Spinner, TextArea };
|
|
13
|
+
export { BirthdayPicker, Button, CheckboxInput, Field, Form, FormActions, FormConsent, FormSubmit, HR, Input, PHONE_MASK, PhoneInput, RadioGroup, RadioInput, Spinner, TextArea };
|
package/dist/styles/global.scss
CHANGED
package/dist/styles/vars.scss
CHANGED
|
@@ -69,7 +69,18 @@
|
|
|
69
69
|
--space-2: 10px;
|
|
70
70
|
--space-3: 15px;
|
|
71
71
|
--space-4: 20px;
|
|
72
|
+
--space-5: 25px;
|
|
72
73
|
--space-6: 30px;
|
|
74
|
+
--space-7: 35px;
|
|
75
|
+
--space-8: 40px;
|
|
76
|
+
--space-9: 45px;
|
|
77
|
+
--space-10: 50px;
|
|
78
|
+
--space-11: 55px;
|
|
79
|
+
--space-12: 60px;
|
|
80
|
+
--space-13: 65px;
|
|
81
|
+
--space-14: 70px;
|
|
82
|
+
--space-15: 75px;
|
|
83
|
+
--space-16: 80px;
|
|
73
84
|
|
|
74
85
|
// Фокус. Одна обводка на всю библиотеку: и box-shadow у инпута, и outline у кнопки.
|
|
75
86
|
--focus-ring-width: 1px;
|