@nexus-cross/design-system 1.0.10 → 1.0.12

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.
Files changed (39) hide show
  1. package/cursor-rules/nexus-ui-api.mdc +27 -5
  2. package/dist/chunks/{chunk-CV4GMFWP.js → chunk-2WM23PO6.js} +5 -3
  3. package/dist/chunks/chunk-5J63FUAS.mjs +256 -0
  4. package/dist/chunks/{chunk-BMYFRG3M.mjs → chunk-BQ6GJJB6.mjs} +1 -1
  5. package/dist/chunks/{chunk-P73MEU7N.mjs → chunk-HI5XZ4PB.mjs} +5 -3
  6. package/dist/chunks/chunk-LAGQ7J5A.js +279 -0
  7. package/dist/chunks/{chunk-6MT6Y6OF.js → chunk-WXMMOQXZ.js} +1 -1
  8. package/dist/components/ImageUpload.d.ts +23 -0
  9. package/dist/components/ImageUpload.d.ts.map +1 -0
  10. package/dist/components/ToggleGroup.d.ts +2 -0
  11. package/dist/components/ToggleGroup.d.ts.map +1 -1
  12. package/dist/image-upload.js +16 -0
  13. package/dist/image-upload.mjs +3 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +25 -16
  17. package/dist/index.mjs +3 -2
  18. package/dist/schemas/_all.json +87 -0
  19. package/dist/schemas/image-upload.d.ts +45 -0
  20. package/dist/schemas/image-upload.d.ts.map +1 -0
  21. package/dist/schemas/imageUpload.json +83 -0
  22. package/dist/schemas/index.d.ts +1 -0
  23. package/dist/schemas/index.d.ts.map +1 -1
  24. package/dist/schemas/toggle-group.d.ts +3 -0
  25. package/dist/schemas/toggle-group.d.ts.map +1 -1
  26. package/dist/schemas/toggleGroup.json +4 -0
  27. package/dist/schemas.js +19 -0
  28. package/dist/schemas.mjs +19 -1
  29. package/dist/styles/.generated/built.d.ts +1 -1
  30. package/dist/styles/.generated/built.d.ts.map +1 -1
  31. package/dist/styles/layer.js +2 -2
  32. package/dist/styles/layer.mjs +1 -1
  33. package/dist/styles.css +269 -15
  34. package/dist/styles.js +2 -2
  35. package/dist/styles.layered.css +269 -15
  36. package/dist/styles.mjs +1 -1
  37. package/dist/toggle-group.js +3 -3
  38. package/dist/toggle-group.mjs +1 -1
  39. package/package.json +2 -2
@@ -1097,14 +1097,14 @@ Empty state placeholder. Shown when data is empty or unavailable.
1097
1097
 
1098
1098
  ## Breadcrumb
1099
1099
 
1100
- Breadcrumb navigation. Shows current location path.
1100
+ Breadcrumb navigation (compound component pattern). Use <Breadcrumb.Item> children instead of items array. Each Item can wrap arbitrary ReactNode (Link, Select, plain text, etc.).
1101
1101
 
1102
1102
  | Prop | Type | Default | Description |
1103
1103
  |---|---|---|---|
1104
- | `items` | `object`[] | - | Breadcrumb items array (required) |
1105
- | `separator` | `ReactNode` | - | Custom separator (ReactNode). Default: chevron |
1106
- | `maxItems` | `number` | - | Max visible items (collapses middle with "...") |
1107
- | `className` | `string` | - | Style override |
1104
+ | `children` | `ReactNode` | - | One or more <Breadcrumb.Item> elements |
1105
+ | `separator` | `ReactNode` | - | Custom separator (ReactNode). Default: chevron icon |
1106
+ | `maxItems` | `number` | - | Max visible items. When exceeded, middle items collapse with "" |
1107
+ | `className` | `string` | - | Style override for the root nav element |
1108
1108
 
1109
1109
  ---
1110
1110
 
@@ -1235,6 +1235,28 @@ DatePicker. Calendar popup for date selection. Based on react-day-picker.
1235
1235
 
1236
1236
  ---
1237
1237
 
