@spectrabrain/eds 0.0.4 → 0.1.1
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/AI.md +13 -40
- package/AI_CONTEXT.json +90 -0
- package/CHANGELOG.md +120 -0
- package/CONSUMER_AGENT.md +152 -0
- package/FRONTEND_CONVENTIONS.md +252 -0
- package/QUICKSTART.md +42 -0
- package/README.md +38 -69
- package/TAILWIND_V4.md +57 -243
- package/TOKEN_CONTEXT.md +98 -0
- package/consumer/align/templates/components.json +21 -0
- package/consumer/align/templates/env.mock +1 -0
- package/consumer/align/templates/eslint.config.js +35 -0
- package/consumer/align/templates/package.scripts.json +10 -0
- package/consumer/align/templates/prettierignore +2 -0
- package/consumer/align/templates/prettierrc.json +7 -0
- package/consumer/align/templates/src/app/App.tsx +16 -0
- package/consumer/align/templates/src/app/main.tsx +27 -0
- package/consumer/align/templates/src/app/providers/ErrorBoundary.tsx +58 -0
- package/consumer/align/templates/src/app/providers/ErrorHandler.tsx +29 -0
- package/consumer/align/templates/src/app/router/index.tsx +23 -0
- package/consumer/align/templates/src/app/styles/index.css +2 -0
- package/consumer/align/templates/src/mocks/browser.ts +5 -0
- package/consumer/align/templates/src/mocks/handlers.ts +4 -0
- package/consumer/align/templates/src/shared/api/index.ts +23 -0
- package/consumer/align/templates/src/shared/api/queryClient.ts +24 -0
- package/consumer/align/templates/src/shared/lib/eventBus.ts +44 -0
- package/consumer/align/templates/src/shared/lib/utils/index.ts +6 -0
- package/consumer/align/templates/src/vite-env.d.ts +2 -0
- package/consumer/align/templates/tsconfig.json +22 -0
- package/consumer/align/templates/vite.config.babel.ts +36 -0
- package/consumer/align/templates/vite.config.rolldown.ts +35 -0
- package/consumer/align-analysis.mjs +1159 -0
- package/consumer/align-plan.mjs +377 -0
- package/consumer/align-rules.mjs +473 -0
- package/consumer/align.mjs +401 -0
- package/consumer/cli.mjs +157 -0
- package/consumer/css-root.mjs +259 -0
- package/consumer/doctor.mjs +204 -0
- package/consumer/generate-tailwind.mjs +83 -0
- package/consumer/init.mjs +186 -0
- package/consumer/managed-block.mjs +126 -0
- package/consumer/paths.mjs +266 -0
- package/consumer/register-scripts.mjs +129 -0
- package/consumer/scan-classes.mjs +71 -0
- package/consumer/sync.mjs +155 -0
- package/consumer/update-agent-context.mjs +143 -0
- package/consumer-postinstall.mjs +60 -0
- package/dist/base.css +4 -0
- package/dist/components/Button/README.md +70 -80
- package/dist/components/Button/spec.json +71 -133
- package/dist/components.css +102 -368
- package/dist/index.d.mts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +34 -6250
- package/dist/index.mjs +36 -6252
- package/dist/styles.css +1 -0
- package/dist/tokens.d.mts +5 -2
- package/dist/tokens.d.ts +5 -2
- package/dist/tokens.js +6 -7
- package/dist/tokens.mjs +6 -7
- package/llms.txt +22 -9
- package/package.json +33 -2
package/TAILWIND_V4.md
CHANGED
|
@@ -1,284 +1,98 @@
|
|
|
1
|
-
#
|
|
1
|
+
# EDS token을 Tailwind CSS v4에서 쓰는 방법
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tailwind CSS v4 저장소에서 `eds sync`가 공개 token을 `text-eds-*`·`bg-eds-*`·`border-eds-*`·`eds-typography-*` class로 쓸 수 있게 연결하는 설정 절차입니다. 필수 규칙은 package root의 `QUICKSTART.md`에, class를 고르는 기준은 `TOKEN_CONTEXT.md`에 있습니다.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 공개 경로
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
CSS 진입점은 `@spectrabrain/eds/styles.css` 하나를 권장합니다. 개별 파일(`@spectrabrain/eds/tokens.css`, `@spectrabrain/eds/components.css`, `@spectrabrain/eds/fonts.css`, 선택 `@spectrabrain/eds/base.css`)의 구성 규칙은 `CONSUMER_AGENT.md` 「font 정책」에 있습니다. 전체 공개 경로는 `README.md`의 「공개 경로」 표에 있으며, 그 밖의 패키지 내부 경로와 token 값은 소비처에 복사하지 않습니다.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## 설정 순서
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| `@spectrabrain/eds/tokens.json` | 공개 token 이름과 타입을 도구에서 확인합니다. |
|
|
16
|
-
| `@spectrabrain/eds/tokens` | TypeScript token 이름과 `cssVar()`를 사용합니다. |
|
|
11
|
+
1. 「adapter 선택」 표에서 저장소에 맞는 행 하나를 골라 설치하고 연결합니다.
|
|
12
|
+
2. 앱의 전역 CSS 파일 하나에 `@import "tailwindcss";`를 둡니다. 이 파일이 Tailwind root이며, 이미 있으면 새로 만들지 않습니다.
|
|
13
|
+
3. `npm run sync:eds`를 실행합니다. script가 없으면 `npx @spectrabrain/eds init`을 실행합니다(패키지 매니저별 명령은 `CONSUMER_AGENT.md` 「첫 setup 절차」). init을 2단계보다 먼저 실행했다면 2단계 뒤에 sync를 다시 실행합니다.
|
|
14
|
+
4. 「완료 기준」을 확인합니다.
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
CSS가 `@tailwind` 지시어를 쓰는 Tailwind CSS v3 저장소는 이 절차 대신 `CONSUMER_AGENT.md` 「Tailwind root 결정」을 따릅니다.
|
|
19
17
|
|
|
20
|
-
##
|
|
18
|
+
## adapter 선택
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
- Tailwind CSS v4를 사용합니다.
|
|
24
|
-
- 프로젝트의 framework와 bundler가 확인되어 있습니다.
|
|
25
|
-
- 전역 CSS 진입점이 하나 지정되어 있습니다.
|
|
20
|
+
명령은 npm 기준이며 다른 패키지 매니저는 그 add 명령을 씁니다.
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
~~~sh
|
|
32
|
-
npm install @spectrabrain/eds
|
|
33
|
-
~~~
|
|
34
|
-
|
|
35
|
-
Vite 프로젝트는 Tailwind Vite adapter를 추가합니다.
|
|
36
|
-
|
|
37
|
-
~~~sh
|
|
38
|
-
npm install -D tailwindcss @tailwindcss/vite
|
|
39
|
-
~~~
|
|
40
|
-
|
|
41
|
-
PostCSS 기반 프로젝트는 PostCSS adapter를 추가합니다.
|
|
42
|
-
|
|
43
|
-
~~~sh
|
|
44
|
-
npm install -D tailwindcss @tailwindcss/postcss postcss
|
|
45
|
-
~~~
|
|
46
|
-
|
|
47
|
-
이미 설치된 패키지는 중복 설치하지 않으며 Vite adapter와 PostCSS adapter를 동시에 추가하지 않습니다.
|
|
48
|
-
|
|
49
|
-
## 설치 시 자동화 정책
|
|
50
|
-
|
|
51
|
-
EDS는 `npm install`의 `postinstall`에서 소비처의 CSS나 설정 파일을 수정하지 않습니다. 설치 스크립트가 파일을 덮어쓰면 소비처의 기존 Tailwind 설정, CI 정책, package manager 설정과 충돌할 수 있기 때문입니다.
|
|
52
|
-
|
|
53
|
-
token alias를 EDS 버전과 함께 자동 갱신하려면 소비처 저장소가 다음 작업을 소유하는 명시적인 생성 스크립트를 둡니다.
|
|
54
|
-
|
|
55
|
-
1. `@spectrabrain/eds/tokens.json`에서 token 이름만 읽습니다.
|
|
56
|
-
2. `color-*` token을 `var(--eds-...)`로 연결하는 `@theme inline` 블록을 생성합니다.
|
|
57
|
-
3. raw `$value`나 비공개 파일 경로는 생성물에 기록하지 않습니다.
|
|
58
|
-
4. `npm run generate:eds-tailwind`처럼 명시적으로 실행하고, 필요할 때만 소비처의 `postinstall` 또는 CI에 연결합니다.
|
|
59
|
-
|
|
60
|
-
설치 스크립트를 사용하지 않는 환경에서도 동작하도록 생성된 `eds-tokens.css`는 저장소에 두거나 CI에서 재생성합니다. 현재 EDS가 제공하는 자동화 범위는 공개 token manifest와 이 설치 가이드이며, 소비처 파일을 직접 변경하는 CLI는 제공하지 않습니다.
|
|
61
|
-
|
|
62
|
-
다음은 소비처가 소유할 수 있는 color alias 생성 스크립트 예시입니다. 이 스크립트는 token의 실제 값을 읽지 않고 공개 이름만 읽습니다.
|
|
63
|
-
|
|
64
|
-
~~~js
|
|
65
|
-
// scripts/generate-eds-tailwind.mjs
|
|
66
|
-
import fs from "node:fs";
|
|
67
|
-
import path from "node:path";
|
|
68
|
-
import { createRequire } from "node:module";
|
|
69
|
-
|
|
70
|
-
const require = createRequire(import.meta.url);
|
|
71
|
-
const manifestPath = require.resolve("@spectrabrain/eds/tokens.json");
|
|
72
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
73
|
-
const colorNames = Object.keys(manifest.tokens).filter((name) => name.startsWith("color-"));
|
|
74
|
-
const aliases = colorNames
|
|
75
|
-
.map((name) => ` --color-eds-${name.slice("color-".length)}: var(--eds-${name});`)
|
|
76
|
-
.join("\n");
|
|
77
|
-
const output = `@theme inline {\n${aliases}\n}\n`;
|
|
78
|
-
const outputPath = path.resolve("src/styles/eds-tokens.css");
|
|
79
|
-
|
|
80
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
81
|
-
fs.writeFileSync(outputPath, output);
|
|
82
|
-
~~~
|
|
83
|
-
|
|
84
|
-
소비처의 `package.json`에는 기존 script를 덮어쓰지 않는 범위에서 다음 명령을 추가할 수 있습니다.
|
|
85
|
-
|
|
86
|
-
~~~json
|
|
87
|
-
{
|
|
88
|
-
"scripts": {
|
|
89
|
-
"generate:eds-tailwind": "node scripts/generate-eds-tailwind.mjs"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
~~~
|
|
93
|
-
|
|
94
|
-
`postinstall`에 연결하려면 소비처의 보안·CI 정책이 허용하는 경우에만 기존 `postinstall` 뒤에 `npm run generate:eds-tailwind`를 추가합니다. 설치 시 script 실행이 차단된 환경도 고려해 생성 파일을 커밋하거나 CI에서 `generate:eds-tailwind`를 먼저 실행합니다.
|
|
95
|
-
|
|
96
|
-
## Tailwind v4 연결
|
|
97
|
-
|
|
98
|
-
### Vite
|
|
22
|
+
| 저장소 | 설치 | 연결 |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `vite.config.*`가 있음 | `npm install -D tailwindcss @tailwindcss/vite` | `vite.config`의 `plugins`에 `tailwindcss()`를 추가합니다. `postcss.config.*`에 `@tailwindcss/postcss` 항목이 있으면 그 항목과 devDependency를 지웁니다. |
|
|
25
|
+
| `vite.config.*` 없이 PostCSS를 씀 | `npm install -D tailwindcss @tailwindcss/postcss postcss` | PostCSS 설정의 `plugins`에 `"@tailwindcss/postcss": {}`를 추가합니다. |
|
|
99
26
|
|
|
100
|
-
`
|
|
27
|
+
Vite에서 `@tailwindcss/postcss`로 빌드하면 import한 EDS CSS의 상대 `url()`이 옮겨지지 않아 `fonts.css`의 Pretendard 파일이 번들에 들어가지 않습니다. 기존 framework plugin과 Tailwind 외 PostCSS plugin은 유지합니다.
|
|
101
28
|
|
|
102
|
-
|
|
29
|
+
```ts
|
|
30
|
+
import react from "@vitejs/plugin-react"; // 기존 import는 유지합니다.
|
|
103
31
|
import tailwindcss from "@tailwindcss/vite";
|
|
104
32
|
import { defineConfig } from "vite";
|
|
105
33
|
|
|
106
34
|
export default defineConfig({
|
|
107
|
-
|
|
35
|
+
// 기존 설정(server.proxy, resolve.alias 등)은 그대로 둡니다.
|
|
36
|
+
plugins: [react(), tailwindcss()], // 기존 plugin 배열 끝에 tailwindcss()만 추가합니다.
|
|
108
37
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
기존 React plugin 등 다른 plugin은 제거하지 않습니다.
|
|
38
|
+
```
|
|
112
39
|
|
|
113
|
-
|
|
40
|
+
`vite.config.*`가 없는 저장소의 PostCSS 설정은 다음과 같습니다.
|
|
114
41
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
~~~js
|
|
42
|
+
```js
|
|
118
43
|
export default {
|
|
119
44
|
plugins: {
|
|
45
|
+
// 기존 plugin 항목에 추가합니다.
|
|
120
46
|
"@tailwindcss/postcss": {},
|
|
121
47
|
},
|
|
122
48
|
};
|
|
123
|
-
|
|
49
|
+
```
|
|
124
50
|
|
|
125
|
-
##
|
|
51
|
+
## sync가 만드는 것
|
|
126
52
|
|
|
127
|
-
|
|
53
|
+
sync는 설치된 package의 공개 `@spectrabrain/eds/tokens.json`에서 token 이름만 읽어 `src/styles/eds-tokens.css`를 생성하고, Tailwind root의 마지막 Tailwind import 바로 다음 줄에 `@spectrabrain/eds/styles.css`와 `eds-tokens.css`의 `@import`를 한 번씩 추가합니다. `tailwindcss/theme.css`와 `tailwindcss/utilities.css`를 따로 불러오는 root에서는 `utilities.css` 다음 줄입니다. 생성 CSS의 상대 경로는 root 위치에 맞춰 sync가 계산하므로 경로를 직접 쓰지 않습니다. `src/index.css`가 root이면 다음과 같습니다.
|
|
128
54
|
|
|
129
|
-
|
|
55
|
+
```css
|
|
130
56
|
@import "tailwindcss";
|
|
131
|
-
@import "@spectrabrain/eds/
|
|
132
|
-
@import "./eds-tokens.css";
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Pretendard font도 사용하려면 `eds-tokens.css`보다 앞이나 뒤에 다음 import를 추가합니다.
|
|
136
|
-
|
|
137
|
-
~~~css
|
|
138
|
-
@import "@spectrabrain/eds/fonts.css";
|
|
139
|
-
~~~
|
|
140
|
-
|
|
141
|
-
`tokens.css`는 token variable만 제공하며 애플리케이션의 font-family를 자동으로 지정하지 않습니다.
|
|
142
|
-
|
|
143
|
-
## Color token alias
|
|
57
|
+
@import "@spectrabrain/eds/styles.css";
|
|
58
|
+
@import "./styles/eds-tokens.css";
|
|
59
|
+
```
|
|
144
60
|
|
|
145
|
-
|
|
61
|
+
생성 CSS의 color alias는 공개 color token마다 한 줄씩 생기며, 형태는 다음과 같습니다.
|
|
146
62
|
|
|
147
|
-
|
|
63
|
+
```css
|
|
64
|
+
/* Generated from @spectrabrain/eds@<version>. Do not edit manually. */
|
|
148
65
|
@theme inline {
|
|
149
66
|
--color-eds-fg-brand: var(--eds-color-fg-brand);
|
|
150
|
-
--
|
|
151
|
-
--color-eds-fg-neutral-secondary: var(--eds-color-fg-neutral-secondary);
|
|
152
|
-
--color-eds-fg-neutral-inverted: var(--eds-color-fg-neutral-inverted);
|
|
153
|
-
|
|
154
|
-
--color-eds-bg-brand-primary: var(--eds-color-bg-brand-primary);
|
|
155
|
-
--color-eds-bg-brand-primary-hover: var(--eds-color-bg-brand-primary-hover);
|
|
156
|
-
--color-eds-bg-neutral-primary: var(--eds-color-bg-neutral-primary);
|
|
157
|
-
--color-eds-bg-neutral-primary-hover: var(--eds-color-bg-neutral-primary-hover);
|
|
158
|
-
--color-eds-bg-neutral-weak: var(--eds-color-bg-neutral-weak);
|
|
159
|
-
--color-eds-bg-disabled: var(--eds-color-bg-disabled);
|
|
160
|
-
|
|
161
|
-
--color-eds-stroke-neutral-secondary: var(--eds-color-stroke-neutral-secondary);
|
|
162
|
-
--color-eds-stroke-focusRing: var(--eds-color-stroke-focusRing);
|
|
67
|
+
--font-sans: var(--eds-typography-body1-fontFamily);
|
|
163
68
|
}
|
|
164
|
-
~~~
|
|
165
|
-
|
|
166
|
-
다음 규칙으로 추가 alias를 작성합니다.
|
|
167
|
-
|
|
168
|
-
| EDS token | Tailwind alias | 생성되는 utility 예시 |
|
|
169
|
-
| --- | --- | --- |
|
|
170
|
-
| `color-fg-brand` | `--color-eds-fg-brand` | `text-eds-fg-brand` |
|
|
171
|
-
| `color-bg-brand-primary` | `--color-eds-bg-brand-primary` | `bg-eds-bg-brand-primary` |
|
|
172
|
-
| `color-stroke-neutral-secondary` | `--color-eds-stroke-neutral-secondary` | `border-eds-stroke-neutral-secondary` |
|
|
173
|
-
|
|
174
|
-
사용 예시는 다음과 같습니다.
|
|
175
|
-
|
|
176
|
-
~~~tsx
|
|
177
|
-
<div className="bg-eds-bg-neutral-primary text-eds-fg-neutral border border-eds-stroke-neutral-secondary">
|
|
178
|
-
콘텐츠입니다.
|
|
179
|
-
</div>
|
|
180
|
-
~~~
|
|
181
69
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
~~~
|
|
189
|
-
|
|
190
|
-
## Typography preset utility
|
|
191
|
-
|
|
192
|
-
EDS typography preset은 font family, weight, size, line height, letter spacing을 함께 적용합니다. Tailwind의 `text-*` theme variable만으로는 다섯 속성을 하나의 preset으로 묶을 수 없으므로 `@utility`를 사용합니다.
|
|
193
|
-
|
|
194
|
-
~~~css
|
|
195
|
-
@utility eds-typography-title3 {
|
|
196
|
-
font-family: var(--eds-typography-title3-fontFamily);
|
|
197
|
-
font-weight: var(--eds-typography-title3-fontWeight);
|
|
198
|
-
font-size: var(--eds-typography-title3-fontSize);
|
|
199
|
-
line-height: var(--eds-typography-title3-lineHeight);
|
|
200
|
-
letter-spacing: var(--eds-typography-title3-letterSpacing);
|
|
70
|
+
@utility eds-typography-body2 {
|
|
71
|
+
font-family: var(--eds-typography-body2-fontFamily);
|
|
72
|
+
font-weight: var(--eds-typography-body2-fontWeight);
|
|
73
|
+
font-size: var(--eds-typography-body2-fontSize);
|
|
74
|
+
line-height: var(--eds-typography-body2-lineHeight);
|
|
75
|
+
letter-spacing: var(--eds-typography-body2-letterSpacing);
|
|
201
76
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
다음 preset을 같은 규칙으로 제공합니다.
|
|
205
|
-
|
|
206
|
-
~~~text
|
|
207
|
-
display1, display2
|
|
208
|
-
heading1, heading2, heading3, heading4
|
|
209
|
-
title1, title2, title3, title4
|
|
210
|
-
body1, body2, body3, body4
|
|
211
|
-
caption1, caption2, caption3
|
|
212
|
-
~~~
|
|
213
|
-
|
|
214
|
-
사용 예시는 다음과 같습니다.
|
|
215
|
-
|
|
216
|
-
~~~tsx
|
|
217
|
-
<h1 className="eds-typography-heading1 text-eds-fg-neutral">
|
|
218
|
-
제목입니다.
|
|
219
|
-
</h1>
|
|
220
|
-
|
|
221
|
-
<p className="eds-typography-body2 text-eds-fg-neutral-secondary">
|
|
222
|
-
본문입니다.
|
|
223
|
-
</p>
|
|
224
|
-
~~~
|
|
225
|
-
|
|
226
|
-
## Spacing과 radius
|
|
227
|
-
|
|
228
|
-
현재 공개 semantic token에 없는 spacing·radius 값을 소비처에서 임의로 alias로 만들지 않습니다. 공개 token이 필요한 경우 다음 정보를 디자인 시스템 담당자에게 요청합니다.
|
|
229
|
-
|
|
230
|
-
- 필요한 token 이름입니다.
|
|
231
|
-
- 사용하려는 UI 목적입니다.
|
|
232
|
-
- 필요한 상태나 variant입니다.
|
|
233
|
-
- 기존 Tailwind scale로 대체할 수 없는 이유입니다.
|
|
234
|
-
|
|
235
|
-
내부 파일 경로를 참조하거나 raw 값을 복사해 `spacing-eds-*`, `radius-eds-*` utility를 만들지 않습니다.
|
|
236
|
-
|
|
237
|
-
## TypeScript token 자동완성
|
|
238
|
-
|
|
239
|
-
CSS가 아닌 CSS-in-JS 또는 inline style에서 token을 선택해야 하면 공개 TypeScript API를 사용합니다.
|
|
240
|
-
|
|
241
|
-
~~~ts
|
|
242
|
-
import { cssVar, type SemanticTokenName } from "@spectrabrain/eds/tokens";
|
|
243
|
-
|
|
244
|
-
const token: SemanticTokenName = "color-fg-brand";
|
|
245
|
-
const color = cssVar(token);
|
|
246
|
-
// var(--eds-color-fg-brand)
|
|
247
|
-
~~~
|
|
248
|
-
|
|
249
|
-
`SemanticTokenName`은 공개 semantic token 이름 자동완성을 제공합니다. token 값 자체를 복사해 스타일에 넣지 않고 `cssVar()` 결과를 사용합니다.
|
|
250
|
-
|
|
251
|
-
## 권장 파일 구조
|
|
252
|
-
|
|
253
|
-
~~~text
|
|
254
|
-
src/styles/
|
|
255
|
-
globals.css
|
|
256
|
-
eds-tokens.css
|
|
257
|
-
~~~
|
|
258
|
-
|
|
259
|
-
`globals.css`에서 `tailwindcss`, EDS token CSS, `eds-tokens.css`를 한 번씩 import합니다. alias와 typography utility는 `eds-tokens.css`에서 관리합니다.
|
|
77
|
+
```
|
|
260
78
|
|
|
261
|
-
|
|
79
|
+
- `--font-sans`는 `@theme inline`의 마지막 줄이며 Tailwind의 기본 sans font를 EDS body1 font family로 바꿉니다.
|
|
80
|
+
- `@theme`의 EDS alias는 sync가 생성한 `var(--eds-*)` 참조만 두고, EDS를 업데이트할 때마다 `npm run sync:eds`로 다시 생성합니다.
|
|
81
|
+
- `eds-tokens.css`는 `@theme`·`@utility`를 담으므로 Tailwind root CSS에서만 import합니다. `@spectrabrain/eds/styles.css`와 `eds-tokens.css`는 각각 한 곳에서만 import합니다.
|
|
82
|
+
- `src/styles/eds-tokens.css`는 생성물이지만 저장소에 commit합니다. install은 이 파일을 만들지 않으므로, 파일이 없으면 Tailwind root의 `@import`가 깨지고, `pretypecheck`·`prebuild` hook(yarn berry는 `typecheck`·`build` 앞의 `eds doctor`)과 `--print-ci` CI job의 `eds doctor`가 `missing:`으로 실패합니다.
|
|
262
83
|
|
|
263
|
-
##
|
|
84
|
+
## Tailwind root 지정
|
|
264
85
|
|
|
265
|
-
|
|
266
|
-
- raw hex·px·rem 값을 EDS token 대신 작성하지 않습니다.
|
|
267
|
-
- `@theme`에 EDS token의 resolved 값을 직접 입력하지 않습니다.
|
|
268
|
-
- `@spectrabrain/eds` 패키지 내부의 비공개 경로를 deep import하지 않습니다.
|
|
269
|
-
- Tailwind class로 EDS가 관리하는 UI 스타일을 임의로 덮어쓰지 않습니다.
|
|
270
|
-
- EDS token 이름과 다른 의미의 alias를 만들어 역할을 바꾸지 않습니다.
|
|
86
|
+
`eds sync`와 `eds doctor`는 `@import "tailwindcss"`가 있는 CSS 파일 하나를 Tailwind root로 찾습니다. 후보가 둘 이상이면 둘 다 exit 2로 멈추며, 사용자에게 root를 확인하는 절차는 `CONSUMER_AGENT.md` 「Tailwind root 결정」을 따릅니다. 확인한 root는 다음처럼 한 번 지정하고, sync가 그 경로를 소비처 `package.json`의 `eds.tailwindRoot`에 기록합니다.
|
|
271
87
|
|
|
272
|
-
|
|
88
|
+
```sh
|
|
89
|
+
EDS_TAILWIND_ROOT=src/app/styles/index.css npm run sync:eds
|
|
90
|
+
```
|
|
273
91
|
|
|
274
|
-
|
|
92
|
+
## 완료 기준
|
|
275
93
|
|
|
276
|
-
1.
|
|
277
|
-
2.
|
|
278
|
-
3.
|
|
279
|
-
4. typography preset utility가 모든 공개 preset에 대해 생성되어 있습니다.
|
|
280
|
-
5. 생성 CSS에 raw 값이나 비공개 token 경로가 없습니다.
|
|
281
|
-
6. 예시 `className`이 실제 빌드 결과에 포함됩니다.
|
|
282
|
-
7. 소비처의 typecheck, lint, test, build가 통과합니다.
|
|
94
|
+
1. `npm run eds:doctor`가 `[EDS] synced with` 줄을 출력하고, 출력에 `No Tailwind root CSS found`·`Tailwind CSS v3 detected` note가 없습니다.
|
|
95
|
+
2. Vite 저장소는 `vite.config.*`의 `plugins`에 `@tailwindcss/vite`의 `tailwindcss()`가 있고, 그 밖의 저장소는 PostCSS 설정에 `@tailwindcss/postcss`가 있습니다. doctor는 이 연결을 점검하지 않습니다.
|
|
96
|
+
3. `@spectrabrain/eds/styles.css`는 Tailwind root CSS나 앱 진입점 가운데 한 곳에서만 import됩니다.
|
|
283
97
|
|
|
284
|
-
|
|
98
|
+
EDS를 업데이트한 뒤에는 `CONSUMER_AGENT.md` 「버전 업데이트」를 따릅니다.
|
package/TOKEN_CONTEXT.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# EVA semantic token 사용 맥락
|
|
2
|
+
|
|
3
|
+
이 문서는 `@spectrabrain/eds`의 공개 semantic token을 화면 맥락에 맞춰 고르는 방법을 안내합니다. 필수 규칙은 package root의 `QUICKSTART.md`에 있습니다. class 이름은 `src/styles/eds-tokens.css`에, CSS variable과 `cssVar()`에 쓰는 token 이름은 설치된 버전의 `@spectrabrain/eds/tokens`(`SemanticTokenName`)와 `dist/tokens.json`에 있습니다.
|
|
4
|
+
|
|
5
|
+
## 색상 역할
|
|
6
|
+
|
|
7
|
+
| 역할 | 적용 대상 | Tailwind class 패턴 | CSS variable 패턴 |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| `fg` | 제목, 본문, placeholder, 아이콘, inverted content | `text-eds-fg-*`(SVG 아이콘은 `currentColor`로 따라갑니다), `fill-eds-fg-*` | `var(--eds-color-fg-*)` |
|
|
10
|
+
| `bg` | 페이지, 카드, 입력, 버튼, 상태 surface | `bg-eds-bg-*` | `var(--eds-color-bg-*)` |
|
|
11
|
+
| `stroke` | border, divider, keyboard focus ring | `border-eds-stroke-*`, `divide-eds-stroke-*`, `outline-eds-stroke-*`, `ring-eds-stroke-*` | `var(--eds-color-stroke-*)` |
|
|
12
|
+
|
|
13
|
+
Tailwind class는 Tailwind root CSS가 있는 저장소에서 `eds sync`가 생성하며, 그 밖의 저장소에서는 CSS variable 또는 `cssVar()`를 씁니다. class 이름은 token 이름의 대소문자를 그대로 씁니다(예: focus ring은 `focus-visible:outline-eds-stroke-focusRing`). 공개 token이 없는 `eds-*` class는 Tailwind가 오류 없이 버리며 doctor의 `unknown:`으로만 드러납니다.
|
|
14
|
+
|
|
15
|
+
역할을 고른 뒤 의미, 강도, 상태를 아래 표의 이름에서 고릅니다. 의미는 주변 화면의 색이 아니라 콘텐츠가 전달하는 상태로 고릅니다: `critical` 오류·위험, `warning` 주의, `positive` 성공·완료, `informative` 정보·안내, `brand` 제품 강조, `neutral` 그 밖의 모든 것.
|
|
16
|
+
|
|
17
|
+
앞의 두 열은 `color-<역할>-<의미>` 뒤에 붙는 이름이고, 마지막 열은 `color-<역할>-` 뒤에 바로 붙는 이름입니다(예: `color-bg-critical-weak`, `color-fg-neutral`, `color-bg-layer-default`). 표에 없는 조합은 없는 token입니다. hover 상태는 bg 이름 뒤에 `-hover`를 붙이며(`bg-disabled` 제외), fg와 stroke에는 hover token이 없습니다.
|
|
18
|
+
|
|
19
|
+
| 역할 | 의미 색(`brand`·`critical`·`informative`·`positive`·`warning`) | `neutral` | 의미 없이 쓰는 이름 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `bg` | `-primary`, `-weak` | `-primary`, `-weak`, `-inverted` | `layer-basement`, `layer-default`, `layer-floating`, `transparent`, `disabled` |
|
|
22
|
+
| `fg` | 접미사 없음, `-contrast` | 접미사 없음, `-secondary`, `-tertiary`, `-inverted` | `disabled`, `placeholder`, `focus` |
|
|
23
|
+
| `stroke` | `-primary`, `-weak` | `-contrast`, `-primary`, `-secondary`, `-tertiary`, `-weak` | `focusRing` |
|
|
24
|
+
|
|
25
|
+
Button 계약에 있는 배경·글자 조합은 `bg-critical-weak`와 `fg-critical`, `bg-neutral-weak`와 `fg-neutral`, `bg-brand-primary`·`bg-neutral-inverted`와 `fg-neutral-inverted`입니다. 본문보다 낮은 위계의 글자는 `fg-neutral-secondary`, 그보다 낮으면 `fg-neutral-tertiary`를 씁니다. 그 밖의 배경 위 글자 색(`brand`·`informative`·`positive`·`warning`의 `weak`·`primary` 배경), 상태 안내(오류 border, badge)의 stroke·fg 조합, `-contrast` fg의 용도, `layer` 계열과 `neutral` 배경의 구분은 `미결`입니다.
|
|
26
|
+
|
|
27
|
+
exact token이 없으면 아래 순서로 조건을 하나씩 완화하고, 처음 존재하는 이름을 씁니다.
|
|
28
|
+
|
|
29
|
+
1. 역할(`fg`·`bg`·`stroke`)은 바꾸지 않습니다.
|
|
30
|
+
2. disabled는 `fg`·`bg`에서 `color-<역할>-disabled`를 쓰고, keyboard focus는 `color-stroke-focusRing`(focus ring)이나 `color-fg-focus`를 씁니다. disabled·hover token이 없는 역할은 기본 token을 그대로 씁니다.
|
|
31
|
+
3. 강도를 같은 의미 안의 가까운 단계로 바꿉니다(`primary`↔`weak`, `neutral`의 `secondary`·`tertiary`).
|
|
32
|
+
4. 의미를 `neutral`로 바꿉니다. `critical`·`warning`·`positive`·`informative`를 `neutral`로 바꿔야 한다면 구현을 멈추고 사용자에게 확인합니다.
|
|
33
|
+
|
|
34
|
+
버튼은 token을 조합하지 않고 EDS `Button`의 `role`·`variant`를 씁니다. 행동별 조합은 `QUICKSTART.md` 표와 `dist/components/Button/README.md` 「Role과 Variant」에 있습니다.
|
|
35
|
+
|
|
36
|
+
## Typography
|
|
37
|
+
|
|
38
|
+
text style은 `fontFamily`, `fontWeight`, `fontSize`, `lineHeight`, `letterSpacing`을 함께 적용하는 preset입니다. 한 속성만 떼어 쓰지 않고 preset 하나를 통째로 적용합니다. preset을 적용한 요소에는 `text-lg`(크기), `font-bold`(weight), `leading-*`, `tracking-*` 같은 typography utility를 덧붙이지 않고, 크기나 강조가 달라야 하면 다른 preset을 고릅니다.
|
|
39
|
+
|
|
40
|
+
`src/styles/eds-tokens.css`가 있는 저장소에서는 `eds-typography-*` utility로, 그 밖의 저장소에서는 같은 preset의 `--eds-typography-<preset>-*` 변수 다섯 개로 적용하며, 설치된 manifest에 있는 preset 이름만 고릅니다. `npm run sync:eds`가 생성하는 `@theme inline`에는 `--font-sans: var(--eds-typography-body1-fontFamily);`가 포함되므로 Tailwind의 `font-sans`와 기본 sans font도 Pretendard가 됩니다. 이 font family는 `@spectrabrain/eds/fonts.css`가 선언합니다.
|
|
41
|
+
|
|
42
|
+
| 그룹 | preset | 권장 맥락 |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| Display | `display1`, `display2` | 제품의 큰 메시지와 페이지 진입점입니다. |
|
|
45
|
+
| Heading | `heading1` … `heading4` | 페이지·섹션·그룹의 구조를 나타냅니다. |
|
|
46
|
+
| Title | `title1` … `title4` | 카드·행·대화상자의 핵심 제목과 control label입니다. |
|
|
47
|
+
| Body | `body1` … `body4` | 설명, 입력 안내, 일반 본문입니다. |
|
|
48
|
+
| Caption | `caption1` … `caption3` | 보조 설명, 메타데이터, 짧은 상태 안내입니다. |
|
|
49
|
+
|
|
50
|
+
같은 그룹에서는 번호가 작을수록 글자가 크거나 같고, 크기가 같은 preset은 weight로 구분합니다. Title과 Body는 같은 번호끼리 크기가 같고 Title이 더 굵습니다.
|
|
51
|
+
|
|
52
|
+
## 예시
|
|
53
|
+
|
|
54
|
+
`src/styles/eds-tokens.css`가 있는 저장소에서는 `className`으로 조합합니다.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<section className="text-eds-fg-neutral">
|
|
58
|
+
<h2 className="eds-typography-heading2 text-eds-fg-neutral">
|
|
59
|
+
카메라 그룹
|
|
60
|
+
</h2>
|
|
61
|
+
<p className="eds-typography-body2 text-eds-fg-neutral-secondary">
|
|
62
|
+
등록된 장치를 관리합니다.
|
|
63
|
+
</p>
|
|
64
|
+
<hr className="border-t border-eds-stroke-neutral-secondary" />
|
|
65
|
+
</section>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
divider의 stroke 단계는 `미결`이며, 예시는 Button `outline`의 경계와 같은 `neutral-secondary`를 씁니다.
|
|
69
|
+
|
|
70
|
+
Tailwind가 없는 저장소에서는 공개 CSS variable로 같은 조합을 씁니다.
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
.cameraGroupTitle {
|
|
74
|
+
color: var(--eds-color-fg-neutral);
|
|
75
|
+
font-family: var(--eds-typography-heading2-fontFamily);
|
|
76
|
+
font-weight: var(--eds-typography-heading2-fontWeight);
|
|
77
|
+
font-size: var(--eds-typography-heading2-fontSize);
|
|
78
|
+
line-height: var(--eds-typography-heading2-lineHeight);
|
|
79
|
+
letter-spacing: var(--eds-typography-heading2-letterSpacing);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
CSS-in-JS나 inline style에서는 `cssVar()`로 같은 이름을 씁니다. `cssVar()`는 CSS 변수 문자열만 반환하므로 앱 진입점에서 `@spectrabrain/eds/styles.css`를 한 번 import해 두어야 합니다.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { cssVar } from "@spectrabrain/eds/tokens";
|
|
87
|
+
|
|
88
|
+
<p style={{ color: cssVar("color-fg-neutral-secondary") }}>…</p>; // var(--eds-color-fg-neutral-secondary)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Spacing과 radius
|
|
92
|
+
|
|
93
|
+
현재 공개 semantic token에는 소비처용 spacing·radius alias가 없습니다. 소비처의 기존 scale(Tailwind를 쓰는 저장소는 Tailwind 기본 scale)을 씁니다. 기존 scale로 표현할 수 없으면 임의 값을 만들기 전에 멈추고 사용자에게 확인합니다.
|
|
94
|
+
|
|
95
|
+
## 확인 목록
|
|
96
|
+
|
|
97
|
+
- 새로 쓴 `var(--eds-*)` 이름이 모두 `tokens.json` key 앞에 `--eds-`를 붙인 이름입니다. doctor는 Tailwind class만, typecheck는 `cssVar()` 인자만 검사하므로 CSS·inline style의 변수는 직접 대조합니다.
|
|
98
|
+
- typography는 preset 하나(`eds-typography-*` class 또는 같은 preset의 변수 다섯 개)로 지정합니다.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "{{eds.srcDir}}/app/styles/index.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/shared/ui",
|
|
15
|
+
"utils": "@/shared/lib/utils",
|
|
16
|
+
"ui": "@/shared/ui",
|
|
17
|
+
"lib": "@/shared/lib",
|
|
18
|
+
"hooks": "@/shared/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VITE_API_MOCKING=true
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
4
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
// Build output, generated files and backend code are not linted by this frontend's config.
|
|
10
|
+
globalIgnores([{{eds.eslintIgnores}}]),
|
|
11
|
+
{
|
|
12
|
+
files: ["**/*.{ts,tsx}"],
|
|
13
|
+
extends: [
|
|
14
|
+
js.configs.recommended,
|
|
15
|
+
tseslint.configs.recommended,
|
|
16
|
+
// Includes the React Compiler rules.
|
|
17
|
+
reactHooks.configs.flat.recommended,
|
|
18
|
+
reactRefresh.configs.vite,
|
|
19
|
+
],
|
|
20
|
+
rules: {
|
|
21
|
+
"@typescript-eslint/no-unused-vars": [
|
|
22
|
+
"error",
|
|
23
|
+
{
|
|
24
|
+
ignoreRestSiblings: true,
|
|
25
|
+
argsIgnorePattern: "^_",
|
|
26
|
+
varsIgnorePattern: "^_",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
languageOptions: {
|
|
31
|
+
ecmaVersion: 2020,
|
|
32
|
+
globals: globals.browser,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Toaster } from "sonner";
|
|
2
|
+
import Router from "./router";
|
|
3
|
+
import { ErrorHandlerProvider } from "./providers/ErrorHandler";
|
|
4
|
+
|
|
5
|
+
function App() {
|
|
6
|
+
return (
|
|
7
|
+
<ErrorHandlerProvider>
|
|
8
|
+
<main>
|
|
9
|
+
<Router />
|
|
10
|
+
<Toaster position="top-right" richColors />
|
|
11
|
+
</main>
|
|
12
|
+
</ErrorHandlerProvider>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default App;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "./styles/index.css";
|
|
2
|
+
import { StrictMode } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { BrowserRouter } from "react-router-dom";
|
|
5
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
6
|
+
import { queryClient } from "@/shared/api/queryClient";
|
|
7
|
+
import App from "./App.tsx";
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
// The dev:mock script loads .env.mock, which sets VITE_API_MOCKING=true.
|
|
11
|
+
if (import.meta.env.VITE_API_MOCKING === "true") {
|
|
12
|
+
const { worker } = await import("@/mocks/browser");
|
|
13
|
+
await worker.start({ onUnhandledRequest: "bypass" });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
createRoot(document.getElementById("root")!).render(
|
|
17
|
+
<StrictMode>
|
|
18
|
+
<BrowserRouter>
|
|
19
|
+
<QueryClientProvider client={queryClient}>
|
|
20
|
+
<App />
|
|
21
|
+
</QueryClientProvider>
|
|
22
|
+
</BrowserRouter>
|
|
23
|
+
</StrictMode>,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
main();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Component, type ReactNode } from "react";
|
|
2
|
+
import { Button } from "@spectrabrain/eds";
|
|
3
|
+
|
|
4
|
+
export function ErrorFallback() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex min-h-screen flex-col items-center justify-center gap-4 bg-eds-bg-layer-basement p-8 text-center">
|
|
7
|
+
<h1 className="eds-typography-title2 text-eds-fg-neutral">
|
|
8
|
+
문제가 발생했습니다
|
|
9
|
+
</h1>
|
|
10
|
+
<p className="eds-typography-body2 text-eds-fg-neutral-secondary">
|
|
11
|
+
페이지를 불러오지 못했습니다. 새로고침 후 다시 시도해 주세요.
|
|
12
|
+
</p>
|
|
13
|
+
<Button
|
|
14
|
+
size="md"
|
|
15
|
+
role="neutral"
|
|
16
|
+
variant="weak"
|
|
17
|
+
type="button"
|
|
18
|
+
onClick={() => window.location.reload()}
|
|
19
|
+
>
|
|
20
|
+
새로고침
|
|
21
|
+
</Button>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ErrorBoundaryProps {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ErrorBoundaryState {
|
|
31
|
+
hasError: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Catches render errors, including lazy page chunks that fail to load after a deploy,
|
|
36
|
+
* and shows a recoverable fallback instead of a blank screen.
|
|
37
|
+
*/
|
|
38
|
+
export class ErrorBoundary extends Component<
|
|
39
|
+
ErrorBoundaryProps,
|
|
40
|
+
ErrorBoundaryState
|
|
41
|
+
> {
|
|
42
|
+
state: ErrorBoundaryState = { hasError: false };
|
|
43
|
+
|
|
44
|
+
static getDerivedStateFromError(): ErrorBoundaryState {
|
|
45
|
+
return { hasError: true };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
componentDidCatch(error: unknown) {
|
|
49
|
+
console.error("Unhandled render error:", error);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
render() {
|
|
53
|
+
if (this.state.hasError) {
|
|
54
|
+
return <ErrorFallback />;
|
|
55
|
+
}
|
|
56
|
+
return this.props.children;
|
|
57
|
+
}
|
|
58
|
+
}
|