@tide-tds/foundation 0.1.0 → 0.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.
@@ -0,0 +1,46 @@
1
+ import { ColorSlot, SlotRule } from "./color-slot.js";
2
+ import { SemanticColorName, SemanticNamespace, ThemeName } from "./semantic.js";
3
+ /** Primitive 변수 이름. 예: `--tide-color-blue-600` */
4
+ export declare function primitiveVar(name: string): string;
5
+ /** 계열 변수 이름. 예: `--tide-blue-solid` */
6
+ export declare function familyVar(family: string, slot: ColorSlot): string;
7
+ /** accent 슬롯 변수 이름. 예: `--tide-accent-solid` */
8
+ export declare function accentVar(slot: ColorSlot): string;
9
+ /** Semantic Token 변수 이름. 예: `--tide-bg-primary-solid` */
10
+ export declare function semanticVar(className: string): string;
11
+ /**
12
+ * 계열 하나의 Theme 블록 선언(9줄).
13
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
14
+ */
15
+ export declare function familyThemeDeclarations(family: string, steps: Readonly<Record<ColorSlot, SlotRule>>, theme: ThemeName): string[];
16
+ /**
17
+ * `[data-color="<색 이름>"]` 규칙 하나. Role은 Role Token을, 계열은 계열 변수를 꽂는다.
18
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
19
+ */
20
+ export declare function dataColorRule(name: string): string;
21
+ /**
22
+ * Theme 블록의 accent 기본값(primary). data-color가 없는 요소도 그 Theme의 값을 읽는다.
23
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
24
+ */
25
+ export declare function accentDefaultDeclarations(): string[];
26
+ /**
27
+ * Tide 색 계열 전체의 Theme 블록 선언.
28
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
29
+ */
30
+ export declare function tideFamilyThemeDeclarations(theme: ThemeName): string[];
31
+ /**
32
+ * Role Token이 Theme에서 계열 변수의 별칭이면 그 변수 참조를 돌려준다. 덮어쓴 슬롯이거나 Role Token이 아니면 undefined다.
33
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
34
+ */
35
+ export declare function roleAliasCss(className: SemanticColorName, theme: ThemeName): string | undefined;
36
+ /** accent Utility class 하나. key는 Utility class에서 접두사를 뺀 이름이다. */
37
+ export interface AccentUtility {
38
+ readonly namespace: SemanticNamespace;
39
+ readonly key: string;
40
+ readonly slot: ColorSlot;
41
+ }
42
+ /**
43
+ * accent Utility class 목록. 예: `bg-accent-solid` → `--tide-accent-solid`, `text-accent` → `--tide-accent-text`
44
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
45
+ */
46
+ export declare const accentUtilities: readonly AccentUtility[];
@@ -0,0 +1,105 @@
1
+ import { colorFamilyNames, isRole, roleNames, roleSlotToken } from "./color-name.js";
2
+ import { colorSlots, familySlotSteps, slotPrimitive } from "./color-slot.js";
3
+ import { roleFamily, semanticEntries } from "./semantic.js";
4
+
5
+ // src/color-css.ts
6
+ /** Primitive 변수 이름. 예: `--tide-color-blue-600` */
7
+ function primitiveVar(name) {
8
+ return `--tide-color-${name}`;
9
+ }
10
+ /** 계열 변수 이름. 예: `--tide-blue-solid` */
11
+ function familyVar(family, slot) {
12
+ return `--tide-${family}-${slot}`;
13
+ }
14
+ /** accent 슬롯 변수 이름. 예: `--tide-accent-solid` */
15
+ function accentVar(slot) {
16
+ return `--tide-accent-${slot}`;
17
+ }
18
+ /** Semantic Token 변수 이름. 예: `--tide-bg-primary-solid` */
19
+ function semanticVar(className) {
20
+ return `--tide-${className}`;
21
+ }
22
+ /** 슬롯 값의 CSS 표현. 계열 단계는 Primitive 변수를, white·black은 Tide Primitive 변수를 가리킨다. */
23
+ function slotCss(family, step) {
24
+ return `var(${primitiveVar(slotPrimitive(family, step))})`;
25
+ }
26
+ /**
27
+ * 계열 하나의 Theme 블록 선언(9줄).
28
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
29
+ */
30
+ function familyThemeDeclarations(family, steps, theme) {
31
+ return colorSlots.map((slot) => ` ${familyVar(family, slot)}: ${slotCss(family, steps[slot][theme])};`);
32
+ }
33
+ /** accent 슬롯에 변수를 꽂는 선언 */
34
+ function accentDeclarations(source) {
35
+ return colorSlots.map((slot) => ` ${accentVar(slot)}: var(${source(slot)});`);
36
+ }
37
+ /**
38
+ * `[data-color="<색 이름>"]` 규칙 하나. Role은 Role Token을, 계열은 계열 변수를 꽂는다.
39
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
40
+ */
41
+ function dataColorRule(name) {
42
+ const source = isRole(name) ? (slot) => semanticVar(roleSlotToken(name, slot)) : (slot) => familyVar(name, slot);
43
+ return [
44
+ `[data-color="${name}"] {`,
45
+ ...accentDeclarations(source),
46
+ "}"
47
+ ].join("\n");
48
+ }
49
+ /**
50
+ * Theme 블록의 accent 기본값(primary). data-color가 없는 요소도 그 Theme의 값을 읽는다.
51
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
52
+ */
53
+ function accentDefaultDeclarations() {
54
+ return accentDeclarations((slot) => semanticVar(roleSlotToken("primary", slot)));
55
+ }
56
+ /**
57
+ * Tide 색 계열 전체의 Theme 블록 선언.
58
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
59
+ */
60
+ function tideFamilyThemeDeclarations(theme) {
61
+ return colorFamilyNames.flatMap((family) => familyThemeDeclarations(family, familySlotSteps(family), theme));
62
+ }
63
+ const roleAliases = new Map(roleNames.flatMap((role) => colorSlots.map((slot) => [roleSlotToken(role, slot), slot]).filter(([token]) => token.includes(`-${role}`)).map(([token, slot]) => [token, {
64
+ family: roleFamily[role],
65
+ slot
66
+ }])));
67
+ /**
68
+ * Role Token이 Theme에서 계열 변수의 별칭이면 그 변수 참조를 돌려준다. 덮어쓴 슬롯이거나 Role Token이 아니면 undefined다.
69
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
70
+ */
71
+ function roleAliasCss(className, theme) {
72
+ const alias = roleAliases.get(className);
73
+ if (!alias) return void 0;
74
+ const family = alias.family;
75
+ const familyValue = slotPrimitive(family, familySlotSteps(family)[alias.slot][theme]);
76
+ return semanticEntries().find((candidate) => candidate.className === className).value[theme] === familyValue ? `var(${familyVar(family, alias.slot)})` : void 0;
77
+ }
78
+ /**
79
+ * accent Utility class 목록. 예: `bg-accent-solid` → `--tide-accent-solid`, `text-accent` → `--tide-accent-text`
80
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
81
+ */
82
+ const accentUtilities = [
83
+ ...colorSlots.filter((slot) => slot !== "text" && slot !== "border" && slot !== "on-solid").map((slot) => ({
84
+ namespace: "background",
85
+ key: `accent-${slot}`,
86
+ slot
87
+ })),
88
+ {
89
+ namespace: "text",
90
+ key: "accent",
91
+ slot: "text"
92
+ },
93
+ {
94
+ namespace: "text",
95
+ key: "accent-on-solid",
96
+ slot: "on-solid"
97
+ },
98
+ {
99
+ namespace: "border",
100
+ key: "accent",
101
+ slot: "border"
102
+ }
103
+ ];
104
+
105
+ export { accentDefaultDeclarations, accentUtilities, accentVar, dataColorRule, familyThemeDeclarations, familyVar, primitiveVar, roleAliasCss, semanticVar, tideFamilyThemeDeclarations };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `@tide-tds/foundation/color-family`: 앱이 자기 브랜드 색 계열을 더하는 API.
3
+ *
4
+ * 앱은 `defineColorFamily`로 11단계 스케일을 정의하고, `tide-color build <파일>`로 CSS를 만든다.
5
+ * CSS는 Tide 계열과 같은 매핑 규칙으로 light·dark 계열 변수와 `[data-color="<이름>"]` 규칙을 담는다.
6
+ * @packageDocumentation
7
+ */
8
+ import { ColorStep } from "./color.js";
9
+ import { ColorSlot, SlotOverrides, SlotRule, SlotStep } from "./color-slot.js";
10
+ import { ColorContrastResult, ColorContrastRule } from "./contrast.js";
11
+ import { ThemeName } from "./semantic.js";
12
+ /** 50~950의 11단계 스케일. 값은 `#rrggbb` hex다. */
13
+ export type ColorScale = Readonly<Record<ColorStep, string>>;
14
+ /** `defineColorFamily`에 넘기는 값 */
15
+ export interface ColorFamilyInput<Name extends string> {
16
+ /** 색 이름. `data-color`와 변수 이름(`--tide-<이름>-solid`)에 쓴다. 소문자, 숫자, `-`만 쓴다. */
17
+ readonly name: Name;
18
+ /** 11단계 스케일 전체 */
19
+ readonly scale: ColorScale;
20
+ /** 매핑 규칙에서 덮어쓸 슬롯 값. 예: 밝은 계열의 `on-solid`를 `{ light: 950, dark: 950 }`으로 */
21
+ readonly overrides?: SlotOverrides;
22
+ }
23
+ /** 검증을 마친 앱 색 계열 */
24
+ export interface ColorFamilyDefinition<Name extends string = string> {
25
+ readonly name: Name;
26
+ readonly scale: ColorScale;
27
+ readonly overrides: SlotOverrides;
28
+ }
29
+ /**
30
+ * 앱 브랜드 색 계열을 정의한다. 이름, 스케일, 덮어쓸 값이 올바른지 확인하고 그대로 돌려준다.
31
+ *
32
+ * ```ts
33
+ * // brand.ts
34
+ * import { defineColorFamily } from "@tide-tds/foundation/color-family";
35
+ *
36
+ * export default defineColorFamily({
37
+ * name: "brand",
38
+ * scale: { 50: "#f5f3ff", 100: "#ede9fe", ... 950: "#2e1065" },
39
+ * });
40
+ * ```
41
+ *
42
+ * @throws 이름이 Tide 색 이름과 겹치거나, 스케일 단계가 빠졌거나, hex가 `#rrggbb`가 아니면
43
+ */
44
+ export declare function defineColorFamily<const Name extends string>(input: ColorFamilyInput<Name>): ColorFamilyDefinition<Name>;
45
+ /** 앱 계열의 Theme별 슬롯 hex */
46
+ export declare function colorFamilySlotHex(family: ColorFamilyDefinition): Record<ThemeName, Record<ColorSlot, string>>;
47
+ /** 앱 계열을 Tide와 같은 대비 쌍 규칙으로 light·dark에서 검사한다. 미달이면 `pass`가 false다. */
48
+ export declare function checkColorFamily(family: ColorFamilyDefinition): ColorContrastResult[];
49
+ /**
50
+ * 앱 계열의 CSS. Primitive 변수, light·dark 계열 변수, `[data-color]` 규칙을 담는다.
51
+ * Tide CSS(`@tide-tds/components/styles.css` 또는 foundation CSS) 다음에 불러온다.
52
+ */
53
+ export declare function colorFamilyCss(families: readonly ColorFamilyDefinition[]): string;
54
+ export type { ColorContrastResult, ColorContrastRule, ColorSlot, SlotOverrides, SlotRule, SlotStep };
@@ -0,0 +1,112 @@
1
+ import { color } from "./color.js";
2
+ import { tideColorNames } from "./color-name.js";
3
+ import { dataColorRule, familyThemeDeclarations, primitiveVar } from "./color-css.js";
4
+ import { colorSlots, resolveSlotSteps, slotPrimitive } from "./color-slot.js";
5
+ import { checkColorContrast } from "./contrast.js";
6
+
7
+ // src/color-family.ts
8
+ /**
9
+ * `@tide-tds/foundation/color-family`: 앱이 자기 브랜드 색 계열을 더하는 API.
10
+ *
11
+ * 앱은 `defineColorFamily`로 11단계 스케일을 정의하고, `tide-color build <파일>`로 CSS를 만든다.
12
+ * CSS는 Tide 계열과 같은 매핑 규칙으로 light·dark 계열 변수와 `[data-color="<이름>"]` 규칙을 담는다.
13
+ * @packageDocumentation
14
+ */
15
+ const steps = Object.keys(color.blue).map(Number);
16
+ const reservedNames = /* @__PURE__ */ new Set([
17
+ ...tideColorNames,
18
+ "accent",
19
+ "white",
20
+ "black",
21
+ "color",
22
+ "bg",
23
+ "text",
24
+ "border",
25
+ "outline",
26
+ "font",
27
+ "rounded",
28
+ "shadow",
29
+ "duration",
30
+ "ease",
31
+ "z",
32
+ "focus"
33
+ ]);
34
+ function isSlotStep(value) {
35
+ return value === "white" || value === "black" || steps.includes(value);
36
+ }
37
+ /**
38
+ * 앱 브랜드 색 계열을 정의한다. 이름, 스케일, 덮어쓸 값이 올바른지 확인하고 그대로 돌려준다.
39
+ *
40
+ * ```ts
41
+ * // brand.ts
42
+ * import { defineColorFamily } from "@tide-tds/foundation/color-family";
43
+ *
44
+ * export default defineColorFamily({
45
+ * name: "brand",
46
+ * scale: { 50: "#f5f3ff", 100: "#ede9fe", ... 950: "#2e1065" },
47
+ * });
48
+ * ```
49
+ *
50
+ * @throws 이름이 Tide 색 이름과 겹치거나, 스케일 단계가 빠졌거나, hex가 `#rrggbb`가 아니면
51
+ */
52
+ function defineColorFamily(input) {
53
+ const { name, scale, overrides = {} } = input;
54
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name)) throw new Error(`색 이름은 소문자로 시작하는 kebab-case여야 한다: "${name}"`);
55
+ if (reservedNames.has(name)) throw new Error(`"${name}"은 Tide가 쓰는 이름이라 앱 색 계열 이름으로 쓸 수 없다.`);
56
+ for (const step of steps) {
57
+ const hex = scale[step];
58
+ if (typeof hex !== "string" || !/^#[0-9a-f]{6}$/i.test(hex)) throw new Error(`${name}-${step}: #rrggbb hex가 아니다(${String(hex)})`);
59
+ }
60
+ const extraSteps = Object.keys(scale).filter((step) => !steps.includes(Number(step)));
61
+ if (extraSteps.length > 0) throw new Error(`${name}: 스케일에 없는 단계가 있다(${extraSteps.join(", ")})`);
62
+ for (const [slot, rule] of Object.entries(overrides)) {
63
+ if (!colorSlots.includes(slot)) throw new Error(`${name}: 없는 슬롯을 덮어썼다(${slot})`);
64
+ for (const [theme, step] of Object.entries(rule ?? {})) if (theme !== "light" && theme !== "dark" || !isSlotStep(step)) throw new Error(`${name}: ${slot}.${theme} 값이 단계(50~950, white, black)가 아니다`);
65
+ }
66
+ return {
67
+ name,
68
+ scale: Object.fromEntries(steps.map((step) => [step, scale[step].toLowerCase()])),
69
+ overrides
70
+ };
71
+ }
72
+ /** 앱 계열의 슬롯별 단계(규칙 + 덮어쓸 값) */
73
+ function slotSteps(family) {
74
+ return resolveSlotSteps(family.overrides);
75
+ }
76
+ /** 앱 계열의 Theme별 슬롯 hex */
77
+ function colorFamilySlotHex(family) {
78
+ const resolved = slotSteps(family);
79
+ const hex = (step) => typeof step === "number" ? family.scale[step] : color[step];
80
+ return {
81
+ light: Object.fromEntries(colorSlots.map((slot) => [slot, hex(resolved[slot].light)])),
82
+ dark: Object.fromEntries(colorSlots.map((slot) => [slot, hex(resolved[slot].dark)]))
83
+ };
84
+ }
85
+ /** 앱 계열을 Tide와 같은 대비 쌍 규칙으로 light·dark에서 검사한다. 미달이면 `pass`가 false다. */
86
+ function checkColorFamily(family) {
87
+ return checkColorContrast(colorFamilySlotHex(family));
88
+ }
89
+ /**
90
+ * 앱 계열의 CSS. Primitive 변수, light·dark 계열 변수, `[data-color]` 규칙을 담는다.
91
+ * Tide CSS(`@tide-tds/components/styles.css` 또는 foundation CSS) 다음에 불러온다.
92
+ */
93
+ function colorFamilyCss(families) {
94
+ const themeBlock = (theme) => families.flatMap((family) => familyThemeDeclarations(family.name, slotSteps(family), theme));
95
+ return [
96
+ ":root {",
97
+ ...families.flatMap((family) => steps.map((step) => ` ${primitiveVar(slotPrimitive(family.name, step))}: ${family.scale[step]};`)),
98
+ "}",
99
+ "",
100
+ ":root,\n[data-theme=\"light\"] {",
101
+ ...themeBlock("light"),
102
+ "}",
103
+ "",
104
+ "[data-theme=\"dark\"] {",
105
+ ...themeBlock("dark"),
106
+ "}",
107
+ "",
108
+ ...families.flatMap((family) => [dataColorRule(family.name), ""])
109
+ ].join("\n");
110
+ }
111
+
112
+ export { checkColorFamily, colorFamilyCss, colorFamilySlotHex, defineColorFamily };
@@ -0,0 +1,40 @@
1
+ import { ColorFamily } from "./color.js";
2
+ import { ColorSlot } from "./color-slot.js";
3
+ import { SemanticColorName, ThemeName, ThemedColor, TideRole } from "./semantic.js";
4
+ /** Tide에 들어 있는 색 이름. 앱이 augmentation으로 더한 이름은 들어 있지 않다. */
5
+ export type TideColor = TideRole | ColorFamily;
6
+ /**
7
+ * Role 이름 목록(neutral, primary, success, warning, danger, info 순서).
8
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
9
+ */
10
+ export declare const roleNames: readonly TideRole[];
11
+ /**
12
+ * Tide 색 계열 목록(`color`의 11단계 계열 순서).
13
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
14
+ */
15
+ export declare const colorFamilyNames: readonly ColorFamily[];
16
+ /**
17
+ * Tide 색 이름 목록. Role 다음에 색 계열이 온다.
18
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
19
+ */
20
+ export declare const tideColorNames: readonly TideColor[];
21
+ /**
22
+ * Tide Role인지 확인한다. 앱 Role은 Role Token이 없어서 false다.
23
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
24
+ */
25
+ export declare function isRole(name: string): name is TideRole;
26
+ /**
27
+ * Role 슬롯이 가리키는 Semantic Token. 예: (`primary`, `solid`) → `bg-primary-solid`, (`neutral`, `text`) → `text-default`
28
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
29
+ */
30
+ export declare function roleSlotToken(role: TideRole, slot: ColorSlot): SemanticColorName;
31
+ /**
32
+ * Tide 색 이름 하나의 슬롯별 값(Theme별 Primitive).
33
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
34
+ */
35
+ export declare function colorSlotValues(name: TideColor): Record<ColorSlot, ThemedColor>;
36
+ /**
37
+ * Tide 색 이름 하나의 슬롯별 hex(Theme 하나).
38
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
39
+ */
40
+ export declare function colorSlotHex(name: TideColor, theme: ThemeName): Record<ColorSlot, string>;
@@ -0,0 +1,61 @@
1
+ import { color } from "./color.js";
2
+ import { colorSlots, familySlotSteps, slotPrimitive } from "./color-slot.js";
3
+ import { colorValueHex, roleFamily, roleSharedSlots, semanticEntries } from "./semantic.js";
4
+
5
+ // src/color-name.ts
6
+ /**
7
+ * Role 이름 목록(neutral, primary, success, warning, danger, info 순서).
8
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
9
+ */
10
+ const roleNames = Object.keys(roleFamily);
11
+ /**
12
+ * Tide 색 계열 목록(`color`의 11단계 계열 순서).
13
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
14
+ */
15
+ const colorFamilyNames = Object.keys(color).filter((name) => typeof color[name] !== "string");
16
+ /**
17
+ * Tide 색 이름 목록. Role 다음에 색 계열이 온다.
18
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
19
+ */
20
+ const tideColorNames = [...roleNames, ...colorFamilyNames];
21
+ /**
22
+ * Tide Role인지 확인한다. 앱 Role은 Role Token이 없어서 false다.
23
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
24
+ */
25
+ function isRole(name) {
26
+ return Object.hasOwn(roleFamily, name);
27
+ }
28
+ /**
29
+ * Role 슬롯이 가리키는 Semantic Token. 예: (`primary`, `solid`) → `bg-primary-solid`, (`neutral`, `text`) → `text-default`
30
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
31
+ */
32
+ function roleSlotToken(role, slot) {
33
+ if (slot === "on-solid") return "text-on-solid";
34
+ if (slot === "text" || slot === "border") return roleSharedSlots[role]?.[slot] ?? `${slot}-${role}`;
35
+ return `bg-${role}-${slot}`;
36
+ }
37
+ /**
38
+ * Tide 색 이름 하나의 슬롯별 값(Theme별 Primitive).
39
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
40
+ */
41
+ function colorSlotValues(name) {
42
+ if (isRole(name)) {
43
+ const entries = new Map(semanticEntries().map((entry) => [entry.className, entry.value]));
44
+ return Object.fromEntries(colorSlots.map((slot) => [slot, entries.get(roleSlotToken(name, slot))]));
45
+ }
46
+ const steps = familySlotSteps(name);
47
+ return Object.fromEntries(colorSlots.map((slot) => [slot, {
48
+ light: slotPrimitive(name, steps[slot].light),
49
+ dark: slotPrimitive(name, steps[slot].dark)
50
+ }]));
51
+ }
52
+ /**
53
+ * Tide 색 이름 하나의 슬롯별 hex(Theme 하나).
54
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
55
+ */
56
+ function colorSlotHex(name, theme) {
57
+ const values = colorSlotValues(name);
58
+ return Object.fromEntries(colorSlots.map((slot) => [slot, colorValueHex(values[slot][theme])]));
59
+ }
60
+
61
+ export { colorFamilyNames, colorSlotHex, colorSlotValues, isRole, roleNames, roleSlotToken, tideColorNames };
@@ -0,0 +1,80 @@
1
+ import { ColorFamily, ColorStep } from "./color.js";
2
+ /**
3
+ * 색 계열 변수 묶음의 슬롯. CSS에서는 `--tide-<색 이름>-<슬롯>`과 `--tide-accent-<슬롯>`으로 나간다.
4
+ */
5
+ export declare const colorSlots: readonly ["solid", "solid-hover", "solid-active", "subtle", "subtle-hover", "subtle-active", "text", "border", "on-solid"];
6
+ /** 색 계열 변수 묶음의 슬롯 이름. 예: `solid`, `subtle-hover`, `on-solid` */
7
+ export type ColorSlot = (typeof colorSlots)[number];
8
+ /** 배경 슬롯(solid 3단계, subtle 3단계) */
9
+ export type BackgroundSlot = Exclude<ColorSlot, "text" | "border" | "on-solid">;
10
+ /** 슬롯이 가리키는 값: 그 계열 스케일의 단계, 또는 `white`·`black` */
11
+ export type SlotStep = ColorStep | "white" | "black";
12
+ /** Theme별 슬롯 값 */
13
+ export interface SlotRule {
14
+ readonly light: SlotStep;
15
+ readonly dark: SlotStep;
16
+ }
17
+ /** 계열마다 덮어쓸 슬롯 값. Theme 하나만 덮어써도 된다. */
18
+ export type SlotOverrides = { readonly [Slot in ColorSlot]?: Partial<SlotRule>; };
19
+ /**
20
+ * 슬롯 매핑 규칙.
21
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
22
+ */
23
+ export declare const slotRule: {
24
+ readonly solid: {
25
+ readonly light: 600;
26
+ readonly dark: 600;
27
+ };
28
+ readonly "solid-hover": {
29
+ readonly light: 700;
30
+ readonly dark: 700;
31
+ };
32
+ readonly "solid-active": {
33
+ readonly light: 800;
34
+ readonly dark: 800;
35
+ };
36
+ readonly subtle: {
37
+ readonly light: 50;
38
+ readonly dark: 950;
39
+ };
40
+ readonly "subtle-hover": {
41
+ readonly light: 100;
42
+ readonly dark: 900;
43
+ };
44
+ readonly "subtle-active": {
45
+ readonly light: 200;
46
+ readonly dark: 800;
47
+ };
48
+ readonly text: {
49
+ readonly light: 800;
50
+ readonly dark: 200;
51
+ };
52
+ readonly border: {
53
+ readonly light: 600;
54
+ readonly dark: 400;
55
+ };
56
+ readonly "on-solid": {
57
+ readonly light: "white";
58
+ readonly dark: "white";
59
+ };
60
+ };
61
+ /**
62
+ * Tide 계열별 덮어쓸 값.
63
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
64
+ */
65
+ export declare const familyOverrides: {};
66
+ /**
67
+ * 규칙에 덮어쓸 값을 얹은 슬롯별 단계.
68
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
69
+ */
70
+ export declare function resolveSlotSteps(...overrides: (SlotOverrides | undefined)[]): Record<ColorSlot, SlotRule>;
71
+ /**
72
+ * 슬롯 값을 Primitive 이름으로 바꾼다. 예: (`blue`, 600) → `blue-600`, (`blue`, `white`) → `white`
73
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
74
+ */
75
+ export declare function slotPrimitive(family: string, step: SlotStep): string;
76
+ /**
77
+ * Tide 계열 하나의 슬롯별 단계(규칙 + 그 계열의 덮어쓸 값).
78
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
79
+ */
80
+ export declare function familySlotSteps(family: ColorFamily): Record<ColorSlot, SlotRule>;
@@ -0,0 +1,90 @@
1
+ // src/color-slot.ts
2
+ /**
3
+ * 색 계열 변수 묶음의 슬롯. CSS에서는 `--tide-<색 이름>-<슬롯>`과 `--tide-accent-<슬롯>`으로 나간다.
4
+ */
5
+ const colorSlots = [
6
+ "solid",
7
+ "solid-hover",
8
+ "solid-active",
9
+ "subtle",
10
+ "subtle-hover",
11
+ "subtle-active",
12
+ "text",
13
+ "border",
14
+ "on-solid"
15
+ ];
16
+ /**
17
+ * 슬롯 매핑 규칙.
18
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
19
+ */
20
+ const slotRule = {
21
+ solid: {
22
+ light: 600,
23
+ dark: 600
24
+ },
25
+ "solid-hover": {
26
+ light: 700,
27
+ dark: 700
28
+ },
29
+ "solid-active": {
30
+ light: 800,
31
+ dark: 800
32
+ },
33
+ subtle: {
34
+ light: 50,
35
+ dark: 950
36
+ },
37
+ "subtle-hover": {
38
+ light: 100,
39
+ dark: 900
40
+ },
41
+ "subtle-active": {
42
+ light: 200,
43
+ dark: 800
44
+ },
45
+ text: {
46
+ light: 800,
47
+ dark: 200
48
+ },
49
+ border: {
50
+ light: 600,
51
+ dark: 400
52
+ },
53
+ "on-solid": {
54
+ light: "white",
55
+ dark: "white"
56
+ }
57
+ };
58
+ /**
59
+ * Tide 계열별 덮어쓸 값.
60
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
61
+ */
62
+ const familyOverrides = {};
63
+ /**
64
+ * 규칙에 덮어쓸 값을 얹은 슬롯별 단계.
65
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
66
+ */
67
+ function resolveSlotSteps(...overrides) {
68
+ return Object.fromEntries(colorSlots.map((slot) => {
69
+ return [slot, overrides.reduce((current, override) => ({
70
+ ...current,
71
+ ...override?.[slot]
72
+ }), slotRule[slot])];
73
+ }));
74
+ }
75
+ /**
76
+ * 슬롯 값을 Primitive 이름으로 바꾼다. 예: (`blue`, 600) → `blue-600`, (`blue`, `white`) → `white`
77
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
78
+ */
79
+ function slotPrimitive(family, step) {
80
+ return typeof step === "number" ? `${family}-${step}` : step;
81
+ }
82
+ /**
83
+ * Tide 계열 하나의 슬롯별 단계(규칙 + 그 계열의 덮어쓸 값).
84
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
85
+ */
86
+ function familySlotSteps(family) {
87
+ return resolveSlotSteps(familyOverrides[family]);
88
+ }
89
+
90
+ export { colorSlots, familyOverrides, familySlotSteps, resolveSlotSteps, slotPrimitive, slotRule };
package/dist/color.d.ts CHANGED
@@ -80,13 +80,13 @@ export declare const color: {
80
80
  readonly 950: "#002f3d";
81
81
  };
