@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.
- package/cursor-rules/nexus-ui-api.mdc +27 -5
- package/dist/chunks/{chunk-CV4GMFWP.js → chunk-2WM23PO6.js} +5 -3
- package/dist/chunks/chunk-5J63FUAS.mjs +256 -0
- package/dist/chunks/{chunk-BMYFRG3M.mjs → chunk-BQ6GJJB6.mjs} +1 -1
- package/dist/chunks/{chunk-P73MEU7N.mjs → chunk-HI5XZ4PB.mjs} +5 -3
- package/dist/chunks/chunk-LAGQ7J5A.js +279 -0
- package/dist/chunks/{chunk-6MT6Y6OF.js → chunk-WXMMOQXZ.js} +1 -1
- package/dist/components/ImageUpload.d.ts +23 -0
- package/dist/components/ImageUpload.d.ts.map +1 -0
- package/dist/components/ToggleGroup.d.ts +2 -0
- package/dist/components/ToggleGroup.d.ts.map +1 -1
- 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 +25 -16
- package/dist/index.mjs +3 -2
- package/dist/schemas/_all.json +87 -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/toggle-group.d.ts +3 -0
- package/dist/schemas/toggle-group.d.ts.map +1 -1
- package/dist/schemas/toggleGroup.json +4 -0
- package/dist/schemas.js +19 -0
- package/dist/schemas.mjs +19 -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 +269 -15
- package/dist/styles.js +2 -2
- package/dist/styles.layered.css +269 -15
- package/dist/styles.mjs +1 -1
- package/dist/toggle-group.js +3 -3
- package/dist/toggle-group.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
|
|
@@ -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 };
|