@poodle-kit/ui 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,54 +20,996 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- Button: () => Button
23
+ Button: () => Button,
24
+ ImageUploader: () => ImageUploader,
25
+ Input: () => Input,
26
+ InputMessage: () => InputMessage,
27
+ ThemeProvider: () => ThemeProvider,
28
+ buttonVariants: () => buttonVariants,
29
+ cn: () => cn,
30
+ defaultDarkTheme: () => defaultDarkTheme,
31
+ defaultLightTheme: () => defaultLightTheme,
32
+ defineTheme: () => defineTheme,
33
+ generateThemeCss: () => generateThemeCss,
34
+ imageUploaderVariants: () => imageUploaderVariants,
35
+ inputVariants: () => inputVariants,
36
+ useImageUploader: () => useImageUploader,
37
+ useTheme: () => useTheme
24
38
  });
25
39
  module.exports = __toCommonJS(index_exports);
26
40
 
27
- // src/components/Button.tsx
28
- var import_jsx_runtime = require("react/jsx-runtime");
29
- var base = "inline-flex items-center justify-center rounded-lg font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
30
- var sizes = {
31
- sm: "h-8 px-3 text-sm",
32
- md: "h-10 px-4 text-sm",
33
- lg: "h-12 px-5 text-base"
34
- };
35
- var variants = {
36
- primary: "bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-600 shadow-sm",
37
- secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-400 border border-gray-200",
38
- ghost: "bg-transparent text-gray-900 hover:bg-gray-100 focus-visible:ring-gray-400",
39
- danger: "bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-600 shadow-sm"
40
- };
41
- function cx(...classes) {
42
- return classes.filter(Boolean).join(" ");
41
+ // src/components/button/button.tsx
42
+ var import_react = require("react");
43
+ var import_react_slot = require("@radix-ui/react-slot");
44
+ var import_class_variance_authority = require("class-variance-authority");
45
+
46
+ // src/lib/cn.ts
47
+ var import_clsx = require("clsx");
48
+ var import_tailwind_merge = require("tailwind-merge");
49
+ function cn(...inputs) {
50
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
43
51
  }
44
- function Button({
45
- label,
52
+
53
+ // src/components/button/button.tsx
54
+ var import_jsx_runtime = require("react/jsx-runtime");
55
+ var buttonVariants = (0, import_class_variance_authority.cva)(
56
+ "inline-flex items-center justify-center rounded-[var(--radius-md)] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
57
+ {
58
+ variants: {
59
+ variant: {
60
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
61
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
62
+ info: "bg-info text-info-foreground hover:bg-info/90",
63
+ danger: "bg-danger text-danger-foreground hover:bg-danger/90",
64
+ warning: "bg-warning text-warning-foreground hover:bg-warning/90",
65
+ success: "bg-success text-success-foreground hover:bg-success/90",
66
+ ghost: "hover:bg-accent hover:text-accent-foreground",
67
+ outline: "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
68
+ link: "text-primary underline-offset-4 hover:underline"
69
+ },
70
+ size: {
71
+ default: "h-10 px-4 py-2",
72
+ sm: "h-9 rounded-[var(--radius-md)] px-3",
73
+ lg: "h-11 rounded-[var(--radius-md)] px-8",
74
+ icon: "h-10 w-10"
75
+ }
76
+ },
77
+ defaultVariants: {
78
+ variant: "default",
79
+ size: "default"
80
+ }
81
+ }
82
+ );
83
+ var Button = (0, import_react.forwardRef)(
84
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
85
+ const Comp = asChild ? import_react_slot.Slot : "button";
86
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
87
+ Comp,
88
+ {
89
+ className: cn(buttonVariants({ variant, size, className })),
90
+ ref,
91
+ type: "button",
92
+ ...props
93
+ }
94
+ );
95
+ }
96
+ );
97
+ Button.displayName = "Button";
98
+
99
+ // src/components/input/input.tsx
100
+ var import_react2 = require("react");
101
+ var import_class_variance_authority2 = require("class-variance-authority");
102
+ var import_jsx_runtime2 = require("react/jsx-runtime");
103
+ var inputVariants = (0, import_class_variance_authority2.cva)(
104
+ "flex w-full rounded-[var(--radius-md)] border border-input bg-background text-sm placeholder:text-muted-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
105
+ {
106
+ variants: {
107
+ size: {
108
+ default: "h-10 px-3 py-2",
109
+ sm: "h-9 px-2 text-xs",
110
+ lg: "h-11 px-4"
111
+ },
112
+ state: {
113
+ default: "focus-visible:ring-ring",
114
+ error: "border-danger focus-visible:ring-danger",
115
+ success: "border-success focus-visible:ring-success"
116
+ }
117
+ },
118
+ defaultVariants: {
119
+ size: "default",
120
+ state: "default"
121
+ }
122
+ }
123
+ );
124
+ var Input = (0, import_react2.forwardRef)(
125
+ ({
126
+ className,
127
+ inputClassName,
128
+ size,
129
+ error,
130
+ success,
131
+ label,
132
+ id,
133
+ leftIcon,
134
+ rightIcon,
135
+ onLeftIconClick,
136
+ onRightIconClick,
137
+ required,
138
+ disabled,
139
+ ...props
140
+ }, ref) => {
141
+ const state = error ? "error" : success ? "success" : "default";
142
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("flex flex-col gap-1", className), children: [
143
+ label && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
144
+ "label",
145
+ {
146
+ htmlFor: id,
147
+ className: cn(
148
+ "text-sm font-medium",
149
+ disabled && "opacity-50"
150
+ ),
151
+ children: [
152
+ label,
153
+ required && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-1 text-danger", "aria-label": "\uD544\uC218", children: "*" })
154
+ ]
155
+ }
156
+ ),
157
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative", children: [
158
+ leftIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
159
+ "button",
160
+ {
161
+ type: "button",
162
+ disabled: !onLeftIconClick,
163
+ "aria-label": "left icon",
164
+ className: cn(
165
+ "absolute left-3 top-1/2 z-10 -translate-y-1/2",
166
+ onLeftIconClick ? "cursor-pointer" : "pointer-events-none cursor-default",
167
+ disabled && "opacity-50"
168
+ ),
169
+ onClick: onLeftIconClick,
170
+ children: leftIcon
171
+ }
172
+ ),
173
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
174
+ "input",
175
+ {
176
+ id,
177
+ ref,
178
+ required,
179
+ disabled,
180
+ "aria-invalid": error || void 0,
181
+ className: cn(
182
+ inputVariants({ size, state }),
183
+ // 아이콘이 있을 때 텍스트가 아이콘에 가리지 않도록 패딩 추가
184
+ leftIcon && "pl-9",
185
+ rightIcon && "pr-9",
186
+ inputClassName
187
+ ),
188
+ ...props
189
+ }
190
+ ),
191
+ rightIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
192
+ "button",
193
+ {
194
+ type: "button",
195
+ disabled: !onRightIconClick,
196
+ "aria-label": "right icon",
197
+ className: cn(
198
+ "absolute right-3 top-1/2 z-10 -translate-y-1/2",
199
+ onRightIconClick ? "cursor-pointer" : "pointer-events-none cursor-default",
200
+ disabled && "opacity-50"
201
+ ),
202
+ onClick: onRightIconClick,
203
+ children: rightIcon
204
+ }
205
+ )
206
+ ] })
207
+ ] });
208
+ }
209
+ );
210
+ Input.displayName = "Input";
211
+
212
+ // src/components/input/input-message.tsx
213
+ var import_jsx_runtime3 = require("react/jsx-runtime");
214
+ function InputMessage({
46
215
  children,
47
- variant = "primary",
48
- size = "md",
49
- fullWidth = false,
50
- className,
51
- ...rest
216
+ error,
217
+ className
218
+ }) {
219
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
220
+ "p",
221
+ {
222
+ className: cn(
223
+ "text-xs",
224
+ error ? "text-danger" : "text-muted-foreground",
225
+ className
226
+ ),
227
+ children
228
+ }
229
+ );
230
+ }
231
+
232
+ // src/components/image-uploader/image-uploader.tsx
233
+ var import_react4 = require("react");
234
+ var import_class_variance_authority3 = require("class-variance-authority");
235
+
236
+ // src/icons/index.tsx
237
+ var import_jsx_runtime4 = require("react/jsx-runtime");
238
+ function createIcon(paths, defaultSize = 24) {
239
+ return function Icon({
240
+ width = defaultSize,
241
+ height = defaultSize,
242
+ ...props
243
+ }) {
244
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
245
+ "svg",
246
+ {
247
+ xmlns: "http://www.w3.org/2000/svg",
248
+ width,
249
+ height,
250
+ viewBox: "0 0 24 24",
251
+ fill: "none",
252
+ stroke: "currentColor",
253
+ strokeWidth: "2",
254
+ strokeLinecap: "round",
255
+ strokeLinejoin: "round",
256
+ "aria-hidden": "true",
257
+ ...props,
258
+ children: paths
259
+ }
260
+ );
261
+ };
262
+ }
263
+ var PlusIcon = createIcon(
264
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
265
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M5 12h14" }),
266
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 5v14" })
267
+ ] })
268
+ );
269
+ var XIcon = createIcon(
270
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M18 6 6 18" }),
272
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 6 12 12" })
273
+ ] }),
274
+ 12
275
+ );
276
+
277
+ // src/components/image-uploader/use-image-uploader.ts
278
+ var import_react3 = require("react");
279
+ function matchesAccept(file, accept) {
280
+ return accept.split(",").map((s) => s.trim().toLowerCase()).some((pattern) => {
281
+ if (pattern === "*" || pattern === "*/*") return true;
282
+ if (pattern.startsWith("."))
283
+ return file.name.toLowerCase().endsWith(pattern);
284
+ if (pattern.endsWith("/*"))
285
+ return file.type.startsWith(pattern.slice(0, -2));
286
+ return file.type === pattern;
287
+ });
288
+ }
289
+ function useImageUploader({
290
+ initialImages,
291
+ maxImages = 4,
292
+ maxSizeMb,
293
+ accept = "image/*",
294
+ onFileSelect,
295
+ onDeleteExisting,
296
+ onError
52
297
  }) {
53
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
298
+ const [newImages, setNewImages] = (0, import_react3.useState)([]);
299
+ const [deletedIds, setDeletedIds] = (0, import_react3.useState)([]);
300
+ const [isDragActive, setIsDragActive] = (0, import_react3.useState)(false);
301
+ const inputRef = (0, import_react3.useRef)(null);
302
+ const onFileSelectRef = (0, import_react3.useRef)(onFileSelect);
303
+ const onDeleteExistingRef = (0, import_react3.useRef)(onDeleteExisting);
304
+ const onErrorRef = (0, import_react3.useRef)(onError);
305
+ (0, import_react3.useEffect)(() => {
306
+ onFileSelectRef.current = onFileSelect;
307
+ onDeleteExistingRef.current = onDeleteExisting;
308
+ onErrorRef.current = onError;
309
+ });
310
+ const newImagesRef = (0, import_react3.useRef)([]);
311
+ const deletedIdsRef = (0, import_react3.useRef)([]);
312
+ const existingCountRef = (0, import_react3.useRef)(0);
313
+ const existingImages = (0, import_react3.useMemo)(
314
+ () => (initialImages != null ? initialImages : []).filter(
315
+ (img) => !deletedIds.includes(img.id)
316
+ ),
317
+ [initialImages, deletedIds]
318
+ );
319
+ (0, import_react3.useEffect)(() => {
320
+ newImagesRef.current = newImages;
321
+ }, [newImages]);
322
+ (0, import_react3.useEffect)(() => {
323
+ deletedIdsRef.current = deletedIds;
324
+ }, [deletedIds]);
325
+ (0, import_react3.useEffect)(() => {
326
+ existingCountRef.current = existingImages.length;
327
+ }, [existingImages.length]);
328
+ (0, import_react3.useEffect)(() => {
329
+ return () => {
330
+ newImagesRef.current.forEach(
331
+ (item) => URL.revokeObjectURL(item.preview)
332
+ );
333
+ };
334
+ }, []);
335
+ const processFiles = (0, import_react3.useCallback)(
336
+ (files) => {
337
+ const currentTotal = existingCountRef.current + newImagesRef.current.length;
338
+ const onErr = onErrorRef.current;
339
+ const onSelect = onFileSelectRef.current;
340
+ if (currentTotal >= maxImages) {
341
+ onErr == null ? void 0 : onErr({
342
+ type: "MAX_IMAGES",
343
+ message: `\uC774\uBBF8\uC9C0\uB294 \uCD5C\uB300 ${maxImages}\uC7A5\uAE4C\uC9C0 \uCD94\uAC00\uD560 \uC218 \uC788\uC5B4\uC694.`,
344
+ maxImages
345
+ });
346
+ return;
347
+ }
348
+ const remainingSlots = maxImages - currentTotal;
349
+ if (files.length > remainingSlots) {
350
+ onErr == null ? void 0 : onErr({
351
+ type: "MAX_IMAGES",
352
+ message: `\uC774\uBBF8\uC9C0\uB294 \uCD5C\uB300 ${maxImages}\uC7A5\uAE4C\uC9C0 \uCD94\uAC00\uD560 \uC218 \uC788\uC5B4\uC694.`,
353
+ maxImages
354
+ });
355
+ }
356
+ const validFiles = [];
357
+ for (const file of files.slice(0, remainingSlots)) {
358
+ if (!matchesAccept(file, accept)) {
359
+ onErr == null ? void 0 : onErr({
360
+ type: "INVALID_TYPE",
361
+ message: `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC774\uC5D0\uC694.`,
362
+ file,
363
+ accept
364
+ });
365
+ continue;
366
+ }
367
+ if (maxSizeMb !== void 0 && file.size > maxSizeMb * 1024 * 1024) {
368
+ onErr == null ? void 0 : onErr({
369
+ type: "MAX_SIZE",
370
+ message: `${file.name}\uC758 \uD06C\uAE30\uAC00 ${maxSizeMb}MB\uB97C \uCD08\uACFC\uD574\uC694.`,
371
+ maxSizeMb,
372
+ file
373
+ });
374
+ continue;
375
+ }
376
+ validFiles.push({
377
+ file,
378
+ preview: URL.createObjectURL(file),
379
+ id: crypto.randomUUID()
380
+ });
381
+ }
382
+ if (validFiles.length === 0) return;
383
+ const next = [...newImagesRef.current, ...validFiles];
384
+ newImagesRef.current = next;
385
+ setNewImages(next);
386
+ onSelect == null ? void 0 : onSelect(next.map((item) => item.file));
387
+ },
388
+ [maxImages, accept, maxSizeMb]
389
+ );
390
+ const removeExisting = (0, import_react3.useCallback)((id) => {
391
+ var _a;
392
+ const next = [...deletedIdsRef.current, id];
393
+ deletedIdsRef.current = next;
394
+ setDeletedIds(next);
395
+ (_a = onDeleteExistingRef.current) == null ? void 0 : _a.call(onDeleteExistingRef, next);
396
+ }, []);
397
+ const removeNew = (0, import_react3.useCallback)((id) => {
398
+ var _a;
399
+ const current = newImagesRef.current;
400
+ const toRemove = current.find((item) => item.id === id);
401
+ if (toRemove) URL.revokeObjectURL(toRemove.preview);
402
+ const next = current.filter((item) => item.id !== id);
403
+ newImagesRef.current = next;
404
+ setNewImages(next);
405
+ (_a = onFileSelectRef.current) == null ? void 0 : _a.call(onFileSelectRef, next.map((item) => item.file));
406
+ }, []);
407
+ const handleChange = (0, import_react3.useCallback)(
408
+ (e) => {
409
+ if (!e.target.files) return;
410
+ processFiles(Array.from(e.target.files));
411
+ e.target.value = "";
412
+ },
413
+ [processFiles]
414
+ );
415
+ const handleDragOver = (0, import_react3.useCallback)((e) => {
416
+ e.preventDefault();
417
+ e.stopPropagation();
418
+ setIsDragActive(true);
419
+ }, []);
420
+ const handleDragLeave = (0, import_react3.useCallback)((e) => {
421
+ e.preventDefault();
422
+ e.stopPropagation();
423
+ if (e.currentTarget.contains(e.relatedTarget)) return;
424
+ setIsDragActive(false);
425
+ }, []);
426
+ const handleDrop = (0, import_react3.useCallback)(
427
+ (e) => {
428
+ e.preventDefault();
429
+ e.stopPropagation();
430
+ setIsDragActive(false);
431
+ processFiles(Array.from(e.dataTransfer.files));
432
+ },
433
+ [processFiles]
434
+ );
435
+ return {
436
+ existingImages,
437
+ newImages,
438
+ totalCount: existingImages.length + newImages.length,
439
+ isDragActive,
440
+ inputRef,
441
+ openFilePicker: () => {
442
+ var _a;
443
+ return (_a = inputRef.current) == null ? void 0 : _a.click();
444
+ },
445
+ removeExisting,
446
+ removeNew,
447
+ dragHandlers: {
448
+ onDragOver: handleDragOver,
449
+ onDragLeave: handleDragLeave,
450
+ onDrop: handleDrop
451
+ },
452
+ inputProps: {
453
+ onChange: handleChange,
454
+ accept,
455
+ multiple: true
456
+ }
457
+ };
458
+ }
459
+
460
+ // src/components/image-uploader/image-uploader.tsx
461
+ var import_jsx_runtime5 = require("react/jsx-runtime");
462
+ var imageUploaderVariants = (0, import_class_variance_authority3.cva)("flex gap-2", {
463
+ variants: {
464
+ layout: {
465
+ /** 가로 스크롤 행 (기본값) */
466
+ row: "flex-row flex-nowrap overflow-x-auto",
467
+ /** 자동 줄바꿈 격자 */
468
+ grid: "flex-wrap"
469
+ }
470
+ },
471
+ defaultVariants: { layout: "row" }
472
+ });
473
+ function AddButton({
474
+ onClick,
475
+ disabled,
476
+ current,
477
+ max,
478
+ children
479
+ }) {
480
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
54
481
  "button",
55
482
  {
56
483
  type: "button",
57
- className: cx(
58
- base,
59
- sizes[size],
60
- variants[variant],
61
- fullWidth && "w-full",
62
- className
484
+ onClick,
485
+ disabled,
486
+ "aria-label": `\uC774\uBBF8\uC9C0 \uCD94\uAC00 (${current}/${max})`,
487
+ className: cn(
488
+ "shrink-0 w-20 h-20",
489
+ "flex flex-col items-center justify-center gap-1",
490
+ "rounded-lg",
491
+ "border-2 border-dashed border-border",
492
+ "bg-muted text-muted-foreground",
493
+ "cursor-pointer select-none",
494
+ "transition-colors duration-150",
495
+ "hover:bg-accent hover:text-accent-foreground hover:border-border",
496
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
497
+ "disabled:pointer-events-none disabled:opacity-50"
498
+ ),
499
+ children: children != null ? children : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PlusIcon, { className: "w-5 h-5" }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-[10px] font-medium leading-none tabular-nums", children: [
502
+ current,
503
+ "/",
504
+ max
505
+ ] })
506
+ ] })
507
+ }
508
+ );
509
+ }
510
+ function ImageItem({ src, alt, onRemove, disabled }) {
511
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative shrink-0 w-20 h-20 group/item", children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
513
+ "img",
514
+ {
515
+ src,
516
+ alt,
517
+ draggable: false,
518
+ className: "w-full h-full object-cover rounded-lg"
519
+ }
520
+ ),
521
+ !disabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
522
+ "button",
523
+ {
524
+ type: "button",
525
+ onClick: onRemove,
526
+ "aria-label": `${alt} \uC0AD\uC81C`,
527
+ className: cn(
528
+ "absolute -top-1.5 -right-1.5",
529
+ "w-5 h-5 rounded-full",
530
+ "flex items-center justify-center",
531
+ "bg-foreground text-background",
532
+ "transition-all duration-150",
533
+ "opacity-0 scale-75",
534
+ "group-hover/item:opacity-100 group-hover/item:scale-100",
535
+ "group-focus-within/item:opacity-100 group-focus-within/item:scale-100",
536
+ "hover:scale-110 active:scale-90",
537
+ "focus-visible:opacity-100 focus-visible:scale-100",
538
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
539
+ ),
540
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { strokeWidth: "2.5" })
541
+ }
542
+ )
543
+ ] });
544
+ }
545
+ var ImageUploader = (0, import_react4.forwardRef)(
546
+ ({
547
+ className,
548
+ layout,
549
+ disabled,
550
+ placeholder,
551
+ initialImages,
552
+ maxImages = 4,
553
+ maxSizeMb,
554
+ accept,
555
+ onFileSelect,
556
+ onDeleteExisting,
557
+ onError
558
+ }, ref) => {
559
+ const uid = (0, import_react4.useId)();
560
+ const {
561
+ existingImages,
562
+ newImages,
563
+ totalCount,
564
+ isDragActive,
565
+ inputRef,
566
+ openFilePicker,
567
+ removeExisting,
568
+ removeNew,
569
+ dragHandlers,
570
+ inputProps
571
+ } = useImageUploader({
572
+ initialImages,
573
+ maxImages,
574
+ maxSizeMb,
575
+ accept,
576
+ onFileSelect,
577
+ onDeleteExisting,
578
+ onError
579
+ });
580
+ const canAdd = !disabled && totalCount < maxImages;
581
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref, className: cn("flex flex-col gap-2", className), children: [
582
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
583
+ "div",
584
+ {
585
+ role: "status",
586
+ "aria-live": "polite",
587
+ "aria-atomic": "true",
588
+ className: "sr-only",
589
+ children: totalCount > 0 ? `\uC774\uBBF8\uC9C0 ${totalCount}\uAC1C \uC120\uD0DD\uB428. \uCD5C\uB300 ${maxImages}\uAC1C\uAE4C\uC9C0 \uCD94\uAC00\uD560 \uC218 \uC788\uC5B4\uC694.` : `\uC774\uBBF8\uC9C0\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694. \uCD5C\uB300 ${maxImages}\uAC1C\uAE4C\uC9C0 \uCD94\uAC00\uD560 \uC218 \uC788\uC5B4\uC694.`
590
+ }
591
+ ),
592
+ isDragActive && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { role: "alert", className: "sr-only", children: "\uD30C\uC77C\uC744 \uC5EC\uAE30\uC5D0 \uB193\uC544\uC8FC\uC138\uC694" }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
594
+ "input",
595
+ {
596
+ ref: inputRef,
597
+ id: `${uid}-input`,
598
+ type: "file",
599
+ tabIndex: -1,
600
+ "aria-hidden": "true",
601
+ className: "sr-only",
602
+ disabled,
603
+ ...inputProps
604
+ }
63
605
  ),
64
- ...rest,
65
- children: children || label
606
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
607
+ "div",
608
+ {
609
+ role: "group",
610
+ "aria-label": `\uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB354, ${totalCount}/${maxImages}\uAC1C \uC120\uD0DD\uB428`,
611
+ className: cn(
612
+ imageUploaderVariants({ layout }),
613
+ "rounded-lg p-1 -ml-1",
614
+ "transition-all duration-200",
615
+ isDragActive && "ring-2 ring-primary ring-offset-2 bg-primary/5"
616
+ ),
617
+ ...canAdd ? dragHandlers : {},
618
+ children: [
619
+ canAdd && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
620
+ AddButton,
621
+ {
622
+ onClick: openFilePicker,
623
+ disabled,
624
+ current: totalCount,
625
+ max: maxImages,
626
+ children: placeholder
627
+ }
628
+ ),
629
+ sortBySequence(existingImages).map((img, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
630
+ ImageItem,
631
+ {
632
+ src: img.url,
633
+ alt: `\uC774\uBBF8\uC9C0 ${index + 1}`,
634
+ onRemove: () => removeExisting(img.id),
635
+ disabled
636
+ },
637
+ `existing-${img.id}`
638
+ )),
639
+ newImages.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
640
+ ImageItem,
641
+ {
642
+ src: item.preview,
643
+ alt: `\uC0C8 \uC774\uBBF8\uC9C0 ${existingImages.length + index + 1}`,
644
+ onRemove: () => removeNew(item.id),
645
+ disabled
646
+ },
647
+ `new-${item.id}`
648
+ ))
649
+ ]
650
+ }
651
+ )
652
+ ] });
653
+ }
654
+ );
655
+ ImageUploader.displayName = "ImageUploader";
656
+ function sortBySequence(images) {
657
+ return [...images].sort(
658
+ (a, b) => {
659
+ var _a, _b;
660
+ return ((_a = a.sequence) != null ? _a : 0) - ((_b = b.sequence) != null ? _b : 0);
661
+ }
662
+ );
663
+ }
664
+
665
+ // src/theme/provider.tsx
666
+ var import_react5 = require("react");
667
+
668
+ // src/theme/theme-to-css-vars.ts
669
+ function themeToCssVars(theme) {
670
+ const cssVars = {};
671
+ if (theme.colors) {
672
+ Object.entries(theme.colors).forEach(([key, value]) => {
673
+ if (typeof value === "string") {
674
+ cssVars[`--color-${key}`] = value;
675
+ } else if (typeof value === "object" && value !== null) {
676
+ if ("DEFAULT" in value) {
677
+ cssVars[`--color-${key}`] = value.DEFAULT;
678
+ }
679
+ if ("foreground" in value) {
680
+ cssVars[`--color-${key}-foreground`] = value.foreground;
681
+ }
682
+ }
683
+ });
684
+ }
685
+ if (theme.radius) {
686
+ Object.entries(theme.radius).forEach(([key, value]) => {
687
+ cssVars[`--radius-${key}`] = value;
688
+ });
689
+ }
690
+ if (theme.fontSize) {
691
+ Object.entries(theme.fontSize).forEach(([key, value]) => {
692
+ if (Array.isArray(value)) {
693
+ const [size, config] = value;
694
+ cssVars[`--text-${key}`] = size;
695
+ if (config.lineHeight) {
696
+ cssVars[`--leading-${key}`] = config.lineHeight;
697
+ }
698
+ } else {
699
+ cssVars[`--text-${key}`] = value;
700
+ }
701
+ });
702
+ }
703
+ if (theme.fontFamily) {
704
+ Object.entries(theme.fontFamily).forEach(([key, value]) => {
705
+ cssVars[`--font-${key}`] = value.join(", ");
706
+ });
707
+ }
708
+ if (theme.fontWeight) {
709
+ Object.entries(theme.fontWeight).forEach(([key, value]) => {
710
+ cssVars[`--font-weight-${key}`] = value;
711
+ });
712
+ }
713
+ if (theme.shadows) {
714
+ Object.entries(theme.shadows).forEach(([key, value]) => {
715
+ cssVars[`--shadow-${key}`] = value;
716
+ });
717
+ }
718
+ if (theme.zIndex) {
719
+ Object.entries(theme.zIndex).forEach(([key, value]) => {
720
+ cssVars[`--z-${key}`] = value.toString();
721
+ });
722
+ }
723
+ if (theme.animation) {
724
+ if (theme.animation.duration) {
725
+ Object.entries(theme.animation.duration).forEach(
726
+ ([key, value]) => {
727
+ cssVars[`--duration-${key}`] = value;
728
+ }
729
+ );
730
+ }
731
+ if (theme.animation.ease) {
732
+ Object.entries(theme.animation.ease).forEach(([key, value]) => {
733
+ cssVars[`--ease-${key}`] = value;
734
+ });
735
+ }
736
+ }
737
+ if (theme.spacing) {
738
+ Object.entries(theme.spacing).forEach(([key, value]) => {
739
+ cssVars[`--spacing-${key}`] = value;
740
+ });
741
+ }
742
+ return cssVars;
743
+ }
744
+ function applyCssVars(element, cssVars) {
745
+ Object.entries(cssVars).forEach(([key, value]) => {
746
+ element.style.setProperty(key, value);
747
+ });
748
+ }
749
+ function formatCssVars(cssVars) {
750
+ return Object.entries(cssVars).map(([key, value]) => ` ${key}: ${value};`).join("\n");
751
+ }
752
+ function generateThemeCss(theme) {
753
+ const cssVars = themeToCssVars(theme);
754
+ return formatCssVars(cssVars);
755
+ }
756
+
757
+ // src/theme/provider.tsx
758
+ var import_jsx_runtime6 = require("react/jsx-runtime");
759
+ var ThemeContext = (0, import_react5.createContext)(void 0);
760
+ function ThemeProvider({
761
+ children,
762
+ defaultTheme = "system",
763
+ storageKey = "poodle-ui-theme",
764
+ config,
765
+ darkConfig,
766
+ customThemes = {},
767
+ enableColorSchemeSync = true
768
+ }) {
769
+ const [theme, setTheme] = (0, import_react5.useState)(() => {
770
+ if (typeof window === "undefined") {
771
+ return defaultTheme;
772
+ }
773
+ try {
774
+ return localStorage.getItem(storageKey) || defaultTheme;
775
+ } catch {
776
+ return defaultTheme;
66
777
  }
778
+ });
779
+ const lightCssVars = (0, import_react5.useMemo)(
780
+ () => config ? themeToCssVars(config) : null,
781
+ [config]
67
782
  );
783
+ const darkCssVars = (0, import_react5.useMemo)(
784
+ () => darkConfig ? themeToCssVars(darkConfig) : null,
785
+ [darkConfig]
786
+ );
787
+ (0, import_react5.useEffect)(() => {
788
+ var _a, _b;
789
+ const root = window.document.documentElement;
790
+ root.classList.remove(
791
+ "light",
792
+ "dark",
793
+ ...Object.keys(customThemes)
794
+ );
795
+ let actualTheme;
796
+ if (theme === "system") {
797
+ actualTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
798
+ } else {
799
+ actualTheme = theme;
800
+ }
801
+ root.classList.add(actualTheme);
802
+ if (enableColorSchemeSync) {
803
+ const isDark = actualTheme === "dark" || ((_a = customThemes[actualTheme]) == null ? void 0 : _a.dark);
804
+ root.style.colorScheme = isDark ? "dark" : "light";
805
+ }
806
+ if (actualTheme === "light" && lightCssVars) {
807
+ applyCssVars(root, lightCssVars);
808
+ } else if (actualTheme === "dark" && darkCssVars) {
809
+ applyCssVars(root, darkCssVars);
810
+ } else if ((_b = customThemes[actualTheme]) == null ? void 0 : _b.cssVars) {
811
+ Object.entries(customThemes[actualTheme].cssVars).forEach(
812
+ ([key, value2]) => {
813
+ root.style.setProperty(key, value2);
814
+ }
815
+ );
816
+ }
817
+ }, [
818
+ theme,
819
+ lightCssVars,
820
+ darkCssVars,
821
+ customThemes,
822
+ enableColorSchemeSync
823
+ ]);
824
+ (0, import_react5.useEffect)(() => {
825
+ if (theme !== "system") return;
826
+ const mediaQuery = window.matchMedia(
827
+ "(prefers-color-scheme: dark)"
828
+ );
829
+ const handler = () => {
830
+ setTheme("system");
831
+ };
832
+ mediaQuery.addEventListener("change", handler);
833
+ return () => mediaQuery.removeEventListener("change", handler);
834
+ }, [theme]);
835
+ const handleSetTheme = (newTheme) => {
836
+ try {
837
+ localStorage.setItem(storageKey, newTheme);
838
+ } catch {
839
+ }
840
+ setTheme(newTheme);
841
+ };
842
+ const value = {
843
+ theme,
844
+ setTheme: handleSetTheme
845
+ };
846
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ThemeContext.Provider, { value, children });
68
847
  }