82
82
  };
83
- type Color = typeof color;
83
+ type ColorTable = typeof color;
84
84
  /** 11단계 스케일을 가진 계열 이름. 예: `blue` */
85
- export type ColorFamily = { [Key in keyof Color]: Color[Key] extends string ? never : Key; }[keyof Color];
85
+ export type ColorFamily = { [Key in keyof ColorTable]: ColorTable[Key] extends string ? never : Key; }[keyof ColorTable];
86
86
  /** 스케일 단계. 50~950의 11단계 */
87
- export type ColorStep = keyof Color["blue"];
87
+ export type ColorStep = keyof ColorTable["blue"];
88
88
  /** Primitive 이름. 예: `blue-600`, `white` */
89
- export type PrimitiveColorName = { [Key in keyof Color]: Color[Key] extends string ? Key : never; }[keyof Color] | { [Family in ColorFamily]: `${Family}-${ColorStep}`; }[ColorFamily];
89
+ export type PrimitiveColorName = { [Key in keyof ColorTable]: ColorTable[Key] extends string ? Key : never; }[keyof ColorTable] | { [Family in ColorFamily]: `${Family}-${ColorStep}`; }[ColorFamily];
90
90
  /**
91
91
  * Primitive 이름을 정본 hex로 바꾼다.
92
92
  * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
@@ -1,4 +1,5 @@
1
- import { SemanticColorName } from "./semantic.js";
1
+ import { ColorSlot } from "./color-slot.js";
2
+ import { SemanticColorName, ThemeName } from "./semantic.js";
2
3
  /** 함께 쓰이는 글자(또는 경계)와 배경의 짝. Utility class 이름으로 적는다. */
