@nexus-cross/design-system 2.0.0-beta.1 → 2.0.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/claude-rules/nexus/CLAUDE.md +68 -0
- package/cursor-rules/nexus-project-setup.mdc +50 -3
- package/cursor-rules/nexus-ui-api.mdc +8 -2
- package/dist/chunks/{chunk-NZHK76R3.js → chunk-LYPBQI3Y.js} +31 -6
- package/dist/chunks/{chunk-6BWOKTVQ.mjs → chunk-WZFKTTVX.mjs} +31 -6
- package/dist/components/NxImage.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +1 -1
- package/dist/nx-image.js +2 -2
- package/dist/nx-image.mjs +1 -1
- package/dist/tokens/company.css +1 -1
- package/dist/tokens/css.css +1 -1
- package/dist/tokens-domains/prediction-vars.css +1 -1
- package/dist/tokens-domains/prediction.css +1 -1
- package/package.json +3 -3
|
@@ -99,6 +99,74 @@ const nextConfig = {
|
|
|
99
99
|
|
|
100
100
|
---
|
|
101
101
|
|
|
102
|
+
## Global Setup — `<Toaster />` / `<ModalContainer />`
|
|
103
|
+
|
|
104
|
+
`Toaster` / `ModalContainer`는 내부적으로 `useState`를 사용하는 **클라이언트 컴포넌트**입니다.
|
|
105
|
+
|
|
106
|
+
| 환경 | 패턴 |
|
|
107
|
+
|---|---|
|
|
108
|
+
| Next.js App Router (root layout이 server component) | **반드시 `"use client"` wrapper로 분리** |
|
|
109
|
+
| Vite, CRA, Pages Router 등 root가 이미 client | 직접 import 가능 |
|
|
110
|
+
|
|
111
|
+
### Pattern A — Next.js App Router (권장)
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// app/providers.tsx
|
|
115
|
+
"use client";
|
|
116
|
+
|
|
117
|
+
import { Toaster } from '@nexus-cross/design-system';
|
|
118
|
+
import { ModalContainer } from '@nexus-cross/design-system/modal';
|
|
119
|
+
|
|
120
|
+
export function NexusProviders() {
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
<ModalContainer />
|
|
124
|
+
<Toaster position="top-right" />
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
// app/layout.tsx (server component — "use client" 추가 금지)
|
|
132
|
+
import { NexusProviders } from './providers';
|
|
133
|
+
import './globals.css';
|
|
134
|
+
|
|
135
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
136
|
+
return (
|
|
137
|
+
<html lang="ko">
|
|
138
|
+
<body>
|
|
139
|
+
{children}
|
|
140
|
+
<NexusProviders />
|
|
141
|
+
</body>
|
|
142
|
+
</html>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> ❌ server `layout.tsx`에서 `Toaster` / `ModalContainer`를 직접 import하면
|
|
148
|
+
> `TypeError: useState only works in Client Components.` 에러 발생.
|
|
149
|
+
|
|
150
|
+
### Pattern B — Vite / CRA / 이미 client인 root
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// App.tsx
|
|
154
|
+
import { Toaster } from '@nexus-cross/design-system';
|
|
155
|
+
import { ModalContainer } from '@nexus-cross/design-system/modal';
|
|
156
|
+
|
|
157
|
+
export default function App() {
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
{/* routes / pages */}
|
|
161
|
+
<ModalContainer />
|
|
162
|
+
<Toaster position="top-right" />
|
|
163
|
+
</>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
102
170
|
## Token Path Convention
|
|
103
171
|
|
|
104
172
|
모든 디자인 토큰은 5-segment 형식: `color.semantic.{namespace}.{slot}.{state}`
|
|
@@ -203,15 +203,62 @@ The following components MUST be placed at the app root (layout.tsx or App.tsx).
|
|
|
203
203
|
| `<ModalContainer />` | `@nexus-cross/design-system/modal` | Using `modal()` or `useModal()` | Modal rendering container |
|
|
204
204
|
| `<Toaster />` | `@nexus-cross/design-system` | Using `toast()` | Toast notification renderer |
|
|
205
205
|
|
|
206
|
+
> Both components use `useState` internally → they are **client components**.
|
|
207
|
+
> In environments where the root layout is a **server component** (e.g. Next.js App Router), import them through a `"use client"` wrapper as shown in **Pattern A**.
|
|
208
|
+
> If the root layout is already a client component (e.g. Vite, CRA, Pages Router with `"use client"` already declared), you can use them directly as in **Pattern B**.
|
|
209
|
+
|
|
210
|
+
### Pattern A — Next.js App Router / RSC (Recommended)
|
|
211
|
+
|
|
212
|
+
Server components cannot directly render client components that use hooks. Wrap providers in a single client file:
|
|
213
|
+
|
|
206
214
|
```tsx
|
|
207
|
-
//
|
|
215
|
+
// app/providers.tsx
|
|
216
|
+
"use client";
|
|
217
|
+
|
|
218
|
+
import { Toaster } from '@nexus-cross/design-system';
|
|
208
219
|
import { ModalContainer } from '@nexus-cross/design-system/modal';
|
|
220
|
+
|
|
221
|
+
export function NexusProviders() {
|
|
222
|
+
return (
|
|
223
|
+
<>
|
|
224
|
+
<ModalContainer />
|
|
225
|
+
<Toaster position="top-right" />
|
|
226
|
+
</>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
// app/layout.tsx (server component — DO NOT add "use client" here)
|
|
233
|
+
import { NexusProviders } from './providers';
|
|
234
|
+
import './globals.css';
|
|
235
|
+
|
|
236
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
237
|
+
return (
|
|
238
|
+
<html lang="en">
|
|
239
|
+
<body>
|
|
240
|
+
{children}
|
|
241
|
+
<NexusProviders />
|
|
242
|
+
</body>
|
|
243
|
+
</html>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
> ❌ Importing `Toaster` / `ModalContainer` directly inside a server `layout.tsx` throws:
|
|
249
|
+
> `TypeError: useState only works in Client Components.`
|
|
250
|
+
|
|
251
|
+
### Pattern B — Vite / CRA / Already-client roots
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
// App.tsx (already a client component)
|
|
209
255
|
import { Toaster } from '@nexus-cross/design-system';
|
|
256
|
+
import { ModalContainer } from '@nexus-cross/design-system/modal';
|
|
210
257
|
|
|
211
|
-
export default function
|
|
258
|
+
export default function App() {
|
|
212
259
|
return (
|
|
213
260
|
<>
|
|
214
|
-
{
|
|
261
|
+
{/* ... your routes/pages ... */}
|
|
215
262
|
<ModalContainer />
|
|
216
263
|
<Toaster position="top-right" />
|
|
217
264
|
</>
|
|
@@ -1023,7 +1023,10 @@ function MyModal({ close, resolve }: { close: () => void; resolve: (value: any)
|
|
|
1023
1023
|
```tsx
|
|
1024
1024
|
import { modal, useModal, ModalContainer } from '@nexus-cross/design-system/modal';
|
|
1025
1025
|
|
|
1026
|
-
// ModalContainer MUST be placed at the app root
|
|
1026
|
+
// ModalContainer MUST be placed at the app root.
|
|
1027
|
+
// It uses useState internally → it's a client component.
|
|
1028
|
+
// In Next.js App Router, wrap it in a "use client" file (e.g. providers.tsx)
|
|
1029
|
+
// and render that wrapper from the (server) layout. NEVER add "use client" to layout.tsx itself.
|
|
1027
1030
|
<ModalContainer />
|
|
1028
1031
|
|
|
1029
1032
|
// Method 1: modal() function
|
|
@@ -2164,7 +2167,10 @@ Toast notifications. Sonner-based.
|
|
|
2164
2167
|
```tsx
|
|
2165
2168
|
import { toast, Toaster } from '@nexus-cross/design-system';
|
|
2166
2169
|
|
|
2167
|
-
// Place Toaster at app root
|
|
2170
|
+
// Place Toaster at the app root.
|
|
2171
|
+
// It uses useState internally → it's a client component.
|
|
2172
|
+
// In Next.js App Router, wrap it in a "use client" file (e.g. providers.tsx)
|
|
2173
|
+
// and render that wrapper from the (server) layout. NEVER add "use client" to layout.tsx itself.
|
|
2168
2174
|
<Toaster position="top-right" />
|
|
2169
2175
|
|
|
2170
2176
|
// Usage
|
|
@@ -48,9 +48,34 @@ var NxImage = React__namespace.forwardRef(
|
|
|
48
48
|
const [status, setStatus] = React__namespace.useState(
|
|
49
49
|
"loading"
|
|
50
50
|
);
|
|
51
|
+
const [currentSrc, setCurrentSrc] = React__namespace.useState(src);
|
|
52
|
+
const innerRef = React__namespace.useRef(null);
|
|
53
|
+
const setRefs = React__namespace.useCallback(
|
|
54
|
+
(node) => {
|
|
55
|
+
innerRef.current = node;
|
|
56
|
+
if (typeof ref === "function") {
|
|
57
|
+
ref(node);
|
|
58
|
+
} else if (ref) {
|
|
59
|
+
ref.current = node;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
[ref]
|
|
63
|
+
);
|
|
51
64
|
React__namespace.useEffect(() => {
|
|
52
|
-
|
|
65
|
+
setCurrentSrc(src);
|
|
53
66
|
}, [src]);
|
|
67
|
+
React__namespace.useEffect(() => {
|
|
68
|
+
setStatus("loading");
|
|
69
|
+
const img = innerRef.current;
|
|
70
|
+
if (!img || !currentSrc) return;
|
|
71
|
+
if (img.complete) {
|
|
72
|
+
if (img.naturalHeight > 0) {
|
|
73
|
+
setStatus("loaded");
|
|
74
|
+
} else if (img.src) {
|
|
75
|
+
setStatus("error");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, [currentSrc]);
|
|
54
79
|
const handleLoad = React__namespace.useCallback(
|
|
55
80
|
(e) => {
|
|
56
81
|
setStatus("loaded");
|
|
@@ -60,14 +85,14 @@ var NxImage = React__namespace.forwardRef(
|
|
|
60
85
|
);
|
|
61
86
|
const handleError = React__namespace.useCallback(
|
|
62
87
|
(e) => {
|
|
63
|
-
if (fallbackSrc &&
|
|
64
|
-
|
|
88
|
+
if (fallbackSrc && currentSrc !== fallbackSrc) {
|
|
89
|
+
setCurrentSrc(fallbackSrc);
|
|
65
90
|
return;
|
|
66
91
|
}
|
|
67
92
|
setStatus("error");
|
|
68
93
|
onError?.(e);
|
|
69
94
|
},
|
|
70
|
-
[fallbackSrc, onError]
|
|
95
|
+
[fallbackSrc, currentSrc, onError]
|
|
71
96
|
);
|
|
72
97
|
const wrapperStyle = {
|
|
73
98
|
...style,
|
|
@@ -84,8 +109,8 @@ var NxImage = React__namespace.forwardRef(
|
|
|
84
109
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
85
110
|
"img",
|
|
86
111
|
{
|
|
87
|
-
ref,
|
|
88
|
-
src,
|
|
112
|
+
ref: setRefs,
|
|
113
|
+
src: currentSrc,
|
|
89
114
|
alt: alt ?? "",
|
|
90
115
|
loading: lazy ? "lazy" : "eager",
|
|
91
116
|
className: chunkCZC76ZD5_js.cn(
|
|
@@ -26,9 +26,34 @@ var NxImage = React.forwardRef(
|
|
|
26
26
|
const [status, setStatus] = React.useState(
|
|
27
27
|
"loading"
|
|
28
28
|
);
|
|
29
|
+
const [currentSrc, setCurrentSrc] = React.useState(src);
|
|
30
|
+
const innerRef = React.useRef(null);
|
|
31
|
+
const setRefs = React.useCallback(
|
|
32
|
+
(node) => {
|
|
33
|
+
innerRef.current = node;
|
|
34
|
+
if (typeof ref === "function") {
|
|
35
|
+
ref(node);
|
|
36
|
+
} else if (ref) {
|
|
37
|
+
ref.current = node;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
[ref]
|
|
41
|
+
);
|
|
29
42
|
React.useEffect(() => {
|
|
30
|
-
|
|
43
|
+
setCurrentSrc(src);
|
|
31
44
|
}, [src]);
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
setStatus("loading");
|
|
47
|
+
const img = innerRef.current;
|
|
48
|
+
if (!img || !currentSrc) return;
|
|
49
|
+
if (img.complete) {
|
|
50
|
+
if (img.naturalHeight > 0) {
|
|
51
|
+
setStatus("loaded");
|
|
52
|
+
} else if (img.src) {
|
|
53
|
+
setStatus("error");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}, [currentSrc]);
|
|
32
57
|
const handleLoad = React.useCallback(
|
|
33
58
|
(e) => {
|
|
34
59
|
setStatus("loaded");
|
|
@@ -38,14 +63,14 @@ var NxImage = React.forwardRef(
|
|
|
38
63
|
);
|
|
39
64
|
const handleError = React.useCallback(
|
|
40
65
|
(e) => {
|
|
41
|
-
if (fallbackSrc &&
|
|
42
|
-
|
|
66
|
+
if (fallbackSrc && currentSrc !== fallbackSrc) {
|
|
67
|
+
setCurrentSrc(fallbackSrc);
|
|
43
68
|
return;
|
|
44
69
|
}
|
|
45
70
|
setStatus("error");
|
|
46
71
|
onError?.(e);
|
|
47
72
|
},
|
|
48
|
-
[fallbackSrc, onError]
|
|
73
|
+
[fallbackSrc, currentSrc, onError]
|
|
49
74
|
);
|
|
50
75
|
const wrapperStyle = {
|
|
51
76
|
...style,
|
|
@@ -62,8 +87,8 @@ var NxImage = React.forwardRef(
|
|
|
62
87
|
/* @__PURE__ */ jsx(
|
|
63
88
|
"img",
|
|
64
89
|
{
|
|
65
|
-
ref,
|
|
66
|
-
src,
|
|
90
|
+
ref: setRefs,
|
|
91
|
+
src: currentSrc,
|
|
67
92
|
alt: alt ?? "",
|
|
68
93
|
loading: lazy ? "lazy" : "eager",
|
|
69
94
|
className: cn(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NxImage.d.ts","sourceRoot":"","sources":["../../src/components/NxImage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,UAAU,YAAa,SAAQ,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,CAAC;IACtE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,QAAA,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"NxImage.d.ts","sourceRoot":"","sources":["../../src/components/NxImage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,UAAU,YAAa,SAAQ,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,CAAC;IACtE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,QAAA,MAAM,OAAO,uFA8IZ,CAAC;AAIF,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var chunkC2DSAJTL_js = require('./chunks/chunk-C2DSAJTL.js');
|
|
|
7
7
|
var chunkK3CK7NTP_js = require('./chunks/chunk-K3CK7NTP.js');
|
|
8
8
|
var chunkDICN6GKE_js = require('./chunks/chunk-DICN6GKE.js');
|
|
9
9
|
var chunkDYPPVXQF_js = require('./chunks/chunk-DYPPVXQF.js');
|
|
10
|
-
var
|
|
10
|
+
var chunkLYPBQI3Y_js = require('./chunks/chunk-LYPBQI3Y.js');
|
|
11
11
|
var chunkLTS674LF_js = require('./chunks/chunk-LTS674LF.js');
|
|
12
12
|
var chunkLAGQ7J5A_js = require('./chunks/chunk-LAGQ7J5A.js');
|
|
13
13
|
var chunkQK6NCII4_js = require('./chunks/chunk-QK6NCII4.js');
|
|
@@ -430,7 +430,7 @@ Object.defineProperty(exports, "tagInputVariants", {
|
|
|
430
430
|
});
|
|
431
431
|
Object.defineProperty(exports, "NxImage", {
|
|
432
432
|
enumerable: true,
|
|
433
|
-
get: function () { return
|
|
433
|
+
get: function () { return chunkLYPBQI3Y_js.NxImage; }
|
|
434
434
|
});
|
|
435
435
|
Object.defineProperty(exports, "DatePicker", {
|
|
436
436
|
enumerable: true,
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuRoot,
|
|
|
5
5
|
export { ToggleGroup, toggleGroupVariants } from './chunks/chunk-PIGHBDK5.mjs';
|
|
6
6
|
export { Slider, sliderVariants } from './chunks/chunk-H2G5FMRN.mjs';
|
|
7
7
|
export { TagInput, tagInputVariants } from './chunks/chunk-H2V7RHYV.mjs';
|
|
8
|
-
export { NxImage } from './chunks/chunk-
|
|
8
|
+
export { NxImage } from './chunks/chunk-WZFKTTVX.mjs';
|
|
9
9
|
export { DatePicker } from './chunks/chunk-FBC53TOS.mjs';
|
|
10
10
|
export { ImageUpload, imageUploadVariants } from './chunks/chunk-5J63FUAS.mjs';
|
|
11
11
|
export { ClientOnly } from './chunks/chunk-AOXXE5UQ.mjs';
|
package/dist/nx-image.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkLYPBQI3Y_js = require('./chunks/chunk-LYPBQI3Y.js');
|
|
4
4
|
require('./chunks/chunk-CZC76ZD5.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "NxImage", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkLYPBQI3Y_js.NxImage; }
|
|
11
11
|
});
|
package/dist/nx-image.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { NxImage } from './chunks/chunk-
|
|
1
|
+
export { NxImage } from './chunks/chunk-WZFKTTVX.mjs';
|
|
2
2
|
import './chunks/chunk-MCKOWMLS.mjs';
|
package/dist/tokens/company.css
CHANGED
package/dist/tokens/css.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexus-cross/design-system",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "NEXUS Design System UI Components",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -362,8 +362,8 @@
|
|
|
362
362
|
"sonner": "^2.0.7",
|
|
363
363
|
"vaul": "^1.1.2",
|
|
364
364
|
"zod": "^3.25.76",
|
|
365
|
-
"@nexus-cross/tokens": "2.0.0
|
|
366
|
-
"@nexus-cross/tokens-domains": "2.0.0
|
|
365
|
+
"@nexus-cross/tokens": "2.0.0",
|
|
366
|
+
"@nexus-cross/tokens-domains": "2.0.0"
|
|
367
367
|
},
|
|
368
368
|
"devDependencies": {
|
|
369
369
|
"@testing-library/jest-dom": "^6.9.1",
|