1238
+ ## ImageUpload
1239
+
1240
+ ImageUpload. Drag-and-drop image upload with preview, file validation, and field label/description support.
1241
+
1242
+ | Prop | Type | Default | Description |
1243
+ |---|---|---|---|
1244
+ | `value` | `string` | - | Controlled image URL (string | null) |
1245
+ | `defaultValue` | `string` | - | Default image URL |
1246
+ | `onChange` | `ReactNode` | - | File change callback (file: File | null) => void |
1247
+ | `onError` | `ReactNode` | - | Validation error callback (error: string) => void |
1248
+ | `accept` | `string`[] | `["jpg","jpeg","png","gif","webp"]` | Allowed file extensions |
1249
+ | `maxSize` | `number` | `2097152` | Max file size in bytes (default 2MB) |
1250
+ | `placeholder` | `string` | `"이미지를 드래그하거나 파일 선택"` | Empty state placeholder text |
1251
+ | `formatDescription` | `string` | - | Format description override (default: auto-generated from accept/maxSize) |
1252
+ | `label` | `ReactNode` | - | Field label (ReactNode) |
1253
+ | `description` | `ReactNode` | - | Field description below the upload area (ReactNode) |
1254
+ | `disabled` | `boolean` | `false` | Disabled state |
1255
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Upload area size |
1256
+ | `className` | `string` | - | Style override |
1257
+
1258
+ ---
1259
+
1238
1260
  ## Hooks
1239
1261
 
1240
1262
  ### useModal
@@ -45,7 +45,7 @@ var toggleGroupVariants = classVarianceAuthority.cva("nexus-toggle-group", {
45
45
  }
46
46
  });
