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,236 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/atoms/date-picker/index.tsx
|
|
5
|
+
import { useEffect, useRef, 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/chevron-icon/index.tsx
|
|
34
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
35
|
+
function ChevronIcon(p) {
|
|
36
|
+
return /* @__PURE__ */ jsx2(Svg, { ...p, children: /* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" }) });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/atoms/date-picker/locales.ts
|
|
40
|
+
var locales = {
|
|
41
|
+
previous: "Previous month",
|
|
42
|
+
next: "Next month",
|
|
43
|
+
calendar: "Choose a date",
|
|
44
|
+
today: "Today"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/atoms/date-picker/index.tsx
|
|
48
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
49
|
+
var CONTROL = "w-full min-w-0 rounded-md border border-line-strong bg-field px-4 py-3 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";
|
|
50
|
+
var WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
51
|
+
var MONTHS = Array.from(
|
|
52
|
+
{ length: 12 },
|
|
53
|
+
(_, index) => new Intl.DateTimeFormat("en-US", { month: "short" }).format(new Date(2020, index, 1))
|
|
54
|
+
);
|
|
55
|
+
function dateKey(date) {
|
|
56
|
+
return [
|
|
57
|
+
date.getFullYear(),
|
|
58
|
+
String(date.getMonth() + 1).padStart(2, "0"),
|
|
59
|
+
String(date.getDate()).padStart(2, "0")
|
|
60
|
+
].join("-");
|
|
61
|
+
}
|
|
62
|
+
function parseDate(value) {
|
|
63
|
+
if (!value) return /* @__PURE__ */ new Date();
|
|
64
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
65
|
+
return year && month && day ? new Date(year, month - 1, day) : /* @__PURE__ */ new Date();
|
|
66
|
+
}
|
|
67
|
+
function formatDate(value) {
|
|
68
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
69
|
+
day: "2-digit",
|
|
70
|
+
month: "short",
|
|
71
|
+
year: "numeric"
|
|
72
|
+
}).format(parseDate(value));
|
|
73
|
+
}
|
|
74
|
+
function DatePicker({
|
|
75
|
+
value,
|
|
76
|
+
defaultValue = "",
|
|
77
|
+
onValueChange,
|
|
78
|
+
className = "",
|
|
79
|
+
...props
|
|
80
|
+
}) {
|
|
81
|
+
const initial = value ?? defaultValue;
|
|
82
|
+
const [selected, setSelected] = useState(initial);
|
|
83
|
+
const [month, setMonth] = useState(() => {
|
|
84
|
+
const date = parseDate(initial);
|
|
85
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
86
|
+
});
|
|
87
|
+
const [open, setOpen] = useState(false);
|
|
88
|
+
const [view, setView] = useState("calendar");
|
|
89
|
+
const ref = useRef(null);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
function handleDocument(event) {
|
|
92
|
+
if (!ref.current?.contains(event.target)) setOpen(false);
|
|
93
|
+
}
|
|
94
|
+
function handleKey(event) {
|
|
95
|
+
if (event.key === "Escape") setOpen(false);
|
|
96
|
+
}
|
|
97
|
+
document.addEventListener("mousedown", handleDocument);
|
|
98
|
+
document.addEventListener("keydown", handleKey);
|
|
99
|
+
return () => {
|
|
100
|
+
document.removeEventListener("mousedown", handleDocument);
|
|
101
|
+
document.removeEventListener("keydown", handleKey);
|
|
102
|
+
};
|
|
103
|
+
}, []);
|
|
104
|
+
const days = new Date(month.getFullYear(), month.getMonth() + 1, 0).getDate();
|
|
105
|
+
const offset = new Date(month.getFullYear(), month.getMonth(), 1).getDay();
|
|
106
|
+
const monthLabel = new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric" }).format(
|
|
107
|
+
month
|
|
108
|
+
);
|
|
109
|
+
function choose(date) {
|
|
110
|
+
const next = dateKey(date);
|
|
111
|
+
setSelected(next);
|
|
112
|
+
onValueChange?.(next);
|
|
113
|
+
setOpen(false);
|
|
114
|
+
setView("calendar");
|
|
115
|
+
}
|
|
116
|
+
return /* @__PURE__ */ jsxs(
|
|
117
|
+
"div",
|
|
118
|
+
{
|
|
119
|
+
className: `date-picker-root relative w-full ${open ? "date-picker-root-open" : ""}`,
|
|
120
|
+
ref,
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsx3(
|
|
123
|
+
"input",
|
|
124
|
+
{
|
|
125
|
+
...props,
|
|
126
|
+
className: `${CONTROL} cursor-pointer ${className}`,
|
|
127
|
+
onClick: () => setOpen(true),
|
|
128
|
+
readOnly: true,
|
|
129
|
+
value: selected ? formatDate(selected) : ""
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
/* @__PURE__ */ jsx3(
|
|
133
|
+
ChevronIcon,
|
|
134
|
+
{
|
|
135
|
+
className: "pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 text-ink-muted",
|
|
136
|
+
size: "md"
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
open ? /* @__PURE__ */ jsxs("div", { className: "absolute top-full right-0 z-20 mt-2 w-[min(20rem,calc(100vw-2rem))] rounded-lg border border-line bg-surface p-4 text-ink shadow-popover", children: [
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
141
|
+
/* @__PURE__ */ jsx3(
|
|
142
|
+
"button",
|
|
143
|
+
{
|
|
144
|
+
"aria-label": locales.previous,
|
|
145
|
+
className: "flex size-8 items-center justify-center rounded-sm text-ink-muted transition-colors duration-(--duration-fast) ease-out hover:bg-raised hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
146
|
+
onClick: () => setMonth(new Date(month.getFullYear(), month.getMonth() - 1, 1)),
|
|
147
|
+
type: "button",
|
|
148
|
+
children: /* @__PURE__ */ jsx3(ChevronIcon, { className: "rotate-90", size: "sm" })
|
|
149
|
+
}
|
|
150
|
+
),
|
|
151
|
+
/* @__PURE__ */ jsx3(
|
|
152
|
+
"button",
|
|
153
|
+
{
|
|
154
|
+
className: "rounded-sm px-2 py-1 text-sm font-medium transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
155
|
+
onClick: () => setView(view === "calendar" ? "months" : view === "months" ? "years" : "calendar"),
|
|
156
|
+
type: "button",
|
|
157
|
+
children: monthLabel
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsx3(
|
|
161
|
+
"button",
|
|
162
|
+
{
|
|
163
|
+
"aria-label": locales.next,
|
|
164
|
+
className: "flex size-8 items-center justify-center rounded-sm text-ink-muted transition-colors duration-(--duration-fast) ease-out hover:bg-raised hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
165
|
+
onClick: () => setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1)),
|
|
166
|
+
type: "button",
|
|
167
|
+
children: /* @__PURE__ */ jsx3(ChevronIcon, { className: "-rotate-90", size: "sm" })
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
] }),
|
|
171
|
+
view === "years" ? /* @__PURE__ */ jsx3("div", { className: "mt-4 grid grid-cols-3 gap-2", children: Array.from({ length: 12 }, (_, index) => month.getFullYear() - 5 + index).map(
|
|
172
|
+
(year) => /* @__PURE__ */ jsx3(
|
|
173
|
+
"button",
|
|
174
|
+
{
|
|
175
|
+
className: `rounded-sm px-2 py-3 text-sm transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${year === month.getFullYear() ? "bg-accent text-on-accent" : "text-ink-soft"}`,
|
|
176
|
+
onClick: () => {
|
|
177
|
+
setMonth(new Date(year, month.getMonth(), 1));
|
|
178
|
+
setView("months");
|
|
179
|
+
},
|
|
180
|
+
type: "button",
|
|
181
|
+
children: year
|
|
182
|
+
},
|
|
183
|
+
year
|
|
184
|
+
)
|
|
185
|
+
) }) : null,
|
|
186
|
+
view === "months" ? /* @__PURE__ */ jsx3("div", { className: "mt-4 grid grid-cols-3 gap-2", children: MONTHS.map((name, index) => /* @__PURE__ */ jsx3(
|
|
187
|
+
"button",
|
|
188
|
+
{
|
|
189
|
+
className: `rounded-sm px-2 py-3 text-sm transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${index === month.getMonth() ? "bg-accent text-on-accent" : "text-ink-soft"}`,
|
|
190
|
+
onClick: () => {
|
|
191
|
+
setMonth(new Date(month.getFullYear(), index, 1));
|
|
192
|
+
setView("calendar");
|
|
193
|
+
},
|
|
194
|
+
type: "button",
|
|
195
|
+
children: name
|
|
196
|
+
},
|
|
197
|
+
name
|
|
198
|
+
)) }) : null,
|
|
199
|
+
view === "calendar" ? /* @__PURE__ */ jsxs("div", { "aria-label": locales.calendar, className: "mt-4 grid grid-cols-7 gap-1", role: "grid", children: [
|
|
200
|
+
WEEKDAYS.map((day) => /* @__PURE__ */ jsx3("span", { className: "py-1 text-center text-2xs text-ink-dim", children: day }, day)),
|
|
201
|
+
Array.from({ length: offset }, (_, index) => /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "size-8" }, `empty-${index}`)),
|
|
202
|
+
Array.from({ length: days }, (_, index) => {
|
|
203
|
+
const date = new Date(month.getFullYear(), month.getMonth(), index + 1);
|
|
204
|
+
const active = dateKey(date) === selected;
|
|
205
|
+
return /* @__PURE__ */ jsx3(
|
|
206
|
+
"button",
|
|
207
|
+
{
|
|
208
|
+
"aria-current": active ? "date" : void 0,
|
|
209
|
+
"aria-label": date.toLocaleDateString("en-US", { dateStyle: "long" }),
|
|
210
|
+
className: `flex size-8 items-center justify-center rounded-sm text-xs transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${active ? "bg-accent font-medium text-on-accent hover:bg-accent-ink" : "text-ink-soft"}`,
|
|
211
|
+
onClick: () => choose(date),
|
|
212
|
+
type: "button",
|
|
213
|
+
children: index + 1
|
|
214
|
+
},
|
|
215
|
+
dateKey(date)
|
|
216
|
+
);
|
|
217
|
+
})
|
|
218
|
+
] }) : null,
|
|
219
|
+
/* @__PURE__ */ jsx3(
|
|
220
|
+
"button",
|
|
221
|
+
{
|
|
222
|
+
className: "mt-4 w-full rounded-sm border-t border-line pt-3 text-center text-xs font-medium text-accent-ink transition-colors duration-(--duration-fast) ease-out hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
223
|
+
onClick: () => choose(/* @__PURE__ */ new Date()),
|
|
224
|
+
type: "button",
|
|
225
|
+
children: locales.today
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
] }) : null
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
export {
|
|
234
|
+
DatePicker
|
|
235
|
+
};
|
|
236
|
+
//# sourceMappingURL=date-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/atoms/date-picker/index.tsx","../../src/atoms/icon/svg/index.tsx","../../src/atoms/icon/chevron-icon/index.tsx","../../src/atoms/date-picker/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState, type ComponentProps } from \"react\";\n\nimport { ChevronIcon } from \"../icon\";\nimport { locales } from \"./locales\";\n\nconst CONTROL =\n \"w-full min-w-0 rounded-md border border-line-strong bg-field px-4 py-3 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\";\nconst WEEKDAYS = [\"Su\", \"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\"];\nconst MONTHS = Array.from({ length: 12 }, (_, index) =>\n new Intl.DateTimeFormat(\"en-US\", { month: \"short\" }).format(new Date(2020, index, 1)),\n);\n\nfunction dateKey(date: Date) {\n return [\n date.getFullYear(),\n String(date.getMonth() + 1).padStart(2, \"0\"),\n String(date.getDate()).padStart(2, \"0\"),\n ].join(\"-\");\n}\n\nfunction parseDate(value?: string) {\n if (!value) return new Date();\n const [year, month, day] = value.split(\"-\").map(Number);\n return year && month && day ? new Date(year, month - 1, day) : new Date();\n}\n\nfunction formatDate(value: string) {\n return new Intl.DateTimeFormat(\"en-US\", {\n day: \"2-digit\",\n month: \"short\",\n year: \"numeric\",\n }).format(parseDate(value));\n}\n\nexport function DatePicker({\n value,\n defaultValue = \"\",\n onValueChange,\n className = \"\",\n ...props\n}: Omit<ComponentProps<\"input\">, \"type\" | \"value\" | \"defaultValue\" | \"onChange\"> & {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n}) {\n const initial = value ?? defaultValue;\n const [selected, setSelected] = useState(initial);\n const [month, setMonth] = useState(() => {\n const date = parseDate(initial);\n return new Date(date.getFullYear(), date.getMonth(), 1);\n });\n const [open, setOpen] = useState(false);\n const [view, setView] = useState<\"calendar\" | \"months\" | \"years\">(\"calendar\");\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function handleDocument(event: MouseEvent) {\n if (!ref.current?.contains(event.target as Node)) setOpen(false);\n }\n function handleKey(event: KeyboardEvent) {\n if (event.key === \"Escape\") setOpen(false);\n }\n document.addEventListener(\"mousedown\", handleDocument);\n document.addEventListener(\"keydown\", handleKey);\n return () => {\n document.removeEventListener(\"mousedown\", handleDocument);\n document.removeEventListener(\"keydown\", handleKey);\n };\n }, []);\n\n const days = new Date(month.getFullYear(), month.getMonth() + 1, 0).getDate();\n const offset = new Date(month.getFullYear(), month.getMonth(), 1).getDay();\n const monthLabel = new Intl.DateTimeFormat(\"en-US\", { month: \"long\", year: \"numeric\" }).format(\n month,\n );\n\n function choose(date: Date) {\n const next = dateKey(date);\n setSelected(next);\n onValueChange?.(next);\n setOpen(false);\n setView(\"calendar\");\n }\n\n return (\n <div\n className={`date-picker-root relative w-full ${open ? \"date-picker-root-open\" : \"\"}`}\n ref={ref}\n >\n <input\n {...props}\n className={`${CONTROL} cursor-pointer ${className}`}\n onClick={() => setOpen(true)}\n readOnly\n value={selected ? formatDate(selected) : \"\"}\n />\n <ChevronIcon\n className=\"pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 text-ink-muted\"\n size=\"md\"\n />\n {open ? (\n <div className=\"absolute top-full right-0 z-20 mt-2 w-[min(20rem,calc(100vw-2rem))] rounded-lg border border-line bg-surface p-4 text-ink shadow-popover\">\n <div className=\"flex items-center justify-between\">\n <button\n aria-label={locales.previous}\n className=\"flex size-8 items-center justify-center rounded-sm text-ink-muted transition-colors duration-(--duration-fast) ease-out hover:bg-raised hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent\"\n onClick={() => setMonth(new Date(month.getFullYear(), month.getMonth() - 1, 1))}\n type=\"button\"\n >\n <ChevronIcon className=\"rotate-90\" size=\"sm\" />\n </button>\n <button\n className=\"rounded-sm px-2 py-1 text-sm font-medium transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent\"\n onClick={() =>\n setView(view === \"calendar\" ? \"months\" : view === \"months\" ? \"years\" : \"calendar\")\n }\n type=\"button\"\n >\n {monthLabel}\n </button>\n <button\n aria-label={locales.next}\n className=\"flex size-8 items-center justify-center rounded-sm text-ink-muted transition-colors duration-(--duration-fast) ease-out hover:bg-raised hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent\"\n onClick={() => setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1))}\n type=\"button\"\n >\n <ChevronIcon className=\"-rotate-90\" size=\"sm\" />\n </button>\n </div>\n {view === \"years\" ? (\n <div className=\"mt-4 grid grid-cols-3 gap-2\">\n {Array.from({ length: 12 }, (_, index) => month.getFullYear() - 5 + index).map(\n (year) => (\n <button\n className={`rounded-sm px-2 py-3 text-sm transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${year === month.getFullYear() ? \"bg-accent text-on-accent\" : \"text-ink-soft\"}`}\n key={year}\n onClick={() => {\n setMonth(new Date(year, month.getMonth(), 1));\n setView(\"months\");\n }}\n type=\"button\"\n >\n {year}\n </button>\n ),\n )}\n </div>\n ) : null}\n {view === \"months\" ? (\n <div className=\"mt-4 grid grid-cols-3 gap-2\">\n {MONTHS.map((name, index) => (\n <button\n className={`rounded-sm px-2 py-3 text-sm transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${index === month.getMonth() ? \"bg-accent text-on-accent\" : \"text-ink-soft\"}`}\n key={name}\n onClick={() => {\n setMonth(new Date(month.getFullYear(), index, 1));\n setView(\"calendar\");\n }}\n type=\"button\"\n >\n {name}\n </button>\n ))}\n </div>\n ) : null}\n {view === \"calendar\" ? (\n <div aria-label={locales.calendar} className=\"mt-4 grid grid-cols-7 gap-1\" role=\"grid\">\n {WEEKDAYS.map((day) => (\n <span className=\"py-1 text-center text-2xs text-ink-dim\" key={day}>\n {day}\n </span>\n ))}\n {Array.from({ length: offset }, (_, index) => (\n <span aria-hidden=\"true\" className=\"size-8\" key={`empty-${index}`} />\n ))}\n {Array.from({ length: days }, (_, index) => {\n const date = new Date(month.getFullYear(), month.getMonth(), index + 1);\n const active = dateKey(date) === selected;\n return (\n <button\n aria-current={active ? \"date\" : undefined}\n aria-label={date.toLocaleDateString(\"en-US\", { dateStyle: \"long\" })}\n className={`flex size-8 items-center justify-center rounded-sm text-xs transition-colors duration-(--duration-fast) ease-out hover:bg-raised focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${active ? \"bg-accent font-medium text-on-accent hover:bg-accent-ink\" : \"text-ink-soft\"}`}\n key={dateKey(date)}\n onClick={() => choose(date)}\n type=\"button\"\n >\n {index + 1}\n </button>\n );\n })}\n </div>\n ) : null}\n <button\n className=\"mt-4 w-full rounded-sm border-t border-line pt-3 text-center text-xs font-medium text-accent-ink transition-colors duration-(--duration-fast) ease-out hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent\"\n onClick={() => choose(new Date())}\n type=\"button\"\n >\n {locales.today}\n </button>\n </div>\n ) : null}\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 ChevronIcon(p: Props) {\n return (\n <Svg {...p}>\n <path d=\"m6 9 6 6 6-6\" />\n </Svg>\n );\n}\n","export const locales = {\n previous: \"Previous month\",\n next: \"Next month\",\n calendar: \"Choose a date\",\n today: \"Today\",\n};\n"],"mappings":"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","names":["jsx","jsx"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
declare function FileUpload({ accept, multiple, onFilesChange, children, className, }: {
|
|
5
|
+
accept?: string;
|
|
6
|
+
multiple?: boolean;
|
|
7
|
+
onFilesChange?: (files: FileList | null) => void;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}): react.JSX.Element;
|
|
11
|
+
|
|
12
|
+
export { FileUpload };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/atoms/file-upload/index.tsx
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
|
|
7
|
+
// src/atoms/file-upload/locales.ts
|
|
8
|
+
var locales = { choose: "Choose files" };
|
|
9
|
+
|
|
10
|
+
// src/atoms/file-upload/index.tsx
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
function FileUpload({
|
|
13
|
+
accept,
|
|
14
|
+
multiple = false,
|
|
15
|
+
onFilesChange,
|
|
16
|
+
children = locales.choose,
|
|
17
|
+
className = ""
|
|
18
|
+
}) {
|
|
19
|
+
const [dragging, setDragging] = useState(false);
|
|
20
|
+
function handleChange(event) {
|
|
21
|
+
onFilesChange?.(event.target.files);
|
|
22
|
+
}
|
|
23
|
+
function handleDrop(event) {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
setDragging(false);
|
|
26
|
+
onFilesChange?.(event.dataTransfer.files);
|
|
27
|
+
}
|
|
28
|
+
return /* @__PURE__ */ jsxs(
|
|
29
|
+
"label",
|
|
30
|
+
{
|
|
31
|
+
className: `flex min-h-32 cursor-pointer flex-col items-center justify-center gap-2 rounded-md border border-dashed border-line-strong px-4 py-6 text-sm text-ink-soft transition-[background-color,border-color] duration-(--duration-fast) ease-out focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-accent hover:bg-raised ${dragging ? "border-accent bg-accent-soft" : ""} ${className}`,
|
|
32
|
+
onDragEnter: (event) => {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
setDragging(true);
|
|
35
|
+
},
|
|
36
|
+
onDragLeave: () => setDragging(false),
|
|
37
|
+
onDragOver: (event) => event.preventDefault(),
|
|
38
|
+
onDrop: handleDrop,
|
|
39
|
+
children: [
|
|
40
|
+
children,
|
|
41
|
+
/* @__PURE__ */ jsx(
|
|
42
|
+
"input",
|
|
43
|
+
{
|
|
44
|
+
accept,
|
|
45
|
+
className: "sr-only",
|
|
46
|
+
multiple,
|
|
47
|
+
onChange: handleChange,
|
|
48
|
+
type: "file"
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
FileUpload
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=file-upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/atoms/file-upload/index.tsx","../../src/atoms/file-upload/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useState, type ChangeEvent, type DragEvent, type ReactNode } from \"react\";\n\nimport { locales } from \"./locales\";\n\nexport function FileUpload({\n accept,\n multiple = false,\n onFilesChange,\n children = locales.choose,\n className = \"\",\n}: {\n accept?: string;\n multiple?: boolean;\n onFilesChange?: (files: FileList | null) => void;\n children?: ReactNode;\n className?: string;\n}) {\n const [dragging, setDragging] = useState(false);\n function handleChange(event: ChangeEvent<HTMLInputElement>) {\n onFilesChange?.(event.target.files);\n }\n function handleDrop(event: DragEvent<HTMLLabelElement>) {\n event.preventDefault();\n setDragging(false);\n onFilesChange?.(event.dataTransfer.files);\n }\n\n return (\n <label\n className={`flex min-h-32 cursor-pointer flex-col items-center justify-center gap-2 rounded-md border border-dashed border-line-strong px-4 py-6 text-sm text-ink-soft transition-[background-color,border-color] duration-(--duration-fast) ease-out focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-accent hover:bg-raised ${dragging ? \"border-accent bg-accent-soft\" : \"\"} ${className}`}\n onDragEnter={(event) => {\n event.preventDefault();\n setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDragOver={(event) => event.preventDefault()}\n onDrop={handleDrop}\n >\n {children}\n <input\n accept={accept}\n className=\"sr-only\"\n multiple={multiple}\n onChange={handleChange}\n type=\"file\"\n />\n </label>\n );\n}\n","export const locales = { choose: \"Choose files\" };\n"],"mappings":";;;;AAEA,SAAS,gBAAkE;;;ACFpE,IAAM,UAAU,EAAE,QAAQ,eAAe;;;AD8B5C,SAWE,KAXF;AAxBG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,WAAW,QAAQ;AAAA,EACnB,YAAY;AACd,GAMG;AACD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,WAAS,aAAa,OAAsC;AAC1D,oBAAgB,MAAM,OAAO,KAAK;AAAA,EACpC;AACA,WAAS,WAAW,OAAoC;AACtD,UAAM,eAAe;AACrB,gBAAY,KAAK;AACjB,oBAAgB,MAAM,aAAa,KAAK;AAAA,EAC1C;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,8UAA8U,WAAW,iCAAiC,EAAE,IAAI,SAAS;AAAA,MACpZ,aAAa,CAAC,UAAU;AACtB,cAAM,eAAe;AACrB,oBAAY,IAAI;AAAA,MAClB;AAAA,MACA,aAAa,MAAM,YAAY,KAAK;AAAA,MACpC,YAAY,CAAC,UAAU,MAAM,eAAe;AAAA,MAC5C,QAAQ;AAAA,MAEP;AAAA;AAAA,QACD;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,WAAU;AAAA,YACV;AAAA,YACA,UAAU;AAAA,YACV,MAAK;AAAA;AAAA,QACP;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
declare function Gallery({ photos, alt, fallback, photoLabel, previousLabel, nextLabel, className, }: {
|
|
5
|
+
photos: string[];
|
|
6
|
+
alt: string;
|
|
7
|
+
fallback?: ReactNode;
|
|
8
|
+
photoLabel?: (index: number, total: number) => string;
|
|
9
|
+
previousLabel?: string;
|
|
10
|
+
nextLabel?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
}): react.JSX.Element;
|
|
13
|
+
|
|
14
|
+
export { Gallery };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/molecules/gallery/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/back-icon/index.tsx
|
|
34
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
35
|
+
function BackIcon(p) {
|
|
36
|
+
return /* @__PURE__ */ jsx2(Svg, { ...p, children: /* @__PURE__ */ jsx2("path", { d: "m14 6-6 6 6 6" }) });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/atoms/thumb/index.tsx
|
|
40
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
41
|
+
function Thumb({
|
|
42
|
+
src,
|
|
43
|
+
alt,
|
|
44
|
+
fallback,
|
|
45
|
+
className = ""
|
|
46
|
+
}) {
|
|
47
|
+
return /* @__PURE__ */ jsx3(
|
|
48
|
+
"div",
|
|
49
|
+
{
|
|
50
|
+
className: `flex aspect-[4/3] items-center justify-center overflow-hidden rounded-md bg-raised text-ink-dim ${className}`,
|
|
51
|
+
children: src ? /* @__PURE__ */ jsx3("img", { src, alt, loading: "lazy", className: "h-full w-full object-cover" }) : fallback
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/molecules/gallery/locales.ts
|
|
57
|
+
var GALLERY = {
|
|
58
|
+
photoLabel: (index, total) => `Photo ${index} of ${total}`,
|
|
59
|
+
previous: "Previous photo",
|
|
60
|
+
next: "Next photo"
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/molecules/gallery/index.tsx
|
|
64
|
+
import { Fragment, jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
65
|
+
function Gallery({
|
|
66
|
+
photos,
|
|
67
|
+
alt,
|
|
68
|
+
fallback,
|
|
69
|
+
photoLabel = GALLERY.photoLabel,
|
|
70
|
+
previousLabel = GALLERY.previous,
|
|
71
|
+
nextLabel = GALLERY.next,
|
|
72
|
+
className = ""
|
|
73
|
+
}) {
|
|
74
|
+
const [index, setIndex] = useState(0);
|
|
75
|
+
const total = photos.length;
|
|
76
|
+
const current = Math.min(index, Math.max(0, total - 1));
|
|
77
|
+
const step = (delta) => setIndex((current + delta + total) % total);
|
|
78
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-2 ${className}`, children: [
|
|
79
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
80
|
+
/* @__PURE__ */ jsx4(Thumb, { src: photos[current] ?? null, alt, fallback }),
|
|
81
|
+
total > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
82
|
+
/* @__PURE__ */ jsx4(
|
|
83
|
+
"button",
|
|
84
|
+
{
|
|
85
|
+
type: "button",
|
|
86
|
+
"aria-label": previousLabel,
|
|
87
|
+
onClick: () => step(-1),
|
|
88
|
+
className: "absolute top-1/2 left-2 flex size-8 -translate-y-1/2 items-center justify-center rounded-full bg-app/80 text-ink backdrop-blur transition-colors duration-(--duration-fast) ease-out hover:bg-app",
|
|
89
|
+
children: /* @__PURE__ */ jsx4(BackIcon, { size: "sm" })
|
|
90
|
+
}
|
|
91
|
+
),
|
|
92
|
+
/* @__PURE__ */ jsx4(
|
|
93
|
+
"button",
|
|
94
|
+
{
|
|
95
|
+
type: "button",
|
|
96
|
+
"aria-label": nextLabel,
|
|
97
|
+
onClick: () => step(1),
|
|
98
|
+
className: "absolute top-1/2 right-2 flex size-8 -translate-y-1/2 items-center justify-center rounded-full bg-app/80 text-ink backdrop-blur transition-colors duration-(--duration-fast) ease-out hover:bg-app",
|
|
99
|
+
children: /* @__PURE__ */ jsx4(BackIcon, { size: "sm", className: "rotate-180" })
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ jsx4("span", { className: "absolute right-2 bottom-2 rounded-sm bg-app/80 px-2 py-1 text-2xs text-ink-soft backdrop-blur", children: photoLabel(current + 1, total) })
|
|
103
|
+
] }) : null
|
|
104
|
+
] }),
|
|
105
|
+
total > 1 ? /* @__PURE__ */ jsx4("div", { className: "flex touch-pan-y gap-2 overflow-x-auto overscroll-contain", children: photos.map((photo, i) => /* @__PURE__ */ jsx4(
|
|
106
|
+
"button",
|
|
107
|
+
{
|
|
108
|
+
type: "button",
|
|
109
|
+
"aria-label": photoLabel(i + 1, total),
|
|
110
|
+
"aria-current": i === current,
|
|
111
|
+
onClick: () => setIndex(i),
|
|
112
|
+
className: `w-20 shrink-0 overflow-hidden rounded-sm transition-opacity duration-(--duration-fast) ease-out ${i === current ? "ring-2 ring-accent" : "opacity-60 hover:opacity-100"}`,
|
|
113
|
+
children: /* @__PURE__ */ jsx4(Thumb, { src: photo, alt, fallback })
|
|
114
|
+
},
|
|
115
|
+
photo
|
|
116
|
+
)) }) : null
|
|
117
|
+
] });
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
Gallery
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=gallery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/molecules/gallery/index.tsx","../../src/atoms/icon/svg/index.tsx","../../src/atoms/icon/back-icon/index.tsx","../../src/atoms/thumb/index.tsx","../../src/molecules/gallery/locales.ts"],"sourcesContent":["\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\n\nimport { BackIcon, Thumb } from \"../../atoms\";\nimport { GALLERY as T } from \"./locales\";\n\nexport function Gallery({\n photos,\n alt,\n fallback,\n photoLabel = T.photoLabel,\n previousLabel = T.previous,\n nextLabel = T.next,\n className = \"\",\n}: {\n photos: string[];\n alt: string;\n fallback?: ReactNode;\n photoLabel?: (index: number, total: number) => string;\n previousLabel?: string;\n nextLabel?: string;\n className?: string;\n}) {\n const [index, setIndex] = useState(0);\n\n const total = photos.length;\n const current = Math.min(index, Math.max(0, total - 1));\n const step = (delta: number) => setIndex((current + delta + total) % total);\n\n return (\n <div className={`flex flex-col gap-2 ${className}`}>\n <div className=\"relative\">\n <Thumb src={photos[current] ?? null} alt={alt} fallback={fallback} />\n\n {total > 1 ? (\n <>\n <button\n type=\"button\"\n aria-label={previousLabel}\n onClick={() => step(-1)}\n className=\"absolute top-1/2 left-2 flex size-8 -translate-y-1/2 items-center justify-center rounded-full bg-app/80 text-ink backdrop-blur transition-colors duration-(--duration-fast) ease-out hover:bg-app\"\n >\n <BackIcon size=\"sm\" />\n </button>\n\n <button\n type=\"button\"\n aria-label={nextLabel}\n onClick={() => step(1)}\n className=\"absolute top-1/2 right-2 flex size-8 -translate-y-1/2 items-center justify-center rounded-full bg-app/80 text-ink backdrop-blur transition-colors duration-(--duration-fast) ease-out hover:bg-app\"\n >\n <BackIcon size=\"sm\" className=\"rotate-180\" />\n </button>\n\n <span className=\"absolute right-2 bottom-2 rounded-sm bg-app/80 px-2 py-1 text-2xs text-ink-soft backdrop-blur\">\n {photoLabel(current + 1, total)}\n </span>\n </>\n ) : null}\n </div>\n\n {total > 1 ? (\n <div className=\"flex touch-pan-y gap-2 overflow-x-auto overscroll-contain\">\n {photos.map((photo, i) => (\n <button\n key={photo}\n type=\"button\"\n aria-label={photoLabel(i + 1, total)}\n aria-current={i === current}\n onClick={() => setIndex(i)}\n className={`w-20 shrink-0 overflow-hidden rounded-sm transition-opacity duration-(--duration-fast) ease-out ${\n i === current ? \"ring-2 ring-accent\" : \"opacity-60 hover:opacity-100\"\n }`}\n >\n <Thumb src={photo} alt={alt} fallback={fallback} />\n </button>\n ))}\n </div>\n ) : null}\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 BackIcon(p: Props) {\n return (\n <Svg {...p}>\n <path d=\"m14 6-6 6 6 6\" />\n </Svg>\n );\n}\n","import type { ReactNode } from \"react\";\n\nexport function Thumb({\n src,\n alt,\n fallback,\n className = \"\",\n}: {\n src?: string | null;\n alt: string;\n fallback?: ReactNode;\n className?: string;\n}) {\n return (\n <div\n className={`flex aspect-[4/3] items-center justify-center overflow-hidden rounded-md bg-raised text-ink-dim ${className}`}\n >\n {src ? (\n <img src={src} alt={alt} loading=\"lazy\" className=\"h-full w-full object-cover\" />\n ) : (\n fallback\n )}\n </div>\n );\n}\n","export const GALLERY = {\n photoLabel: (index: number, total: number) => `Photo ${index} of ${total}`,\n previous: \"Previous photo\",\n next: \"Next photo\",\n};\n"],"mappings":"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","names":["jsx","jsx","jsx"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
declare const ALIGNS: {
|
|
5
|
+
readonly start: "left-0";
|
|
6
|
+
readonly end: "right-0";
|
|
7
|
+
};
|
|
8
|
+
declare const WIDTHS: {
|
|
9
|
+
readonly sm: "w-48";
|
|
10
|
+
readonly md: "w-56";
|
|
11
|
+
readonly lg: "w-72";
|
|
12
|
+
};
|
|
13
|
+
type MenuAlign = keyof typeof ALIGNS;
|
|
14
|
+
type MenuWidth = keyof typeof WIDTHS;
|
|
15
|
+
declare function Menu({ label, trigger, align, width, className, children, }: {
|
|
16
|
+
label: string;
|
|
17
|
+
trigger: ReactNode;
|
|
18
|
+
align?: MenuAlign;
|
|
19
|
+
width?: MenuWidth;
|
|
20
|
+
className?: string;
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
}): react.JSX.Element;
|
|
23
|
+
|
|
24
|
+
export { Menu, type MenuAlign, type MenuWidth };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/molecules/menu/menu/index.tsx
|
|
5
|
+
import {
|
|
6
|
+
Children,
|
|
7
|
+
cloneElement,
|
|
8
|
+
isValidElement,
|
|
9
|
+
useEffect,
|
|
10
|
+
useRef,
|
|
11
|
+
useState
|
|
12
|
+
} from "react";
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
var ALIGNS = {
|
|
15
|
+
start: "left-0",
|
|
16
|
+
end: "right-0"
|
|
17
|
+
};
|
|
18
|
+
var WIDTHS = {
|
|
19
|
+
sm: "w-48",
|
|
20
|
+
md: "w-56",
|
|
21
|
+
lg: "w-72"
|
|
22
|
+
};
|
|
23
|
+
function Menu({
|
|
24
|
+
label,
|
|
25
|
+
trigger,
|
|
26
|
+
align = "end",
|
|
27
|
+
width = "md",
|
|
28
|
+
className = "",
|
|
29
|
+
children
|
|
30
|
+
}) {
|
|
31
|
+
const [open, setOpen] = useState(false);
|
|
32
|
+
const root = useRef(null);
|
|
33
|
+
const triggerRef = useRef(null);
|
|
34
|
+
const menuRef = useRef(null);
|
|
35
|
+
const items = Children.toArray(children).map(
|
|
36
|
+
(child) => isValidElement(child) ? cloneElement(child, {
|
|
37
|
+
role: "menuitem",
|
|
38
|
+
tabIndex: -1
|
|
39
|
+
}) : child
|
|
40
|
+
);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!open) return;
|
|
43
|
+
function onPointerDown(event) {
|
|
44
|
+
if (!root.current?.contains(event.target)) setOpen(false);
|
|
45
|
+
}
|
|
46
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
47
|
+
return () => {
|
|
48
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
49
|
+
};
|
|
50
|
+
}, [open]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!open) return;
|
|
53
|
+
menuRef.current?.querySelector('[role="menuitem"]')?.focus();
|
|
54
|
+
}, [open]);
|
|
55
|
+
function onMenuKeyDown(event) {
|
|
56
|
+
const menuItems = [
|
|
57
|
+
...menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []
|
|
58
|
+
];
|
|
59
|
+
const current = menuItems.indexOf(document.activeElement);
|
|
60
|
+
const next = (index) => menuItems[(index + menuItems.length) % menuItems.length]?.focus();
|
|
61
|
+
if (event.key === "Escape") {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
setOpen(false);
|
|
64
|
+
triggerRef.current?.focus();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (event.key === "ArrowDown") {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
next(current + 1);
|
|
70
|
+
}
|
|
71
|
+
if (event.key === "ArrowUp") {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
next(current - 1);
|
|
74
|
+
}
|
|
75
|
+
if (event.key === "Home") {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
menuItems[0]?.focus();
|
|
78
|
+
}
|
|
79
|
+
if (event.key === "End") {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
menuItems.at(-1)?.focus();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return /* @__PURE__ */ jsxs("div", { ref: root, className: `relative ${className}`, children: [
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
"button",
|
|
87
|
+
{
|
|
88
|
+
ref: triggerRef,
|
|
89
|
+
type: "button",
|
|
90
|
+
"aria-label": label,
|
|
91
|
+
"aria-haspopup": "menu",
|
|
92
|
+
"aria-expanded": open,
|
|
93
|
+
onClick: () => setOpen((current) => !current),
|
|
94
|
+
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",
|
|
95
|
+
children: trigger
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
open ? /* @__PURE__ */ jsx(
|
|
99
|
+
"div",
|
|
100
|
+
{
|
|
101
|
+
ref: menuRef,
|
|
102
|
+
role: "menu",
|
|
103
|
+
"aria-label": label,
|
|
104
|
+
onClick: () => setOpen(false),
|
|
105
|
+
onKeyDown: onMenuKeyDown,
|
|
106
|
+
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]}`,
|
|
107
|
+
children: items
|
|
108
|
+
}
|
|
109
|
+
) : null
|
|
110
|
+
] });
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
Menu
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=menu.js.map
|