@spectrabrain/eds 0.3.0 → 0.4.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/AI.md +6 -6
- package/AI_CONTEXT.json +8 -9
- package/CHANGELOG.md +46 -16
- package/CONSUMER_AGENT.md +66 -56
- package/FRONTEND_CONVENTIONS.md +28 -33
- package/QUICKSTART.md +19 -28
- package/README.md +12 -12
- package/TAILWIND_V4.md +5 -5
- package/TOKEN_CONTEXT.md +6 -22
- package/consumer/align/templates/package.scripts.json +0 -1
- package/consumer/align/templates/src/app/main.tsx +9 -19
- package/consumer/align/templates/vite.config.rolldown.ts +1 -1
- package/consumer/align-analysis.mjs +1712 -299
- package/consumer/align-plan.mjs +647 -139
- package/consumer/align-rules.mjs +59 -23
- package/consumer/align.mjs +639 -103
- package/consumer/cli.mjs +7 -7
- package/consumer/generate-tailwind.mjs +0 -30
- package/consumer/scan-classes.mjs +9 -19
- package/dist/components/Button/README.md +3 -5
- package/dist/components/Button/spec.json +6209 -6
- package/dist/components.css +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +6284 -381
- package/dist/index.mjs +6285 -382
- package/dist/tokens.css +0 -30
- package/dist/tokens.d.mts +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +0 -300
- package/dist/tokens.json +0 -360
- package/dist/tokens.mjs +0 -300
- package/llms.txt +7 -9
- package/package.json +3 -2
- package/consumer/align/templates/env.mock +0 -1
- package/consumer/align/templates/src/mocks/browser.ts +0 -5
- package/consumer/align/templates/src/mocks/handlers.ts +0 -4
package/consumer/cli.mjs
CHANGED
|
@@ -8,13 +8,13 @@ const usage = [
|
|
|
8
8
|
"Usage: eds <command> [options]",
|
|
9
9
|
"",
|
|
10
10
|
"Commands:",
|
|
11
|
-
" init
|
|
12
|
-
" write allowScripts on npm 12+, then
|
|
11
|
+
" init Add sync:eds and eds:doctor when missing; add pretypecheck/prebuild when target scripts exist",
|
|
12
|
+
" and hooks are missing; write allowScripts on npm 12+, then sync in the package declaring @spectrabrain/eds",
|
|
13
13
|
" sync Generate src/styles/eds-tokens.css, wire the Tailwind root CSS and update AGENTS.md",
|
|
14
14
|
" and the @AGENTS.md import in CLAUDE.md (or .claude/CLAUDE.md) and GEMINI.md when they exist",
|
|
15
15
|
" (without a Tailwind root only those change); notes say when an agent may not load the block",
|
|
16
|
-
" doctor Check
|
|
17
|
-
" is a public token (exit 1 when something is off)",
|
|
16
|
+
" doctor Check sync status and the EDS font import; with a Tailwind CSS v4 root, also check that each eds-*",
|
|
17
|
+
" class in the sources is a public token (exit 1 when something is off)",
|
|
18
18
|
" align One-time reorganization of a Vite + React frontend into the EVA conventions (FRONTEND_CONVENTIONS.md):",
|
|
19
19
|
" write .eds/align/PLAN.md, the steps an AI agent follows, plus templates; source files are not changed",
|
|
20
20
|
"",
|
|
@@ -25,9 +25,9 @@ const usage = [
|
|
|
25
25
|
" --no-allow-scripts init: do not write allowScripts to package.json on npm 12+",
|
|
26
26
|
" --print-ci github|gitlab init: print a CI job that runs eds doctor with this repository's package manager",
|
|
27
27
|
" --warn doctor: report findings but exit 0 (same as EDS_DOCTOR=warn)",
|
|
28
|
-
" --scan doctor: also list
|
|
29
|
-
" --check align: check
|
|
30
|
-
" the plan's base commit (exit 1
|
|
28
|
+
" --scan doctor: with a Tailwind CSS v4 root, also list palette and arbitrary color classes that bypass EDS tokens",
|
|
29
|
+
" --check align: check conventions and protected, preserved and out-of-package files",
|
|
30
|
+
" since the plan's base commit; repository root lockfiles are excluded (exit 1 on error)",
|
|
31
31
|
" --frontend <dir> align: the frontend package when the current directory is not it",
|
|
32
32
|
" --help Show this message",
|
|
33
33
|
"",
|
|
@@ -11,10 +11,6 @@ const propertyNames = [
|
|
|
11
11
|
|
|
12
12
|
export const fontSansStyleName = "body1";
|
|
13
13
|
|
|
14
|
-
function safeTailwindAlias(name) {
|
|
15
|
-
return "eds-" + name.replace(/\./g, "-");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
14
|
function cssVariableForToken(manifest, name) {
|
|
19
15
|
const cssVariable = manifest.tokens?.[name]?.$extensions?.eds?.cssVariable;
|
|
20
16
|
if (typeof cssVariable === "string") {
|
|
@@ -24,27 +20,9 @@ function cssVariableForToken(manifest, name) {
|
|
|
24
20
|
return "--eds-" + name.replace(/\./g, "\\.");
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
function collectDimensionAliases(manifest, prefix) {
|
|
28
|
-
const aliases = new Map();
|
|
29
|
-
|
|
30
|
-
for (const name of Object.keys(manifest.tokens ?? {}).filter((entry) => entry.startsWith(prefix))) {
|
|
31
|
-
const alias = safeTailwindAlias(name);
|
|
32
|
-
const existing = aliases.get(alias);
|
|
33
|
-
if (existing !== undefined && existing !== name) {
|
|
34
|
-
throw new Error(`Tailwind alias collision: ${existing} and ${name} both map to ${alias}.`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
aliases.set(alias, name);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return [...aliases.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
23
|
export function renderTailwindCss(manifest, version) {
|
|
44
24
|
const tokens = Object.keys(manifest.tokens ?? {});
|
|
45
25
|
const colorNames = tokens.filter((name) => name.startsWith("color-"));
|
|
46
|
-
const spacingAliases = collectDimensionAliases(manifest, "spacing-");
|
|
47
|
-
const radiusAliases = collectDimensionAliases(manifest, "radius-");
|
|
48
26
|
const typography = new Map();
|
|
49
27
|
|
|
50
28
|
for (const name of tokens.filter((entry) => entry.startsWith("typography-"))) {
|
|
@@ -74,12 +52,6 @@ export function renderTailwindCss(manifest, version) {
|
|
|
74
52
|
const colorAliases = colorNames
|
|
75
53
|
.map((name) => ` --color-eds-${name.slice("color-".length)}: var(${cssVariableForToken(manifest, name)});`)
|
|
76
54
|
.join("\n");
|
|
77
|
-
const spacingThemeAliases = spacingAliases
|
|
78
|
-
.map(([alias, name]) => ` --spacing-${alias}: var(${cssVariableForToken(manifest, name)});`)
|
|
79
|
-
.join("\n");
|
|
80
|
-
const radiusThemeAliases = radiusAliases
|
|
81
|
-
.map(([alias, name]) => ` --radius-${alias}: var(${cssVariableForToken(manifest, name)});`)
|
|
82
|
-
.join("\n");
|
|
83
55
|
// Last line of the @theme block: Tailwind's default font family becomes the EDS body font.
|
|
84
56
|
const fontSans = ` --font-sans: var(--eds-typography-${fontSansStyleName}-fontFamily);`;
|
|
85
57
|
const typographyUtilities = [...typography.keys()].sort().map((styleName) => [
|
|
@@ -96,8 +68,6 @@ export function renderTailwindCss(manifest, version) {
|
|
|
96
68
|
`/* Generated from @spectrabrain/eds@${version}. Do not edit manually. */`,
|
|
97
69
|
"@theme inline {",
|
|
98
70
|
colorAliases,
|
|
99
|
-
spacingThemeAliases,
|
|
100
|
-
radiusThemeAliases,
|
|
101
71
|
fontSans,
|
|
102
72
|
"}",
|
|
103
73
|
"",
|
|
@@ -10,9 +10,10 @@ const opacity = "(?:/[0-9]{1,3})?";
|
|
|
10
10
|
|
|
11
11
|
const edsColorClass = new RegExp(boundaryBefore + variantPrefix + colorUtility + "-eds-([A-Za-z0-9-]+)" + opacity + boundaryAfter, "g");
|
|
12
12
|
const edsTypographyClass = new RegExp(boundaryBefore + variantPrefix + "eds-typography-([A-Za-z0-9-]+)" + boundaryAfter, "g");
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const retiredDimensionClass = new RegExp(
|
|
14
|
+
boundaryBefore + variantPrefix + "-?[A-Za-z0-9-]+-eds-(?:spacing|radius)-[A-Za-z0-9-]+" + boundaryAfter,
|
|
15
|
+
"g",
|
|
16
|
+
);
|
|
16
17
|
const rawColorClass = new RegExp(
|
|
17
18
|
boundaryBefore + variantPrefix + colorUtility +
|
|
18
19
|
"-(?:(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|white|black|\\[(?:#|rgba?\\(|hsla?\\(|oklch\\(|oklab\\(|color\\()[^\\]]*\\])" +
|
|
@@ -20,11 +21,10 @@ const rawColorClass = new RegExp(
|
|
|
20
21
|
"g",
|
|
21
22
|
);
|
|
22
23
|
|
|
23
|
-
//
|
|
24
|
+
// EDS publishes color and typography utilities. Spacing and radius use Tailwind CSS v4 defaults.
|
|
24
25
|
export function publicClassNames(manifest) {
|
|
25
26
|
const colors = new Set();
|
|
26
27
|
const typography = new Set();
|
|
27
|
-
const dimensions = new Set();
|
|
28
28
|
for (const name of Object.keys(manifest.tokens ?? {})) {
|
|
29
29
|
if (name.startsWith("color-")) {
|
|
30
30
|
colors.add("eds-" + name.slice("color-".length));
|
|
@@ -35,18 +35,15 @@ export function publicClassNames(manifest) {
|
|
|
35
35
|
typography.add(match[1]);
|
|
36
36
|
continue;
|
|
37
37
|
}
|
|
38
|
-
if (name.startsWith("spacing-") || name.startsWith("radius-")) {
|
|
39
|
-
dimensions.add("eds-" + name.replace(/\./g, "-"));
|
|
40
|
-
}
|
|
41
38
|
}
|
|
42
|
-
return { colors, typography
|
|
39
|
+
return { colors, typography };
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
// files: [{ path, contents }] as readSources() lists them (the generated eds-tokens.css is not among them).
|
|
46
43
|
// unknown: eds-* classes that no public token backs (always a mistake, Tailwind silently drops them).
|
|
47
44
|
// raw: Tailwind palette, white/black and arbitrary color classes that bypass the EDS tokens (informational).
|
|
48
45
|
export function scanClassNames({ consumerRoot, manifest, files }) {
|
|
49
|
-
const { colors, typography
|
|
46
|
+
const { colors, typography } = publicClassNames(manifest);
|
|
50
47
|
const unknown = [];
|
|
51
48
|
const raw = [];
|
|
52
49
|
for (const file of files) {
|
|
@@ -70,15 +67,8 @@ export function scanClassNames({ consumerRoot, manifest, files }) {
|
|
|
70
67
|
unknown.push({ location, className: match[0] });
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
|
-
for (const match of line.matchAll(
|
|
74
|
-
|
|
75
|
-
unknown.push({ location, className: match[0] });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
for (const match of line.matchAll(edsRadiusClass)) {
|
|
79
|
-
if (match[1] !== undefined && !dimensions.has("eds-" + match[1])) {
|
|
80
|
-
unknown.push({ location, className: match[0] });
|
|
81
|
-
}
|
|
70
|
+
for (const match of line.matchAll(retiredDimensionClass)) {
|
|
71
|
+
unknown.push({ location, className: match[0] });
|
|
82
72
|
}
|
|
83
73
|
for (const match of line.matchAll(rawColorClass)) {
|
|
84
74
|
raw.push({ location, className: match[0] });
|
|
@@ -65,10 +65,8 @@ Layout은 prop으로 지정하지 않고 슬롯 조합에서 추론합니다.
|
|
|
65
65
|
| `iconBoth` | `children`과 앞·뒤 아이콘을 모두 지정합니다. | 양쪽 아이콘이 모두 의미를 더하는 명확한 경우에만 사용합니다. |
|
|
66
66
|
| `iconOnly` | `children` 없이 `icon` 또는 `iconName`과 `aria-label`을 지정합니다. | |
|
|
67
67
|
|
|
68
|
-
- 이름 슬롯(`iconName`·`startIconName`·`endIconName`)은
|
|
69
|
-
-
|
|
70
|
-
- 목록 밖 아이콘은 `icon`, `startIcon`, `endIcon`에 `ReactElement`로 전달하며, 같은 슬롯에 이름과 custom element를 함께 지정하지 않습니다. `lucide-react`에서 직접 import할 때는 EDS package root `package.json`의 `dependencies["lucide-react"]`와 같은 exact 버전을 소비처 `package.json`에 선언합니다. pnpm처럼 엄격한 node_modules에서는 선언 없이 resolve되지 않고, 버전이 다르면 아이콘 코드가 두 벌 번들됩니다. custom lucide 아이콘은 lucide 기본 stroke를 그대로 써서 이름으로 지정한 아이콘과 맞춥니다.
|
|
71
|
-
- 목록 밖 아이콘을 `ReactElement`로 썼다면 완료 보고에 아이콘 이름과 사용 파일을 allowlist 추가 후보로 적습니다.
|
|
68
|
+
- 이름 슬롯(`iconName`·`startIconName`·`endIconName`)은 설치된 `lucide-react`의 실제 아이콘 component 이름과 alias를 모두 받습니다. `ButtonIconName`이 이 목록을 자동완성하며, 전체 이름은 `spec.json`의 `api.iconNames`에 있습니다. 잘못된 이름은 TypeScript 오류이고 런타임에도 render 오류입니다.
|
|
69
|
+
- custom 아이콘은 `icon`·`startIcon`·`endIcon`에 `ReactElement`로 전달합니다. 같은 슬롯에 이름과 custom element를 함께 지정하지 않습니다. `lucide-react`에서 직접 import할 때는 EDS package root `package.json`의 `dependencies["lucide-react"]`와 같은 exact 버전을 소비처 `package.json`에 선언합니다. pnpm처럼 엄격한 node_modules에서는 선언 없이 resolve되지 않고, 버전이 다르면 아이콘 코드가 두 벌 번들됩니다. 이름 슬롯은 설치된 아이콘 크기와 stroke를 사용합니다.
|
|
72
70
|
|
|
73
71
|
### State
|
|
74
72
|
|
|
@@ -109,7 +107,7 @@ Layout은 prop으로 지정하지 않고 슬롯 조합에서 추론합니다.
|
|
|
109
107
|
| `aria-label` | 선택적인 `string`이며, 지정하면 비어 있지 않아야 합니다. icon-only에서는 항상 지정합니다. |
|
|
110
108
|
| `disabled` | `boolean`입니다. native button은 `disabled`, link는 href 제거·`aria-disabled="true"`·`tabIndex={-1}`를 적용합니다. |
|
|
111
109
|
| `loading` | `boolean`입니다. label·아이콘 공간을 유지한 progress circle을 표시하고 상호작용을 차단하며 `aria-busy="true"`를 적용합니다. |
|
|
112
|
-
| `className` | `string`입니다. 허용 범위는 `QUICKSTART.md`
|
|
110
|
+
| `className` | `string`입니다. 허용 범위는 `QUICKSTART.md`의 `className` 규칙을 따릅니다. |
|
|
113
111
|
| `style` | 지원하지 않습니다(`never`). 스타일 override는 공개 API가 아닙니다. |
|
|
114
112
|
| `aria-*` / `data-*` | 각 branch의 native HTML 속성으로 전달할 수 있습니다. |
|
|
115
113
|
| `ref` | branch에 따라 `HTMLButtonElement` 또는 `HTMLAnchorElement`를 전달합니다. |
|