sellmate-design-system-react 3.0.0 → 3.2.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.
Files changed (78) hide show
  1. package/AGENTS.md +695 -0
  2. package/README.md +106 -0
  3. package/bin/sellmate-ds.mjs +337 -0
  4. package/dist/components/SActionModal/README.md +41 -0
  5. package/dist/components/SBadge/README.md +25 -0
  6. package/dist/components/SBarcodeInput/README.md +64 -0
  7. package/dist/components/SButton/README.md +49 -0
  8. package/dist/components/SCalendar/README.md +42 -0
  9. package/dist/components/SCallout/README.md +29 -0
  10. package/dist/components/SCard/README.md +12 -0
  11. package/dist/components/SCheckbox/README.md +44 -0
  12. package/dist/components/SChip/README.md +57 -0
  13. package/dist/components/SChipInput/README.md +77 -0
  14. package/dist/components/SCircleProgress/README.md +35 -0
  15. package/dist/components/SConfirmModal/README.md +61 -0
  16. package/dist/components/SDatePicker/README.md +62 -0
  17. package/dist/components/SDateRangePicker/README.md +64 -0
  18. package/dist/components/SDivider/README.md +25 -0
  19. package/dist/components/SDraggableItem/README.md +40 -0
  20. package/dist/components/SDropdownButton/README.md +50 -0
  21. package/dist/components/SExpansionItem/README.md +40 -0
  22. package/dist/components/SField/README.md +91 -0
  23. package/dist/components/SFilePicker/README.md +72 -0
  24. package/dist/components/SForm/README.md +31 -0
  25. package/dist/components/SGhostButton/README.md +76 -0
  26. package/dist/components/SGnb/README.md +56 -0
  27. package/dist/components/SGuide/README.md +34 -0
  28. package/dist/components/SIcon/README.md +91 -0
  29. package/dist/components/SInput/README.md +66 -0
  30. package/dist/components/SKeyValueTable/README.md +59 -0
  31. package/dist/components/SLayout/README.md +42 -0
  32. package/dist/components/SLinearProgress/README.md +17 -0
  33. package/dist/components/SList/README.md +14 -0
  34. package/dist/components/SListItem/README.md +19 -0
  35. package/dist/components/SLoadingContainer/README.md +29 -0
  36. package/dist/components/SLoadingModal/README.md +52 -0
  37. package/dist/components/SModal/README.md +192 -0
  38. package/dist/components/SModalContainer/README.md +49 -0
  39. package/dist/components/SNumberInput/README.md +74 -0
  40. package/dist/components/SPage/README.md +25 -0
  41. package/dist/components/SPagination/README.md +40 -0
  42. package/dist/components/SPopover/README.md +54 -0
  43. package/dist/components/SPopup/README.md +38 -0
  44. package/dist/components/SPortal/README.md +51 -0
  45. package/dist/components/SRadio/README.md +57 -0
  46. package/dist/components/SRadioButton/README.md +24 -0
  47. package/dist/components/SScrollArea/README.md +16 -0
  48. package/dist/components/SSectionHeaderCard/README.md +51 -0
  49. package/dist/components/SSelect/README.md +79 -0
  50. package/dist/components/SStepper/README.md +30 -0
  51. package/dist/components/SSwitch/README.md +24 -0
  52. package/dist/components/STable/README.md +82 -0
  53. package/dist/components/STableBar/README.md +28 -0
  54. package/dist/components/STabs/README.md +37 -0
  55. package/dist/components/STag/README.md +44 -0
  56. package/dist/components/STextLink/README.md +48 -0
  57. package/dist/components/STextarea/README.md +60 -0
  58. package/dist/components/STimePicker/README.md +65 -0
  59. package/dist/components/STimeRangePicker/README.md +63 -0
  60. package/dist/components/SToast/README.md +70 -0
  61. package/dist/components/SToggle/README.md +22 -0
  62. package/dist/components/STooltip/README.md +57 -0
  63. package/dist/llms-full.txt +3530 -0
  64. package/dist/llms.txt +882 -0
  65. package/dist/styles.css +221 -0
  66. package/dist/theme.css +8 -0
  67. package/eslint/index.mjs +69 -0
  68. package/eslint/lib/class-names.mjs +119 -0
  69. package/eslint/lib/table-column.mjs +53 -0
  70. package/eslint/rules/component-group-gap.mjs +186 -0
  71. package/eslint/rules/no-arbitrary-class.mjs +91 -0
  72. package/eslint/rules/no-off-scale-spacing.mjs +67 -0
  73. package/eslint/rules/no-raw-html-control.mjs +117 -0
  74. package/eslint/rules/prefer-typo-preset.mjs +73 -0
  75. package/eslint/rules/require-locale-number.mjs +78 -0
  76. package/eslint/rules/table-numeric-align.mjs +93 -0
  77. package/eslint/scale.gen.mjs +16 -0
  78. package/package.json +15 -4
