@nexus-cross/design-system 1.0.10 → 1.0.11
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/cursor-rules/nexus-ui-api.mdc +27 -5
- package/dist/chunks/chunk-2XV3XP3Y.js +279 -0
- package/dist/chunks/{chunk-BMYFRG3M.mjs → chunk-6J7TZ4GP.mjs} +1 -1
- package/dist/chunks/{chunk-6MT6Y6OF.js → chunk-7B2CTQKB.js} +1 -1
- package/dist/chunks/chunk-XKJO5Y4J.mjs +256 -0
- package/dist/components/ImageUpload.d.ts +23 -0
- package/dist/components/ImageUpload.d.ts.map +1 -0
- package/dist/image-upload.js +16 -0
- package/dist/image-upload.mjs +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -13
- package/dist/index.mjs +2 -1
- package/dist/schemas/_all.json +83 -0
- package/dist/schemas/image-upload.d.ts +45 -0
- package/dist/schemas/image-upload.d.ts.map +1 -0
- package/dist/schemas/imageUpload.json +83 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas.js +18 -0
- package/dist/schemas.mjs +18 -1
- package/dist/styles/.generated/built.d.ts +1 -1
- package/dist/styles/.generated/built.d.ts.map +1 -1
- package/dist/styles/layer.js +2 -2
- package/dist/styles/layer.mjs +1 -1
- package/dist/styles.css +266 -15
- package/dist/styles.js +2 -2
- package/dist/styles.layered.css +266 -15
- package/dist/styles.mjs +1 -1
- 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.
|
|
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
|
-
| `
|
|
1105
|
-
| `separator` | `ReactNode` | - | Custom separator (ReactNode). Default: chevron |
|
|
1106
|
-
| `maxItems` | `number` | - | Max visible items
|
|
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
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkCZC76ZD5_js = require('./chunk-CZC76ZD5.js');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
27
|
+
|
|
28
|
+
var imageUploadVariants = classVarianceAuthority.cva("nexus-image-upload", {
|
|
29
|
+
variants: {
|
|
30
|
+
size: {
|
|
31
|
+
sm: "nexus-image-upload--sm",
|
|
32
|
+
md: "nexus-image-upload--md",
|
|
33
|
+
lg: "nexus-image-upload--lg"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: { size: "md" }
|
|
37
|
+
});
|
|
38
|
+
var DEFAULT_ACCEPT = ["jpg", "jpeg", "png", "gif", "webp"];
|
|
39
|
+
var DEFAULT_MAX_SIZE = 2 * 1024 * 1024;
|
|
40
|
+
var ImageUpIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
41
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
42
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m13.5 19 3-3 3 3" }),
|
|
43
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16.5 22v-6" }),
|
|
44
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "9", r: "2" })
|
|
45
|
+
] });
|
|
46
|
+
var CloseIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4L4 12M4 4l8 8" }) });
|
|
47
|
+
function formatBytes(bytes) {
|
|
48
|
+
if (bytes >= 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
|
|
49
|
+
return `${Math.round(bytes / 1024)}KB`;
|
|
50
|
+
}
|
|
51
|
+
var ImageUpload = React__namespace.forwardRef(
|
|
52
|
+
({
|
|
53
|
+
value: controlledValue,
|
|
54
|
+
defaultValue,
|
|
55
|
+
onChange,
|
|
56
|
+
onError,
|
|
57
|
+
accept = DEFAULT_ACCEPT,
|
|
58
|
+
maxSize = DEFAULT_MAX_SIZE,
|
|
59
|
+
placeholder = "\uC774\uBBF8\uC9C0\uB97C \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD30C\uC77C \uC120\uD0DD",
|
|
60
|
+
formatDescription: formatDescProp,
|
|
61
|
+
label,
|
|
62
|
+
description,
|
|
63
|
+
disabled = false,
|
|
64
|
+
size,
|
|
65
|
+
className
|
|
66
|
+
}, ref) => {
|
|
67
|
+
const isControlled = controlledValue !== void 0;
|
|
68
|
+
const [internalPreview, setInternalPreview] = React__namespace.useState(
|
|
69
|
+
defaultValue ?? null
|
|
70
|
+
);
|
|
71
|
+
const preview = isControlled ? controlledValue : internalPreview;
|
|
72
|
+
const [isDragging, setIsDragging] = React__namespace.useState(false);
|
|
73
|
+
const inputRef = React__namespace.useRef(null);
|
|
74
|
+
const dragCounter = React__namespace.useRef(0);
|
|
75
|
+
const acceptMime = React__namespace.useMemo(
|
|
76
|
+
() => accept.map((ext) => {
|
|
77
|
+
const lower = ext.toLowerCase().replace(/^\./, "");
|
|
78
|
+
if (lower === "jpg" || lower === "jpeg") return "image/jpeg";
|
|
79
|
+
return `image/${lower}`;
|
|
80
|
+
}),
|
|
81
|
+
[accept]
|
|
82
|
+
);
|
|
83
|
+
const formatText = formatDescProp ?? `${[...new Set(accept.map((e) => e.toUpperCase().replace(/^\./, "")))].join(" \xB7 ")} \xB7 \uCD5C\uB300 ${formatBytes(maxSize)}`;
|
|
84
|
+
const validateFile = React__namespace.useCallback(
|
|
85
|
+
(file) => {
|
|
86
|
+
if (!acceptMime.includes(file.type)) {
|
|
87
|
+
return `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4. (${accept.join(", ")})`;
|
|
88
|
+
}
|
|
89
|
+
if (file.size > maxSize) {
|
|
90
|
+
return `\uD30C\uC77C \uD06C\uAE30\uAC00 ${formatBytes(maxSize)}\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
},
|
|
94
|
+
[acceptMime, accept, maxSize]
|
|
95
|
+
);
|
|
96
|
+
const handleFile = React__namespace.useCallback(
|
|
97
|
+
(file) => {
|
|
98
|
+
const error = validateFile(file);
|
|
99
|
+
if (error) {
|
|
100
|
+
onError?.(error);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!isControlled) {
|
|
104
|
+
const url = URL.createObjectURL(file);
|
|
105
|
+
setInternalPreview((prev) => {
|
|
106
|
+
if (prev?.startsWith("blob:")) URL.revokeObjectURL(prev);
|
|
107
|
+
return url;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
onChange?.(file);
|
|
111
|
+
},
|
|
112
|
+
[validateFile, isControlled, onChange, onError]
|
|
113
|
+
);
|
|
114
|
+
const handleRemove = React__namespace.useCallback(() => {
|
|
115
|
+
if (!isControlled) {
|
|
116
|
+
setInternalPreview((prev) => {
|
|
117
|
+
if (prev?.startsWith("blob:")) URL.revokeObjectURL(prev);
|
|
118
|
+
return null;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
onChange?.(null);
|
|
122
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
123
|
+
}, [isControlled, onChange]);
|
|
124
|
+
const handleInputChange = React__namespace.useCallback(
|
|
125
|
+
(e) => {
|
|
126
|
+
const file = e.target.files?.[0];
|
|
127
|
+
if (file) handleFile(file);
|
|
128
|
+
},
|
|
129
|
+
[handleFile]
|
|
130
|
+
);
|
|
131
|
+
const handleClick = React__namespace.useCallback(() => {
|
|
132
|
+
if (!disabled) inputRef.current?.click();
|
|
133
|
+
}, [disabled]);
|
|
134
|
+
const handleDragEnter = React__namespace.useCallback(
|
|
135
|
+
(e) => {
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
e.stopPropagation();
|
|
138
|
+
if (disabled) return;
|
|
139
|
+
dragCounter.current += 1;
|
|
140
|
+
if (dragCounter.current === 1) setIsDragging(true);
|
|
141
|
+
},
|
|
142
|
+
[disabled]
|
|
143
|
+
);
|
|
144
|
+
const handleDragLeave = React__namespace.useCallback((e) => {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
e.stopPropagation();
|
|
147
|
+
dragCounter.current -= 1;
|
|
148
|
+
if (dragCounter.current === 0) setIsDragging(false);
|
|
149
|
+
}, []);
|
|
150
|
+
const handleDragOver = React__namespace.useCallback((e) => {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
}, []);
|
|
154
|
+
const handleDrop = React__namespace.useCallback(
|
|
155
|
+
(e) => {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
e.stopPropagation();
|
|
158
|
+
setIsDragging(false);
|
|
159
|
+
dragCounter.current = 0;
|
|
160
|
+
if (disabled) return;
|
|
161
|
+
const file = e.dataTransfer.files?.[0];
|
|
162
|
+
if (file) handleFile(file);
|
|
163
|
+
},
|
|
164
|
+
[disabled, handleFile]
|
|
165
|
+
);
|
|
166
|
+
React__namespace.useEffect(() => {
|
|
167
|
+
return () => {
|
|
168
|
+
if (!isControlled && internalPreview?.startsWith("blob:")) {
|
|
169
|
+
URL.revokeObjectURL(internalPreview);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}, []);
|
|
173
|
+
const hasField = !!(label || description);
|
|
174
|
+
const hiddenInput = /* @__PURE__ */ jsxRuntime.jsx(
|
|
175
|
+
"input",
|
|
176
|
+
{
|
|
177
|
+
ref: inputRef,
|
|
178
|
+
type: "file",
|
|
179
|
+
accept: acceptMime.join(","),
|
|
180
|
+
onChange: handleInputChange,
|
|
181
|
+
className: "nexus-image-upload__input",
|
|
182
|
+
tabIndex: -1
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
const previewView = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
186
|
+
"div",
|
|
187
|
+
{
|
|
188
|
+
className: chunkCZC76ZD5_js.cn(
|
|
189
|
+
"nexus-image-upload--has-preview",
|
|
190
|
+
size === "sm" && "nexus-image-upload--sm",
|
|
191
|
+
size === "lg" && "nexus-image-upload--lg",
|
|
192
|
+
disabled && "nexus-image-upload--disabled",
|
|
193
|
+
!hasField && className
|
|
194
|
+
),
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nexus-image-upload__preview-wrapper", children: [
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
198
|
+
"img",
|
|
199
|
+
{
|
|
200
|
+
src: preview,
|
|
201
|
+
alt: "Uploaded preview",
|
|
202
|
+
className: "nexus-image-upload__preview"
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
!disabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
206
|
+
"button",
|
|
207
|
+
{
|
|
208
|
+
type: "button",
|
|
209
|
+
className: "nexus-image-upload__remove",
|
|
210
|
+
onClick: handleRemove,
|
|
211
|
+
"aria-label": "\uC774\uBBF8\uC9C0 \uC0AD\uC81C",
|
|
212
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { className: "nexus-image-upload__remove-icon" })
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
] }),
|
|
216
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nexus-image-upload__info", children: [
|
|
217
|
+
!disabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
218
|
+
"button",
|
|
219
|
+
{
|
|
220
|
+
type: "button",
|
|
221
|
+
className: "nexus-image-upload__change-btn",
|
|
222
|
+
onClick: handleClick,
|
|
223
|
+
children: "\uC774\uBBF8\uC9C0 \uBCC0\uACBD"
|
|
224
|
+
}
|
|
225
|
+
),
|
|
226
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nexus-image-upload__format", children: formatText })
|
|
227
|
+
] }),
|
|
228
|
+
hiddenInput
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
const emptyView = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nexus-image-upload__container", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
233
|
+
"div",
|
|
234
|
+
{
|
|
235
|
+
role: "button",
|
|
236
|
+
tabIndex: disabled ? -1 : 0,
|
|
237
|
+
className: chunkCZC76ZD5_js.cn(
|
|
238
|
+
imageUploadVariants({ size }),
|
|
239
|
+
isDragging && "nexus-image-upload--dragging",
|
|
240
|
+
disabled && "nexus-image-upload--disabled",
|
|
241
|
+
!hasField && className
|
|
242
|
+
),
|
|
243
|
+
onClick: handleClick,
|
|
244
|
+
onKeyDown: (e) => {
|
|
245
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
handleClick();
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
onDragEnter: handleDragEnter,
|
|
251
|
+
onDragLeave: handleDragLeave,
|
|
252
|
+
onDragOver: handleDragOver,
|
|
253
|
+
onDrop: handleDrop,
|
|
254
|
+
"aria-label": placeholder,
|
|
255
|
+
children: [
|
|
256
|
+
/* @__PURE__ */ jsxRuntime.jsx(ImageUpIcon, { className: "nexus-image-upload__icon" }),
|
|
257
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nexus-image-upload__text-group", children: [
|
|
258
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nexus-image-upload__text", children: placeholder }),
|
|
259
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nexus-image-upload__format", children: formatText })
|
|
260
|
+
] }),
|
|
261
|
+
hiddenInput
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
) });
|
|
265
|
+
const uploadBox = preview ? previewView : emptyView;
|
|
266
|
+
if (!hasField) {
|
|
267
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className, children: uploadBox });
|
|
268
|
+
}
|
|
269
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: chunkCZC76ZD5_js.cn("nexus-image-upload-field", className), children: [
|
|
270
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nexus-image-upload-field__label", children: label }),
|
|
271
|
+
uploadBox,
|
|
272
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nexus-image-upload-field__description", children: description })
|
|
273
|
+
] });
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
ImageUpload.displayName = "ImageUpload";
|
|
277
|
+
|
|
278
|
+
exports.ImageUpload = ImageUpload;
|
|
279
|
+
exports.imageUploadVariants = imageUploadVariants;
|