@nexus-cross/design-system 2.0.3 → 2.0.5

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 (51) hide show
  1. package/claude-rules/nexus/CLAUDE.md +109 -0
  2. package/cursor-rules/CLAUDE.md +5 -0
  3. package/cursor-rules/nexus-project-setup.mdc +106 -6
  4. package/cursor-rules/nexus-ui-api.mdc +60 -3
  5. package/dist/chunks/{chunk-3VFBPFZF.mjs → chunk-7WWQ5DS3.mjs} +10 -5
  6. package/dist/chunks/{chunk-HHXDOKXY.js → chunk-F24AY3HI.js} +2 -2
  7. package/dist/chunks/{chunk-6H7V2I3X.mjs → chunk-FY2N42XN.mjs} +6 -1
  8. package/dist/chunks/{chunk-JFZLKFFM.mjs → chunk-JBMN6DY3.mjs} +1 -1
  9. package/dist/chunks/{chunk-XEHFB62A.js → chunk-NRO7I4EI.js} +2 -6
  10. package/dist/chunks/{chunk-U53UA76K.js → chunk-R744EATX.js} +26 -21
  11. package/dist/chunks/{chunk-HUPAHDJ7.js → chunk-TAHDSSA6.js} +6 -0
  12. package/dist/chunks/{chunk-YEWKPWK3.mjs → chunk-U6OEUBIF.mjs} +2 -6
  13. package/dist/chunks/{chunk-U56AGSLE.mjs → chunk-WBCXHGRL.mjs} +2 -2
  14. package/dist/chunks/{chunk-7G7U4DP2.js → chunk-YMLPMIWX.js} +1 -1
  15. package/dist/hooks/useCheckDevice.d.ts.map +1 -1
  16. package/dist/hooks/useCheckDevice.js +2 -2
  17. package/dist/hooks/useCheckDevice.mjs +1 -1
  18. package/dist/hooks/useDraggableBottomSheet.js +2 -2
  19. package/dist/hooks/useDraggableBottomSheet.mjs +1 -1
  20. package/dist/hooks/useModal.js +2 -2
  21. package/dist/hooks/useModal.mjs +1 -1
  22. package/dist/index.js +16 -16
  23. package/dist/index.mjs +4 -4
  24. package/dist/modal/components/ModalContainer.d.ts.map +1 -1
  25. package/dist/modal/components/ModalTemplate.d.ts +1 -0
  26. package/dist/modal/components/ModalTemplate.d.ts.map +1 -1
  27. package/dist/modal/constants.d.ts +14 -0
  28. package/dist/modal/constants.d.ts.map +1 -1
  29. package/dist/modal/index.js +14 -14
  30. package/dist/modal/index.mjs +4 -4
  31. package/dist/schemas/_all.json +9 -4
  32. package/dist/schemas/modal.d.ts +3 -0
  33. package/dist/schemas/modal.d.ts.map +1 -1
  34. package/dist/schemas/modalTemplate.json +9 -4
  35. package/dist/schemas.js +17 -4
  36. package/dist/schemas.mjs +17 -4
  37. package/dist/styles/.generated/built.d.ts +1 -1
  38. package/dist/styles/.generated/built.d.ts.map +1 -1
  39. package/dist/styles/layer.js +2 -2
  40. package/dist/styles/layer.mjs +1 -1
  41. package/dist/styles.css +8 -8
  42. package/dist/styles.js +2 -2
  43. package/dist/styles.layered.css +8 -8
  44. package/dist/styles.mjs +1 -1
  45. package/dist/tokens/company.css +1 -1
  46. package/dist/tokens/css.css +1 -1
  47. package/dist/tokens-domains/gamehub-vars.css +122 -0
  48. package/dist/tokens-domains/gamehub.css +121 -0
  49. package/dist/tokens-domains/prediction-vars.css +1 -1
  50. package/dist/tokens-domains/prediction.css +1 -1
  51. package/package.json +4 -5
@@ -32,6 +32,11 @@
32
32
 
33
33
  5. **새 UI 작업 전, 항상 컴포넌트 매핑부터.** native HTML이나 Tailwind 프리미티브로 구현하기 전, `ui-components.md`에 동일 기능이 있는지 먼저 확인.
34
34
 