848
+
849
+ // src/theme/use-theme.ts
850
+ var import_react6 = require("react");
851
+ function useTheme() {
852
+ const context = (0, import_react6.useContext)(ThemeContext);
853
+ if (context === void 0) {
854
+ throw new Error("useTheme must be used within a ThemeProvider");
855
+ }
856
+ return context;
857
+ }
858
+
859
+ // src/theme/types.ts
860
+ function defineTheme(config) {
861
+ return config;
862
+ }
863
+
864
+ // src/theme/defaults/default.ts
865
+ var defaultLightTheme = defineTheme({
866
+ colors: {
867
+ // Page & Container colors
868
+ background: {
869
+ DEFAULT: "oklch(1 0 0)",
870
+ foreground: "oklch(0.09 0 0)"
871
+ },
872
+ muted: {
873
+ DEFAULT: "oklch(0.96 0 0)",
874
+ foreground: "oklch(0.45 0 0)"
875
+ },
876
+ accent: {
877
+ DEFAULT: "oklch(0.96 0 0)",
878
+ foreground: "oklch(0.09 0 0)"
879
+ },
880
+ card: {
881
+ DEFAULT: "oklch(1 0 0)",
882
+ foreground: "oklch(0.09 0 0)"
883
+ },
884
+ popover: {
885
+ DEFAULT: "oklch(1 0 0)",
886
+ foreground: "oklch(0.09 0 0)"
887
+ },
888
+ // Brand & Action colors
889
+ primary: {
890
+ DEFAULT: "oklch(0.09 0 0)",
891
+ foreground: "oklch(0.98 0 0)"
892
+ },
893
+ secondary: {
894
+ DEFAULT: "oklch(0.45 0 0)",
895
+ foreground: "oklch(0.98 0 0)"
896
+ },
897
+ // Semantic colors
898
+ info: {
899
+ DEFAULT: "oklch(0.55 0.15 250)",
900
+ foreground: "oklch(0.98 0 0)"
901
+ },
902
+ danger: {
903
+ DEFAULT: "oklch(0.576 0.204 27.325)",
904
+ foreground: "oklch(0.98 0 0)"
905
+ },
906
+ warning: {
907
+ DEFAULT: "oklch(0.75 0.15 75)",
908
+ foreground: "oklch(0.09 0 0)"
909
+ },
910
+ success: {
911
+ DEFAULT: "oklch(0.60 0.15 145)",
912
+ foreground: "oklch(0.98 0 0)"
913
+ },
914
+ // Border & Input colors
915
+ border: "oklch(0.90 0 0)",
916
+ input: "oklch(0.90 0 0)",
917
+ ring: "oklch(0.09 0 0)"
918
+ },
919
+ radius: {
920
+ none: "0",
921
+ sm: "0.125rem",
922
+ base: "0.25rem",
923
+ md: "0.375rem",
924
+ lg: "0.5rem",
925
+ xl: "0.75rem",
926
+ "2xl": "1rem",
927
+ "3xl": "1.5rem",
928
+ full: "9999px"
929
+ }
930
+ });
931
+ var defaultDarkTheme = defineTheme({
932
+ colors: {
933
+ // Page & Container colors
934
+ background: {
935
+ DEFAULT: "oklch(0.09 0 0)",
936
+ foreground: "oklch(0.98 0 0)"
937
+ },
938
+ muted: {
939
+ DEFAULT: "oklch(0.17 0 0)",
940
+ foreground: "oklch(0.65 0 0)"
941
+ },
942
+ accent: {
943
+ DEFAULT: "oklch(0.17 0 0)",
944
+ foreground: "oklch(0.98 0 0)"
945
+ },
946
+ card: {
947
+ DEFAULT: "oklch(0.09 0 0)",
948
+ foreground: "oklch(0.98 0 0)"
949
+ },
950
+ popover: {
951
+ DEFAULT: "oklch(0.09 0 0)",
952
+ foreground: "oklch(0.98 0 0)"
953
+ },
954
+ // Brand & Action colors
955
+ primary: {
956
+ DEFAULT: "oklch(0.98 0 0)",
957
+ foreground: "oklch(0.09 0 0)"
958
+ },
959
+ secondary: {
960
+ DEFAULT: "oklch(0.65 0 0)",
961
+ foreground: "oklch(0.09 0 0)"
962
+ },
963
+ // Semantic colors
964
+ info: {
965
+ DEFAULT: "oklch(0.65 0.15 250)",
966
+ foreground: "oklch(0.09 0 0)"
967
+ },
968
+ danger: {
969
+ DEFAULT: "oklch(0.701 0.191 29.234)",
970
+ foreground: "oklch(0.09 0 0)"
971
+ },
972
+ warning: {
973
+ DEFAULT: "oklch(0.80 0.15 75)",
974
+ foreground: "oklch(0.09 0 0)"
975
+ },
976
+ success: {
977
+ DEFAULT: "oklch(0.70 0.15 145)",
978
+ foreground: "oklch(0.09 0 0)"
979
+ },
980
+ // Border & Input colors
981
+ border: "oklch(0.27 0 0)",
982
+ input: "oklch(0.27 0 0)",
983
+ ring: "oklch(0.98 0 0)"
984
+ },
985
+ radius: {
986
+ none: "0",
987
+ sm: "0.125rem",
988
+ base: "0.25rem",
989
+ md: "0.375rem",
990
+ lg: "0.5rem",
991
+ xl: "0.75rem",
992
+ "2xl": "1rem",
993
+ "3xl": "1.5rem",
994
+ full: "9999px"
995
+ }
996
+ });
69
997
  // Annotate the CommonJS export names for ESM import in node:
70
998
  0 && (module.exports = {
71
- Button
999
+ Button,
1000
+ ImageUploader,
1001
+ Input,
1002
+ InputMessage,
1003
+ ThemeProvider,
1004
+ buttonVariants,
1005
+ cn,
1006
+ defaultDarkTheme,
1007
+ defaultLightTheme,
1008
+ defineTheme,
1009
+ generateThemeCss,
1010
+ imageUploaderVariants,
1011
+ inputVariants,
1012
+ useImageUploader,
1013
+ useTheme
72
1014
  });
73
1015
  //# sourceMappingURL=index.js.map