3
4
  export interface ContrastPair {
4
5
  readonly foreground: SemanticColorName;
@@ -11,15 +12,40 @@ export interface ContrastExclusion {
11
12
  readonly token: SemanticColorName;
12
13
  readonly reason: string;
13
14
  }
15
+ declare const surfaces: readonly ["bg-canvas", "bg-surface", "bg-raised"];
14
16
  export declare const contrastPairs: readonly ContrastPair[];
15
17
  /**
16
18
  * 대비 검사에서 뺀 Semantic Token과 이유.
17
19
  * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
18
20
  */
19
21
  export declare const contrastExclusions: readonly ContrastExclusion[];
22
+ /** 색 이름 하나에서 함께 쓰는 슬롯 짝. 배경은 같은 색의 슬롯이거나 표면 층 Token이다. */
23
+ export interface ColorContrastRule {
24
+ readonly foreground: ColorSlot;
25
+ readonly background: ColorSlot | (typeof surfaces)[number];
26
+ /** 텍스트는 4.5, UI 경계는 3 */
27
+ readonly minRatio: 4.5 | 3;
28
+ }
29
+ /**
30
+ * 색 이름 하나의 대비 쌍 규칙.
31
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
32
+ */
33
+ export declare const colorContrastRules: readonly ColorContrastRule[];
34
+ /** 대비 쌍 규칙 하나를 Theme 하나에서 검사한 결과 */
35
+ export interface ColorContrastResult extends ColorContrastRule {
36
+ readonly theme: ThemeName;
37
+ readonly ratio: number;
38
+ readonly pass: boolean;
39
+ }
40
+ /**
41
+ * 색 이름 하나의 슬롯 hex로 대비 쌍 규칙을 light·dark에서 검사한다. 표면 층은 Tide Token 값을 쓴다.
42
+ * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
43
+ */
44
+ export declare function checkColorContrast(slotHex: Readonly<Record<ThemeName, Readonly<Record<ColorSlot, string>>>>): ColorContrastResult[];
20
45
  /**
21
46
  * 두 색의 WCAG 2.x 대비비(1~21). 순서는 상관없다.
22
47
  * 반투명 색(8자리 hex)은 받지 않는다. 대비 검사에서 overlay와 shadow는 제외한다.
23
48
  * @internal `@tide-tds/foundation/internal` 전용. 앱 개발자용 공개 계약이 아니다(문서 앱·빌드 스크립트·테스트용).
24
49
  */
25
- export declare function contrastRatio(foreground: string, background: string): number;
50
+ export declare function contrastRatio(foreground: string, background: string): number;
51
+ export {};