package/README.md CHANGED
@@ -13,8 +13,23 @@ Sellmate 디자인 시스템의 React 컴포넌트 라이브러리 (React + Type
13
13
 
14
14
  ```bash
15
15
  npm install sellmate-design-system-react
16
+ npx sellmate-ds init
16
17
  ```
17
18
 
19
+ `init` 이 소비 앱 설정을 자동으로 연결합니다.
20
+
21
+ | 대상 | 하는 일 |
22
+ | --- | --- |
23
+ | `CLAUDE.md` / `AGENTS.md` | AI 에이전트가 규칙(`llms.txt`)을 먼저 읽도록 지침 추가 |
24
+ | `eslint.config.mjs` | 디자인 시스템 ESLint 프리셋 연결 |
25
+ | 전역 CSS | `theme.css` import 와 `@source` 경로(파일 기준 상대경로) 추가 |
26
+
27
+ - **이미 되어 있는 항목은 건너뜁니다** — 여러 번 실행해도 안전합니다.
28
+ - 무엇이 바뀌는지 먼저 보려면 `npx sellmate-ds init --dry-run`.
29
+ - 자동으로 고치기 어려운 형태(예: `defineConfig(...)` 로 감싼 ESLint 설정)는 **파일을 건드리지 않고** 붙여넣을 스니펫을 출력합니다.
30
+
31
+ 수동으로 설정하려면 아래 절을 따르세요.
32
+
18
33
  ## 설정 (방식 1 — 권장, Tailwind v4)
19
34
 
20
35
  소비 앱의 전역 CSS에 다음을 추가합니다.
@@ -105,6 +120,97 @@ export function Example() {
105
120
 
106
121
  `SButton` 은 텍스트를 **`label`(문자열)로만** 받습니다 — `children` 은 받지 않으며, 아이콘은 `icon`/`rightIcon` 을 씁니다. 색상 prop 은 `color` 입니다(`variant` 아님).
107
122
 
123
+ ## 화면 작성 규칙 (AI 에이전트 포함)
124
+
125
+ 컴포넌트 조합·간격·타이포 등 **화면을 만들 때 지켜야 할 규칙**은 패키지에 함께 배포됩니다.
126
+
127
+ | 파일 | 용도 |
128
+ | --- | --- |
129
+ | `node_modules/sellmate-design-system-react/AGENTS.md` | 사람이 읽는 사용 규칙서 |
130
+ | `node_modules/sellmate-design-system-react/dist/llms.txt` | **AI 가 항상 읽는 문서** — 규칙 + 토큰 어휘 + 컴포넌트 인덱스 (약 32KB) |
131
+ | `node_modules/sellmate-design-system-react/dist/components/<이름>/README.md` | 컴포넌트별 Props/Events — 쓸 컴포넌트만 골라서 읽습니다 |
132
+ | `node_modules/sellmate-design-system-react/dist/llms-full.txt` | 위 둘을 한 파일에 합친 판본 (약 106KB) — 단일 파일만 물릴 수 있는 도구용 |
133
+
134
+ Props 를 `llms.txt` 에서 뺀 이유: 전체 컴포넌트 Props 가 분량의 70% 를 차지하는데,
135
+ prop 오류는 TypeScript 가 잡아주지만 **디자인 규칙은 아무도 잡아주지 않습니다.**
136
+ 한정된 컨텍스트를 규칙에 쓰고, Props 는 필요한 것만 정확히 읽게 하는 편이 낫습니다.
137
+
138
+ Claude 등 AI 에이전트를 쓴다면 소비 앱의 `CLAUDE.md` 에 다음 한 줄을 넣어두면 됩니다.
139
+
140
+ ```md
141
+ UI 작업 전 `node_modules/sellmate-design-system-react/dist/llms.txt` 를 먼저 읽을 것.
142
+ ```
143
+
144
+ ## ESLint 설정
145
+
146
+ 위 규칙 중 **정적으로 검출 가능한 것**은 린트로 강제할 수 있습니다. 소비 앱의 `eslint.config.mjs` 에 추가하세요.
147
+
148
+ ```js
149
+ // eslint.config.mjs
150
+ import sellmate from 'sellmate-design-system-react/eslint';
151
+
152
+ export default [
153
+ // ... 기존 설정
154
+ ...sellmate.configs.recommended,
155
+ ];
156
+ ```
157
+
158
+ | 규칙 | 기본 | 검출 대상 |
159
+ | --- | --- | --- |
160
+ | `sellmate/no-raw-html-control` | error | 대응 컴포넌트가 있는 생 HTML (`<button>` `<input>` `<select>` `<textarea>` `<table>` `<form>` `<dialog>` `<hr>` `<details>` `<progress>`), `alert()`/`confirm()` |
161
+ | `sellmate/no-arbitrary-class` | error | **토큰이 있는 속성**(색·타이포·간격·모서리)의 임의 값 — `text-[14px]`, `bg-[#eee]`, `gap-[13px]`, `rounded-[5px]` |
162
+ | `sellmate/no-off-scale-spacing` | error | 스케일 밖 간격 (`gap-13`, `p-15`) — Tailwind v4 에서 **조용히 무시되는** 값이라 눈으로 찾기 어렵다 |
163
+ | `sellmate/table-numeric-align` | error | 숫자 컬럼(금액·수량 등)에 `align: 'right'` 누락 — **`--fix` 로 자동 교정** |
164
+ | `sellmate/require-locale-number` | error | 숫자 컬럼의 `toLocaleString()` 누락 — 세 자리 콤마는 필수 |
165
+ | `sellmate/component-group-gap` | error | 같은 컴포넌트를 나열할 때의 그룹 간격 — 배열 방향에 따라 값이 다르다(체크박스 가로 24 / 세로 8) |
166
+ | `sellmate/prefer-typo-preset` | warn | `text-14 font-bold` 같은 낱개 조합 → `typo-*` 프리셋 |
167
+
168
+ `className` 뿐 아니라 `cn()`/`clsx()` 인자, 템플릿 리터럴, 객체 키 안까지 검사합니다.
169
+
170
+ ### 일부러 잡지 않는 것
171
+
172
+ 과검출을 피하려고 다음은 기본 설정에서 **통과**시킵니다.
173
+
174
+ ```tsx
175
+ // 디자인 토큰이 없는 앱 고유 레이아웃 치수 — 정당한 사용
176
+ <div className="w-[280px] max-w-[1200px] grid-cols-[200px_1fr] top-[64px]" />
177
+
178
+ // 토큰을 var() 로 참조하는 형태
179
+ <div className="h-[var(--sys-size-control-md-height)]" />
180
+
181
+ // 시각 요소가 없어 대체 컴포넌트가 없다
182
+ <input type="hidden" name="csrf" />
183
+
184
+ // 로고 SVG·본문 안 시맨틱 목록·DS 밖 컨트롤의 레이블
185
+ <svg viewBox="0 0 24 24" />
186
+ <ul><li>…</li></ul>
187
+ ```
188
+
189
+ 핵심은 **"디자인 토큰을 하드코딩하지 마라"** 이지 "임의 값을 절대 쓰지 마라" 가 아닙니다.
190
+
191
+ ### 더 엄격하게 / 더 느슨하게
192
+
193
+ `configs.strict` 는 전 규칙을 error 로 올리고 `<ul>` `<ol>` `<li>` `<svg>` `<label>` 까지 검사합니다.
194
+
195
+ ```js
196
+ export default [...sellmate.configs.strict];
197
+ ```
198
+
199
+ 규칙별 예외도 줄 수 있습니다.
200
+
201
+ ```js
202
+ {
203
+ rules: {
204
+ // strict 를 쓰되 로고 SVG 는 허용
205
+ 'sellmate/no-raw-html-control': ['error', { strict: true, allow: ['svg'] }],
206
+ // 그림자만 임의 값 허용
207
+ 'sellmate/no-arbitrary-class': ['error', { ignorePrefixes: ['shadow'] }],
208
+ },
209
+ }
210
+ ```
211
+
212
+ 허용 값 스케일은 `theme.css` 에서 **빌드 시 자동 추출**되므로 토큰이 바뀌면 규칙도 따라갑니다.
213
+
108
214
  ## 제공 컴포넌트
109
215
 
110
216
  분류는 `src/index.ts` 의 export 그룹과 동일합니다.
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sellmate-ds — 소비 앱 설정 자동화 CLI.
4
+ *
5
+ * npx sellmate-ds init [--dry-run]
6
+ *
7
+ * 라이브러리를 설치해도 규칙(AGENTS.md·llms.txt)과 ESLint 프리셋은 소비 앱이
8
+ * 직접 연결해야 동작한다. 이 CLI 가 그 연결을 대신 해준다.
9
+ *
10
+ * 원칙
11
+ * - 덮어쓰지 않는다. 추가/삽입만 하고, 이미 있으면 건너뛴다 (몇 번 실행해도 안전).
12
+ * - 안전하게 고칠 수 없는 파일은 손대지 않고 붙여넣을 스니펫을 출력한다.
13
+ * - 무엇을 바꿨는지 전부 보고한다.
14
+ */
15
+ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "node:fs";
16
+ import { join, relative, dirname, sep } from "node:path";
17
+
18
+ const PKG = "sellmate-design-system-react";
19
+ const LLMS_PATH = `node_modules/${PKG}/dist/llms.txt`;
20
+
21
+ const args = process.argv.slice(2);
22
+ const command = args.find((a) => !a.startsWith("-")) ?? "init";
23
+ const dryRun = args.includes("--dry-run") || args.includes("-n");
24
+
25
+ const cwd = process.cwd();
26
+
27
+ /* ─────────────────────────── 출력 ─────────────────────────── */
28
+
29
+ const c = {
30
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
31
+ green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m", red: "\x1b[31m",
32
+ };
33
+ const paint = (color, s) => (process.stdout.isTTY ? `${c[color]}${s}${c.reset}` : s);
34
+
35
+ /** @type {Array<{status: 'added'|'skipped'|'manual'|'error', file: string, detail: string, snippet?: string}>} */
36
+ const results = [];
37
+ const add = (status, file, detail, snippet) => results.push({ status, file, detail, snippet });
38
+
39
+ /* ─────────────────────────── 유틸 ─────────────────────────── */
40
+
41
+ const read = (p) => readFileSync(p, "utf8");
42
+
43
+ function write(path, content) {
44
+ if (dryRun) return;
45
+ writeFileSync(path, content);
46
+ }
47
+
48
+ /** node_modules 가 있는 프로젝트 루트인지 확인 */
49
+ function assertProjectRoot() {
50
+ if (!existsSync(join(cwd, "package.json"))) {
51
+ console.error(paint("red", "package.json 이 없습니다. 프로젝트 루트에서 실행하세요."));
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ /** 후보 경로 중 처음 존재하는 것 */
57
+ const firstExisting = (candidates) => candidates.find((p) => existsSync(join(cwd, p)));
58
+
59
+ /* ────────────────── 1. 에이전트 지침 파일 ────────────────── */
60
+
61
+ const AGENT_SECTION = `
62
+ ## 디자인 시스템 (${PKG})
63
+
64
+ UI 작업 전 \`${LLMS_PATH}\` 를 **반드시 먼저 읽는다.**
65
+ 컴포넌트 선택 규칙 · 토큰 어휘 · 페이지 표준 골격 · 컴포넌트 인덱스가 들어 있다.
66
+
67
+ **컴포넌트 Props 는 위 문서에 없다.** 쓸 컴포넌트를 정했으면 그 컴포넌트의 README 를 읽는다 —
68
+ \`node_modules/${PKG}/dist/components/<이름>/README.md\`. prop 을 추측해서 쓰지 않는다.
69
+
70
+ - 화면 요소를 만들기 전에 대응 컴포넌트가 있는지 먼저 확인한다 (생 \`<button>\`·\`<table>\` 금지).
71
+ - 색·타이포·간격·모서리는 토큰 유틸리티만 쓴다 (\`text-[14px]\`·\`bg-[#eee]\` 금지).
72
+ - 숫자는 \`toLocaleString()\` 으로 세 자리 콤마를 넣는다.
73
+ `;
74
+
75
+ function stepAgentInstructions() {
76
+ const existing = ["CLAUDE.md", "AGENTS.md"].filter((f) => existsSync(join(cwd, f)));
77
+ const targets = existing.length ? existing : ["CLAUDE.md"];
78
+
79
+ for (const file of targets) {
80
+ const path = join(cwd, file);
81
+ const current = existsSync(path) ? read(path) : "";
82
+
83
+ if (current.includes(LLMS_PATH)) {
84
+ add("skipped", file, "이미 llms.txt 참조가 있습니다");
85
+ continue;
86
+ }
87
+
88
+ const next = current
89
+ ? `${current.replace(/\s*$/, "")}\n${AGENT_SECTION}`
90
+ : `# 프로젝트 지침\n${AGENT_SECTION}`;
91
+
92
+ write(path, next);
93
+ add("added", file, existsSync(path) ? "디자인 시스템 지침 섹션 추가" : "생성 후 지침 추가");
94
+ }
95
+ }
96
+
97
+ /* ────────────────── 2. ESLint 설정 ────────────────── */
98
+
99
+ const ESLINT_IMPORT = `import sellmate from '${PKG}/eslint';`;
100
+ const ESLINT_SPREAD = ` ...sellmate.configs.recommended,`;
101
+ const ESLINT_SNIPPET = `${ESLINT_IMPORT}\n\nexport default [\n // ... 기존 설정\n${ESLINT_SPREAD}\n];`;
102
+
103
+ /** 배열 리터럴의 닫는 대괄호 위치를 균형 계산으로 찾는다 */
104
+ function matchingBracket(src, openIndex) {
105
+ let depth = 0;
106
+ for (let i = openIndex; i < src.length; i++) {
107
+ const ch = src[i];
108
+ if (ch === "[") depth++;
109
+ else if (ch === "]") {
110
+ depth--;
111
+ if (depth === 0) return i;
112
+ }
113
+ }
114
+ return -1;
115
+ }
116
+
117
+ function stepEslint() {
118
+ const file = firstExisting([
119
+ "eslint.config.mjs", "eslint.config.js", "eslint.config.ts", "eslint.config.cjs",
120
+ ]);
121
+
122
+ if (!file) {
123
+ add("manual", "eslint.config.mjs", "설정 파일이 없습니다 — 아래 내용으로 만드세요", ESLINT_SNIPPET);
124
+ return;
125
+ }
126
+
127
+ const path = join(cwd, file);
128
+ const src = read(path);
129
+
130
+ if (src.includes(`${PKG}/eslint`)) {
131
+ add("skipped", file, "이미 ESLint 프리셋이 연결되어 있습니다");
132
+ return;
133
+ }
134
+
135
+ // `export default [` 형태만 자동 삽입한다. 그 외(defineConfig(...), tseslint.config(...) 등)는
136
+ // 배열 경계를 확신할 수 없으므로 손대지 않고 스니펫을 안내한다.
137
+ const exportMatch = src.match(/export\s+default\s*\[/);
138
+ if (!exportMatch) {
139
+ add("manual", file, "자동 삽입이 어려운 형태입니다 — 아래를 직접 추가하세요", ESLINT_SNIPPET);
140
+ return;
141
+ }
142
+
143
+ const openIndex = src.indexOf("[", exportMatch.index);
144
+ const closeIndex = matchingBracket(src, openIndex);
145
+ if (closeIndex === -1) {
146
+ add("manual", file, "배열 끝을 찾지 못했습니다 — 아래를 직접 추가하세요", ESLINT_SNIPPET);
147
+ return;
148
+ }
149
+
150
+ // import 는 마지막 최상위 import 뒤에 붙인다
151
+ const importMatches = [...src.matchAll(/^import .*?;?\s*$/gm)];
152
+ const lastImport = importMatches.at(-1);
153
+ const withImport = lastImport
154
+ ? src.slice(0, lastImport.index + lastImport[0].length) +
155
+ `\n${ESLINT_IMPORT}` +
156
+ src.slice(lastImport.index + lastImport[0].length)
157
+ : `${ESLINT_IMPORT}\n${src}`;
158
+
159
+ // import 삽입으로 밀린 만큼 닫는 괄호 위치를 보정
160
+ const shift = withImport.length - src.length;
161
+ const close = closeIndex + shift;
162
+
163
+ // 배열 마지막 항목 뒤에 삽입한다 (DS 프리셋이 뒤에 와야 앞선 설정을 덮는다)
164
+ const before = withImport.slice(0, close);
165
+ const needsComma = /[^[\s,]\s*$/.test(before);
166
+ const next =
167
+ before.replace(/\s*$/, "") + (needsComma ? "," : "") + `\n${ESLINT_SPREAD}\n` +
168
+ withImport.slice(close);
169
+
170
+ write(path, next);
171
+ add("added", file, "프리셋(configs.recommended) 연결");
172
+ }
173
+
174
+ /* ────────────────── 3. 전역 CSS ────────────────── */
175
+
176
+ /** 앱의 전역 CSS 를 찾는다 */
177
+ function findGlobalCss() {
178
+ const candidates = [
179
+ "app/globals.css", "src/app/globals.css", "src/styles/globals.css",
180
+ "styles/globals.css", "src/index.css", "src/main.css", "src/global.css",
181
+ ];
182
+ const found = firstExisting(candidates);
183
+ if (found) return found;
184
+
185
+ // 후보에 없으면 @import "tailwindcss" 가 있는 css 를 얕게 탐색
186
+ const roots = ["src", "app", "styles"].filter((d) => existsSync(join(cwd, d)));
187
+ for (const root of roots) {
188
+ const hit = walkCss(join(cwd, root), 3);
189
+ if (hit) return relative(cwd, hit);
190
+ }
191
+ return null;
192
+ }
193
+
194
+ function walkCss(dir, depth) {
195
+ if (depth < 0) return null;
196
+ let entries;
197
+ try {
198
+ entries = readdirSync(dir);
199
+ } catch {
200
+ return null;
201
+ }
202
+ for (const name of entries) {
203
+ if (name === "node_modules" || name.startsWith(".")) continue;
204
+ const p = join(dir, name);
205
+ let st;
206
+ try {
207
+ st = statSync(p);
208
+ } catch {
209
+ continue;
210
+ }
211
+ if (st.isDirectory()) {
212
+ const hit = walkCss(p, depth - 1);
213
+ if (hit) return hit;
214
+ } else if (name.endsWith(".css")) {
215
+ try {
216
+ if (read(p).includes("tailwindcss")) return p;
217
+ } catch {
218
+ /* 읽기 실패는 무시 */
219
+ }
220
+ }
221
+ }
222
+ return null;
223
+ }
224
+
225
+ function stepGlobalCss() {
226
+ const file = findGlobalCss();
227
+
228
+ if (!file) {
229
+ add(
230
+ "manual",
231
+ "전역 CSS",
232
+ "전역 CSS 를 찾지 못했습니다 — 앱의 전역 CSS 에 아래를 추가하세요",
233
+ `@import 'tailwindcss';\n@import '${PKG}/theme.css';\n@source "<상대경로>/node_modules/${PKG}/dist";`,
234
+ );
235
+ return;
236
+ }
237
+
238
+ const path = join(cwd, file);
239
+ const src = read(path);
240
+
241
+ // CSS 파일 기준 상대경로로 @source 를 계산한다 (README 주의사항)
242
+ const toNodeModules = relative(dirname(path), join(cwd, "node_modules", PKG, "dist"));
243
+ const sourcePath = toNodeModules.split(sep).join("/");
244
+
245
+ const hasTheme = src.includes(`${PKG}/theme.css`);
246
+ const hasSource = src.includes(`node_modules/${PKG}/dist`);
247
+
248
+ if (hasTheme && hasSource) {
249
+ add("skipped", file, "theme.css · @source 가 이미 설정되어 있습니다");
250
+ return;
251
+ }
252
+
253
+ const lines = [];
254
+ if (!hasTheme) lines.push(`@import '${PKG}/theme.css';`);
255
+ if (!hasSource) lines.push(`@source "${sourcePath}";`);
256
+
257
+ // @import 'tailwindcss' 바로 뒤에 넣는다 (theme.css 는 tailwindcss 를 import 하지 않는다)
258
+ const tw = src.match(/@import\s+["']tailwindcss["'];?/);
259
+ if (!tw) {
260
+ add(
261
+ "manual",
262
+ file,
263
+ "@import 'tailwindcss' 를 찾지 못했습니다 — 아래를 직접 추가하세요",
264
+ lines.join("\n"),
265
+ );
266
+ return;
267
+ }
268
+
269
+ const insertAt = tw.index + tw[0].length;
270
+ const next = src.slice(0, insertAt) + "\n" + lines.join("\n") + src.slice(insertAt);
271
+ write(path, next);
272
+ add("added", file, lines.length === 2 ? "theme.css import · @source 추가" : "누락분 추가");
273
+ }
274
+
275
+ /* ─────────────────────────── 실행 ─────────────────────────── */
276
+
277
+ function printHelp() {
278
+ console.log(`
279
+ ${paint("bold", "sellmate-ds")} — ${PKG} 소비 앱 설정
280
+
281
+ ${paint("cyan", "npx sellmate-ds init")} 설정을 자동으로 연결합니다
282
+ ${paint("cyan", "npx sellmate-ds init --dry-run")} 무엇이 바뀔지만 보여줍니다
283
+
284
+ 연결하는 것
285
+ · CLAUDE.md / AGENTS.md AI 에이전트가 규칙(llms.txt)을 읽도록 지침 추가
286
+ · eslint.config.mjs 디자인 시스템 ESLint 프리셋 연결
287
+ · 전역 CSS theme.css import 와 @source 경로 추가
288
+
289
+ 이미 되어 있는 항목은 건너뛰므로 여러 번 실행해도 안전합니다.
290
+ `);
291
+ }
292
+
293
+ if (command === "help" || args.includes("--help") || args.includes("-h")) {
294
+ printHelp();
295
+ process.exit(0);
296
+ }
297
+
298
+ if (command !== "init") {
299
+ console.error(paint("red", `알 수 없는 명령: ${command}`));
300
+ printHelp();
301
+ process.exit(1);
302
+ }
303
+
304
+ assertProjectRoot();
305
+
306
+ console.log(
307
+ `\n${paint("bold", `${PKG} 설정`)}${dryRun ? paint("yellow", " (dry-run — 파일을 바꾸지 않습니다)") : ""}\n`,
308
+ );
309
+
310
+ stepAgentInstructions();
311
+ stepEslint();
312
+ stepGlobalCss();
313
+
314
+ const icon = { added: paint("green", "✓"), skipped: paint("dim", "·"), manual: paint("yellow", "!"), error: paint("red", "✗") };
315
+
316
+ for (const r of results) {
317
+ console.log(` ${icon[r.status]} ${paint("bold", r.file)} ${paint("dim", r.detail)}`);
318
+ if (r.snippet) {
319
+ console.log(r.snippet.split("\n").map((l) => ` ${paint("cyan", l)}`).join("\n"));
320
+ }
321
+ }
322
+
323
+ const added = results.filter((r) => r.status === "added").length;
324
+ const manual = results.filter((r) => r.status === "manual").length;
325
+
326
+ console.log();
327
+ if (dryRun) {
328
+ console.log(paint("yellow", ` ${added}개 항목이 변경됩니다. --dry-run 을 빼고 다시 실행하세요.`));
329
+ } else if (added) {
330
+ console.log(paint("green", ` ${added}개 항목을 설정했습니다.`));
331
+ } else if (!manual) {
332
+ console.log(paint("dim", " 이미 모두 설정되어 있습니다."));
333
+ }
334
+ if (manual) {
335
+ console.log(paint("yellow", ` ${manual}개 항목은 위 내용을 직접 추가해야 합니다.`));
336
+ }
337
+ console.log();
@@ -0,0 +1,41 @@
1
+ # SActionModal
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SActionModal
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `open?` | `boolean` | — | |
12
+ | `persistent?` | `boolean` | — | true면 백드롭·ESC로 안 닫히고 흔들림 (sd-modal-container persistent) |
13
+ | `modalTitle?` | `string` | `''` | |
14
+ | `button?` | `SActionModalButton` | — | 하단 액션 버튼 (주 액션 1개). 의도적으로 단수다 — 보조 버튼(취소·삭제 등)은 `footerLeft` 슬롯에 직접 배치한다. |
15
+ | `footerLeft?` | `ReactNode` | — | footer 좌측 영역 (sd-action-modal 의 bottom-sub-content 슬롯). 보조 버튼이나 안내 문구를 넣는다. 버튼을 하나 더 쓰고 싶을 때 여기에 SButton 을 넣는다. |
16
+ | `width?` | `number \| string` | — | |
17
+ | `height?` | `number \| string` | — | |
18
+ | `children?` | `ReactNode` | — | |
19
+
20
+ #### Events
21
+
22
+ | Event | Type | Description |
23
+ |-------|------|-------------|
24
+ | `onOpenChange` | `(open: boolean) => void` | |
25
+ | `onClose` | `() => void` | 닫기(X) 버튼 클릭 (sdClose) |
26
+
27
+ ## Dependencies
28
+
29
+ ### Depends on
30
+
31
+ - [SButton](../SButton)
32
+ - [SModalContainer](../SModalContainer)
33
+
34
+ ### Graph
35
+
36
+ ```mermaid
37
+ graph TD;
38
+ SActionModal --> SButton
39
+ SActionModal --> SModalContainer
40
+ style SActionModal fill:#f9f,stroke:#333,stroke-width:4px
41
+ ```
@@ -0,0 +1,25 @@
1
+ # SBadge
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SBadge
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `color?` | `SBadgeColor` | `'blue'` | 뱃지 색상 |
12
+
13
+ ## Dependencies
14
+
15
+ ### Used by
16
+
17
+ - [SList](../SList)
18
+
19
+ ### Graph
20
+
21
+ ```mermaid
22
+ graph TD;
23
+ SList --> SBadge
24
+ style SBadge fill:#f9f,stroke:#333,stroke-width:4px
25
+ ```
@@ -0,0 +1,64 @@
1
+ # SBarcodeInput
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SBarcodeInput
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `value?` | `string \| number \| null` | — | |
12
+ | `size?` | `SBarcodeInputSize` | `'sm'` | |
13
+ | `clearable?` | `boolean` | `false` | |
14
+ | `autoFocus?` | `boolean` | — | |
15
+ | `name?` | `string` | — | |
16
+ | `placeholder?` | `string` | `'입력해 주세요.'` | |
17
+ | `disabled?` | `boolean` | `false` | |
18
+ | `readOnly?` | `boolean` | `false` | |
19
+ | `rules?` | `Rule[]` | — | |
20
+ | `status?` | `SFieldStatus` | — | |
21
+ | `focused?` | `boolean` | — | |
22
+ | `hovered?` | `boolean` | — | |
23
+ | `prefix?` | `ReactNode` | — | 입력 앞 슬롯 |
24
+ | `suffix?` | `ReactNode` | — | 입력 뒤 슬롯 |
25
+ | `inputClass?` | `string` | — | |
26
+ | `inputStyle?` | `CSSProperties` | — | |
27
+ | `label?` | `string` | — | |
28
+ | `labelWidth?` | `number \| string` | — | |
29
+ | `icon?` | `SIconName` | — | |
30
+ | `iconColor?` | `SColor` | — | |
31
+ | `labelTooltip?` | `string` | — | |
32
+ | `labelTooltipProps?` | `Partial<STooltipProps>` | — | |
33
+ | `addonLabel?` | `string` | — | |
34
+ | `addonAlign?` | `SFieldAddonAlign` | — | |
35
+ | `hint?` | `string` | — | |
36
+ | `error?` | `boolean` | — | |
37
+ | `errorMessage?` | `string` | — | |
38
+ | `width?` | `number \| string` | — | |
39
+ | `className?` | `string` | — | |
40
+ | `style?` | `CSSProperties` | — | |
41
+
42
+ #### Events
43
+
44
+ | Event | Type | Description |
45
+ |-------|------|-------------|
46
+ | `onValueChange` | `(value: string) => void` | 값 변경 (sdUpdate) |
47
+ | `onFocus` | `() => void` | |
48
+ | `onBlur` | `() => void` | |
49
+
50
+ ## Dependencies
51
+
52
+ ### Depends on
53
+
54
+ - [SField](../SField)
55
+ - [SGhostButton](../SGhostButton)
56
+
57
+ ### Graph
58
+
59
+ ```mermaid
60
+ graph TD;
61
+ SBarcodeInput --> SField
62
+ SBarcodeInput --> SGhostButton
63
+ style SBarcodeInput fill:#f9f,stroke:#333,stroke-width:4px
64
+ ```
@@ -0,0 +1,49 @@
1
+ # SButton
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SButton
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `color?` | `SButtonColor` | `'primary'` | 색상 — sd-button preset의 색 계열 |
12
+ | `outline?` | `boolean` | `false` | 아웃라인(테두리) 스타일. primary·neutral·danger 만 지원 (secondary 는 무시) |
13
+ | `size?` | `SButtonSize` | `'sm'` | 크기 |
14
+ | `icon?` | `SIconName` | — | 레이블 왼쪽 아이콘 |
15
+ | `rightIcon?` | `SIconName` | — | 레이블 오른쪽 아이콘 |
16
+ | `label?` | `string` | — | 버튼 텍스트 (문자열만 — 아이콘은 icon/rightIcon 사용) |
17
+
18
+ ## Dependencies
19
+
20
+ ### Used by
21
+
22
+ - [SActionModal](../SActionModal)
23
+ - [SConfirmModal](../SConfirmModal)
24
+ - [SDropdownButton](../SDropdownButton)
25
+ - [SKeyValueTable](../SKeyValueTable)
26
+ - [SLoadingModal](../SLoadingModal)
27
+ - [SPopover](../SPopover)
28
+ - [SPopup](../SPopup)
29
+ - [SToast](../SToast)
30
+
31
+ ### Depends on
32
+
33
+ - [SIcon](../SIcon)
34
+
35
+ ### Graph
36
+
37
+ ```mermaid
38
+ graph TD;
39
+ SButton --> SIcon
40
+ SActionModal --> SButton
41
+ SConfirmModal --> SButton
42
+ SDropdownButton --> SButton
43
+ SKeyValueTable --> SButton
44
+ SLoadingModal --> SButton
45
+ SPopover --> SButton
46
+ SPopup --> SButton
47
+ SToast --> SButton
48
+ style SButton fill:#f9f,stroke:#333,stroke-width:4px
49
+ ```
@@ -0,0 +1,42 @@
1
+ # SCalendar
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SCalendar
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `value?` | `string \| null` | — | 선택 날짜 (YYYY-MM-DD) |
12
+ | `selectable?` | `[string, string]` | — | 선택 가능 범위 [시작, 종료] (빈 문자열이면 제한 없음) |
13
+ | `events?` | `SCalendarEventGroup[]` | — | 이벤트 그룹 (날짜별 점) |
14
+ | `elevated?` | `boolean` | `false` | 카드 그림자 |
15
+ | `className?` | `string` | — | |
16
+ | `style?` | `CSSProperties` | — | |
17
+
18
+ #### Events
19
+
20
+ | Event | Type | Description |
21
+ |-------|------|-------------|
22
+ | `onValueChange` | `(date: string) => void` | 선택 변경 (sdUpdate) |
23
+ | `onViewChange` | `(v: { year: number; month: number }) => void` | 보이는 연·월 변경 (sdViewChange) |
24
+
25
+ ## Dependencies
26
+
27
+ ### Used by
28
+
29
+ - [SDatePicker](../SDatePicker)
30
+
31
+ ### Depends on
32
+
33
+ - [SGhostButton](../SGhostButton)
34
+
35
+ ### Graph
36
+
37
+ ```mermaid
38
+ graph TD;
39
+ SCalendar --> SGhostButton
40
+ SDatePicker --> SCalendar
41
+ style SCalendar fill:#f9f,stroke:#333,stroke-width:4px
42
+ ```