@x-plat/design-system 0.5.46 → 0.5.48

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.
@@ -300,6 +300,91 @@ DS 컴포넌트는 자체 외부 margin 없음. 부모가 gap 안 주면 모두
300
300
  </div>
301
301
  ```
302
302
 
303
+ ### 폼 요소 구조 — 중첩 금지
304
+
305
+ **Input / TextArea / Select 안에 다른 컴포넌트를 넣지 않는다.** 레이아웃이 깨진다. 폼 요소는 항상 **형제(sibling)** 로 배치한다.
306
+
307
+ #### ❌ 금지 — 폼 요소 중첩
308
+
309
+ ```tsx
310
+ // Input 안에 Button → 잘못
311
+ <Input>
312
+ <Button>검색</Button>
313
+ </Input>
314
+
315
+ // TextArea 안에 Button → 잘못
316
+ <TextArea>
317
+ <Button>전송</Button>
318
+ </TextArea>
319
+
320
+ // Select 안에 Input → 잘못
321
+ <Select>
322
+ <Input placeholder="검색" />
323
+ </Select>
324
+
325
+ // form 안에 form → 절대 금지
326
+ <form>
327
+ <form>...</form>
328
+ </form>
329
+ ```
330
+
331
+ #### ✅ 올바른 패턴
332
+
333
+ **Pattern 1: "입력 + 전송 버튼" 합성** → `ChatInput` 컴포넌트 사용
334
+ ```tsx
335
+ import { ChatInput } from "@x-plat/design-system/components";
336
+
337
+ <ChatInput
338
+ placeholder="메시지 입력"
339
+ onSubmit={(value) => handleSend(value)}
340
+ />
341
+ ```
342
+
343
+ **Pattern 2: Input 우측 아이콘/소형 액션** → Input 의 `suffix` 슬롯 사용
344
+ ```tsx
345
+ <Input
346
+ placeholder="검색"
347
+ suffix={<Icon name="search" />}
348
+ />
349
+ ```
350
+ > `suffix` 는 작은 아이콘/버튼 1개용. 일반 `<Button>` 컴포넌트 넣지 말 것 (사이즈 충돌).
351
+
352
+ **Pattern 3: 폼 요소 + 액션 버튼** → 형제 배치, wrapper 에서 gap
353
+ ```tsx
354
+ <div style={{ display: "flex", gap: "var(--spacing-space-2)" }}>
355
+ <Input placeholder="이메일" />
356
+ <Button type="primary">확인</Button>
357
+ </div>
358
+ ```
359
+
360
+ **Pattern 4: 폼 요소들 묶음** → 모두 형제, 부모에서 gap
361
+ ```tsx
362
+ <form style={{
363
+ display: "flex",
364
+ flexDirection: "column",
365
+ gap: "var(--spacing-space-4)",
366
+ }}>
367
+ <Input label="이름" />
368
+ <TextArea label="자기소개" />
369
+ <Select label="국가" options={...} />
370
+ <Button type="primary">제출</Button>
371
+ </form>
372
+ ```
373
+
374
+ #### 폼 요소별 children/슬롯 정책
375
+
376
+ | 컴포넌트 | children 받음? | 슬롯 |
377
+ |---|---|---|
378
+ | `Input` | ❌ | `suffix` (아이콘 1개) |
379
+ | `TextArea` | ❌ | 없음 |
380
+ | `Select` | ❌ | `options` prop 사용 (children 아님) |
381
+ | `CheckBox` / `Radio` / `Switch` | ❌ | `label` prop |
382
+ | `DatePicker` / `Calendar` | ❌ | controlled value/onChange |
383
+ | `Button` | ✅ | 텍스트/아이콘만 |
384
+ | `ChatInput` | ❌ | 입력+버튼 합성 컴포넌트 |
385
+
386
+ > "입력 안에 무언가를 넣고 싶다" 는 욕구가 들면 → **다른 형태의 합성 컴포넌트가 있는지 먼저 확인**. 없으면 **wrapper + 형제** 패턴 사용.
387
+
303
388
  ### 케이스별 권장 spacing 값
304
389
 
305
390
  | 케이스 | 권장 값 |
@@ -338,6 +423,88 @@ DS 컴포넌트는 자체 외부 margin 없음. 부모가 gap 안 주면 모두
338
423
 
339
424
  ---
340
425
 
426
+ ## 안전 가이드 — 자주 누락되는 5가지
427
+
428
+ UI 디자인 결정(레이아웃 폭, 색상 조합, 위계 등)은 **사용자 의도를 존중** 한다. 다만 아래 5가지는 빠지면 **레이아웃이 깨지거나 DS 가 오용되는 영역**이라 반드시 적용.
429
+
430
+ ### 1. 반응형 GridItem 누락 방지
431
+
432
+ `GridItem` 의 `column` 은 default(laptop) 만 주면 **모바일에서 깨진다**. tablet(≤8) / mobile(≤4) 한계를 반드시 확인.
433
+
434
+ ```tsx
435
+ // ❌ default 만 → mobile 에서 6/4 = 1.5 칸이 되어 잘림
436
+ <GridItem column={{ default: 6 }}>...</GridItem>
437
+
438
+ // ✅ 브레이크별 명시
439
+ <GridItem column={{ default: 6, tablet: 4, mobile: 4 }}>...</GridItem>
440
+
441
+ // ✅ 전체 폭이면 명시적으로 12/8/4
442
+ <GridItem column={{ default: 12, tablet: 8, mobile: 4 }}>...</GridItem>
443
+ ```
444
+
445
+ ### 2. 컴포넌트 오용 방지 — 상황별 매칭
446
+
447
+ 비슷해 보이는 컴포넌트가 여러 개일 때 잘못 고르면 UX 가 망가진다.
448
+
449
+ | 상황 | 사용할 컴포넌트 | 쓰지 말 것 |
450
+ |---|---|---|
451
+ | 짧은 알림(3-5초 후 사라짐) | `Toast` | Modal, Alert |
452
+ | 인라인 경고/상태 메시지 | `Alert` | Modal |
453
+ | 추가 설명 1-2줄 (hover) | `Tooltip` | PopOver |
454
+ | 풍부한 부가 정보 + 액션 | `PopOver` | Modal, Tooltip |
455
+ | 전용 작업 흐름 (작성/확인) | `Modal` | Drawer (전화면 시) |
456
+ | 사이드 패널 (긴 흐름) | `Drawer` | Modal |
457
+ | 단일 선택 (2-7개) | `Radio` | Select |
458
+ | 단일 선택 (8개 이상) | `Select` | Radio |
459
+ | 다중 선택 | `CheckBox` 리스트 또는 multi-`Select` | Radio |
460
+ | boolean on/off | `Switch` | CheckBox |
461
+
462
+ ### 3. 상태 처리 3종 — Loading / Empty / Error
463
+
464
+ 데이터를 가져오는 곳(API/비동기)에는 반드시 3개 상태를 모두 처리.
465
+
466
+ ```tsx
467
+ // ✅ 3개 상태 모두 처리
468
+ if (loading) return <Skeleton />; // 또는 <Spinner />
469
+ if (error) return <Alert type="error">오류: {error.message}</Alert>;
470
+ if (data.length === 0) return <EmptyState title="데이터가 없습니다" />;
471
+
472
+ return <Table data={data} />;
473
+ ```
474
+
475
+ 데이터 무관 정적 화면이면 이 패턴 생략 가능.
476
+
477
+ ### 4. 접근성 기본 4개
478
+
479
+ 빠지면 시각/키보드 사용자가 못 씀.
480
+
481
+ | 요소 | 필수 처리 |
482
+ |---|---|
483
+ | 이미지 (`<img>`) | `alt` 속성 (장식이면 `alt=""`) |
484
+ | 아이콘 only 버튼 | `aria-label` (예: `<Button aria-label="삭제"><TrashIcon /></Button>`) |
485
+ | Input / TextArea / Select 등 폼 | `label` prop |
486
+ | Modal / Drawer 등 오버레이 | DS 가 처리하지만 닫기 버튼이 키보드로 접근 가능한지 확인 |
487
+
488
+ ### 5. 콘텐츠 안전
489
+
490
+ ```tsx
491
+ // ❌ 자리 채우기 lorem ipsum
492
+ <p>Lorem ipsum dolor sit amet...</p>
493
+
494
+ // ❌ 한국어 컨텍스트에 영어 placeholder
495
+ <Input placeholder="Enter your name" />
496
+
497
+ // ✅ 의미 있는 / 한국어 placeholder
498
+ <Input placeholder="이름을 입력하세요" />
499
+
500
+ // ✅ 데이터가 없을 때 명시적 처리
501
+ <EmptyState title="등록된 게시글이 없습니다" />
502
+ ```
503
+
504
+ 언어 일관성: 사용자가 한국어로 요청했으면 모든 UI 텍스트(label / placeholder / button / message)도 한국어.
505
+
506
+ ---
507
+
341
508
  ## 컴포넌트 사용 패턴
342
509
 
343
510
  ### ✅ 올바른 사용
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-plat/design-system",
3
- "version": "0.5.46",
3
+ "version": "0.5.48",
4
4
  "description": "XPLAT UI Design System",
5
5
  "author": "XPLAT WOONG",
6
6
  "main": "dist/index.cjs",