@de-kit/st-ui 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -143
- package/dist/components/button/button.cjs +26 -13
- package/dist/components/button/button.cjs.map +1 -1
- package/dist/components/button/button.d.cts +9 -8
- package/dist/components/button/button.d.ts +9 -8
- package/dist/components/button/button.js +29 -16
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/button.scss +213 -147
- package/dist/components/button/index.d.cts +2 -2
- package/dist/components/button/index.d.ts +2 -2
- package/dist/components/checkbox/checkbox.cjs.map +1 -1
- package/dist/components/checkbox/checkbox.js.map +1 -1
- package/dist/components/checkbox/checkbox.scss +137 -137
- package/dist/components/field/field-context.cjs.map +1 -1
- package/dist/components/field/field-context.js.map +1 -1
- package/dist/components/field/field.cjs.map +1 -1
- package/dist/components/field/field.js.map +1 -1
- package/dist/components/field/field.scss +43 -43
- package/dist/components/input/input.cjs.map +1 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/input/input.scss +127 -127
- package/dist/components/radio/radio.cjs.map +1 -1
- package/dist/components/radio/radio.js.map +1 -1
- package/dist/components/radio/radio.scss +144 -144
- package/dist/components/spinner/spinner.cjs.map +1 -1
- package/dist/components/spinner/spinner.js.map +1 -1
- package/dist/components/spinner/spinner.scss +29 -29
- package/dist/components/textarea/textarea.cjs.map +1 -1
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/components/textarea/textarea.scss +85 -85
- package/dist/icons/svg.cjs.map +1 -1
- package/dist/icons/svg.js.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/lib/cn.cjs.map +1 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/slot.cjs +100 -0
- package/dist/lib/slot.cjs.map +1 -0
- package/dist/lib/slot.js +100 -0
- package/dist/lib/slot.js.map +1 -0
- package/dist/styles/global.scss +201 -201
- package/dist/styles/mixins.scss +51 -51
- package/dist/styles/vars.scss +87 -87
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spinner.js","names":[],"sources":["../../../src/components/spinner/spinner.tsx"],"sourcesContent":["import {\
|
|
1
|
+
{"version":3,"file":"spinner.js","names":[],"sources":["../../../src/components/spinner/spinner.tsx"],"sourcesContent":["import {\n useId,\n useSyncExternalStore,\n type FC,\n type HTMLAttributes,\n type SVGProps,\n} from 'react';\n\nimport { cn, when } from '@/lib/cn';\nimport './spinner.scss';\n\nconst spinnerPosition = {\n top: 'top',\n center: 'center',\n} as const;\n\nexport type SpinnerPosition =\n (typeof spinnerPosition)[keyof typeof spinnerPosition];\n\nexport interface SpinnerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'size' | 'position'\n> {\n size?: number;\n position?: SpinnerPosition;\n /** Текст для скринридера. Виден только ему. */\n label?: string;\n}\n\nconst reducedMotionQuery = '(prefers-reduced-motion: reduce)';\n\nconst subscribeReducedMotion = (onChange: () => void) => {\n if (typeof window.matchMedia !== 'function') return () => {};\n\n const media = window.matchMedia(reducedMotionQuery);\n media.addEventListener('change', onChange);\n return () => media.removeEventListener('change', onChange);\n};\n\nconst prefersReducedMotion = () =>\n typeof window.matchMedia === 'function' &&\n window.matchMedia(reducedMotionQuery).matches;\n\n// ponytail: сервер всегда false — гидрация не расходится, но первый кадр после неё может мигнуть SMIL, пока клиент не прочитает matchMedia. Апгрейд: Sec-CH-Prefers-Reduced-Motion.\nconst prefersReducedMotionServer = () => false;\n\nconst dotDelays = [\n '0s',\n '0.125s',\n '0.25s',\n '0.375s',\n '0.5s',\n '0.625s',\n '0.75s',\n '0.875s',\n] as const;\n\nconst Dot = ({\n begin,\n rotate,\n reduced,\n}: {\n begin: string;\n rotate: number;\n reduced: boolean;\n}) => (\n <circle\n transform={`rotate(${rotate} 16 16)`}\n cx=\"16\"\n cy=\"3\"\n r={reduced ? 3 : 0}\n >\n {reduced ? null : (\n <animate\n attributeName=\"r\"\n values=\"0;3;0;0\"\n dur=\"1s\"\n repeatCount=\"indefinite\"\n begin={begin}\n keySplines=\"0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8\"\n calcMode=\"spline\"\n />\n )}\n </circle>\n);\n\nconst svgProps: SVGProps<SVGSVGElement> = {\n 'xmlns': 'http://www.w3.org/2000/svg',\n 'viewBox': '0 0 32 32',\n 'fill': 'var(--color-muted)',\n 'aria-hidden': true,\n};\n\nexport const Spinner: FC<SpinnerProps> = ({\n className,\n position = spinnerPosition.center,\n size = 64,\n label = 'Загрузка',\n ...props\n}) => {\n const labelId = useId();\n const reduced = useSyncExternalStore(\n subscribeReducedMotion,\n prefersReducedMotion,\n prefersReducedMotionServer,\n );\n\n return (\n <div\n {...props}\n // eslint-disable-next-line jsx-a11y/prefer-tag-over-role -- live region, не <output>\n role=\"status\"\n aria-labelledby={labelId}\n className={cn(\n 'spinner',\n className,\n when(position === spinnerPosition.top, 'spinner--top'),\n )}\n >\n <span id={labelId} className=\"spinner__label\">\n {label}\n </span>\n <svg {...svgProps} width={size} height={size}>\n {dotDelays.map((begin, index) => (\n <Dot\n key={begin}\n begin={begin}\n rotate={index * 45}\n reduced={reduced}\n />\n ))}\n </svg>\n </div>\n );\n};\n"],"mappings":";;;;;AAWA,MAAM,kBAAkB;CACtB,KAAK;CACL,QAAQ;AACV;AAeA,MAAM,qBAAqB;AAE3B,MAAM,0BAA0B,aAAyB;CACvD,IAAI,OAAO,OAAO,eAAe,YAAY,aAAa,CAAC;CAE3D,MAAM,QAAQ,OAAO,WAAW,kBAAkB;CAClD,MAAM,iBAAiB,UAAU,QAAQ;CACzC,aAAa,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAEA,MAAM,6BACJ,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kBAAkB,CAAC,CAAC;AAGxC,MAAM,mCAAmC;AAEzC,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,OAAO,EACX,OACA,QACA,cAMA,oBAAC,UAAD;CACE,WAAW,UAAU,OAAO;CAC5B,IAAG;CACH,IAAG;CACH,GAAG,UAAU,IAAI;CAEhB,UAAA,UAAU,OACT,oBAAC,WAAD;EACE,eAAc;EACd,QAAO;EACP,KAAI;EACJ,aAAY;EACL;EACP,YAAW;EACX,UAAS;CACV,CAAA;AAEG,CAAA;AAGV,MAAM,WAAoC;CACxC,SAAS;CACT,WAAW;CACX,QAAQ;CACR,eAAe;AACjB;AAEA,MAAa,WAA6B,EACxC,WACA,WAAW,gBAAgB,QAC3B,OAAO,IACP,QAAQ,YACR,GAAG,YACC;CACJ,MAAM,UAAU,MAAM;CACtB,MAAM,UAAU,qBACd,wBACA,sBACA,0BACF;CAEA,OACE,qBAAC,OAAD;EACE,GAAI;EAEJ,MAAK;EACL,mBAAiB;EACjB,WAAW,GACT,WACA,WACA,KAAK,aAAa,gBAAgB,KAAK,cAAc,CACvD;EATF,UAAA,CAWE,oBAAC,QAAD;GAAM,IAAI;GAAS,WAAU;GAC1B,UAAA;EACG,CAAA,GACN,oBAAC,OAAD;GAAK,GAAI;GAAU,OAAO;GAAM,QAAQ;GACrC,UAAA,UAAU,KAAK,OAAO,UACrB,oBAAC,KAAD;IAES;IACP,QAAQ,QAAQ;IACP;GACV,GAJM,KAIN,CACF;EACE,CAAA,CACF;;AAET"}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
// Блок во всю ширину родителя: position раскладывает svg по высоте,
|
|
2
|
-
// поэтому min-height обязателен — без него flex-start и center совпадают.
|
|
3
|
-
// position: relative держит внутри скрытую подпись.
|
|
4
|
-
.spinner {
|
|
5
|
-
position: relative;
|
|
6
|
-
box-sizing: border-box;
|
|
7
|
-
display: flex;
|
|
8
|
-
width: 100%;
|
|
9
|
-
min-height: 100%;
|
|
10
|
-
padding: var(--space-6) 0;
|
|
11
|
-
align-items: center;
|
|
12
|
-
justify-content: center;
|
|
13
|
-
|
|
14
|
-
&--top {
|
|
15
|
-
align-items: flex-start;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.spinner__label {
|
|
20
|
-
position: absolute;
|
|
21
|
-
width: 1px;
|
|
22
|
-
height: 1px;
|
|
23
|
-
padding: 0;
|
|
24
|
-
margin: -1px;
|
|
25
|
-
overflow: hidden;
|
|
26
|
-
clip-path: inset(50%);
|
|
27
|
-
white-space: nowrap;
|
|
28
|
-
border: 0;
|
|
29
|
-
}
|
|
1
|
+
// Блок во всю ширину родителя: position раскладывает svg по высоте,
|
|
2
|
+
// поэтому min-height обязателен — без него flex-start и center совпадают.
|
|
3
|
+
// position: relative держит внутри скрытую подпись.
|
|
4
|
+
.spinner {
|
|
5
|
+
position: relative;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
display: flex;
|
|
8
|
+
width: 100%;
|
|
9
|
+
min-height: 100%;
|
|
10
|
+
padding: var(--space-6) 0;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
|
|
14
|
+
&--top {
|
|
15
|
+
align-items: flex-start;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.spinner__label {
|
|
20
|
+
position: absolute;
|
|
21
|
+
width: 1px;
|
|
22
|
+
height: 1px;
|
|
23
|
+
padding: 0;
|
|
24
|
+
margin: -1px;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
clip-path: inset(50%);
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
border: 0;
|
|
29
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textarea.cjs","names":["forwardRef","useId","useFieldControl","useRef","useState","useCallback","cn","when"],"sources":["../../../src/components/textarea/textarea.tsx"],"sourcesContent":["import {\
|
|
1
|
+
{"version":3,"file":"textarea.cjs","names":["forwardRef","useId","useFieldControl","useRef","useState","useCallback","cn","when"],"sources":["../../../src/components/textarea/textarea.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ChangeEvent,\n type Ref,\n type TextareaHTMLAttributes,\n} from 'react';\n\nimport { useFieldControl } from '@/components/field/field-context';\nimport { cn, when } from '@/lib/cn';\nimport './textarea.scss';\n\nexport type TextAreaSize = 'sm' | 'md' | 'lg';\n\nexport interface TextAreaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n size?: TextAreaSize;\n recommended?: boolean;\n error?: boolean;\n /** Счётчик остатка при `maxLength`. `false` — то, что в приложении было `noMaxMoty`. */\n counter?: boolean;\n}\n\nconst assignRef = (\n ref: Ref<HTMLTextAreaElement> | undefined,\n node: HTMLTextAreaElement | null,\n) => {\n if (typeof ref === 'function') {\n ref(node);\n return;\n }\n\n if (ref) ref.current = node;\n};\n\nexport const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n function TextArea(\n {\n maxLength,\n value,\n size = 'md',\n recommended,\n error,\n counter = true,\n className,\n id,\n onChange,\n 'aria-describedby': ariaDescribedBy,\n ...props\n },\n ref,\n ) {\n const autoId = useId();\n const field = useFieldControl();\n const reportValue = field?.reportValue;\n const textareaId = field?.id ?? id ?? autoId;\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const [length, setLength] = useState(0);\n const withCounter = maxLength !== undefined && counter;\n const isInvalid = Boolean(error) || Boolean(field?.invalid);\n\n const setRef = useCallback(\n (node: HTMLTextAreaElement | null) => {\n textareaRef.current = node;\n assignRef(ref, node);\n },\n [ref],\n );\n\n /**\n * ponytail: длину счётчик читает из DOM — одинаково для `value` и `defaultValue`,\n * включая автозаполнение. Потолок: reset формы в неконтролируемом режиме счётчик\n * не слышит и догоняет на следующем вводе; апгрейд — слушать `reset`, как `Input`.\n * Тот же проход отдаёт показанную строку в Field.\n */\n useEffect(() => {\n const node = textareaRef.current;\n if (!node) return;\n\n if (withCounter) setLength(node.value.length);\n reportValue?.(node.value);\n }, [reportValue, value, withCounter]);\n\n const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {\n if (withCounter) setLength(event.target.value.length);\n if (value === undefined) reportValue?.(event.target.value);\n onChange?.(event);\n };\n\n const counterId = withCounter ? `${textareaId}-counter` : undefined;\n const describedBy = [field?.describedBy, counterId, ariaDescribedBy]\n .filter(part => Boolean(part))\n .join(' ');\n\n return (\n <div className=\"textarea__wrap\">\n <textarea\n {...props}\n ref={setRef}\n id={textareaId}\n className={cn(\n 'textarea',\n className,\n when(size !== 'md', `textarea--${size}`),\n when(recommended, 'textarea--recommended'),\n when(isInvalid, 'textarea--error'),\n )}\n {...(value !== undefined ? { value } : {})}\n maxLength={maxLength}\n onChange={handleChange}\n {...(isInvalid ? { 'aria-invalid': true } : {})}\n {...(field?.required\n ? { 'required': true, 'aria-required': true }\n : {})}\n {...(describedBy ? { 'aria-describedby': describedBy } : {})}\n {...(field?.errorId ? { 'aria-errormessage': field.errorId } : {})}\n />\n {withCounter ? (\n <span id={counterId} className=\"textarea__counter\">\n Осталось {Math.max(0, (maxLength ?? 0) - length)} из {maxLength}{' '}\n символов\n </span>\n ) : null}\n </div>\n );\n },\n);\n"],"mappings":";;;;;;AA0BA,MAAM,aACJ,KACA,SACG;CACH,IAAI,OAAO,QAAQ,YAAY;EAC7B,IAAI,IAAI;EACR;CACF;CAEA,IAAI,KAAK,IAAI,UAAU;AACzB;AAEA,MAAa,YAAA,GAAWA,MAAAA,WAAAA,CACtB,SAAS,SACP,EACE,WACA,OACA,OAAO,MACP,aACA,OACA,UAAU,MACV,WACA,IACA,UACA,oBAAoB,iBACpB,GAAG,SAEL,KACA;CACA,MAAM,UAAA,GAASC,MAAAA,MAAAA,CAAM;CACrB,MAAM,QAAQC,sBAAAA,gBAAgB;CAC9B,MAAM,cAAc,OAAO;CAC3B,MAAM,aAAa,OAAO,MAAM,MAAM;CACtC,MAAM,eAAA,GAAcC,MAAAA,OAAAA,CAA4B,IAAI;CACpD,MAAM,CAAC,QAAQ,cAAA,GAAaC,MAAAA,SAAAA,CAAS,CAAC;CACtC,MAAM,cAAc,cAAc,KAAA,KAAa;CAC/C,MAAM,YAAY,QAAQ,KAAK,KAAK,QAAQ,OAAO,OAAO;CAE1D,MAAM,UAAA,GAASC,MAAAA,YAAAA,EACZ,SAAqC;EACpC,YAAY,UAAU;EACtB,UAAU,KAAK,IAAI;CACrB,GACA,CAAC,GAAG,CACN;;;;;;;CAQA,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,MAAM,OAAO,YAAY;EACzB,IAAI,CAAC,MAAM;EAEX,IAAI,aAAa,UAAU,KAAK,MAAM,MAAM;EAC5C,cAAc,KAAK,KAAK;CAC1B,GAAG;EAAC;EAAa;EAAO;CAAW,CAAC;CAEpC,MAAM,gBAAgB,UAA4C;EAChE,IAAI,aAAa,UAAU,MAAM,OAAO,MAAM,MAAM;EACpD,IAAI,UAAU,KAAA,GAAW,cAAc,MAAM,OAAO,KAAK;EACzD,WAAW,KAAK;CAClB;CAEA,MAAM,YAAY,cAAc,GAAG,WAAW,YAAY,KAAA;CAC1D,MAAM,cAAc;EAAC,OAAO;EAAa;EAAW;CAAe,CAAC,CACjE,QAAO,SAAQ,QAAQ,IAAI,CAAC,CAAC,CAC7B,KAAK,GAAG;CAEX,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,YAAD;GACE,GAAI;GACJ,KAAK;GACL,IAAI;GACJ,WAAWC,WAAAA,GACT,YACA,WACAC,WAAAA,KAAK,SAAS,MAAM,aAAa,MAAM,GACvCA,WAAAA,KAAK,aAAa,uBAAuB,GACzCA,WAAAA,KAAK,WAAW,iBAAiB,CACnC;GACA,GAAK,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;GAC7B;GACX,UAAU;GACV,GAAK,YAAY,EAAE,gBAAgB,KAAK,IAAI,CAAC;GAC7C,GAAK,OAAO,WACR;IAAE,YAAY;IAAM,iBAAiB;GAAK,IAC1C,CAAC;GACL,GAAK,cAAc,EAAE,oBAAoB,YAAY,IAAI,CAAC;GAC1D,GAAK,OAAO,UAAU,EAAE,qBAAqB,MAAM,QAAQ,IAAI,CAAC;EACjE,CAAA,GACA,cACC,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;GAAM,IAAI;GAAW,WAAU;GAA/B,UAAA;IAAmD;IACvC,KAAK,IAAI,IAAI,aAAa,KAAK,MAAM;IAAE;IAAK;IAAW;IAAI;GAEjE;EACJ,CAAA,IAAA,IACD;;AAET,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textarea.js","names":[],"sources":["../../../src/components/textarea/textarea.tsx"],"sourcesContent":["import {\
|
|
1
|
+
{"version":3,"file":"textarea.js","names":[],"sources":["../../../src/components/textarea/textarea.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ChangeEvent,\n type Ref,\n type TextareaHTMLAttributes,\n} from 'react';\n\nimport { useFieldControl } from '@/components/field/field-context';\nimport { cn, when } from '@/lib/cn';\nimport './textarea.scss';\n\nexport type TextAreaSize = 'sm' | 'md' | 'lg';\n\nexport interface TextAreaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n size?: TextAreaSize;\n recommended?: boolean;\n error?: boolean;\n /** Счётчик остатка при `maxLength`. `false` — то, что в приложении было `noMaxMoty`. */\n counter?: boolean;\n}\n\nconst assignRef = (\n ref: Ref<HTMLTextAreaElement> | undefined,\n node: HTMLTextAreaElement | null,\n) => {\n if (typeof ref === 'function') {\n ref(node);\n return;\n }\n\n if (ref) ref.current = node;\n};\n\nexport const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n function TextArea(\n {\n maxLength,\n value,\n size = 'md',\n recommended,\n error,\n counter = true,\n className,\n id,\n onChange,\n 'aria-describedby': ariaDescribedBy,\n ...props\n },\n ref,\n ) {\n const autoId = useId();\n const field = useFieldControl();\n const reportValue = field?.reportValue;\n const textareaId = field?.id ?? id ?? autoId;\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const [length, setLength] = useState(0);\n const withCounter = maxLength !== undefined && counter;\n const isInvalid = Boolean(error) || Boolean(field?.invalid);\n\n const setRef = useCallback(\n (node: HTMLTextAreaElement | null) => {\n textareaRef.current = node;\n assignRef(ref, node);\n },\n [ref],\n );\n\n /**\n * ponytail: длину счётчик читает из DOM — одинаково для `value` и `defaultValue`,\n * включая автозаполнение. Потолок: reset формы в неконтролируемом режиме счётчик\n * не слышит и догоняет на следующем вводе; апгрейд — слушать `reset`, как `Input`.\n * Тот же проход отдаёт показанную строку в Field.\n */\n useEffect(() => {\n const node = textareaRef.current;\n if (!node) return;\n\n if (withCounter) setLength(node.value.length);\n reportValue?.(node.value);\n }, [reportValue, value, withCounter]);\n\n const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {\n if (withCounter) setLength(event.target.value.length);\n if (value === undefined) reportValue?.(event.target.value);\n onChange?.(event);\n };\n\n const counterId = withCounter ? `${textareaId}-counter` : undefined;\n const describedBy = [field?.describedBy, counterId, ariaDescribedBy]\n .filter(part => Boolean(part))\n .join(' ');\n\n return (\n <div className=\"textarea__wrap\">\n <textarea\n {...props}\n ref={setRef}\n id={textareaId}\n className={cn(\n 'textarea',\n className,\n when(size !== 'md', `textarea--${size}`),\n when(recommended, 'textarea--recommended'),\n when(isInvalid, 'textarea--error'),\n )}\n {...(value !== undefined ? { value } : {})}\n maxLength={maxLength}\n onChange={handleChange}\n {...(isInvalid ? { 'aria-invalid': true } : {})}\n {...(field?.required\n ? { 'required': true, 'aria-required': true }\n : {})}\n {...(describedBy ? { 'aria-describedby': describedBy } : {})}\n {...(field?.errorId ? { 'aria-errormessage': field.errorId } : {})}\n />\n {withCounter ? (\n <span id={counterId} className=\"textarea__counter\">\n Осталось {Math.max(0, (maxLength ?? 0) - length)} из {maxLength}{' '}\n символов\n </span>\n ) : null}\n </div>\n );\n },\n);\n"],"mappings":";;;;;;AA0BA,MAAM,aACJ,KACA,SACG;CACH,IAAI,OAAO,QAAQ,YAAY;EAC7B,IAAI,IAAI;EACR;CACF;CAEA,IAAI,KAAK,IAAI,UAAU;AACzB;AAEA,MAAa,WAAW,WACtB,SAAS,SACP,EACE,WACA,OACA,OAAO,MACP,aACA,OACA,UAAU,MACV,WACA,IACA,UACA,oBAAoB,iBACpB,GAAG,SAEL,KACA;CACA,MAAM,SAAS,MAAM;CACrB,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,cAAc,OAAO;CAC3B,MAAM,aAAa,OAAO,MAAM,MAAM;CACtC,MAAM,cAAc,OAA4B,IAAI;CACpD,MAAM,CAAC,QAAQ,aAAa,SAAS,CAAC;CACtC,MAAM,cAAc,cAAc,KAAA,KAAa;CAC/C,MAAM,YAAY,QAAQ,KAAK,KAAK,QAAQ,OAAO,OAAO;CAE1D,MAAM,SAAS,aACZ,SAAqC;EACpC,YAAY,UAAU;EACtB,UAAU,KAAK,IAAI;CACrB,GACA,CAAC,GAAG,CACN;;;;;;;CAQA,gBAAgB;EACd,MAAM,OAAO,YAAY;EACzB,IAAI,CAAC,MAAM;EAEX,IAAI,aAAa,UAAU,KAAK,MAAM,MAAM;EAC5C,cAAc,KAAK,KAAK;CAC1B,GAAG;EAAC;EAAa;EAAO;CAAW,CAAC;CAEpC,MAAM,gBAAgB,UAA4C;EAChE,IAAI,aAAa,UAAU,MAAM,OAAO,MAAM,MAAM;EACpD,IAAI,UAAU,KAAA,GAAW,cAAc,MAAM,OAAO,KAAK;EACzD,WAAW,KAAK;CAClB;CAEA,MAAM,YAAY,cAAc,GAAG,WAAW,YAAY,KAAA;CAC1D,MAAM,cAAc;EAAC,OAAO;EAAa;EAAW;CAAe,CAAC,CACjE,QAAO,SAAQ,QAAQ,IAAI,CAAC,CAAC,CAC7B,KAAK,GAAG;CAEX,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,YAAD;GACE,GAAI;GACJ,KAAK;GACL,IAAI;GACJ,WAAW,GACT,YACA,WACA,KAAK,SAAS,MAAM,aAAa,MAAM,GACvC,KAAK,aAAa,uBAAuB,GACzC,KAAK,WAAW,iBAAiB,CACnC;GACA,GAAK,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;GAC7B;GACX,UAAU;GACV,GAAK,YAAY,EAAE,gBAAgB,KAAK,IAAI,CAAC;GAC7C,GAAK,OAAO,WACR;IAAE,YAAY;IAAM,iBAAiB;GAAK,IAC1C,CAAC;GACL,GAAK,cAAc,EAAE,oBAAoB,YAAY,IAAI,CAAC;GAC1D,GAAK,OAAO,UAAU,EAAE,qBAAqB,MAAM,QAAQ,IAAI,CAAC;EACjE,CAAA,GACA,cACC,qBAAC,QAAD;GAAM,IAAI;GAAW,WAAU;GAA/B,UAAA;IAAmD;IACvC,KAAK,IAAI,IAAI,aAAa,KAAK,MAAM;IAAE;IAAK;IAAW;IAAI;GAEjE;EACJ,CAAA,IAAA,IACD;;AAET,CACF"}
|
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
@use '../../styles/mixins.scss' as mixins;
|
|
2
|
-
|
|
3
|
-
.textarea__wrap {
|
|
4
|
-
position: relative;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.textarea {
|
|
8
|
-
// Паддинги как у Input соответствующего размера, высота — 0.75 / 1 / 1.25 от md.
|
|
9
|
-
--textarea-padding-y: var(--space-2);
|
|
10
|
-
--textarea-padding-x: var(--space-3);
|
|
11
|
-
--textarea-min-height: 160px;
|
|
12
|
-
box-sizing: border-box;
|
|
13
|
-
width: 100%;
|
|
14
|
-
min-height: var(--textarea-min-height);
|
|
15
|
-
padding: var(--textarea-padding-y) var(--textarea-padding-x);
|
|
16
|
-
font-size: var(--text-sm);
|
|
17
|
-
line-height: var(--text-sm--line-height);
|
|
18
|
-
background-color: transparent;
|
|
19
|
-
color: var(--color-foreground);
|
|
20
|
-
border-radius: var(--radius-sm);
|
|
21
|
-
resize: vertical;
|
|
22
|
-
font-family: var(--font-sans);
|
|
23
|
-
border: 1px solid var(--color-input);
|
|
24
|
-
outline: var(--focus-ring-width) solid transparent;
|
|
25
|
-
transition:
|
|
26
|
-
background 0.3s,
|
|
27
|
-
border-color 0.3s,
|
|
28
|
-
box-shadow 0.2s;
|
|
29
|
-
|
|
30
|
-
&:focus {
|
|
31
|
-
border-color: var(--color-ring);
|
|
32
|
-
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
&:disabled {
|
|
36
|
-
background-color: var(--color-muted);
|
|
37
|
-
border-color: var(--color-muted);
|
|
38
|
-
color: var(--color-muted-foreground);
|
|
39
|
-
fill: var(--color-muted-foreground);
|
|
40
|
-
resize: none;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@include mixins.min-width(md) {
|
|
44
|
-
--textarea-min-height: 80px;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Медиазапрос базы стоит выше и при равной специфичности проиграет, поэтому у
|
|
48
|
-
// каждого размера своя пара значений.
|
|
49
|
-
&--sm {
|
|
50
|
-
--textarea-padding-y: 4px;
|
|
51
|
-
--textarea-padding-x: 12px;
|
|
52
|
-
--textarea-min-height: 120px;
|
|
53
|
-
|
|
54
|
-
@include mixins.min-width(md) {
|
|
55
|
-
--textarea-min-height: 60px;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
&--lg {
|
|
60
|
-
--textarea-padding-y: var(--space-3);
|
|
61
|
-
--textarea-padding-x: 24px;
|
|
62
|
-
--textarea-min-height: 200px;
|
|
63
|
-
|
|
64
|
-
@include mixins.min-width(md) {
|
|
65
|
-
--textarea-min-height: 100px;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
&--recommended {
|
|
70
|
-
background-color: var(--color-recommended);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
&--error {
|
|
74
|
-
border-color: color-mix(in srgb, var(--color-destructive) 50%, transparent);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.textarea__counter {
|
|
79
|
-
display: block;
|
|
80
|
-
margin-top: var(--space-1);
|
|
81
|
-
font-size: var(--text-xs);
|
|
82
|
-
line-height: var(--text-sm--line-height);
|
|
83
|
-
color: var(--color-muted-foreground);
|
|
84
|
-
text-align: left;
|
|
85
|
-
}
|
|
1
|
+
@use '../../styles/mixins.scss' as mixins;
|
|
2
|
+
|
|
3
|
+
.textarea__wrap {
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.textarea {
|
|
8
|
+
// Паддинги как у Input соответствующего размера, высота — 0.75 / 1 / 1.25 от md.
|
|
9
|
+
--textarea-padding-y: var(--space-2);
|
|
10
|
+
--textarea-padding-x: var(--space-3);
|
|
11
|
+
--textarea-min-height: 160px;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
width: 100%;
|
|
14
|
+
min-height: var(--textarea-min-height);
|
|
15
|
+
padding: var(--textarea-padding-y) var(--textarea-padding-x);
|
|
16
|
+
font-size: var(--text-sm);
|
|
17
|
+
line-height: var(--text-sm--line-height);
|
|
18
|
+
background-color: transparent;
|
|
19
|
+
color: var(--color-foreground);
|
|
20
|
+
border-radius: var(--radius-sm);
|
|
21
|
+
resize: vertical;
|
|
22
|
+
font-family: var(--font-sans);
|
|
23
|
+
border: 1px solid var(--color-input);
|
|
24
|
+
outline: var(--focus-ring-width) solid transparent;
|
|
25
|
+
transition:
|
|
26
|
+
background 0.3s,
|
|
27
|
+
border-color 0.3s,
|
|
28
|
+
box-shadow 0.2s;
|
|
29
|
+
|
|
30
|
+
&:focus {
|
|
31
|
+
border-color: var(--color-ring);
|
|
32
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&:disabled {
|
|
36
|
+
background-color: var(--color-muted);
|
|
37
|
+
border-color: var(--color-muted);
|
|
38
|
+
color: var(--color-muted-foreground);
|
|
39
|
+
fill: var(--color-muted-foreground);
|
|
40
|
+
resize: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@include mixins.min-width(md) {
|
|
44
|
+
--textarea-min-height: 80px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Медиазапрос базы стоит выше и при равной специфичности проиграет, поэтому у
|
|
48
|
+
// каждого размера своя пара значений.
|
|
49
|
+
&--sm {
|
|
50
|
+
--textarea-padding-y: 4px;
|
|
51
|
+
--textarea-padding-x: 12px;
|
|
52
|
+
--textarea-min-height: 120px;
|
|
53
|
+
|
|
54
|
+
@include mixins.min-width(md) {
|
|
55
|
+
--textarea-min-height: 60px;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&--lg {
|
|
60
|
+
--textarea-padding-y: var(--space-3);
|
|
61
|
+
--textarea-padding-x: 24px;
|
|
62
|
+
--textarea-min-height: 200px;
|
|
63
|
+
|
|
64
|
+
@include mixins.min-width(md) {
|
|
65
|
+
--textarea-min-height: 100px;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&--recommended {
|
|
70
|
+
background-color: var(--color-recommended);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&--error {
|
|
74
|
+
border-color: color-mix(in srgb, var(--color-destructive) 50%, transparent);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.textarea__counter {
|
|
79
|
+
display: block;
|
|
80
|
+
margin-top: var(--space-1);
|
|
81
|
+
font-size: var(--text-xs);
|
|
82
|
+
line-height: var(--text-sm--line-height);
|
|
83
|
+
color: var(--color-muted-foreground);
|
|
84
|
+
text-align: left;
|
|
85
|
+
}
|
package/dist/icons/svg.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg.cjs","names":[],"sources":["../../src/icons/svg.tsx"],"sourcesContent":["import type { SVGProps } from 'react';\
|
|
1
|
+
{"version":3,"file":"svg.cjs","names":[],"sources":["../../src/icons/svg.tsx"],"sourcesContent":["import type { SVGProps } from 'react';\n\nexport interface IconProps extends SVGProps<SVGSVGElement> {\n title?: string;\n}\n\nexport const Svg = ({ title, children, ...props }: IconProps) => {\n const accessibleName = props['aria-label'] ?? title;\n const labelled = Boolean(accessibleName);\n\n return (\n <svg\n {...props}\n xmlns=\"http://www.w3.org/2000/svg\"\n role={props.role ?? (labelled ? 'img' : undefined)}\n aria-label={labelled ? accessibleName : undefined}\n aria-hidden={props['aria-hidden'] ?? (labelled ? undefined : true)}\n >\n {title ? <title>{title}</title> : null}\n {children}\n </svg>\n );\n};\n"],"mappings":";;AAMA,MAAa,OAAO,EAAE,OAAO,UAAU,GAAG,YAAuB;CAC/D,MAAM,iBAAiB,MAAM,iBAAiB;CAC9C,MAAM,WAAW,QAAQ,cAAc;CAEvC,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EACE,GAAI;EACJ,OAAM;EACN,MAAM,MAAM,SAAS,WAAW,QAAQ,KAAA;EACxC,cAAY,WAAW,iBAAiB,KAAA;EACxC,eAAa,MAAM,mBAAmB,WAAW,KAAA,IAAY;EAL/D,UAAA,CAOG,QAAQ,iBAAA,GAAA,kBAAA,IAAA,CAAC,SAAD,EAAA,UAAQ,MAAa,CAAA,IAAI,MACjC,QACE;;AAET"}
|
package/dist/icons/svg.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg.js","names":[],"sources":["../../src/icons/svg.tsx"],"sourcesContent":["import type { SVGProps } from 'react';\
|
|
1
|
+
{"version":3,"file":"svg.js","names":[],"sources":["../../src/icons/svg.tsx"],"sourcesContent":["import type { SVGProps } from 'react';\n\nexport interface IconProps extends SVGProps<SVGSVGElement> {\n title?: string;\n}\n\nexport const Svg = ({ title, children, ...props }: IconProps) => {\n const accessibleName = props['aria-label'] ?? title;\n const labelled = Boolean(accessibleName);\n\n return (\n <svg\n {...props}\n xmlns=\"http://www.w3.org/2000/svg\"\n role={props.role ?? (labelled ? 'img' : undefined)}\n aria-label={labelled ? accessibleName : undefined}\n aria-hidden={props['aria-hidden'] ?? (labelled ? undefined : true)}\n >\n {title ? <title>{title}</title> : null}\n {children}\n </svg>\n );\n};\n"],"mappings":";;AAMA,MAAa,OAAO,EAAE,OAAO,UAAU,GAAG,YAAuB;CAC/D,MAAM,iBAAiB,MAAM,iBAAiB;CAC9C,MAAM,WAAW,QAAQ,cAAc;CAEvC,OACE,qBAAC,OAAD;EACE,GAAI;EACJ,OAAM;EACN,MAAM,MAAM,SAAS,WAAW,QAAQ,KAAA;EACxC,cAAY,WAAW,iBAAiB,KAAA;EACxC,eAAa,MAAM,mBAAmB,WAAW,KAAA,IAAY;EAL/D,UAAA,CAOG,QAAQ,oBAAC,SAAD,EAAA,UAAQ,MAAa,CAAA,IAAI,MACjC,QACE;;AAET"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button,
|
|
1
|
+
import { Button, ButtonAsChildProps, ButtonComponentProps, ButtonProps, ButtonSize, ButtonVariant } from "./components/button/button.cjs";
|
|
2
2
|
import "./components/button/index.cjs";
|
|
3
3
|
import { CheckboxInput, CheckboxInputProps } from "./components/checkbox/checkbox.cjs";
|
|
4
4
|
import "./components/checkbox/index.cjs";
|
|
@@ -12,4 +12,4 @@ import { Spinner, SpinnerPosition, SpinnerProps } from "./components/spinner/spi
|
|
|
12
12
|
import "./components/spinner/index.cjs";
|
|
13
13
|
import { TextArea, TextAreaProps, TextAreaSize } from "./components/textarea/textarea.cjs";
|
|
14
14
|
import "./components/textarea/index.cjs";
|
|
15
|
-
export { Button, type
|
|
15
|
+
export { Button, type ButtonAsChildProps, type ButtonComponentProps, type ButtonProps, type ButtonSize, type ButtonVariant, CheckboxInput, type CheckboxInputProps, Field, type FieldHintPlacement, type FieldProps, Input, type InputProps, type InputSize, RadioGroup, type RadioGroupProps, RadioInput, type RadioInputProps, Spinner, type SpinnerPosition, type SpinnerProps, TextArea, type TextAreaProps, type TextAreaSize };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button,
|
|
1
|
+
import { Button, ButtonAsChildProps, ButtonComponentProps, ButtonProps, ButtonSize, ButtonVariant } from "./components/button/button.js";
|
|
2
2
|
import "./components/button/index.js";
|
|
3
3
|
import { CheckboxInput, CheckboxInputProps } from "./components/checkbox/checkbox.js";
|
|
4
4
|
import "./components/checkbox/index.js";
|
|
@@ -12,4 +12,4 @@ import { Spinner, SpinnerPosition, SpinnerProps } from "./components/spinner/spi
|
|
|
12
12
|
import "./components/spinner/index.js";
|
|
13
13
|
import { TextArea, TextAreaProps, TextAreaSize } from "./components/textarea/textarea.js";
|
|
14
14
|
import "./components/textarea/index.js";
|
|
15
|
-
export { Button, type
|
|
15
|
+
export { Button, type ButtonAsChildProps, type ButtonComponentProps, type ButtonProps, type ButtonSize, type ButtonVariant, CheckboxInput, type CheckboxInputProps, Field, type FieldHintPlacement, type FieldProps, Input, type InputProps, type InputSize, RadioGroup, type RadioGroupProps, RadioInput, type RadioInputProps, Spinner, type SpinnerPosition, type SpinnerProps, TextArea, type TextAreaProps, type TextAreaSize };
|
package/dist/lib/cn.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cn.cjs","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["type ClassDictionary = Record<string, boolean | null | undefined>;\
|
|
1
|
+
{"version":3,"file":"cn.cjs","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["type ClassDictionary = Record<string, boolean | null | undefined>;\ntype ClassValue =\n | string\n | number\n | null\n | undefined\n | false\n | ClassDictionary\n | ClassValue[];\n\nconst pushClass = (classes: string[], value: ClassValue): void => {\n if (!value) return;\n\n if (typeof value === 'string' || typeof value === 'number') {\n classes.push(String(value));\n return;\n }\n\n if (Array.isArray(value)) {\n value.forEach(item => pushClass(classes, item));\n return;\n }\n\n for (const key of Object.keys(value)) {\n if (value[key]) classes.push(key);\n }\n};\n\n/** Склейка классов. Без tailwind-merge: одноимённые правила решает специфичность. */\nexport const cn = (...inputs: ClassValue[]): string => {\n const classes: string[] = [];\n inputs.forEach(input => pushClass(classes, input));\n return classes.join(' ');\n};\n\n/** Условный класс. Пустая строка и `undefined` в `cn` не попадают. */\nexport const when = (\n enabled: boolean | undefined,\n className: string | undefined,\n): string | false => (Boolean(enabled) && className ? className : false);\n"],"mappings":";AAUA,MAAM,aAAa,SAAmB,UAA4B;CAChE,IAAI,CAAC,OAAO;CAEZ,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;EAC1D,QAAQ,KAAK,OAAO,KAAK,CAAC;EAC1B;CACF;CAEA,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,MAAM,SAAQ,SAAQ,UAAU,SAAS,IAAI,CAAC;EAC9C;CACF;CAEA,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,MAAM,MAAM,QAAQ,KAAK,GAAG;AAEpC;;AAGA,MAAa,MAAM,GAAG,WAAiC;CACrD,MAAM,UAAoB,CAAC;CAC3B,OAAO,SAAQ,UAAS,UAAU,SAAS,KAAK,CAAC;CACjD,OAAO,QAAQ,KAAK,GAAG;AACzB;;AAGA,MAAa,QACX,SACA,cACoB,QAAQ,OAAO,KAAK,YAAY,YAAY"}
|
package/dist/lib/cn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cn.js","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["type ClassDictionary = Record<string, boolean | null | undefined>;\
|
|
1
|
+
{"version":3,"file":"cn.js","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["type ClassDictionary = Record<string, boolean | null | undefined>;\ntype ClassValue =\n | string\n | number\n | null\n | undefined\n | false\n | ClassDictionary\n | ClassValue[];\n\nconst pushClass = (classes: string[], value: ClassValue): void => {\n if (!value) return;\n\n if (typeof value === 'string' || typeof value === 'number') {\n classes.push(String(value));\n return;\n }\n\n if (Array.isArray(value)) {\n value.forEach(item => pushClass(classes, item));\n return;\n }\n\n for (const key of Object.keys(value)) {\n if (value[key]) classes.push(key);\n }\n};\n\n/** Склейка классов. Без tailwind-merge: одноимённые правила решает специфичность. */\nexport const cn = (...inputs: ClassValue[]): string => {\n const classes: string[] = [];\n inputs.forEach(input => pushClass(classes, input));\n return classes.join(' ');\n};\n\n/** Условный класс. Пустая строка и `undefined` в `cn` не попадают. */\nexport const when = (\n enabled: boolean | undefined,\n className: string | undefined,\n): string | false => (Boolean(enabled) && className ? className : false);\n"],"mappings":";AAUA,MAAM,aAAa,SAAmB,UAA4B;CAChE,IAAI,CAAC,OAAO;CAEZ,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;EAC1D,QAAQ,KAAK,OAAO,KAAK,CAAC;EAC1B;CACF;CAEA,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,MAAM,SAAQ,SAAQ,UAAU,SAAS,IAAI,CAAC;EAC9C;CACF;CAEA,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,MAAM,MAAM,QAAQ,KAAK,GAAG;AAEpC;;AAGA,MAAa,MAAM,GAAG,WAAiC;CACrD,MAAM,UAAoB,CAAC;CAC3B,OAAO,SAAQ,UAAS,UAAU,SAAS,KAAK,CAAC;CACjD,OAAO,QAAQ,KAAK,GAAG;AACzB;;AAGA,MAAa,QACX,SACA,cACoB,QAAQ,OAAO,KAAK,YAAY,YAAY"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const require_cn = require("./cn.cjs");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
// src/lib/slot.tsx
|
|
5
|
+
const isHandlerKey = (key) => /^on[A-Z]/.test(key);
|
|
6
|
+
const asHandler = (value) => {
|
|
7
|
+
if (typeof value !== "function") return void 0;
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
const asClassName = (value) => {
|
|
11
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
12
|
+
};
|
|
13
|
+
const asStyle = (value) => {
|
|
14
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
const isPrevented = (event) => typeof event === "object" && event !== null && "defaultPrevented" in event && event.defaultPrevented === true;
|
|
18
|
+
const composeHandlers = (childHandler, slotHandler) => {
|
|
19
|
+
const child = asHandler(childHandler);
|
|
20
|
+
const slot = asHandler(slotHandler);
|
|
21
|
+
if (!child) return slot;
|
|
22
|
+
if (!slot) return child;
|
|
23
|
+
return (...args) => {
|
|
24
|
+
child(...args);
|
|
25
|
+
if (!isPrevented(args[0])) slot(...args);
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const warnsOnRefRead = (getter) => {
|
|
29
|
+
if (!getter || !("isReactWarning" in getter)) return false;
|
|
30
|
+
return Boolean(getter.isReactWarning);
|
|
31
|
+
};
|
|
32
|
+
const readRef = (source) => {
|
|
33
|
+
if (!("ref" in source)) return void 0;
|
|
34
|
+
return source.ref ?? void 0;
|
|
35
|
+
};
|
|
36
|
+
const getElementRef = (element) => {
|
|
37
|
+
const props = "props" in element && element.props && typeof element.props === "object" ? element.props : {};
|
|
38
|
+
const propsGetter = Object.getOwnPropertyDescriptor(props, "ref")?.get;
|
|
39
|
+
if (warnsOnRefRead(propsGetter)) return readRef(element);
|
|
40
|
+
const elementGetter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
|
41
|
+
if (warnsOnRefRead(elementGetter)) return readRef(props);
|
|
42
|
+
return readRef(props) ?? readRef(element);
|
|
43
|
+
};
|
|
44
|
+
const mergeProps = (childProps, slotProps) => {
|
|
45
|
+
const merged = { ...slotProps };
|
|
46
|
+
for (const key of Object.keys(childProps)) {
|
|
47
|
+
if (key === "ref") continue;
|
|
48
|
+
const childValue = childProps[key];
|
|
49
|
+
const slotValue = slotProps[key];
|
|
50
|
+
if (key === "className") continue;
|
|
51
|
+
if (key === "style") {
|
|
52
|
+
merged.style = {
|
|
53
|
+
...asStyle(slotValue),
|
|
54
|
+
...asStyle(childValue)
|
|
55
|
+
};
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (key === "onClick") continue;
|
|
59
|
+
if (isHandlerKey(key)) {
|
|
60
|
+
merged[key] = composeHandlers(childValue, slotValue);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (key === "disabled" && slotProps.disabled || key === "aria-busy" && slotProps["aria-busy"] || key === "aria-disabled" && slotProps["aria-disabled"]) continue;
|
|
64
|
+
merged[key] = childValue;
|
|
65
|
+
}
|
|
66
|
+
const className = require_cn.cn(asClassName(slotProps.className), asClassName(childProps.className));
|
|
67
|
+
if (className) merged.className = className;
|
|
68
|
+
if (slotProps.onClick) merged.onClick = slotProps.onClick;
|
|
69
|
+
return merged;
|
|
70
|
+
};
|
|
71
|
+
const attachRef = (ref, node) => {
|
|
72
|
+
if (typeof ref === "function") return ref(node);
|
|
73
|
+
if (ref) ref.current = node;
|
|
74
|
+
};
|
|
75
|
+
/** Сливает пропы в единственного ребёнка, без своего DOM-узла. Как Radix Slot. */
|
|
76
|
+
const Slot = (0, react.forwardRef)(function Slot(props, slotRef) {
|
|
77
|
+
const { children, ...slotProps } = props;
|
|
78
|
+
if (!(0, react.isValidElement)(children)) throw new Error("Button с asChild ждёт ровно один React-элемент.");
|
|
79
|
+
const childProps = children.props;
|
|
80
|
+
const childRef = getElementRef(children);
|
|
81
|
+
const Comp = children.type;
|
|
82
|
+
const merged = mergeProps(childProps, slotProps);
|
|
83
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Comp, {
|
|
84
|
+
...merged,
|
|
85
|
+
ref: (node) => {
|
|
86
|
+
const slotCleanup = attachRef(slotRef, node);
|
|
87
|
+
const childCleanup = attachRef(childRef, node);
|
|
88
|
+
if (typeof slotCleanup !== "function" && typeof childCleanup !== "function") return;
|
|
89
|
+
return () => {
|
|
90
|
+
if (typeof slotCleanup === "function") slotCleanup();
|
|
91
|
+
else attachRef(slotRef, null);
|
|
92
|
+
if (typeof childCleanup === "function") childCleanup();
|
|
93
|
+
else attachRef(childRef, null);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
exports.Slot = Slot;
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=slot.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot.cjs","names":["cn","forwardRef","isValidElement"],"sources":["../../src/lib/slot.tsx"],"sourcesContent":["import {\n forwardRef,\n isValidElement,\n type CSSProperties,\n type ElementType,\n type ForwardedRef,\n type MouseEventHandler,\n type ReactNode,\n type Ref,\n} from 'react';\n\nimport { cn } from './cn';\n\ninterface SlotProps {\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n onClick?: MouseEventHandler<HTMLElement>;\n disabled?: boolean;\n [key: string]: unknown;\n}\n\ntype AnyHandler = (...args: unknown[]) => void;\n\nconst isHandlerKey = (key: string) => /^on[A-Z]/.test(key);\n\nconst asHandler = (value: unknown): AnyHandler | undefined => {\n if (typeof value !== 'function') return undefined;\n return value as AnyHandler;\n};\n\nconst asClassName = (value: unknown) => {\n if (typeof value === 'string' || typeof value === 'number')\n return String(value);\n return undefined;\n};\n\nconst asStyle = (value: unknown): CSSProperties | undefined => {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return undefined;\n return value as CSSProperties;\n};\n\nconst isPrevented = (event: unknown) =>\n typeof event === 'object' &&\n event !== null &&\n 'defaultPrevented' in event &&\n event.defaultPrevented === true;\n\nconst composeHandlers = (childHandler: unknown, slotHandler: unknown) => {\n const child = asHandler(childHandler);\n const slot = asHandler(slotHandler);\n if (!child) return slot;\n if (!slot) return child;\n\n return (...args: unknown[]) => {\n child(...args);\n if (!isPrevented(args[0])) slot(...args);\n };\n};\n\nconst warnsOnRefRead = (getter: PropertyDescriptor['get']) => {\n if (!getter || !('isReactWarning' in getter)) return false;\n return Boolean((getter as { isReactWarning?: boolean }).isReactWarning);\n};\n\nconst readRef = (source: object): Ref<HTMLElement> | undefined => {\n if (!('ref' in source)) return undefined;\n const value = (source as { ref?: Ref<HTMLElement> | null }).ref;\n return value ?? undefined;\n};\n\n// React 18 кладёт ref на элемент, React 19 — в props. Геттер-заглушка\n// ругается при чтении «не той» стороны, поэтому берём ту, что без предупреждения.\nconst getElementRef = (element: object) => {\n const props =\n 'props' in element && element.props && typeof element.props === 'object'\n ? element.props\n : {};\n const propsGetter = Object.getOwnPropertyDescriptor(props, 'ref')?.get;\n if (warnsOnRefRead(propsGetter)) return readRef(element);\n\n const elementGetter = Object.getOwnPropertyDescriptor(element, 'ref')?.get;\n if (warnsOnRefRead(elementGetter)) return readRef(props);\n\n return readRef(props) ?? readRef(element);\n};\n\nconst mergeProps = (\n childProps: Record<string, unknown>,\n slotProps: SlotProps,\n) => {\n const merged: Record<string, unknown> = { ...slotProps };\n\n for (const key of Object.keys(childProps)) {\n if (key === 'ref') continue;\n\n const childValue = childProps[key];\n const slotValue = slotProps[key];\n\n if (key === 'className') continue;\n\n if (key === 'style') {\n merged.style = { ...asStyle(slotValue), ...asStyle(childValue) };\n continue;\n }\n\n if (key === 'onClick') continue;\n\n if (isHandlerKey(key)) {\n merged[key] = composeHandlers(childValue, slotValue);\n continue;\n }\n\n if (\n (key === 'disabled' && slotProps.disabled) ||\n (key === 'aria-busy' && slotProps['aria-busy']) ||\n (key === 'aria-disabled' && slotProps['aria-disabled'])\n ) {\n continue;\n }\n\n merged[key] = childValue;\n }\n\n const className = cn(\n asClassName(slotProps.className),\n asClassName(childProps.className),\n );\n if (className) merged.className = className;\n if (slotProps.onClick) merged.onClick = slotProps.onClick;\n\n return merged;\n};\n\nconst attachRef = (\n ref: ForwardedRef<HTMLElement> | Ref<HTMLElement> | undefined,\n node: HTMLElement | null,\n) => {\n if (typeof ref === 'function') return ref(node);\n if (ref) ref.current = node;\n return undefined;\n};\n\n/** Сливает пропы в единственного ребёнка, без своего DOM-узла. Как Radix Slot. */\nexport const Slot = forwardRef(function Slot(\n props: SlotProps,\n slotRef: ForwardedRef<HTMLElement>,\n) {\n const { children, ...slotProps } = props;\n\n if (!isValidElement(children)) {\n throw new Error('Button с asChild ждёт ровно один React-элемент.');\n }\n\n const childProps = children.props as Record<string, unknown>;\n const childRef = getElementRef(children);\n const Comp = children.type as ElementType;\n const merged = mergeProps(childProps, slotProps);\n\n return (\n <Comp\n {...merged}\n ref={(node: HTMLElement | null) => {\n const slotCleanup = attachRef(slotRef, node);\n const childCleanup = attachRef(childRef, node);\n if (\n typeof slotCleanup !== 'function' &&\n typeof childCleanup !== 'function'\n ) {\n return undefined;\n }\n\n return () => {\n if (typeof slotCleanup === 'function') slotCleanup();\n else attachRef(slotRef, null);\n if (typeof childCleanup === 'function') childCleanup();\n else attachRef(childRef, null);\n };\n }}\n />\n );\n});\n"],"mappings":"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"}
|
package/dist/lib/slot.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { forwardRef, isValidElement } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
// src/lib/slot.tsx
|
|
5
|
+
const isHandlerKey = (key) => /^on[A-Z]/.test(key);
|
|
6
|
+
const asHandler = (value) => {
|
|
7
|
+
if (typeof value !== "function") return void 0;
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
const asClassName = (value) => {
|
|
11
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
12
|
+
};
|
|
13
|
+
const asStyle = (value) => {
|
|
14
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
const isPrevented = (event) => typeof event === "object" && event !== null && "defaultPrevented" in event && event.defaultPrevented === true;
|
|
18
|
+
const composeHandlers = (childHandler, slotHandler) => {
|
|
19
|
+
const child = asHandler(childHandler);
|
|
20
|
+
const slot = asHandler(slotHandler);
|
|
21
|
+
if (!child) return slot;
|
|
22
|
+
if (!slot) return child;
|
|
23
|
+
return (...args) => {
|
|
24
|
+
child(...args);
|
|
25
|
+
if (!isPrevented(args[0])) slot(...args);
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const warnsOnRefRead = (getter) => {
|
|
29
|
+
if (!getter || !("isReactWarning" in getter)) return false;
|
|
30
|
+
return Boolean(getter.isReactWarning);
|
|
31
|
+
};
|
|
32
|
+
const readRef = (source) => {
|
|
33
|
+
if (!("ref" in source)) return void 0;
|
|
34
|
+
return source.ref ?? void 0;
|
|
35
|
+
};
|
|
36
|
+
const getElementRef = (element) => {
|
|
37
|
+
const props = "props" in element && element.props && typeof element.props === "object" ? element.props : {};
|
|
38
|
+
const propsGetter = Object.getOwnPropertyDescriptor(props, "ref")?.get;
|
|
39
|
+
if (warnsOnRefRead(propsGetter)) return readRef(element);
|
|
40
|
+
const elementGetter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
|
41
|
+
if (warnsOnRefRead(elementGetter)) return readRef(props);
|
|
42
|
+
return readRef(props) ?? readRef(element);
|
|
43
|
+
};
|
|
44
|
+
const mergeProps = (childProps, slotProps) => {
|
|
45
|
+
const merged = { ...slotProps };
|
|
46
|
+
for (const key of Object.keys(childProps)) {
|
|
47
|
+
if (key === "ref") continue;
|
|
48
|
+
const childValue = childProps[key];
|
|
49
|
+
const slotValue = slotProps[key];
|
|
50
|
+
if (key === "className") continue;
|
|
51
|
+
if (key === "style") {
|
|
52
|
+
merged.style = {
|
|
53
|
+
...asStyle(slotValue),
|
|
54
|
+
...asStyle(childValue)
|
|
55
|
+
};
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (key === "onClick") continue;
|
|
59
|
+
if (isHandlerKey(key)) {
|
|
60
|
+
merged[key] = composeHandlers(childValue, slotValue);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (key === "disabled" && slotProps.disabled || key === "aria-busy" && slotProps["aria-busy"] || key === "aria-disabled" && slotProps["aria-disabled"]) continue;
|
|
64
|
+
merged[key] = childValue;
|
|
65
|
+
}
|
|
66
|
+
const className = cn(asClassName(slotProps.className), asClassName(childProps.className));
|
|
67
|
+
if (className) merged.className = className;
|
|
68
|
+
if (slotProps.onClick) merged.onClick = slotProps.onClick;
|
|
69
|
+
return merged;
|
|
70
|
+
};
|
|
71
|
+
const attachRef = (ref, node) => {
|
|
72
|
+
if (typeof ref === "function") return ref(node);
|
|
73
|
+
if (ref) ref.current = node;
|
|
74
|
+
};
|
|
75
|
+
/** Сливает пропы в единственного ребёнка, без своего DOM-узла. Как Radix Slot. */
|
|
76
|
+
const Slot = forwardRef(function Slot(props, slotRef) {
|
|
77
|
+
const { children, ...slotProps } = props;
|
|
78
|
+
if (!isValidElement(children)) throw new Error("Button с asChild ждёт ровно один React-элемент.");
|
|
79
|
+
const childProps = children.props;
|
|
80
|
+
const childRef = getElementRef(children);
|
|
81
|
+
const Comp = children.type;
|
|
82
|
+
const merged = mergeProps(childProps, slotProps);
|
|
83
|
+
return /* @__PURE__ */ jsx(Comp, {
|
|
84
|
+
...merged,
|
|
85
|
+
ref: (node) => {
|
|
86
|
+
const slotCleanup = attachRef(slotRef, node);
|
|
87
|
+
const childCleanup = attachRef(childRef, node);
|
|
88
|
+
if (typeof slotCleanup !== "function" && typeof childCleanup !== "function") return;
|
|
89
|
+
return () => {
|
|
90
|
+
if (typeof slotCleanup === "function") slotCleanup();
|
|
91
|
+
else attachRef(slotRef, null);
|
|
92
|
+
if (typeof childCleanup === "function") childCleanup();
|
|
93
|
+
else attachRef(childRef, null);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
export { Slot };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=slot.js.map
|