35
+ 6. **모달 콘텐츠 컴포넌트 작성 시:**
36
+ - props 타입은 **반드시 `ModalPropsType<T>` 를 확장**할 것 (`close`, `resolve`, `className`, `layout` 시그니처가 시스템에서 자동 주입됨)
37
+ - `<ModalTemplate>` 에는 시스템 주입 **`className` 과 `layout` 을 그대로 전달**할 것 (둘 중 하나라도 누락하면 wrap 스타일 / 모바일 bottom-sheet 자동 전환이 동작하지 않음)
38
+ - 자세한 패턴은 아래 "Modal Writing Rules" 섹션 참조
39
+
35
40
  ---
36
41
 
37
42
  ## Quick Install (단일 패키지)
@@ -167,6 +172,110 @@ export default function App() {
167
172
 
168
173
  ---
169
174
 
175
+ ## Modal Writing Rules
176
+
177
+ 모달 콘텐츠 컴포넌트는 다음 3가지 규칙을 **반드시** 지켜야 합니다.
178
+
179
+ ### Rule 1. props 타입은 반드시 `ModalPropsType<T>` 를 확장하라
180
+
181
+ `modal()` / `useModal()` 로 모달이 mount 될 때 시스템이 다음 4개 prop 을 자동 주입합니다.
182
+
183
+ | 주입 prop | 타입 | 의미 |
184
+ |---|---|---|
185
+ | `close` | `(isAnimation?: boolean) => void` | 모달을 닫음 |
186
+ | `resolve` | `(value: T, useClose?: boolean) => void` | 호출자에게 결과 전달 (T 는 사용자가 정함) |
187
+ | `className` | `string` | 모달 wrap layer (full-screen 중앙 정렬 컨테이너) 에 들어가야 하는 시스템 클래스 |
188
+ | `layout` | `ModalLayout` | 시스템이 결정한 layout. 모바일 viewport 에서는 자동으로 `bottom-sheet` 등으로 변환됨 |
189
+
190
+ 이 4개 prop 을 직접 손으로 타이핑하지 말고 **`ModalPropsType<T>` 를 확장하세요.** 손으로 정의하면 시스템 시그니처 변경 시 모달이 동작하지 않게 됩니다.
191
+
192
+ ```tsx
193
+ // CORRECT — ModalPropsType 확장
194
+ import type { ModalPropsType } from '@nexus-cross/design-system';
195
+
196
+ // ModalPropsType<T> 의 T 는 resolve(value) 의 value 타입
197
+ type Props = ModalPropsType<boolean> & {
198
+ title: string;
199
+ };
200
+
201
+ function ConfirmModal({ className, layout, close, resolve, title }: Props) {
202
+ // ...
203
+ }
204
+ ```
205
+
206
+ ```tsx
207
+ // WRONG — 시스템 주입 prop 을 직접 손으로 정의
208
+ function ConfirmModal({
209
+ close,
210
+ resolve,
211
+ title,
212
+ }: {
213
+ close: () => void;
214
+ resolve: (v: boolean) => void;
215
+ title: string;
216
+ }) {
217
+ // className, layout 누락 → wrap 스타일 / 모바일 bottom-sheet 자동 전환 동작 X
218
+ }
219
+ ```
220
+
221
+ ### Rule 2. `<ModalTemplate>` 에는 시스템 주입 `className` 과 `layout` 을 그대로 전달하라
222
+
223
+ `className` 은 모달 **wrap layer** (full-screen `position: fixed; inset: 0` 중앙 정렬 컨테이너) 에 적용되는 시스템 클래스, `layout` 은 layout 별 분기(애니메이션, bottom-sheet 드래그, slide-left/right 등) 의 단일 source of truth 입니다. **둘 중 하나라도 누락하면 위치, dim, 애니메이션, 모바일 bottom-sheet 동작이 깨집니다.**
224
+
225
+ ```tsx
226
+ // CORRECT — className / layout 그대로 통과
227
+ function ConfirmModal({ className, layout, close, resolve, title }: Props) {
228
+ return (
229
+ <ModalTemplate
230
+ className={className}
231
+ layout={layout}
232
+ title={title}
233
+ close={close}
234
+ footer={<Button onClick={() => resolve(true)}>확인</Button>}
235
+ >
236
+ <p className="text-text-secondary">정말 진행하시겠습니까?</p>
237
+ </ModalTemplate>
238
+ );
239
+ }
240
+ ```
241
+
242
+ ```tsx
243
+ // WRONG — 시스템 className / layout 누락
244
+ function ConfirmModal({ close, resolve, title }: Props) {
245
+ return (
246
+ // className, layout 안 넘김 → wrap 스타일 유실, 모바일 bottom-sheet 미적용
247
+ <ModalTemplate title={title} close={close}>
248
+ ...
249
+ </ModalTemplate>
250
+ );
251
+ }
252
+
253
+ // WRONG — 시스템 className 을 자체 className 으로 덮어씀
254
+ function BadModal({ className, layout, close }: Props) {
255
+ return (
256
+ // className 자리에 자체 클래스만 넣음 → 시스템 className 유실
257
+ <ModalTemplate className="bg-bg-default" layout={layout} close={close} title="…">
258
+ ...
259
+ </ModalTemplate>
260
+ );
261
+ }
262
+ ```
263
+
264
+ > 자체 클래스를 wrap 에 추가하고 싶다면 `cn(className, '내 클래스')` 형태로 결합. **wrap 에는 sizing utility (`w-*`, `max-w-*`, `h-*`) 금지** — sizing 은 `innerClassName` 으로.
265
+
266
+ ### Rule 3. 모달 wrapper 는 직접 만들지 말 것
267
+
268
+ 모달 콘텐츠는 **반드시 `ModalTemplate` 안에** 들어가야 합니다. 일반 `<div>` 로 만들면 focus trap / ESC handling / scroll lock / 애니메이션 / dim / a11y 가 모두 사라집니다.
269
+
270
+ ### Self-check (모달 작성 후 출력 직전 검증)
271
+
272
+ - [ ] props 타입이 `ModalPropsType<T>` 를 확장하고 있는가?
273
+ - [ ] `<ModalTemplate>` 에 `className={className}` 와 `layout={layout}` 을 그대로 넘겼는가?
274
+ - [ ] `<ModalTemplate>` 외부에 별도 wrapper `<div>` 를 두지 않았는가?
275
+ - [ ] 자체 wrap 클래스를 추가했다면 `cn(className, '...')` 으로 시스템 className 을 보존했는가?
276
+
277
+ ---
278
+
170
279
  ## Token Path Convention
171
280
 
172
281
  모든 디자인 토큰은 5-segment 형식: `color.semantic.{namespace}.{slot}.{state}`
@@ -25,6 +25,11 @@
25
25
 
26
26
  4. **`className` 오버라이드 시 `!important` 금지.** `cn()` 유틸이 프리픽스 충돌 자동 해소.
27
27
 
28
+ 5. **모달 콘텐츠 컴포넌트 작성 시 두 가지 필수.**
29
+ - props 타입은 **반드시 `ModalPropsType<T>` 를 확장** (`close`, `resolve`, `className`, `layout` 시그니처가 시스템에서 자동 주입)
30
+ - `<ModalTemplate>` 에 시스템 주입 **`className` 과 `layout` 을 그대로 전달** (둘 중 하나라도 누락하면 wrap 스타일 / 모바일 bottom-sheet 자동 전환 동작 X)
31
+ - 자세한 패턴: `.cursor/rules/nexus-project-setup.mdc` 의 "Modal Writing Rules" 섹션
32
+
28
33
  ## 설치 (단일 패키지)
29
34
 
30
35
  v2.0부터 `@nexus-cross/design-system` 1개 install로 토큰까지 모두 사용 가능합니다.
@@ -292,27 +292,120 @@ import { getPredictionTheme } from '@nexus-cross/design-system/tokens-domains';
292
292
 
293
293
  ## Modal Writing Rules
294
294
 
295
- Modal components **MUST be wrapped with `ModalTemplate`.** Do NOT create modals with plain `<div>`.
295
+ 모달 콘텐츠 컴포넌트는 다음 3가지 규칙을 **반드시** 지켜야 합니다.
296
+
297
+ ### Rule 1. props 타입은 반드시 `ModalPropsType<T>` 를 확장하라
298
+
299
+ `modal()` / `useModal()` 로 모달이 mount 될 때 시스템이 다음 4개 prop 을 자동 주입합니다.
300
+
301
+ | 주입 prop | 타입 | 의미 |
302
+ |---|---|---|
303
+ | `close` | `(isAnimation?: boolean) => void` | 모달을 닫음 |
304
+ | `resolve` | `(value: T, useClose?: boolean) => void` | 호출자에게 결과 전달 (T 는 사용자가 정함) |
305
+ | `className` | `string` | 모달 wrap layer (full-screen 중앙 정렬 컨테이너) 에 들어가야 하는 시스템 클래스 |
306
+ | `layout` | `ModalLayout` | 시스템이 결정한 layout. 모바일 viewport 에서는 자동으로 `bottom-sheet` 등으로 변환됨 |
307
+
308
+ 이 4개 prop 을 직접 손으로 타이핑하지 말고 **`ModalPropsType<T>` 를 확장하세요.** 손으로 정의하면 시스템 주입 시그니처가 바뀔 때 모달이 동작하지 않게 됩니다.
296
309
 
297
310
  ```tsx
298
- // CORRECT modal
311
+ // CORRECT — ModalPropsType 확장
312
+ import type { ModalPropsType } from '@nexus-cross/design-system';
313
+
314
+ // ModalPropsType<T> 의 T 는 resolve(value) 의 value 타입
315
+ type Props = ModalPropsType<boolean> & {
316
+ title: string;
317
+ };
318
+
319
+ function ConfirmModal({ className, layout, close, resolve, title }: Props) {
320
+ // ...
321
+ }
322
+ ```
323
+
324
+ ```tsx
325
+ // WRONG — 시스템 주입 prop 을 직접 손으로 정의
326
+ function ConfirmModal({
327
+ close,
328
+ resolve,
329
+ title,
330
+ }: {
331
+ close: () => void;
332
+ resolve: (v: boolean) => void;
333
+ title: string;
334
+ }) {
335
+ // className, layout 누락 → wrap 스타일 / 모바일 bottom-sheet 자동 전환 동작 X
336
+ }
337
+ ```
338
+
339
+ ### Rule 2. `<ModalTemplate>` 에는 시스템 주입 `className` 과 `layout` 을 그대로 전달하라
340
+
341
+ `className` 은 모달 **wrap layer** (full-screen `position: fixed; inset: 0` 중앙 정렬 컨테이너) 에 적용되는 시스템 클래스, `layout` 은 layout 별 분기(애니메이션, bottom-sheet 드래그, slide-left/right 등) 의 단일 source of truth 입니다. **둘 중 하나라도 누락하면 위치, dim, 애니메이션, 모바일 bottom-sheet 동작이 깨집니다.**
342
+
343
+ ```tsx
344
+ // CORRECT — className / layout 그대로 통과
345
+ function ConfirmModal({ className, layout, close, resolve, title }: Props) {
346
+ return (
347
+ <ModalTemplate
348
+ className={className}
349
+ layout={layout}
350
+ title={title}
351
+ close={close}
352
+ footer={<Button onClick={() => resolve(true)}>확인</Button>}
353
+ >
354
+ <p className="text-text-secondary">정말 진행하시겠습니까?</p>
355
+ </ModalTemplate>
356
+ );
357
+ }
358
+ ```
359
+
360
+ ```tsx
361
+ // WRONG — 시스템 className / layout 누락
362
+ function ConfirmModal({ close, resolve, title }: Props) {
363
+ return (
364
+ // className, layout 안 넘김 → wrap 스타일 유실, 모바일 bottom-sheet 미적용
365
+ <ModalTemplate title={title} close={close}>
366
+ ...
367
+ </ModalTemplate>
368
+ );
369
+ }
370
+
371
+ // WRONG — 시스템 className 을 자체 className 으로 덮어씀
372
+ function BadModal({ className, layout, close }: Props) {
373
+ return (
374
+ // className 자리에 자체 클래스만 넣음 → 시스템 className 유실
375
+ <ModalTemplate className="bg-bg-default" layout={layout} close={close} title="…">
376
+ ...
377
+ </ModalTemplate>
378
+ );
379
+ }
380
+ ```
381
+
382
+ > 자체 클래스를 wrap 에 추가하고 싶다면 `cn(className, '내 클래스')` 형태로 결합. **wrap 에는 sizing utility (`w-*`, `max-w-*`, `h-*`) 금지** — sizing 은 `innerClassName` 으로.
383
+
384
+ ### Rule 3. 모달 wrapper 는 직접 만들지 말 것
385
+
386
+ 모달 콘텐츠는 **반드시 `ModalTemplate` 안에** 들어가야 합니다. 일반 `<div>` 로 만들면 focus trap / ESC handling / scroll lock / 애니메이션 / dim / a11y 가 모두 사라집니다.
387
+
388
+ ```tsx
389
+ // CORRECT
299
390
  import { ModalTemplate } from '@nexus-cross/design-system/modal';
391
+ import type { ModalPropsType } from '@nexus-cross/design-system';
392
+
393
+ type Props = ModalPropsType<{ confirmed: boolean }>;
300
394
 
301
- function MyModal({ close, resolve }: { close: () => void; resolve: (value: any) => void }) {
395
+ function MyModal({ className, layout, close, resolve }: Props) {
302
396
  return (
303
- <ModalTemplate title="Title" close={close}>
397
+ <ModalTemplate className={className} layout={layout} title="Title" close={close}>
304
398
  <p className="text-text-secondary">Content</p>
305
399
  <Button onClick={() => resolve({ confirmed: true })}>Confirm</Button>
306
400
  </ModalTemplate>
307
401
  );
308
402
  }
309
403
 
310
- // Call
311
404
  const result = await modal({ component: MyModal });
312
405
  ```
313
406
 
314
407
  ```tsx
315
- // WRONG modal using div without ModalTemplate
408
+ // WRONG — div 모달을 직접 만든 경우
316
409
  function MyModal({ close }: { close: () => void }) {
317
410
  return (
318
411
  <div className="p-6 bg-white rounded-lg">
@@ -323,6 +416,13 @@ function MyModal({ close }: { close: () => void }) {
323
416
  }
324
417
  ```
325
418
 
419
+ ### Self-check (모달 작성 후 출력 직전 검증)
420
+
421
+ - [ ] props 타입이 `ModalPropsType<T>` 를 확장하고 있는가?
422
+ - [ ] `<ModalTemplate>` 에 `className={className}` 와 `layout={layout}` 을 그대로 넘겼는가?
423
+ - [ ] `<ModalTemplate>` 외부에 별도 wrapper `<div>` 를 두지 않았는가?
424
+ - [ ] 자체 wrap 클래스를 추가했다면 `cn(className, '...')` 으로 시스템 className 을 보존했는가?
425
+
326
426
  ## Toast Usage Rules
327
427
 
328
428
  ```tsx
@@ -985,11 +985,19 @@ WHEN TO USE:
985
985
 
986
986
  PREFERRED API: use modal() / useModal() imperative API rather than mounting <ModalTemplate> directly. modal() handles stacking, focus return, ESC, and background scroll automatically.
987
987
 
988
+ SIZING (must read — most common AI mistake):
989
+ • The modal has TWO layers:
990
+ 1. wrap = full-screen `position: fixed; inset: 0` flex container → controlled by `className`
991
+ 2. panel = the actual dialog box rendered at the center → controlled by `innerClassName`
992
+ • Sizing utilities (`w-*`, `max-w-*`, `min-w-*`, `h-*`, `max-h-*`) MUST go on `innerClassName`.
993
+ • Putting `w-[400px] max-w-[92vw]` on `className` shrinks the wrap (the centering layer), NOT the panel — the panel keeps its default width and the modal looks broken.
994
+
988
995
  ANTI-PATTERNS:
989
996
  ✗ Custom <div className="fixed inset-0"> → modal() (loses focus trap, a11y)
990
997
  ✗ Direct <ModalTemplate> mount in render tree → wrap with modal()
991
998
  ✗ Modal with no title for screen readers → always pass title prop
992
999
  ✗ Multiple modals stacking confusingly → use isAlone:true to close prior modals
1000
+ ✗ <ModalTemplate className="w-[400px] max-w-[92vw]"> → put sizing on `innerClassName` instead
993
1001
 
994
1002
  | Prop | Type | Default | Description |
995
1003
  |---|---|---|---|
@@ -1000,15 +1008,16 @@ ANTI-PATTERNS:
1000
1008
  | `dimClose` | `boolean` | `true` | Close on dim click |
1001
1009
  | `hideHeader` | `boolean` | `false` | Hide header |
1002
1010
  | `hideFooter` | `boolean` | `true` | Hide footer |
1011
+ | `hideCloseBtn` | `boolean` | `false` | Hide the X close button in the header |
1003
1012
  | `footer` | `ReactNode` | - | Custom footer (ReactElement) |
1004
1013
  | `animation` | `object` | - | Modal animation |
1005
1014
  | `enableDrag` | `boolean` | `true` | Enable drag (bottom-sheet/draggable layouts) |
1006
1015
  | `dragPersistKey` | `string` | - | Drag position persistence key |
1007
1016
  | `close` | `ReactNode` | - | Modal close function (isAnimation?: boolean) => void (auto-injected) |
1008
1017
  | `children` | `ReactNode` | - | Modal body (ReactNode, required) |
1009
- | `className` | `string` | - | Root wrapper style |
1010
- | `innerClassName` | `string` | - | Modal body style |
1011
- | `bodyClassName` | `string` | - | Body area style |
1018
+ | `className` | `string` | - | Root wrapper style — applied to the full-screen `position: fixed; inset: 0` flex container that centers the modal. NOTE: width / max-width / height utilities (`w-[400px]`, `max-w-[92vw]`, `h-*`) MUST NOT go here — they will resize the wrap layer, not the panel. Put sizing on `innerClassName` instead. |
1019
+ | `innerClassName` | `string` | - | Modal panel (the actual dialog box) style — apply ALL sizing utilities here: `w-[400px]`, `max-w-[92vw]`, `min-h-*`, `max-h-[80vh]`, `rounded-*`, panel background, etc. This is the most common override point. |
1020
+ | `bodyClassName` | `string` | - | Body area style (inside the panel, between header and footer) |
1012
1021
  | `footerClassName` | `string` | - | Footer area style |
1013
1022
  | `dimClassName` | `string` | - | Dim overlay style |
1014
1023
  | `headerClassName` | `string` | - | Header area style |
@@ -1056,6 +1065,53 @@ function MyModal({ close, resolve }: { close: () => void; resolve: (value: any)
1056
1065
  }
1057
1066
  ```
1058
1067
 
1068
+ ### Sizing the modal (`className` vs `innerClassName`)
1069
+
1070
+ ModalTemplate has **two layers**:
1071
+
1072
+ | Prop | DOM target | Use for |
1073
+ |---|---|---|
1074
+ | `className` | full-screen wrap (`position: fixed; inset: 0` flex centering layer) | dim/centering overrides only |
1075
+ | `innerClassName` | the actual modal panel (the visible dialog box) | **all sizing**: `w-*`, `max-w-*`, `min-w-*`, `h-*`, `max-h-*`, panel `rounded-*`, panel padding |
1076
+
1077
+ Putting `w-[400px] max-w-[92vw]` on `className` shrinks the centering wrap, **not** the panel — the panel keeps its default width and the modal renders incorrectly. This is the single most common Modal mistake AI assistants make. **Always put sizing utilities on `innerClassName`.**
1078
+
1079
+ ```tsx
1080
+ // ❌ WRONG — sizing on the wrap layer (panel size unchanged, layout breaks)
1081
+ <ModalTemplate
1082
+ title="Title"
1083
+ close={close}
1084
+ className="w-[400px] max-w-[92vw]"
1085
+ >
1086
+ ...
1087
+ </ModalTemplate>
1088
+
1089
+ // ✅ CORRECT — sizing on the panel
1090
+ <ModalTemplate
1091
+ title="Title"
1092
+ close={close}
1093
+ innerClassName="w-[400px] max-w-[92vw]"
1094
+ >
1095
+ ...
1096
+ </ModalTemplate>
1097
+
1098
+ // ✅ Larger modal with constrained height + scrollable body
1099
+ <ModalTemplate
1100
+ title="Long form"
1101
+ close={close}
1102
+ innerClassName="w-[640px] max-w-[92vw] max-h-[80vh]"
1103
+ bodyClassName="overflow-y-auto"
1104
+ >
1105
+ ...
1106
+ </ModalTemplate>
1107
+
1108
+ // ✅ Same applies when calling via modal() — pass through props
1109
+ modal({
1110
+ component: MyModal,
1111
+ props: { innerClassName: 'w-[480px] max-w-[92vw]' },
1112
+ });
1113
+ ```
1114
+
1059
1115
  ### Calling a Modal
1060
1116
 
1061
1117
  ```tsx
@@ -1086,6 +1142,7 @@ openModal({
1086
1142
  - Do NOT create modal components without ModalTemplate (using plain `<div>`)
1087
1143
  - Do NOT define the `close` prop manually (the system injects it automatically)
1088
1144
  - Do NOT implement a separate dim/overlay inside the modal
1145
+ - Do NOT put sizing utilities (`w-*`, `max-w-*`, `min-w-*`, `h-*`, `max-h-*`) on `className` — they belong on `innerClassName` (the panel). `className` only styles the full-screen wrap.
1089
1146
 
1090
1147
  ---
1091
1148
 
@@ -1,9 +1,9 @@
1
- import { useDraggableBottomSheet } from './chunk-U56AGSLE.mjs';
1
+ import { useDraggableBottomSheet } from './chunk-WBCXHGRL.mjs';
2
2
  import { useDraggableWindow } from './chunk-4J3GCZ7W.mjs';
3
3
  import { scrollRelease, scrollFreeze } from './chunk-54IA2P2Z.mjs';
4
- import { useCheckDevice_default } from './chunk-YEWKPWK3.mjs';
4
+ import { useCheckDevice_default } from './chunk-U6OEUBIF.mjs';
5
5
  import { useClickOutside_default } from './chunk-OTGS6BDQ.mjs';
6
- import { Modal_default } from './chunk-6H7V2I3X.mjs';
6
+ import { Modal_default, defaultBreakPoints } from './chunk-FY2N42XN.mjs';
7
7
  import { cn } from './chunk-MCKOWMLS.mjs';
8
8
  import React, { forwardRef, useState, useMemo, useCallback, useEffect, useImperativeHandle, useRef, Suspense } from 'react';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -184,7 +184,11 @@ var ModalPortal = ({ children, selector }) => {
184
184
  return mounted && element && children ? createPortal(children, element) : null;
185
185
  };
186
186
  var ModalPortal_default = ModalPortal;
187
- var ModalContainer = ({ className = "", breakPoints, ...defaultOption }) => {
187
+ var ModalContainer = ({
188
+ className = "",
189
+ breakPoints = defaultBreakPoints,
190
+ ...defaultOption
191
+ }) => {
188
192
  const [modals, setModals] = useState(Modal_default.modalList);
189
193
  const setModalClose = useCallback((params) => {
190
194
  if (!params) return;
@@ -408,6 +412,7 @@ var ModalTemplate = forwardRef(
408
412
  desc,
409
413
  hideHeader = false,
410
414
  hideFooter = true,
415
+ hideCloseBtn = false,
411
416
  dimClose,
412
417
  footer,
413
418
  close,
@@ -562,7 +567,7 @@ var ModalTemplate = forwardRef(
562
567
  ref: modalHeaderRef,
563
568
  onMouseDown: isDraggable && enableDrag ? windowDrag.handleMouseDown : void 0,
564
569
  children: [
565
- /* @__PURE__ */ jsx(
570
+ !hideCloseBtn && /* @__PURE__ */ jsx(
566
571
  "button",
567
572
  {
568
573
  type: "button",
@@ -25,9 +25,9 @@ function useDraggableBottomSheet({
25
25
  if (!enabled) return;
26
26
  const touch = e.touches[0];
27
27
  const target = e.currentTarget;
28
- const modalInner = target.querySelector(".modal-inner");
28
+ const modalInner = target.querySelector(".nexus-modal-inner");
29
29
  if (!modalInner) return;
30
- const scrollableElement = e.target.closest(".modal-body");
30
+ const scrollableElement = e.target.closest(".nexus-modal-body");
31
31
  if (scrollableElement && scrollableElement.scrollTop > 0) return;
32
32
  dragStateRef.current = {
33
33
  isDragging: true,
@@ -8,6 +8,11 @@ var defaultModalOption = {
8
8
  animation: { name: "pop-fade", duration: 300 },
9
9
  dimCloseEnable: true
10
10
  };
11
+ var defaultBreakPoints = {
12
+ isDesktop: "(min-width: 1280px)",
13
+ isTablet: "screen and (min-width: 768px) and (max-width: 1279px)",
14
+ isMobile: "(max-width: 767px)"
15
+ };
11
16
 
12
17
  // src/modal/Modal.ts
13
18
  var Modal = class {
@@ -267,4 +272,4 @@ function useModal() {
267
272
  };
268
273
  }
269
274
 
270
- export { Modal_default, useModal };
275
+ export { Modal_default, defaultBreakPoints, useModal };