jvdm-ui 1.0.5
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/LICENSE +21 -0
- package/README.md +221 -0
- package/bin/jvdm-ui.mjs +49 -0
- package/dist/atoms/index.d.ts +343 -0
- package/dist/atoms/index.js +3 -0
- package/dist/atoms/index.js.map +1 -0
- package/dist/chunk-AHE2576G.js +682 -0
- package/dist/chunk-AHE2576G.js.map +1 -0
- package/dist/chunk-CGDYSB4G.js +3 -0
- package/dist/chunk-CGDYSB4G.js.map +1 -0
- package/dist/chunk-JYNDKPAH.js +362 -0
- package/dist/chunk-JYNDKPAH.js.map +1 -0
- package/dist/chunk-WWOXGKLF.js +724 -0
- package/dist/chunk-WWOXGKLF.js.map +1 -0
- package/dist/client/code-block.d.ts +9 -0
- package/dist/client/code-block.js +65 -0
- package/dist/client/code-block.js.map +1 -0
- package/dist/client/confirm-dialog.d.ts +16 -0
- package/dist/client/confirm-dialog.js +181 -0
- package/dist/client/confirm-dialog.js.map +1 -0
- package/dist/client/date-picker.d.ts +10 -0
- package/dist/client/date-picker.js +236 -0
- package/dist/client/date-picker.js.map +1 -0
- package/dist/client/file-upload.d.ts +12 -0
- package/dist/client/file-upload.js +58 -0
- package/dist/client/file-upload.js.map +1 -0
- package/dist/client/gallery.d.ts +14 -0
- package/dist/client/gallery.js +122 -0
- package/dist/client/gallery.js.map +1 -0
- package/dist/client/menu.d.ts +24 -0
- package/dist/client/menu.js +115 -0
- package/dist/client/menu.js.map +1 -0
- package/dist/client/modal.d.ts +19 -0
- package/dist/client/modal.js +147 -0
- package/dist/client/modal.js.map +1 -0
- package/dist/client/multi-select.d.ts +16 -0
- package/dist/client/multi-select.js +84 -0
- package/dist/client/multi-select.js.map +1 -0
- package/dist/client/number-input.d.ts +6 -0
- package/dist/client/number-input.js +59 -0
- package/dist/client/number-input.js.map +1 -0
- package/dist/client/password-input.d.ts +16 -0
- package/dist/client/password-input.js +103 -0
- package/dist/client/password-input.js.map +1 -0
- package/dist/client/pin-input.d.ts +10 -0
- package/dist/client/pin-input.js +55 -0
- package/dist/client/pin-input.js.map +1 -0
- package/dist/client/theme-toggle.d.ts +8 -0
- package/dist/client/theme-toggle.js +126 -0
- package/dist/client/theme-toggle.js.map +1 -0
- package/dist/client/theme.d.ts +8 -0
- package/dist/client/theme.js +32 -0
- package/dist/client/theme.js.map +1 -0
- package/dist/client/time-picker.d.ts +10 -0
- package/dist/client/time-picker.js +140 -0
- package/dist/client/time-picker.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/molecules/index.d.ts +309 -0
- package/dist/molecules/index.js +4 -0
- package/dist/molecules/index.js.map +1 -0
- package/dist/organisms/index.d.ts +113 -0
- package/dist/organisms/index.js +4 -0
- package/dist/organisms/index.js.map +1 -0
- package/dist/presets/gavel.css +19 -0
- package/dist/theme/index.d.ts +60 -0
- package/dist/theme/index.js +85 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/tokens/index.d.ts +8 -0
- package/dist/tokens/index.js +3 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens.css +129 -0
- package/package.json +114 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/molecules/menu/menu/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Children,\n cloneElement,\n isValidElement,\n useEffect,\n useRef,\n useState,\n type ReactElement,\n type ReactNode,\n} from \"react\";\n\nconst ALIGNS = {\n start: \"left-0\",\n end: \"right-0\",\n} as const;\n\nconst WIDTHS = {\n sm: \"w-48\",\n md: \"w-56\",\n lg: \"w-72\",\n} as const;\n\nexport type MenuAlign = keyof typeof ALIGNS;\nexport type MenuWidth = keyof typeof WIDTHS;\n\nexport function Menu({\n label,\n trigger,\n align = \"end\",\n width = \"md\",\n className = \"\",\n children,\n}: {\n label: string;\n trigger: ReactNode;\n align?: MenuAlign;\n width?: MenuWidth;\n className?: string;\n children: ReactNode;\n}) {\n const [open, setOpen] = useState(false);\n const root = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n const items = Children.toArray(children).map((child) =>\n isValidElement(child)\n ? cloneElement(child as ReactElement<Record<string, unknown>>, {\n role: \"menuitem\",\n tabIndex: -1,\n })\n : child,\n );\n\n useEffect(() => {\n if (!open) return;\n\n function onPointerDown(event: PointerEvent) {\n if (!root.current?.contains(event.target as Node)) setOpen(false);\n }\n\n document.addEventListener(\"pointerdown\", onPointerDown);\n\n return () => {\n document.removeEventListener(\"pointerdown\", onPointerDown);\n };\n }, [open]);\n\n useEffect(() => {\n if (!open) return;\n menuRef.current?.querySelector<HTMLElement>('[role=\"menuitem\"]')?.focus();\n }, [open]);\n\n function onMenuKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n const menuItems = [\n ...(menuRef.current?.querySelectorAll<HTMLElement>('[role=\"menuitem\"]') ?? []),\n ];\n const current = menuItems.indexOf(document.activeElement as HTMLElement);\n const next = (index: number) =>\n menuItems[(index + menuItems.length) % menuItems.length]?.focus();\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n setOpen(false);\n triggerRef.current?.focus();\n return;\n }\n\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n next(current + 1);\n }\n\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n next(current - 1);\n }\n\n if (event.key === \"Home\") {\n event.preventDefault();\n menuItems[0]?.focus();\n }\n\n if (event.key === \"End\") {\n event.preventDefault();\n menuItems.at(-1)?.focus();\n }\n }\n\n return (\n <div ref={root} className={`relative ${className}`}>\n <button\n ref={triggerRef}\n type=\"button\"\n aria-label={label}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={() => setOpen((current) => !current)}\n className=\"flex items-center rounded-md transition-colors duration-(--duration-fast) ease-out focus-visible:ring-2 focus-visible:ring-accent/30 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent\"\n >\n {trigger}\n </button>\n\n {open ? (\n <div\n ref={menuRef}\n role=\"menu\"\n aria-label={label}\n onClick={() => setOpen(false)}\n onKeyDown={onMenuKeyDown}\n className={`absolute top-full z-10 mt-2 flex flex-col gap-1 rounded-md border border-line-strong bg-raised p-2 ${ALIGNS[align]} ${WIDTHS[width]}`}\n >\n {items}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAqGH,SACE,KADF;AAnGJ,IAAM,SAAS;AAAA,EACb,OAAO;AAAA,EACP,KAAK;AACP;AAEA,IAAM,SAAS;AAAA,EACb,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAKO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ;AACF,GAOG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,OAAO,OAAuB,IAAI;AACxC,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,UAAU,OAAuB,IAAI;AAE3C,QAAM,QAAQ,SAAS,QAAQ,QAAQ,EAAE;AAAA,IAAI,CAAC,UAC5C,eAAe,KAAK,IAChB,aAAa,OAAgD;AAAA,MAC3D,MAAM;AAAA,MACN,UAAU;AAAA,IACZ,CAAC,IACD;AAAA,EACN;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AAEX,aAAS,cAAc,OAAqB;AAC1C,UAAI,CAAC,KAAK,SAAS,SAAS,MAAM,MAAc,EAAG,SAAQ,KAAK;AAAA,IAClE;AAEA,aAAS,iBAAiB,eAAe,aAAa;AAEtD,WAAO,MAAM;AACX,eAAS,oBAAoB,eAAe,aAAa;AAAA,IAC3D;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,YAAQ,SAAS,cAA2B,mBAAmB,GAAG,MAAM;AAAA,EAC1E,GAAG,CAAC,IAAI,CAAC;AAET,WAAS,cAAc,OAA4C;AACjE,UAAM,YAAY;AAAA,MAChB,GAAI,QAAQ,SAAS,iBAA8B,mBAAmB,KAAK,CAAC;AAAA,IAC9E;AACA,UAAM,UAAU,UAAU,QAAQ,SAAS,aAA4B;AACvE,UAAM,OAAO,CAAC,UACZ,WAAW,QAAQ,UAAU,UAAU,UAAU,MAAM,GAAG,MAAM;AAElE,QAAI,MAAM,QAAQ,UAAU;AAC1B,YAAM,eAAe;AACrB,cAAQ,KAAK;AACb,iBAAW,SAAS,MAAM;AAC1B;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,aAAa;AAC7B,YAAM,eAAe;AACrB,WAAK,UAAU,CAAC;AAAA,IAClB;AAEA,QAAI,MAAM,QAAQ,WAAW;AAC3B,YAAM,eAAe;AACrB,WAAK,UAAU,CAAC;AAAA,IAClB;AAEA,QAAI,MAAM,QAAQ,QAAQ;AACxB,YAAM,eAAe;AACrB,gBAAU,CAAC,GAAG,MAAM;AAAA,IACtB;AAEA,QAAI,MAAM,QAAQ,OAAO;AACvB,YAAM,eAAe;AACrB,gBAAU,GAAG,EAAE,GAAG,MAAM;AAAA,IAC1B;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,KAAK,MAAM,WAAW,YAAY,SAAS,IAC9C;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,iBAAc;AAAA,QACd,iBAAe;AAAA,QACf,SAAS,MAAM,QAAQ,CAAC,YAAY,CAAC,OAAO;AAAA,QAC5C,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IAEC,OACC;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,SAAS,MAAM,QAAQ,KAAK;AAAA,QAC5B,WAAW;AAAA,QACX,WAAW,sGAAsG,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC;AAAA,QAE9I;AAAA;AAAA,IACH,IACE;AAAA,KACN;AAEJ;","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
declare const SIZE_CLASSES: {
|
|
5
|
+
sm: string;
|
|
6
|
+
md: string;
|
|
7
|
+
lg: string;
|
|
8
|
+
xl: string;
|
|
9
|
+
};
|
|
10
|
+
declare function Modal({ isOpen, onClose, title, children, size, closeLabel, }: {
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
title?: ReactNode;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
size?: keyof typeof SIZE_CLASSES;
|
|
16
|
+
closeLabel?: string;
|
|
17
|
+
}): react.JSX.Element;
|
|
18
|
+
|
|
19
|
+
export { Modal };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/molecules/modal/index.tsx
|
|
5
|
+
import { useEffect, useId, useRef } from "react";
|
|
6
|
+
|
|
7
|
+
// src/atoms/button/button-style.ts
|
|
8
|
+
var VARIANTS = {
|
|
9
|
+
primary: "bg-accent font-medium text-on-accent hover:bg-accent-ink",
|
|
10
|
+
secondary: "border border-line-strong hover:bg-raised",
|
|
11
|
+
ghost: "text-ink-muted hover:bg-raised hover:text-ink-soft",
|
|
12
|
+
danger: "border border-danger/50 text-danger hover:bg-danger-soft"
|
|
13
|
+
};
|
|
14
|
+
var SIZES = {
|
|
15
|
+
sm: "gap-2 px-3 py-2 text-xs",
|
|
16
|
+
md: "gap-2 px-4 py-3 text-sm",
|
|
17
|
+
lg: "gap-2 px-4 py-4 text-base",
|
|
18
|
+
icon: "h-10 w-10 shrink-0",
|
|
19
|
+
"icon-sm": "h-8 w-8 shrink-0"
|
|
20
|
+
};
|
|
21
|
+
var BASE = "inline-flex touch-manipulation select-none items-center justify-center rounded-md transition-[background-color,border-color,color,box-shadow,transform] duration-(--duration-fast) ease-out active:scale-95 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent focus-visible:ring-2 focus-visible:ring-accent/30 disabled:pointer-events-none disabled:opacity-50";
|
|
22
|
+
function buttonClass({
|
|
23
|
+
variant = "secondary",
|
|
24
|
+
size = "md",
|
|
25
|
+
className = ""
|
|
26
|
+
} = {}) {
|
|
27
|
+
return `${BASE} ${VARIANTS[variant]} ${SIZES[size]} ${className}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/atoms/icon/svg/index.tsx
|
|
31
|
+
import { jsx } from "react/jsx-runtime";
|
|
32
|
+
var SIZES2 = {
|
|
33
|
+
sm: "h-3 w-3",
|
|
34
|
+
md: "h-4 w-4",
|
|
35
|
+
lg: "h-5 w-5",
|
|
36
|
+
xl: "h-6 w-6",
|
|
37
|
+
"2xl": "h-8 w-8"
|
|
38
|
+
};
|
|
39
|
+
function Svg({ size = "md", className = "", children }) {
|
|
40
|
+
return /* @__PURE__ */ jsx(
|
|
41
|
+
"svg",
|
|
42
|
+
{
|
|
43
|
+
className: `${SIZES2[size]} shrink-0 ${className}`,
|
|
44
|
+
viewBox: "0 0 24 24",
|
|
45
|
+
fill: "none",
|
|
46
|
+
stroke: "currentColor",
|
|
47
|
+
strokeWidth: "1.6",
|
|
48
|
+
strokeLinecap: "round",
|
|
49
|
+
strokeLinejoin: "round",
|
|
50
|
+
"aria-hidden": "true",
|
|
51
|
+
children
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/atoms/icon/close-icon/index.tsx
|
|
57
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
58
|
+
function CloseIcon(p) {
|
|
59
|
+
return /* @__PURE__ */ jsx2(Svg, { ...p, children: /* @__PURE__ */ jsx2("path", { d: "M6 6l12 12M18 6 6 18" }) });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/atoms/icon-button/index.tsx
|
|
63
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
64
|
+
function IconButton({
|
|
65
|
+
label,
|
|
66
|
+
variant = "ghost",
|
|
67
|
+
size = "icon",
|
|
68
|
+
className,
|
|
69
|
+
...props
|
|
70
|
+
}) {
|
|
71
|
+
return /* @__PURE__ */ jsx3(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
type: "button",
|
|
75
|
+
...props,
|
|
76
|
+
"aria-label": label,
|
|
77
|
+
title: label,
|
|
78
|
+
className: buttonClass({ variant, size, className })
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/molecules/modal/locales.ts
|
|
84
|
+
var MODAL = {
|
|
85
|
+
label: { "en-US": "Dialog", "pt-BR": "Di\xE1logo" },
|
|
86
|
+
close: { "en-US": "Close", "pt-BR": "Fechar" }
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/molecules/modal/index.tsx
|
|
90
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
91
|
+
var SIZE_CLASSES = {
|
|
92
|
+
sm: "sm:max-w-md",
|
|
93
|
+
md: "sm:max-w-2xl",
|
|
94
|
+
lg: "sm:max-w-4xl",
|
|
95
|
+
xl: "sm:max-w-6xl"
|
|
96
|
+
};
|
|
97
|
+
function Modal({
|
|
98
|
+
isOpen,
|
|
99
|
+
onClose,
|
|
100
|
+
title,
|
|
101
|
+
children,
|
|
102
|
+
size = "md",
|
|
103
|
+
closeLabel = MODAL.close["en-US"]
|
|
104
|
+
}) {
|
|
105
|
+
const dialogRef = useRef(null);
|
|
106
|
+
const titleId = useId();
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const dialog = dialogRef.current;
|
|
109
|
+
if (!dialog) return;
|
|
110
|
+
if (isOpen && !dialog.open) dialog.showModal();
|
|
111
|
+
if (!isOpen && dialog.open) dialog.close();
|
|
112
|
+
}, [isOpen]);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const dialog = dialogRef.current;
|
|
115
|
+
if (!dialog) return;
|
|
116
|
+
dialog.addEventListener("close", onClose);
|
|
117
|
+
return () => dialog.removeEventListener("close", onClose);
|
|
118
|
+
}, [onClose]);
|
|
119
|
+
return /* @__PURE__ */ jsx4(
|
|
120
|
+
"dialog",
|
|
121
|
+
{
|
|
122
|
+
ref: dialogRef,
|
|
123
|
+
className: `fixed inset-x-0 top-auto bottom-0 z-50 m-0 mt-auto w-full max-w-none rounded-t-xl border border-b-0 border-line bg-surface p-0 text-ink shadow-modal backdrop:bg-app/70 backdrop:backdrop-blur-sm sm:inset-0 sm:m-auto sm:rounded-xl sm:border-b ${SIZE_CLASSES[size]}`,
|
|
124
|
+
"aria-modal": "true",
|
|
125
|
+
"aria-label": title ? void 0 : MODAL.label["en-US"],
|
|
126
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
127
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex max-h-[92dvh] flex-col sm:max-h-[88dvh]", children: [
|
|
128
|
+
title ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-b border-line py-3 pr-3 pl-5 sm:py-4 sm:pl-6", children: [
|
|
129
|
+
/* @__PURE__ */ jsx4("h2", { id: titleId, className: "text-lg font-bold text-ink", children: title }),
|
|
130
|
+
/* @__PURE__ */ jsx4(IconButton, { label: closeLabel, onClick: onClose, children: /* @__PURE__ */ jsx4(CloseIcon, { size: "md" }) })
|
|
131
|
+
] }) : null,
|
|
132
|
+
/* @__PURE__ */ jsx4(
|
|
133
|
+
"div",
|
|
134
|
+
{
|
|
135
|
+
className: "flex-1 overflow-y-auto overscroll-contain px-5 pt-4 pb-5 sm:px-6",
|
|
136
|
+
style: { paddingBottom: "max(1.25rem, env(safe-area-inset-bottom))" },
|
|
137
|
+
children
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
] })
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
Modal
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=modal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/molecules/modal/index.tsx","../../src/atoms/button/button-style.ts","../../src/atoms/icon/svg/index.tsx","../../src/atoms/icon/close-icon/index.tsx","../../src/atoms/icon-button/index.tsx","../../src/molecules/modal/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useId, useRef, type ReactNode } from \"react\";\nimport { CloseIcon, IconButton } from \"../../atoms\";\nimport { MODAL } from \"./locales\";\n\nconst SIZE_CLASSES = {\n sm: \"sm:max-w-md\",\n md: \"sm:max-w-2xl\",\n lg: \"sm:max-w-4xl\",\n xl: \"sm:max-w-6xl\",\n};\n\nexport function Modal({\n isOpen,\n onClose,\n title,\n children,\n size = \"md\",\n closeLabel = MODAL.close[\"en-US\"],\n}: {\n isOpen: boolean;\n onClose: () => void;\n title?: ReactNode;\n children: ReactNode;\n size?: keyof typeof SIZE_CLASSES;\n closeLabel?: string;\n}) {\n const dialogRef = useRef<HTMLDialogElement>(null);\n const titleId = useId();\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (isOpen && !dialog.open) dialog.showModal();\n if (!isOpen && dialog.open) dialog.close();\n }, [isOpen]);\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n dialog.addEventListener(\"close\", onClose);\n return () => dialog.removeEventListener(\"close\", onClose);\n }, [onClose]);\n\n return (\n <dialog\n ref={dialogRef}\n className={`fixed inset-x-0 top-auto bottom-0 z-50 m-0 mt-auto w-full max-w-none rounded-t-xl border border-b-0 border-line bg-surface p-0 text-ink shadow-modal backdrop:bg-app/70 backdrop:backdrop-blur-sm sm:inset-0 sm:m-auto sm:rounded-xl sm:border-b ${SIZE_CLASSES[size]}`}\n aria-modal=\"true\"\n aria-label={title ? undefined : MODAL.label[\"en-US\"]}\n aria-labelledby={title ? titleId : undefined}\n >\n <div className=\"flex max-h-[92dvh] flex-col sm:max-h-[88dvh]\">\n {title ? (\n <div className=\"flex items-center justify-between gap-4 border-b border-line py-3 pr-3 pl-5 sm:py-4 sm:pl-6\">\n <h2 id={titleId} className=\"text-lg font-bold text-ink\">\n {title}\n </h2>\n <IconButton label={closeLabel} onClick={onClose}>\n <CloseIcon size=\"md\" />\n </IconButton>\n </div>\n ) : null}\n <div\n className=\"flex-1 overflow-y-auto overscroll-contain px-5 pt-4 pb-5 sm:px-6\"\n style={{ paddingBottom: \"max(1.25rem, env(safe-area-inset-bottom))\" }}\n >\n {children}\n </div>\n </div>\n </dialog>\n );\n}\n","export type ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\" | \"icon\" | \"icon-sm\";\n\nconst VARIANTS: Record<ButtonVariant, string> = {\n primary: \"bg-accent font-medium text-on-accent hover:bg-accent-ink\",\n secondary: \"border border-line-strong hover:bg-raised\",\n ghost: \"text-ink-muted hover:bg-raised hover:text-ink-soft\",\n danger: \"border border-danger/50 text-danger hover:bg-danger-soft\",\n};\n\nconst SIZES: Record<ButtonSize, string> = {\n sm: \"gap-2 px-3 py-2 text-xs\",\n md: \"gap-2 px-4 py-3 text-sm\",\n lg: \"gap-2 px-4 py-4 text-base\",\n icon: \"h-10 w-10 shrink-0\",\n \"icon-sm\": \"h-8 w-8 shrink-0\",\n};\n\nconst BASE =\n \"inline-flex touch-manipulation select-none items-center justify-center rounded-md transition-[background-color,border-color,color,box-shadow,transform] duration-(--duration-fast) ease-out active:scale-95\" +\n \" focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent focus-visible:ring-2 focus-visible:ring-accent/30\" +\n \" disabled:pointer-events-none disabled:opacity-50\";\n\nexport type ButtonOptions = {\n variant?: ButtonVariant;\n size?: ButtonSize;\n className?: string;\n};\n\nexport function buttonClass({\n variant = \"secondary\",\n size = \"md\",\n className = \"\",\n}: ButtonOptions = {}) {\n return `${BASE} ${VARIANTS[variant]} ${SIZES[size]} ${className}`;\n}\n","import type { ReactNode } from \"react\";\n\nexport type IconSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\nconst SIZES: Record<IconSize, string> = {\n sm: \"h-3 w-3\",\n md: \"h-4 w-4\",\n lg: \"h-5 w-5\",\n xl: \"h-6 w-6\",\n \"2xl\": \"h-8 w-8\",\n};\n\nexport type Props = { size?: IconSize; className?: string };\n\nexport function Svg({ size = \"md\", className = \"\", children }: Props & { children: ReactNode }) {\n return (\n <svg\n className={`${SIZES[size]} shrink-0 ${className}`}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {children}\n </svg>\n );\n}\n","import { Svg, type Props } from \"../svg\";\n\nexport function CloseIcon(p: Props) {\n return (\n <Svg {...p}>\n <path d=\"M6 6l12 12M18 6 6 18\" />\n </Svg>\n );\n}\n","import type { ComponentProps } from \"react\";\n\nimport { buttonClass, type ButtonVariant } from \"../button\";\n\nexport type IconButtonSize = \"icon\" | \"icon-sm\";\n\nexport function IconButton({\n label,\n variant = \"ghost\",\n size = \"icon\",\n className,\n ...props\n}: {\n label: string;\n variant?: ButtonVariant;\n size?: IconButtonSize;\n} & Omit<ComponentProps<\"button\">, \"aria-label\" | \"size\">) {\n return (\n <button\n type=\"button\"\n {...props}\n aria-label={label}\n title={label}\n className={buttonClass({ variant, size, className })}\n />\n );\n}\n","export const MODAL = {\n label: { \"en-US\": \"Dialog\", \"pt-BR\": \"Diálogo\" },\n close: { \"en-US\": \"Close\", \"pt-BR\": \"Fechar\" },\n};\n"],"mappings":"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","names":["SIZES","jsx","jsx","jsx"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type MultiSelectOption = {
|
|
5
|
+
value: string;
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
declare function MultiSelect({ options, selected, onSelectedChange, label, className, }: {
|
|
9
|
+
options: readonly MultiSelectOption[];
|
|
10
|
+
selected?: readonly string[];
|
|
11
|
+
onSelectedChange?: (values: string[]) => void;
|
|
12
|
+
label?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}): react.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { MultiSelect, type MultiSelectOption };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/atoms/multi-select/index.tsx
|
|
5
|
+
import { useMemo, useState } from "react";
|
|
6
|
+
|
|
7
|
+
// src/atoms/input/control.ts
|
|
8
|
+
var CONTROL = "w-full min-w-0 rounded-md border border-line-strong bg-field text-base text-ink sm:text-sm outline-none transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out focus:border-accent focus:ring-2 focus:ring-accent/30 aria-invalid:border-danger aria-invalid:ring-2 aria-invalid:ring-danger/20";
|
|
9
|
+
var CONTROL_SIZES = {
|
|
10
|
+
md: "px-4 py-3",
|
|
11
|
+
sm: "px-3 py-2"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/atoms/input/input/index.tsx
|
|
15
|
+
import { jsx } from "react/jsx-runtime";
|
|
16
|
+
function Input({
|
|
17
|
+
size = "md",
|
|
18
|
+
className = "",
|
|
19
|
+
...props
|
|
20
|
+
}) {
|
|
21
|
+
return /* @__PURE__ */ jsx("input", { ...props, className: `${CONTROL} ${CONTROL_SIZES[size]} ${className}` });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/atoms/multi-select/locales.ts
|
|
25
|
+
var locales = { empty: "Select options", selected: "selected", search: "Search options" };
|
|
26
|
+
|
|
27
|
+
// src/atoms/multi-select/index.tsx
|
|
28
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
29
|
+
function MultiSelect({
|
|
30
|
+
options,
|
|
31
|
+
selected = [],
|
|
32
|
+
onSelectedChange,
|
|
33
|
+
label = locales.empty,
|
|
34
|
+
className = ""
|
|
35
|
+
}) {
|
|
36
|
+
function toggle(value) {
|
|
37
|
+
const next = selected.includes(value) ? selected.filter((item) => item !== value) : [...selected, value];
|
|
38
|
+
onSelectedChange?.(next);
|
|
39
|
+
}
|
|
40
|
+
const [query, setQuery] = useState("");
|
|
41
|
+
const visibleOptions = useMemo(
|
|
42
|
+
() => options.filter((option) => String(option.label).toLowerCase().includes(query.toLowerCase())),
|
|
43
|
+
[options, query]
|
|
44
|
+
);
|
|
45
|
+
return /* @__PURE__ */ jsxs("details", { className: `group relative w-full ${className}`, children: [
|
|
46
|
+
/* @__PURE__ */ jsxs("summary", { className: "flex min-h-12 cursor-pointer list-none items-center justify-between rounded-md border border-line-strong bg-field px-4 py-3 text-sm text-ink transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out outline-none hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent [&::-webkit-details-marker]:hidden", children: [
|
|
47
|
+
/* @__PURE__ */ jsx2("span", { children: selected.length ? `${selected.length} ${locales.selected}` : label }),
|
|
48
|
+
/* @__PURE__ */ jsx2("span", { className: "text-ink-muted transition-transform duration-(--duration-fast) ease-out group-open:rotate-180", children: "\u2304" })
|
|
49
|
+
] }),
|
|
50
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute top-full z-20 mt-2 flex w-full flex-col gap-1 rounded-md border border-line bg-surface p-2 shadow-popover", children: [
|
|
51
|
+
/* @__PURE__ */ jsx2(
|
|
52
|
+
Input,
|
|
53
|
+
{
|
|
54
|
+
"aria-label": locales.search,
|
|
55
|
+
onChange: (event) => setQuery(event.target.value),
|
|
56
|
+
placeholder: locales.search,
|
|
57
|
+
value: query
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
visibleOptions.map((option) => /* @__PURE__ */ jsxs(
|
|
61
|
+
"label",
|
|
62
|
+
{
|
|
63
|
+
className: "flex cursor-pointer items-center gap-3 rounded-sm px-3 py-2 text-sm text-ink-soft hover:bg-raised",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsx2(
|
|
66
|
+
"input",
|
|
67
|
+
{
|
|
68
|
+
checked: selected.includes(String(option.value)),
|
|
69
|
+
onChange: () => toggle(String(option.value)),
|
|
70
|
+
type: "checkbox"
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
option.label
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
String(option.value)
|
|
77
|
+
))
|
|
78
|
+
] })
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
MultiSelect
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=multi-select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/atoms/multi-select/index.tsx","../../src/atoms/input/control.ts","../../src/atoms/input/input/index.tsx","../../src/atoms/multi-select/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { Input } from \"../input\";\nimport { locales } from \"./locales\";\n\nexport type MultiSelectOption = { value: string; label: ReactNode };\n\nexport function MultiSelect({\n options,\n selected = [],\n onSelectedChange,\n label = locales.empty,\n className = \"\",\n}: {\n options: readonly MultiSelectOption[];\n selected?: readonly string[];\n onSelectedChange?: (values: string[]) => void;\n label?: string;\n className?: string;\n}) {\n function toggle(value: string) {\n const next = selected.includes(value)\n ? selected.filter((item) => item !== value)\n : [...selected, value];\n onSelectedChange?.(next);\n }\n const [query, setQuery] = useState(\"\");\n const visibleOptions = useMemo(\n () =>\n options.filter((option) => String(option.label).toLowerCase().includes(query.toLowerCase())),\n [options, query],\n );\n return (\n <details className={`group relative w-full ${className}`}>\n <summary className=\"flex min-h-12 cursor-pointer list-none items-center justify-between rounded-md border border-line-strong bg-field px-4 py-3 text-sm text-ink transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out outline-none hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent [&::-webkit-details-marker]:hidden\">\n <span>{selected.length ? `${selected.length} ${locales.selected}` : label}</span>\n <span className=\"text-ink-muted transition-transform duration-(--duration-fast) ease-out group-open:rotate-180\">\n ⌄\n </span>\n </summary>\n <div className=\"absolute top-full z-20 mt-2 flex w-full flex-col gap-1 rounded-md border border-line bg-surface p-2 shadow-popover\">\n <Input\n aria-label={locales.search}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={locales.search}\n value={query}\n />\n {visibleOptions.map((option) => (\n <label\n className=\"flex cursor-pointer items-center gap-3 rounded-sm px-3 py-2 text-sm text-ink-soft hover:bg-raised\"\n key={String(option.value)}\n >\n <input\n checked={selected.includes(String(option.value))}\n onChange={() => toggle(String(option.value))}\n type=\"checkbox\"\n />\n {option.label}\n </label>\n ))}\n </div>\n </details>\n );\n}\n","export const CONTROL =\n \"w-full min-w-0 rounded-md border border-line-strong bg-field text-base text-ink sm:text-sm\" +\n \" outline-none transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out focus:border-accent focus:ring-2 focus:ring-accent/30 aria-invalid:border-danger aria-invalid:ring-2 aria-invalid:ring-danger/20\";\n\nexport const CONTROL_SIZES = {\n md: \"px-4 py-3\",\n sm: \"px-3 py-2\",\n} as const;\n\nexport type ControlSize = keyof typeof CONTROL_SIZES;\n","import type { ComponentProps } from \"react\";\n\nimport { CONTROL, CONTROL_SIZES, type ControlSize } from \"../control\";\n\nexport function Input({\n size = \"md\",\n className = \"\",\n ...props\n}: { size?: ControlSize } & Omit<ComponentProps<\"input\">, \"size\">) {\n return <input {...props} className={`${CONTROL} ${CONTROL_SIZES[size]} ${className}`} />;\n}\n","export const locales = { empty: \"Select options\", selected: \"selected\", search: \"Search options\" };\n"],"mappings":";;;;AAEA,SAAS,SAAS,gBAAgC;;;ACF3C,IAAM,UACX;AAGK,IAAM,gBAAgB;AAAA,EAC3B,IAAI;AAAA,EACJ,IAAI;AACN;;;ACES;AALF,SAAS,MAAM;AAAA,EACpB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,GAAG;AACL,GAAmE;AACjE,SAAO,oBAAC,WAAO,GAAG,OAAO,WAAW,GAAG,OAAO,IAAI,cAAc,IAAI,CAAC,IAAI,SAAS,IAAI;AACxF;;;ACVO,IAAM,UAAU,EAAE,OAAO,kBAAkB,UAAU,YAAY,QAAQ,iBAAiB;;;AHoC3F,SACE,OAAAA,MADF;AA3BC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,WAAW,CAAC;AAAA,EACZ;AAAA,EACA,QAAQ,QAAQ;AAAA,EAChB,YAAY;AACd,GAMG;AACD,WAAS,OAAO,OAAe;AAC7B,UAAM,OAAO,SAAS,SAAS,KAAK,IAChC,SAAS,OAAO,CAAC,SAAS,SAAS,KAAK,IACxC,CAAC,GAAG,UAAU,KAAK;AACvB,uBAAmB,IAAI;AAAA,EACzB;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,iBAAiB;AAAA,IACrB,MACE,QAAQ,OAAO,CAAC,WAAW,OAAO,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC;AAAA,IAC7F,CAAC,SAAS,KAAK;AAAA,EACjB;AACA,SACE,qBAAC,aAAQ,WAAW,yBAAyB,SAAS,IACpD;AAAA,yBAAC,aAAQ,WAAU,8XACjB;AAAA,sBAAAA,KAAC,UAAM,mBAAS,SAAS,GAAG,SAAS,MAAM,IAAI,QAAQ,QAAQ,KAAK,OAAM;AAAA,MAC1E,gBAAAA,KAAC,UAAK,WAAU,iGAAgG,oBAEhH;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,sHACb;AAAA,sBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,cAAY,QAAQ;AAAA,UACpB,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,UAChD,aAAa,QAAQ;AAAA,UACrB,OAAO;AAAA;AAAA,MACT;AAAA,MACC,eAAe,IAAI,CAAC,WACnB;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UAGV;AAAA,4BAAAA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,SAAS,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,gBAC/C,UAAU,MAAM,OAAO,OAAO,OAAO,KAAK,CAAC;AAAA,gBAC3C,MAAK;AAAA;AAAA,YACP;AAAA,YACC,OAAO;AAAA;AAAA;AAAA,QAPH,OAAO,OAAO,KAAK;AAAA,MAQ1B,CACD;AAAA,OACH;AAAA,KACF;AAEJ;","names":["jsx"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/atoms/number-input/index.tsx
|
|
5
|
+
import { useRef } from "react";
|
|
6
|
+
|
|
7
|
+
// src/atoms/number-input/locales.ts
|
|
8
|
+
var locales = { increase: "Increase value", decrease: "Decrease value" };
|
|
9
|
+
|
|
10
|
+
// src/atoms/number-input/index.tsx
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
function NumberInput({ className = "", ...props }) {
|
|
13
|
+
const ref = useRef(null);
|
|
14
|
+
const change = (direction) => {
|
|
15
|
+
const input = ref.current;
|
|
16
|
+
if (!input) return;
|
|
17
|
+
if (direction === "up") input.stepUp();
|
|
18
|
+
else input.stepDown();
|
|
19
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
20
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
21
|
+
};
|
|
22
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative flex w-full", children: [
|
|
23
|
+
/* @__PURE__ */ jsx(
|
|
24
|
+
"input",
|
|
25
|
+
{
|
|
26
|
+
...props,
|
|
27
|
+
ref,
|
|
28
|
+
className: `w-full min-w-0 rounded-md border border-line-strong bg-field px-4 py-3 pr-12 text-base text-ink transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out outline-none focus:border-accent focus:ring-2 focus:ring-accent/30 sm:text-sm ${className}`,
|
|
29
|
+
type: "number"
|
|
30
|
+
}
|
|
31
|
+
),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute top-1/2 right-1 flex -translate-y-1/2 flex-col", children: [
|
|
33
|
+
/* @__PURE__ */ jsx(
|
|
34
|
+
"button",
|
|
35
|
+
{
|
|
36
|
+
"aria-label": locales.increase,
|
|
37
|
+
className: "flex h-4 w-8 items-center justify-center rounded-xs text-2xs text-ink-muted hover:bg-raised hover:text-ink",
|
|
38
|
+
onClick: () => change("up"),
|
|
39
|
+
type: "button",
|
|
40
|
+
children: "+"
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
"aria-label": locales.decrease,
|
|
47
|
+
className: "flex h-4 w-8 items-center justify-center rounded-xs text-2xs text-ink-muted hover:bg-raised hover:text-ink",
|
|
48
|
+
onClick: () => change("down"),
|
|
49
|
+
type: "button",
|
|
50
|
+
children: "\u2212"
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
] })
|
|
54
|
+
] });
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
NumberInput
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=number-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/atoms/number-input/index.tsx","../../src/atoms/number-input/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useRef, type ComponentProps } from \"react\";\n\nimport { locales } from \"./locales\";\n\nexport function NumberInput({ className = \"\", ...props }: ComponentProps<\"input\">) {\n const ref = useRef<HTMLInputElement>(null);\n const change = (direction: \"up\" | \"down\") => {\n const input = ref.current;\n if (!input) return;\n if (direction === \"up\") input.stepUp();\n else input.stepDown();\n input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n };\n\n return (\n <div className=\"relative flex w-full\">\n <input\n {...props}\n ref={ref}\n className={`w-full min-w-0 rounded-md border border-line-strong bg-field px-4 py-3 pr-12 text-base text-ink transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out outline-none focus:border-accent focus:ring-2 focus:ring-accent/30 sm:text-sm ${className}`}\n type=\"number\"\n />\n <div className=\"absolute top-1/2 right-1 flex -translate-y-1/2 flex-col\">\n <button\n aria-label={locales.increase}\n className=\"flex h-4 w-8 items-center justify-center rounded-xs text-2xs text-ink-muted hover:bg-raised hover:text-ink\"\n onClick={() => change(\"up\")}\n type=\"button\"\n >\n +\n </button>\n <button\n aria-label={locales.decrease}\n className=\"flex h-4 w-8 items-center justify-center rounded-xs text-2xs text-ink-muted hover:bg-raised hover:text-ink\"\n onClick={() => change(\"down\")}\n type=\"button\"\n >\n −\n </button>\n </div>\n </div>\n );\n}\n","export const locales = { increase: \"Increase value\", decrease: \"Decrease value\" };\n"],"mappings":";;;;AAEA,SAAS,cAAmC;;;ACFrC,IAAM,UAAU,EAAE,UAAU,kBAAkB,UAAU,iBAAiB;;;ADmB1E,cAMA,YANA;AAbC,SAAS,YAAY,EAAE,YAAY,IAAI,GAAG,MAAM,GAA4B;AACjF,QAAM,MAAM,OAAyB,IAAI;AACzC,QAAM,SAAS,CAAC,cAA6B;AAC3C,UAAM,QAAQ,IAAI;AAClB,QAAI,CAAC,MAAO;AACZ,QAAI,cAAc,KAAM,OAAM,OAAO;AAAA,QAChC,OAAM,SAAS;AACpB,UAAM,cAAc,IAAI,MAAM,SAAS,EAAE,SAAS,KAAK,CAAC,CAAC;AACzD,UAAM,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,EAC5D;AAEA,SACE,qBAAC,SAAI,WAAU,wBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ;AAAA,QACA,WAAW,2QAA2Q,SAAS;AAAA,QAC/R,MAAK;AAAA;AAAA,IACP;AAAA,IACA,qBAAC,SAAI,WAAU,2DACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,cAAY,QAAQ;AAAA,UACpB,WAAU;AAAA,UACV,SAAS,MAAM,OAAO,IAAI;AAAA,UAC1B,MAAK;AAAA,UACN;AAAA;AAAA,MAED;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,cAAY,QAAQ;AAAA,UACpB,WAAU;AAAA,UACV,SAAS,MAAM,OAAO,MAAM;AAAA,UAC5B,MAAK;AAAA,UACN;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
declare const CONTROL_SIZES: {
|
|
5
|
+
readonly md: "px-4 py-3";
|
|
6
|
+
readonly sm: "px-3 py-2";
|
|
7
|
+
};
|
|
8
|
+
type ControlSize = keyof typeof CONTROL_SIZES;
|
|
9
|
+
|
|
10
|
+
declare function PasswordInput({ className, showLabel, hideLabel, ...props }: {
|
|
11
|
+
size?: ControlSize;
|
|
12
|
+
showLabel?: string;
|
|
13
|
+
hideLabel?: string;
|
|
14
|
+
} & Omit<ComponentProps<"input">, "size" | "type">): react.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { PasswordInput };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/atoms/input/password-input/index.tsx
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
|
|
7
|
+
// src/atoms/icon/svg/index.tsx
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
var SIZES = {
|
|
10
|
+
sm: "h-3 w-3",
|
|
11
|
+
md: "h-4 w-4",
|
|
12
|
+
lg: "h-5 w-5",
|
|
13
|
+
xl: "h-6 w-6",
|
|
14
|
+
"2xl": "h-8 w-8"
|
|
15
|
+
};
|
|
16
|
+
function Svg({ size = "md", className = "", children }) {
|
|
17
|
+
return /* @__PURE__ */ jsx(
|
|
18
|
+
"svg",
|
|
19
|
+
{
|
|
20
|
+
className: `${SIZES[size]} shrink-0 ${className}`,
|
|
21
|
+
viewBox: "0 0 24 24",
|
|
22
|
+
fill: "none",
|
|
23
|
+
stroke: "currentColor",
|
|
24
|
+
strokeWidth: "1.6",
|
|
25
|
+
strokeLinecap: "round",
|
|
26
|
+
strokeLinejoin: "round",
|
|
27
|
+
"aria-hidden": "true",
|
|
28
|
+
children
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/atoms/icon/eye-icon/index.tsx
|
|
34
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
35
|
+
function EyeIcon(p) {
|
|
36
|
+
return /* @__PURE__ */ jsxs(Svg, { ...p, children: [
|
|
37
|
+
/* @__PURE__ */ jsx2("path", { d: "M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" }),
|
|
38
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "3" })
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/atoms/icon/eye-off-icon/index.tsx
|
|
43
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
44
|
+
function EyeOffIcon(p) {
|
|
45
|
+
return /* @__PURE__ */ jsxs2(Svg, { ...p, children: [
|
|
46
|
+
/* @__PURE__ */ jsx3("path", { d: "M9.9 5.8A9.3 9.3 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4.1" }),
|
|
47
|
+
/* @__PURE__ */ jsx3("path", { d: "M6.4 7.6A17 17 0 0 0 2.5 12S6 18.5 12 18.5a9.5 9.5 0 0 0 3.7-.7" }),
|
|
48
|
+
/* @__PURE__ */ jsx3("path", { d: "M10 10a2.8 2.8 0 0 0 4 4" }),
|
|
49
|
+
/* @__PURE__ */ jsx3("path", { d: "m4 4 16 16" })
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/atoms/input/control.ts
|
|
54
|
+
var CONTROL = "w-full min-w-0 rounded-md border border-line-strong bg-field text-base text-ink sm:text-sm outline-none transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out focus:border-accent focus:ring-2 focus:ring-accent/30 aria-invalid:border-danger aria-invalid:ring-2 aria-invalid:ring-danger/20";
|
|
55
|
+
var CONTROL_SIZES = {
|
|
56
|
+
md: "px-4 py-3",
|
|
57
|
+
sm: "px-3 py-2"
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/atoms/input/input/index.tsx
|
|
61
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
62
|
+
function Input({
|
|
63
|
+
size = "md",
|
|
64
|
+
className = "",
|
|
65
|
+
...props
|
|
66
|
+
}) {
|
|
67
|
+
return /* @__PURE__ */ jsx4("input", { ...props, className: `${CONTROL} ${CONTROL_SIZES[size]} ${className}` });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/atoms/input/locales.ts
|
|
71
|
+
var PASSWORD_INPUT = {
|
|
72
|
+
show: "Show password",
|
|
73
|
+
hide: "Hide password"
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/atoms/input/password-input/index.tsx
|
|
77
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
78
|
+
function PasswordInput({
|
|
79
|
+
className = "",
|
|
80
|
+
showLabel = PASSWORD_INPUT.show,
|
|
81
|
+
hideLabel = PASSWORD_INPUT.hide,
|
|
82
|
+
...props
|
|
83
|
+
}) {
|
|
84
|
+
const [visible, setVisible] = useState(false);
|
|
85
|
+
return /* @__PURE__ */ jsxs3("div", { className: "relative flex min-w-0", children: [
|
|
86
|
+
/* @__PURE__ */ jsx5(Input, { ...props, type: visible ? "text" : "password", className: `pr-11 ${className}` }),
|
|
87
|
+
/* @__PURE__ */ jsx5(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
type: "button",
|
|
91
|
+
onClick: () => setVisible((current) => !current),
|
|
92
|
+
"aria-label": visible ? hideLabel : showLabel,
|
|
93
|
+
"aria-pressed": visible,
|
|
94
|
+
className: "absolute inset-y-0 right-0 flex w-11 touch-manipulation items-center justify-center rounded-r-md text-ink-dim transition-colors select-none hover:text-ink",
|
|
95
|
+
children: visible ? /* @__PURE__ */ jsx5(EyeOffIcon, {}) : /* @__PURE__ */ jsx5(EyeIcon, {})
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
PasswordInput
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=password-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/atoms/input/password-input/index.tsx","../../src/atoms/icon/svg/index.tsx","../../src/atoms/icon/eye-icon/index.tsx","../../src/atoms/icon/eye-off-icon/index.tsx","../../src/atoms/input/control.ts","../../src/atoms/input/input/index.tsx","../../src/atoms/input/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useState, type ComponentProps } from \"react\";\nimport { EyeIcon, EyeOffIcon } from \"../../icon\";\nimport type { ControlSize } from \"../control\";\nimport { Input } from \"../input\";\nimport { PASSWORD_INPUT as T } from \"../locales\";\n\nexport function PasswordInput({\n className = \"\",\n showLabel = T.show,\n hideLabel = T.hide,\n ...props\n}: {\n size?: ControlSize;\n showLabel?: string;\n hideLabel?: string;\n} & Omit<ComponentProps<\"input\">, \"size\" | \"type\">) {\n const [visible, setVisible] = useState(false);\n\n return (\n <div className=\"relative flex min-w-0\">\n <Input {...props} type={visible ? \"text\" : \"password\"} className={`pr-11 ${className}`} />\n <button\n type=\"button\"\n onClick={() => setVisible((current) => !current)}\n aria-label={visible ? hideLabel : showLabel}\n aria-pressed={visible}\n className=\"absolute inset-y-0 right-0 flex w-11 touch-manipulation items-center justify-center rounded-r-md text-ink-dim transition-colors select-none hover:text-ink\"\n >\n {visible ? <EyeOffIcon /> : <EyeIcon />}\n </button>\n </div>\n );\n}\n","import type { ReactNode } from \"react\";\n\nexport type IconSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\nconst SIZES: Record<IconSize, string> = {\n sm: \"h-3 w-3\",\n md: \"h-4 w-4\",\n lg: \"h-5 w-5\",\n xl: \"h-6 w-6\",\n \"2xl\": \"h-8 w-8\",\n};\n\nexport type Props = { size?: IconSize; className?: string };\n\nexport function Svg({ size = \"md\", className = \"\", children }: Props & { children: ReactNode }) {\n return (\n <svg\n className={`${SIZES[size]} shrink-0 ${className}`}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {children}\n </svg>\n );\n}\n","import { Svg, type Props } from \"../svg\";\n\nexport function EyeIcon(p: Props) {\n return (\n <Svg {...p}>\n <path d=\"M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </Svg>\n );\n}\n","import { Svg, type Props } from \"../svg\";\n\nexport function EyeOffIcon(p: Props) {\n return (\n <Svg {...p}>\n <path d=\"M9.9 5.8A9.3 9.3 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4.1\" />\n <path d=\"M6.4 7.6A17 17 0 0 0 2.5 12S6 18.5 12 18.5a9.5 9.5 0 0 0 3.7-.7\" />\n <path d=\"M10 10a2.8 2.8 0 0 0 4 4\" />\n <path d=\"m4 4 16 16\" />\n </Svg>\n );\n}\n","export const CONTROL =\n \"w-full min-w-0 rounded-md border border-line-strong bg-field text-base text-ink sm:text-sm\" +\n \" outline-none transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out focus:border-accent focus:ring-2 focus:ring-accent/30 aria-invalid:border-danger aria-invalid:ring-2 aria-invalid:ring-danger/20\";\n\nexport const CONTROL_SIZES = {\n md: \"px-4 py-3\",\n sm: \"px-3 py-2\",\n} as const;\n\nexport type ControlSize = keyof typeof CONTROL_SIZES;\n","import type { ComponentProps } from \"react\";\n\nimport { CONTROL, CONTROL_SIZES, type ControlSize } from \"../control\";\n\nexport function Input({\n size = \"md\",\n className = \"\",\n ...props\n}: { size?: ControlSize } & Omit<ComponentProps<\"input\">, \"size\">) {\n return <input {...props} className={`${CONTROL} ${CONTROL_SIZES[size]} ${className}`} />;\n}\n","export const PASSWORD_INPUT = {\n show: \"Show password\",\n hide: \"Hide password\",\n};\n"],"mappings":";;;;AAEA,SAAS,gBAAqC;;;ACc1C;AAZJ,IAAM,QAAkC;AAAA,EACtC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,OAAO;AACT;AAIO,SAAS,IAAI,EAAE,OAAO,MAAM,YAAY,IAAI,SAAS,GAAoC;AAC9F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,MAAM,IAAI,CAAC,aAAa,SAAS;AAAA,MAC/C,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,QAAO;AAAA,MACP,aAAY;AAAA,MACZ,eAAc;AAAA,MACd,gBAAe;AAAA,MACf,eAAY;AAAA,MAEX;AAAA;AAAA,EACH;AAEJ;;;ACzBI,SACE,OAAAA,MADF;AAFG,SAAS,QAAQ,GAAU;AAChC,SACE,qBAAC,OAAK,GAAG,GACP;AAAA,oBAAAA,KAAC,UAAK,GAAE,uEAAsE;AAAA,IAC9E,gBAAAA,KAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI;AAAA,KAChC;AAEJ;;;ACLI,SACE,OAAAC,MADF,QAAAC,aAAA;AAFG,SAAS,WAAW,GAAU;AACnC,SACE,gBAAAA,MAAC,OAAK,GAAG,GACP;AAAA,oBAAAD,KAAC,UAAK,GAAE,yEAAwE;AAAA,IAChF,gBAAAA,KAAC,UAAK,GAAE,mEAAkE;AAAA,IAC1E,gBAAAA,KAAC,UAAK,GAAE,4BAA2B;AAAA,IACnC,gBAAAA,KAAC,UAAK,GAAE,cAAa;AAAA,KACvB;AAEJ;;;ACXO,IAAM,UACX;AAGK,IAAM,gBAAgB;AAAA,EAC3B,IAAI;AAAA,EACJ,IAAI;AACN;;;ACES,gBAAAE,YAAA;AALF,SAAS,MAAM;AAAA,EACpB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,GAAG;AACL,GAAmE;AACjE,SAAO,gBAAAA,KAAC,WAAO,GAAG,OAAO,WAAW,GAAG,OAAO,IAAI,cAAc,IAAI,CAAC,IAAI,SAAS,IAAI;AACxF;;;ACVO,IAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,MAAM;AACR;;;ANkBI,SACE,OAAAC,MADF,QAAAC,aAAA;AAbG,SAAS,cAAc;AAAA,EAC5B,YAAY;AAAA,EACZ,YAAY,eAAE;AAAA,EACd,YAAY,eAAE;AAAA,EACd,GAAG;AACL,GAIoD;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,SACE,gBAAAA,MAAC,SAAI,WAAU,yBACb;AAAA,oBAAAD,KAAC,SAAO,GAAG,OAAO,MAAM,UAAU,SAAS,YAAY,WAAW,SAAS,SAAS,IAAI;AAAA,IACxF,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,WAAW,CAAC,YAAY,CAAC,OAAO;AAAA,QAC/C,cAAY,UAAU,YAAY;AAAA,QAClC,gBAAc;AAAA,QACd,WAAU;AAAA,QAET,oBAAU,gBAAAA,KAAC,cAAW,IAAK,gBAAAA,KAAC,WAAQ;AAAA;AAAA,IACvC;AAAA,KACF;AAEJ;","names":["jsx","jsx","jsxs","jsx","jsx","jsxs"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
declare function PinInput({ length, value, onValueChange, className, }: {
|
|
4
|
+
length?: number;
|
|
5
|
+
value?: string;
|
|
6
|
+
onValueChange?: (value: string) => void;
|
|
7
|
+
className?: string;
|
|
8
|
+
}): react.JSX.Element;
|
|
9
|
+
|
|
10
|
+
export { PinInput };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/atoms/pin-input/index.tsx
|
|
5
|
+
import { useRef } from "react";
|
|
6
|
+
|
|
7
|
+
// src/atoms/pin-input/locales.ts
|
|
8
|
+
var locales = { digit: "Digit" };
|
|
9
|
+
|
|
10
|
+
// src/atoms/pin-input/index.tsx
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
function PinInput({
|
|
13
|
+
length = 6,
|
|
14
|
+
value = "",
|
|
15
|
+
onValueChange,
|
|
16
|
+
className = ""
|
|
17
|
+
}) {
|
|
18
|
+
const refs = useRef([]);
|
|
19
|
+
const digits = Array.from({ length }, (_, index) => value[index] ?? "");
|
|
20
|
+
function update(index, digit) {
|
|
21
|
+
const next = digits.map((item, itemIndex) => itemIndex === index ? digit : item).join("");
|
|
22
|
+
onValueChange?.(next);
|
|
23
|
+
if (digit && index < length - 1) refs.current[index + 1]?.focus();
|
|
24
|
+
}
|
|
25
|
+
function handleKey(event, index) {
|
|
26
|
+
if (event.key === "Backspace" && !digits[index] && index > 0) refs.current[index - 1]?.focus();
|
|
27
|
+
if (event.key === "ArrowLeft" && index > 0) refs.current[index - 1]?.focus();
|
|
28
|
+
if (event.key === "ArrowRight" && index < length - 1) refs.current[index + 1]?.focus();
|
|
29
|
+
}
|
|
30
|
+
function handlePaste(event) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
onValueChange?.(event.clipboardData.getData("text").replace(/\D/g, "").slice(0, length));
|
|
33
|
+
}
|
|
34
|
+
return /* @__PURE__ */ jsx("div", { className: `flex gap-2 ${className}`, children: digits.map((digit, index) => /* @__PURE__ */ jsx(
|
|
35
|
+
"input",
|
|
36
|
+
{
|
|
37
|
+
"aria-label": `${locales.digit} ${index + 1}`,
|
|
38
|
+
className: "size-12 rounded-md border border-line-strong bg-field text-center text-lg text-ink transition-[border-color,box-shadow] duration-(--duration-fast) ease-out outline-none focus:border-accent focus:ring-2 focus:ring-accent/30",
|
|
39
|
+
inputMode: "numeric",
|
|
40
|
+
maxLength: 1,
|
|
41
|
+
onChange: (event) => update(index, event.target.value.replace(/\D/g, "").slice(-1)),
|
|
42
|
+
onKeyDown: (event) => handleKey(event, index),
|
|
43
|
+
onPaste: handlePaste,
|
|
44
|
+
ref: (element) => {
|
|
45
|
+
refs.current[index] = element;
|
|
46
|
+
},
|
|
47
|
+
value: digit
|
|
48
|
+
},
|
|
49
|
+
index
|
|
50
|
+
)) });
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
PinInput
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=pin-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/atoms/pin-input/index.tsx","../../src/atoms/pin-input/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useRef, type ClipboardEvent, type KeyboardEvent } from \"react\";\n\nimport { locales } from \"./locales\";\n\nexport function PinInput({\n length = 6,\n value = \"\",\n onValueChange,\n className = \"\",\n}: {\n length?: number;\n value?: string;\n onValueChange?: (value: string) => void;\n className?: string;\n}) {\n const refs = useRef<Array<HTMLInputElement | null>>([]);\n const digits = Array.from({ length }, (_, index) => value[index] ?? \"\");\n function update(index: number, digit: string) {\n const next = digits.map((item, itemIndex) => (itemIndex === index ? digit : item)).join(\"\");\n onValueChange?.(next);\n if (digit && index < length - 1) refs.current[index + 1]?.focus();\n }\n function handleKey(event: KeyboardEvent<HTMLInputElement>, index: number) {\n if (event.key === \"Backspace\" && !digits[index] && index > 0) refs.current[index - 1]?.focus();\n if (event.key === \"ArrowLeft\" && index > 0) refs.current[index - 1]?.focus();\n if (event.key === \"ArrowRight\" && index < length - 1) refs.current[index + 1]?.focus();\n }\n function handlePaste(event: ClipboardEvent<HTMLInputElement>) {\n event.preventDefault();\n onValueChange?.(event.clipboardData.getData(\"text\").replace(/\\D/g, \"\").slice(0, length));\n }\n return (\n <div className={`flex gap-2 ${className}`}>\n {digits.map((digit, index) => (\n <input\n aria-label={`${locales.digit} ${index + 1}`}\n className=\"size-12 rounded-md border border-line-strong bg-field text-center text-lg text-ink transition-[border-color,box-shadow] duration-(--duration-fast) ease-out outline-none focus:border-accent focus:ring-2 focus:ring-accent/30\"\n inputMode=\"numeric\"\n key={index}\n maxLength={1}\n onChange={(event) => update(index, event.target.value.replace(/\\D/g, \"\").slice(-1))}\n onKeyDown={(event) => handleKey(event, index)}\n onPaste={handlePaste}\n ref={(element) => {\n refs.current[index] = element;\n }}\n value={digit}\n />\n ))}\n </div>\n );\n}\n","export const locales = { digit: \"Digit\" };\n"],"mappings":";;;;AAEA,SAAS,cAAuD;;;ACFzD,IAAM,UAAU,EAAE,OAAO,QAAQ;;;ADoChC;AA9BD,SAAS,SAAS;AAAA,EACvB,SAAS;AAAA,EACT,QAAQ;AAAA,EACR;AAAA,EACA,YAAY;AACd,GAKG;AACD,QAAM,OAAO,OAAuC,CAAC,CAAC;AACtD,QAAM,SAAS,MAAM,KAAK,EAAE,OAAO,GAAG,CAAC,GAAG,UAAU,MAAM,KAAK,KAAK,EAAE;AACtE,WAAS,OAAO,OAAe,OAAe;AAC5C,UAAM,OAAO,OAAO,IAAI,CAAC,MAAM,cAAe,cAAc,QAAQ,QAAQ,IAAK,EAAE,KAAK,EAAE;AAC1F,oBAAgB,IAAI;AACpB,QAAI,SAAS,QAAQ,SAAS,EAAG,MAAK,QAAQ,QAAQ,CAAC,GAAG,MAAM;AAAA,EAClE;AACA,WAAS,UAAU,OAAwC,OAAe;AACxE,QAAI,MAAM,QAAQ,eAAe,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAG,MAAK,QAAQ,QAAQ,CAAC,GAAG,MAAM;AAC7F,QAAI,MAAM,QAAQ,eAAe,QAAQ,EAAG,MAAK,QAAQ,QAAQ,CAAC,GAAG,MAAM;AAC3E,QAAI,MAAM,QAAQ,gBAAgB,QAAQ,SAAS,EAAG,MAAK,QAAQ,QAAQ,CAAC,GAAG,MAAM;AAAA,EACvF;AACA,WAAS,YAAY,OAAyC;AAC5D,UAAM,eAAe;AACrB,oBAAgB,MAAM,cAAc,QAAQ,MAAM,EAAE,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;AAAA,EACzF;AACA,SACE,oBAAC,SAAI,WAAW,cAAc,SAAS,IACpC,iBAAO,IAAI,CAAC,OAAO,UAClB;AAAA,IAAC;AAAA;AAAA,MACC,cAAY,GAAG,QAAQ,KAAK,IAAI,QAAQ,CAAC;AAAA,MACzC,WAAU;AAAA,MACV,WAAU;AAAA,MAEV,WAAW;AAAA,MACX,UAAU,CAAC,UAAU,OAAO,OAAO,MAAM,OAAO,MAAM,QAAQ,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC;AAAA,MAClF,WAAW,CAAC,UAAU,UAAU,OAAO,KAAK;AAAA,MAC5C,SAAS;AAAA,MACT,KAAK,CAAC,YAAY;AAChB,aAAK,QAAQ,KAAK,IAAI;AAAA,MACxB;AAAA,MACA,OAAO;AAAA;AAAA,IARF;AAAA,EASP,CACD,GACH;AAEJ;","names":[]}
|