47
47
  var ToggleGroup = React__namespace.forwardRef(
48
- ({ className, variant, size, items, disabled, ...props }, ref) => {
48
+ ({ className, variant, size, items, disabled, required, ...props }, ref) => {
49
49
  const rootRef = React__namespace.useRef(null);
50
50
  const [animated, setAnimated] = React__namespace.useState(false);
51
51
  const [pos, setPos] = React__namespace.useState(null);
@@ -98,21 +98,23 @@ var ToggleGroup = React__namespace.forwardRef(
98
98
  }, []);
99
99
  const handleSingleChange = React__namespace.useCallback(
100
100
  (val) => {
101
+ if (required && !val) return;
101
102
  setTrackedValue(val);
102
103
  if (props.type !== "multiple") {
103
104
  props.onValueChange?.(val);
104
105
  }
105
106
  },
106
- [props.type, props.onValueChange]
107
+ [props.type, props.onValueChange, required]
107
108
  );
108
109
  const handleMultipleChange = React__namespace.useCallback(
109
110
  (val) => {
111
+ if (required && val.length === 0) return;
110
112
  setTrackedValue(val);
111
113
  if (props.type === "multiple") {
112
114
  props.onValueChange?.(val);
113
115
  }
114
116
  },
115
- [props.type, props.onValueChange]
117
+ [props.type, props.onValueChange, required]
116
118
  );
117
119
  const rootProps = props.type === "multiple" ? {
118
120
  type: "multiple",
@@ -0,0 +1,256 @@
1
+ import { cn } from './chunk-MCKOWMLS.mjs';
2
+ import * as React from 'react';
3
+ import { cva } from 'class-variance-authority';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ var imageUploadVariants = cva("nexus-image-upload", {
7
+ variants: {
8
+ size: {
9
+ sm: "nexus-image-upload--sm",
10
+ md: "nexus-image-upload--md",
11
+ lg: "nexus-image-upload--lg"
12
+ }
13
+ },
14
+ defaultVariants: { size: "md" }
15
+ });
16
+ var DEFAULT_ACCEPT = ["jpg", "jpeg", "png", "gif", "webp"];
17
+ var DEFAULT_MAX_SIZE = 2 * 1024 * 1024;
18
+ var ImageUpIcon = ({ className }) => /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
19
+ /* @__PURE__ */ jsx("path", { d: "M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21" }),
20
+ /* @__PURE__ */ jsx("path", { d: "m13.5 19 3-3 3 3" }),
21
+ /* @__PURE__ */ jsx("path", { d: "M16.5 22v-6" }),
22
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" })
23
+ ] });
24
+ var CloseIcon = ({ className }) => /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M12 4L4 12M4 4l8 8" }) });
25
+ function formatBytes(bytes) {
26
+ if (bytes >= 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
27
+ return `${Math.round(bytes / 1024)}KB`;
28
+ }
29
+ var ImageUpload = React.forwardRef(
30
+ ({
31
+ value: controlledValue,
32
+ defaultValue,
33
+ onChange,
34
+ onError,
35
+ accept = DEFAULT_ACCEPT,
36
+ maxSize = DEFAULT_MAX_SIZE,
37
+ placeholder = "\uC774\uBBF8\uC9C0\uB97C \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD30C\uC77C \uC120\uD0DD",
38
+ formatDescription: formatDescProp,
39
+ label,
40
+ description,
41
+ disabled = false,
42
+ size,
43
+ className
44
+ }, ref) => {
45
+ const isControlled = controlledValue !== void 0;
46
+ const [internalPreview, setInternalPreview] = React.useState(
47
+ defaultValue ?? null
48
+ );
49
+ const preview = isControlled ? controlledValue : internalPreview;
50
+ const [isDragging, setIsDragging] = React.useState(false);
51
+ const inputRef = React.useRef(null);
52
+ const dragCounter = React.useRef(0);
53
+ const acceptMime = React.useMemo(
54
+ () => accept.map((ext) => {
55
+ const lower = ext.toLowerCase().replace(/^\./, "");
56
+ if (lower === "jpg" || lower === "jpeg") return "image/jpeg";
57
+ return `image/${lower}`;
58
+ }),
59
+ [accept]
60
+ );
61
+ const formatText = formatDescProp ?? `${[...new Set(accept.map((e) => e.toUpperCase().replace(/^\./, "")))].join(" \xB7 ")} \xB7 \uCD5C\uB300 ${formatBytes(maxSize)}`;
62
+ const validateFile = React.useCallback(
63
+ (file) => {
64
+ if (!acceptMime.includes(file.type)) {
65
+ return `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4. (${accept.join(", ")})`;
66
+ }
67
+ if (file.size > maxSize) {
68
+ return `\uD30C\uC77C \uD06C\uAE30\uAC00 ${formatBytes(maxSize)}\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`;
69
+ }
70
+ return null;
71
+ },
72
+ [acceptMime, accept, maxSize]
73
+ );
74
+ const handleFile = React.useCallback(
75
+ (file) => {
76
+ const error = validateFile(file);
77
+ if (error) {
78
+ onError?.(error);
79
+ return;
80
+ }
81
+ if (!isControlled) {
82
+ const url = URL.createObjectURL(file);
83
+ setInternalPreview((prev) => {
84
+ if (prev?.startsWith("blob:")) URL.revokeObjectURL(prev);
85
+ return url;
86
+ });
87
+ }
88
+ onChange?.(file);
89
+ },
90
+ [validateFile, isControlled, onChange, onError]
91
+ );
92
+ const handleRemove = React.useCallback(() => {
93
+ if (!isControlled) {
94
+ setInternalPreview((prev) => {
95
+ if (prev?.startsWith("blob:")) URL.revokeObjectURL(prev);
96
+ return null;
97
+ });
98
+ }
99
+ onChange?.(null);
100
+ if (inputRef.current) inputRef.current.value = "";
101
+ }, [isControlled, onChange]);
102
+ const handleInputChange = React.useCallback(
103
+ (e) => {
104
+ const file = e.target.files?.[0];
105
+ if (file) handleFile(file);
106
+ },
107
+ [handleFile]
108
+ );
109
+ const handleClick = React.useCallback(() => {
110
+ if (!disabled) inputRef.current?.click();
111
+ }, [disabled]);
112
+ const handleDragEnter = React.useCallback(
113
+ (e) => {
114
+ e.preventDefault();
115
+ e.stopPropagation();
116
+ if (disabled) return;
117
+ dragCounter.current += 1;
118
+ if (dragCounter.current === 1) setIsDragging(true);
119
+ },
120
+ [disabled]
121
+ );
122
+ const handleDragLeave = React.useCallback((e) => {
123
+ e.preventDefault();
124
+ e.stopPropagation();
125
+ dragCounter.current -= 1;
126
+ if (dragCounter.current === 0) setIsDragging(false);
127
+ }, []);
128
+ const handleDragOver = React.useCallback((e) => {
129
+ e.preventDefault();
130
+ e.stopPropagation();
131
+ }, []);
132
+ const handleDrop = React.useCallback(
133
+ (e) => {
134
+ e.preventDefault();
135
+ e.stopPropagation();
136
+ setIsDragging(false);
137
+ dragCounter.current = 0;
138
+ if (disabled) return;
139
+ const file = e.dataTransfer.files?.[0];
140
+ if (file) handleFile(file);
141
+ },
142
+ [disabled, handleFile]
143
+ );
144
+ React.useEffect(() => {
145
+ return () => {
146
+ if (!isControlled && internalPreview?.startsWith("blob:")) {
147
+ URL.revokeObjectURL(internalPreview);
148
+ }
149
+ };
150
+ }, []);
151
+ const hasField = !!(label || description);
152
+ const hiddenInput = /* @__PURE__ */ jsx(
153
+ "input",
154
+ {
155
+ ref: inputRef,
156
+ type: "file",
157
+ accept: acceptMime.join(","),
158
+ onChange: handleInputChange,
159
+ className: "nexus-image-upload__input",
160
+ tabIndex: -1
161
+ }
162
+ );
163
+ const previewView = /* @__PURE__ */ jsxs(
164
+ "div",
165
+ {
166
+ className: cn(
167
+ "nexus-image-upload--has-preview",
168
+ size === "sm" && "nexus-image-upload--sm",
169
+ size === "lg" && "nexus-image-upload--lg",
170
+ disabled && "nexus-image-upload--disabled",
171
+ !hasField && className
172
+ ),
173
+ children: [
174
+ /* @__PURE__ */ jsxs("div", { className: "nexus-image-upload__preview-area", children: [
175
+ /* @__PURE__ */ jsx("div", { className: "nexus-image-upload__preview-wrapper", children: /* @__PURE__ */ jsx(
176
+ "img",
177
+ {
178
+ src: preview,
179
+ alt: "Uploaded preview",
180
+ className: "nexus-image-upload__preview"
181
+ }
182
+ ) }),
183
+ !disabled && /* @__PURE__ */ jsx(
184
+ "button",
185
+ {
186
+ type: "button",
187
+ className: "nexus-image-upload__remove",
188
+ onClick: handleRemove,
189
+ "aria-label": "\uC774\uBBF8\uC9C0 \uC0AD\uC81C",
190
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "nexus-image-upload__remove-icon" })
191
+ }
192
+ )
193
+ ] }),
194
+ /* @__PURE__ */ jsxs("div", { className: "nexus-image-upload__info", children: [
195
+ !disabled && /* @__PURE__ */ jsx(
196
+ "button",
197
+ {
198
+ type: "button",
199
+ className: "nexus-image-upload__change-btn",
200
+ onClick: handleClick,
201
+ children: "\uC774\uBBF8\uC9C0 \uBCC0\uACBD"
202
+ }
203
+ ),
204
+ /* @__PURE__ */ jsx("span", { className: "nexus-image-upload__format", children: formatText })
205
+ ] }),
206
+ hiddenInput
207
+ ]
208
+ }
209
+ );
210
+ const emptyView = /* @__PURE__ */ jsx("div", { className: "nexus-image-upload__container", children: /* @__PURE__ */ jsxs(
211
+ "div",
212
+ {
213
+ role: "button",
214
+ tabIndex: disabled ? -1 : 0,
215
+ className: cn(
216
+ imageUploadVariants({ size }),
217
+ isDragging && "nexus-image-upload--dragging",
218
+ disabled && "nexus-image-upload--disabled",
219
+ !hasField && className
220
+ ),
221
+ onClick: handleClick,
222
+ onKeyDown: (e) => {
223
+ if (e.key === "Enter" || e.key === " ") {
224
+ e.preventDefault();
225
+ handleClick();
226
+ }
227
+ },
228
+ onDragEnter: handleDragEnter,
229
+ onDragLeave: handleDragLeave,
230
+ onDragOver: handleDragOver,
231
+ onDrop: handleDrop,
232
+ "aria-label": placeholder,
233
+ children: [
234
+ /* @__PURE__ */ jsx(ImageUpIcon, { className: "nexus-image-upload__icon" }),
235
+ /* @__PURE__ */ jsxs("div", { className: "nexus-image-upload__text-group", children: [
236
+ /* @__PURE__ */ jsx("span", { className: "nexus-image-upload__text", children: placeholder }),
237
+ /* @__PURE__ */ jsx("span", { className: "nexus-image-upload__format", children: formatText })
238
+ ] }),
239
+ hiddenInput
240
+ ]
241
+ }
242
+ ) });
243
+ const uploadBox = preview ? previewView : emptyView;
244
+ if (!hasField) {
245
+ return /* @__PURE__ */ jsx("div", { ref, className, children: uploadBox });
246
+ }
247
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("nexus-image-upload-field", className), children: [
248
+ label && /* @__PURE__ */ jsx("span", { className: "nexus-image-upload-field__label", children: label }),
249
+ uploadBox,
250
+ description && /* @__PURE__ */ jsx("p", { className: "nexus-image-upload-field__description", children: description })
251
+ ] });
252
+ }
253
+ );
254
+ ImageUpload.displayName = "ImageUpload";
255
+
256
+ export { ImageUpload, imageUploadVariants };