@lessonkit/themes 0.3.1 → 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/README.md +37 -4
- package/base.css +30 -0
- package/dist/index.cjs +439 -7
- package/dist/index.d.cts +93 -14
- package/dist/index.d.ts +93 -14
- package/dist/index.js +415 -7
- package/package.json +10 -4
- package/theme-catalog.v1.json +187 -0
- package/theme-contract.v1.json +93 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,96 @@
|
|
|
1
|
-
type
|
|
1
|
+
type ThemeColorKey = "background" | "foreground" | "primary" | "muted" | "border" | "panel" | "danger" | "success" | "warning";
|
|
2
|
+
type ThemeSpacingKey = "xs" | "sm" | "md" | "lg" | "xl";
|
|
3
|
+
type ThemeTypographyKey = "fontFamily" | "fontSizeBase" | "lineHeightBase" | "fontWeightNormal" | "fontWeightStrong";
|
|
4
|
+
type ThemeRadiusKey = "sm" | "md" | "lg";
|
|
5
|
+
type ThemeShadowKey = "sm" | "md" | "lg";
|
|
6
|
+
type LessonkitThemeColors = Record<ThemeColorKey, string> & {
|
|
7
|
+
extra?: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
type LessonkitThemeSpacing = Record<ThemeSpacingKey, string>;
|
|
10
|
+
type LessonkitThemeTypography = Record<ThemeTypographyKey, string>;
|
|
11
|
+
type LessonkitThemeRadius = Record<ThemeRadiusKey, string>;
|
|
12
|
+
type LessonkitThemeShadows = Record<ThemeShadowKey, string>;
|
|
13
|
+
/** Full design token schema v1. */
|
|
14
|
+
type LessonkitThemeV1 = {
|
|
2
15
|
name: string;
|
|
3
|
-
colors
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
colors: LessonkitThemeColors;
|
|
17
|
+
spacing: LessonkitThemeSpacing;
|
|
18
|
+
typography: LessonkitThemeTypography;
|
|
19
|
+
radius: LessonkitThemeRadius;
|
|
20
|
+
shadows: LessonkitThemeShadows;
|
|
21
|
+
};
|
|
22
|
+
/** @deprecated Use `LessonkitThemeV1` — alias kept for backward compatibility. */
|
|
23
|
+
type LessonkitTheme = LessonkitThemeV1;
|
|
24
|
+
type PartialLessonkitThemeV1 = {
|
|
25
|
+
name?: string;
|
|
26
|
+
colors?: Partial<LessonkitThemeColors>;
|
|
27
|
+
spacing?: Partial<LessonkitThemeSpacing>;
|
|
28
|
+
typography?: Partial<LessonkitThemeTypography>;
|
|
29
|
+
radius?: Partial<LessonkitThemeRadius>;
|
|
30
|
+
shadows?: Partial<LessonkitThemeShadows>;
|
|
31
|
+
};
|
|
32
|
+
type ThemeValidationIssue = {
|
|
33
|
+
path: string;
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
type ThemeValidationResult = {
|
|
37
|
+
ok: true;
|
|
38
|
+
theme: LessonkitThemeV1;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
issues: ThemeValidationIssue[];
|
|
42
|
+
};
|
|
43
|
+
/** Validate a value as a complete `LessonkitThemeV1`. */
|
|
44
|
+
declare function validateTheme(input: unknown): ThemeValidationResult;
|
|
45
|
+
declare const REQUIRED_COLOR_KEYS: ThemeColorKey[];
|
|
46
|
+
declare const REQUIRED_SPACING_KEYS: ThemeSpacingKey[];
|
|
47
|
+
declare const REQUIRED_TYPOGRAPHY_KEYS: ThemeTypographyKey[];
|
|
48
|
+
declare const REQUIRED_RADIUS_KEYS: ThemeRadiusKey[];
|
|
49
|
+
declare const REQUIRED_SHADOW_KEYS: ThemeShadowKey[];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Deep-merge theme objects left-to-right. Last writer wins for leaf token values.
|
|
53
|
+
*/
|
|
54
|
+
declare function mergeThemes(base: LessonkitThemeV1, ...overrides: (PartialLessonkitThemeV1 | undefined)[]): LessonkitThemeV1;
|
|
55
|
+
|
|
56
|
+
/** Map a token path segment to kebab-case for CSS custom property names. */
|
|
57
|
+
declare function tokenKeyToKebab(key: string): string;
|
|
58
|
+
/** CSS custom property name for a color token (required keys). */
|
|
59
|
+
declare function colorVarName(key: string): string;
|
|
60
|
+
/** CSS custom property name for an extension color. */
|
|
61
|
+
declare function colorExtraVarName(key: string): string;
|
|
62
|
+
declare function spacingVarName(key: string): string;
|
|
63
|
+
declare function typographyVarName(key: string): string;
|
|
64
|
+
declare function radiusVarName(key: string): string;
|
|
65
|
+
declare function shadowVarName(key: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Convert a complete theme to a flat map of `--lk-*` CSS custom properties.
|
|
68
|
+
* Keys are sorted alphabetically for stable snapshots.
|
|
69
|
+
*/
|
|
70
|
+
declare function themeToCssVariables(theme: LessonkitThemeV1): Record<string, string>;
|
|
71
|
+
/** Emit a `:root { ... }` or inline style declaration block. */
|
|
72
|
+
declare function themeToCssDeclarationBlock(theme: LessonkitThemeV1, opts?: {
|
|
73
|
+
selector?: string;
|
|
74
|
+
}): string;
|
|
75
|
+
|
|
76
|
+
declare const defaultTheme: LessonkitThemeV1;
|
|
77
|
+
declare const lightTheme: LessonkitThemeV1;
|
|
78
|
+
declare const darkTheme: LessonkitThemeV1;
|
|
79
|
+
/** Brand deltas merged onto the active mode palette (light/dark). */
|
|
80
|
+
declare const brandThemeOverrides: PartialLessonkitThemeV1;
|
|
81
|
+
/** Full dark reference palette for `getPresetTheme("brand")` and catalog validation. */
|
|
82
|
+
declare const brandTheme: LessonkitThemeV1;
|
|
83
|
+
type ThemePresetName = "default" | "light" | "dark" | "brand";
|
|
84
|
+
declare function getPresetTheme(preset: ThemePresetName): LessonkitThemeV1;
|
|
85
|
+
|
|
86
|
+
type ThemeCatalogEntry = {
|
|
87
|
+
tokenPath: string;
|
|
88
|
+
cssVariable: string;
|
|
89
|
+
type: "color" | "spacing" | "typography" | "radius" | "shadow" | "color-extra";
|
|
90
|
+
required: boolean;
|
|
91
|
+
description: string;
|
|
14
92
|
};
|
|
15
|
-
|
|
93
|
+
/** Enumerable catalog of themeable tokens (v1). */
|
|
94
|
+
declare function buildThemeCatalog(): ThemeCatalogEntry[];
|
|
16
95
|
|
|
17
|
-
export { type LessonkitTheme, defaultTheme };
|
|
96
|
+
export { type LessonkitTheme, type LessonkitThemeColors, type LessonkitThemeRadius, type LessonkitThemeShadows, type LessonkitThemeSpacing, type LessonkitThemeTypography, type LessonkitThemeV1, type PartialLessonkitThemeV1, REQUIRED_COLOR_KEYS, REQUIRED_RADIUS_KEYS, REQUIRED_SHADOW_KEYS, REQUIRED_SPACING_KEYS, REQUIRED_TYPOGRAPHY_KEYS, type ThemeCatalogEntry, type ThemeColorKey, type ThemePresetName, type ThemeRadiusKey, type ThemeShadowKey, type ThemeSpacingKey, type ThemeTypographyKey, type ThemeValidationIssue, type ThemeValidationResult, brandTheme, brandThemeOverrides, buildThemeCatalog, colorExtraVarName, colorVarName, darkTheme, defaultTheme, getPresetTheme, lightTheme, mergeThemes, radiusVarName, shadowVarName, spacingVarName, themeToCssDeclarationBlock, themeToCssVariables, tokenKeyToKebab, typographyVarName, validateTheme };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,426 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/schema.ts
|
|
2
|
+
var COLOR_KEYS = [
|
|
3
|
+
"background",
|
|
4
|
+
"foreground",
|
|
5
|
+
"primary",
|
|
6
|
+
"muted",
|
|
7
|
+
"border",
|
|
8
|
+
"panel",
|
|
9
|
+
"danger",
|
|
10
|
+
"success",
|
|
11
|
+
"warning"
|
|
12
|
+
];
|
|
13
|
+
var SPACING_KEYS = ["xs", "sm", "md", "lg", "xl"];
|
|
14
|
+
var TYPOGRAPHY_KEYS = [
|
|
15
|
+
"fontFamily",
|
|
16
|
+
"fontSizeBase",
|
|
17
|
+
"lineHeightBase",
|
|
18
|
+
"fontWeightNormal",
|
|
19
|
+
"fontWeightStrong"
|
|
20
|
+
];
|
|
21
|
+
var RADIUS_KEYS = ["sm", "md", "lg"];
|
|
22
|
+
var SHADOW_KEYS = ["sm", "md", "lg"];
|
|
23
|
+
function isNonEmptyString(v) {
|
|
24
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
25
|
+
}
|
|
26
|
+
function validateRequiredGroup(group, value, keys, issues) {
|
|
27
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
28
|
+
issues.push({ path: group, message: "must be an object" });
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const obj = value;
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
const v = obj[key];
|
|
35
|
+
if (!isNonEmptyString(v)) {
|
|
36
|
+
issues.push({ path: `${group}.${key}`, message: "required non-empty string" });
|
|
37
|
+
} else {
|
|
38
|
+
out[key] = v;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function validateColorsExtra(value, issues) {
|
|
44
|
+
if (value === void 0) return void 0;
|
|
45
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
46
|
+
issues.push({ path: "colors.extra", message: "must be an object when provided" });
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
const extra = {};
|
|
50
|
+
for (const [k, v] of Object.entries(value)) {
|
|
51
|
+
if (!isNonEmptyString(v)) {
|
|
52
|
+
issues.push({ path: `colors.extra.${k}`, message: "must be a non-empty string" });
|
|
53
|
+
} else {
|
|
54
|
+
extra[k] = v;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return extra;
|
|
58
|
+
}
|
|
59
|
+
function validateTheme(input) {
|
|
60
|
+
const issues = [];
|
|
61
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
62
|
+
return { ok: false, issues: [{ path: "", message: "theme must be an object" }] };
|
|
63
|
+
}
|
|
64
|
+
const raw = input;
|
|
65
|
+
if (!isNonEmptyString(raw.name)) {
|
|
66
|
+
issues.push({ path: "name", message: "required non-empty string" });
|
|
67
|
+
}
|
|
68
|
+
const colorsBase = validateRequiredGroup("colors", raw.colors, COLOR_KEYS, issues);
|
|
69
|
+
let colorsExtra;
|
|
70
|
+
if (raw.colors !== null && typeof raw.colors === "object" && !Array.isArray(raw.colors)) {
|
|
71
|
+
colorsExtra = validateColorsExtra(raw.colors.extra, issues);
|
|
72
|
+
}
|
|
73
|
+
const spacing = validateRequiredGroup("spacing", raw.spacing, SPACING_KEYS, issues);
|
|
74
|
+
const typography = validateRequiredGroup("typography", raw.typography, TYPOGRAPHY_KEYS, issues);
|
|
75
|
+
const radius = validateRequiredGroup("radius", raw.radius, RADIUS_KEYS, issues);
|
|
76
|
+
const shadows = validateRequiredGroup("shadows", raw.shadows, SHADOW_KEYS, issues);
|
|
77
|
+
if (issues.length > 0) {
|
|
78
|
+
return { ok: false, issues };
|
|
79
|
+
}
|
|
80
|
+
const colors = { ...colorsBase };
|
|
81
|
+
if (colorsExtra && Object.keys(colorsExtra).length > 0) {
|
|
82
|
+
colors.extra = colorsExtra;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
theme: {
|
|
87
|
+
name: raw.name,
|
|
88
|
+
colors,
|
|
89
|
+
spacing,
|
|
90
|
+
typography,
|
|
91
|
+
radius,
|
|
92
|
+
shadows
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
var REQUIRED_COLOR_KEYS = COLOR_KEYS;
|
|
97
|
+
var REQUIRED_SPACING_KEYS = SPACING_KEYS;
|
|
98
|
+
var REQUIRED_TYPOGRAPHY_KEYS = TYPOGRAPHY_KEYS;
|
|
99
|
+
var REQUIRED_RADIUS_KEYS = RADIUS_KEYS;
|
|
100
|
+
var REQUIRED_SHADOW_KEYS = SHADOW_KEYS;
|
|
101
|
+
|
|
102
|
+
// src/merge.ts
|
|
103
|
+
function mergeExtra(base, override) {
|
|
104
|
+
if (!base && !override) return void 0;
|
|
105
|
+
return { ...base, ...override };
|
|
106
|
+
}
|
|
107
|
+
function mergeColors(base, override) {
|
|
108
|
+
if (!override) return base;
|
|
109
|
+
const { extra: overrideExtra, ...rest } = override;
|
|
110
|
+
const merged = { ...base, ...rest };
|
|
111
|
+
const extra = mergeExtra(base.extra, overrideExtra);
|
|
112
|
+
if (extra) merged.extra = extra;
|
|
113
|
+
else delete merged.extra;
|
|
114
|
+
return merged;
|
|
115
|
+
}
|
|
116
|
+
function mergeGroup(base, override) {
|
|
117
|
+
if (!override) return base;
|
|
118
|
+
return { ...base, ...override };
|
|
119
|
+
}
|
|
120
|
+
function mergeThemes(base, ...overrides) {
|
|
121
|
+
let result = { ...base };
|
|
122
|
+
for (const o of overrides) {
|
|
123
|
+
if (!o) continue;
|
|
124
|
+
result = {
|
|
125
|
+
name: o.name ?? result.name,
|
|
126
|
+
colors: mergeColors(result.colors, o.colors),
|
|
127
|
+
spacing: mergeGroup(result.spacing, o.spacing),
|
|
128
|
+
typography: mergeGroup(result.typography, o.typography),
|
|
129
|
+
radius: mergeGroup(result.radius, o.radius),
|
|
130
|
+
shadows: mergeGroup(result.shadows, o.shadows)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/cssVariables.ts
|
|
137
|
+
function tokenKeyToKebab(key) {
|
|
138
|
+
return key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
139
|
+
}
|
|
140
|
+
function colorVarName(key) {
|
|
141
|
+
return `--lk-color-${tokenKeyToKebab(key)}`;
|
|
142
|
+
}
|
|
143
|
+
function colorExtraVarName(key) {
|
|
144
|
+
return `--lk-color-extra-${tokenKeyToKebab(key)}`;
|
|
145
|
+
}
|
|
146
|
+
function spacingVarName(key) {
|
|
147
|
+
return `--lk-space-${key}`;
|
|
148
|
+
}
|
|
149
|
+
function typographyVarName(key) {
|
|
150
|
+
return `--lk-${tokenKeyToKebab(key)}`;
|
|
151
|
+
}
|
|
152
|
+
function radiusVarName(key) {
|
|
153
|
+
return `--lk-radius-${key}`;
|
|
154
|
+
}
|
|
155
|
+
function shadowVarName(key) {
|
|
156
|
+
return `--lk-shadow-${key}`;
|
|
157
|
+
}
|
|
158
|
+
function themeToCssVariables(theme) {
|
|
159
|
+
const vars = {};
|
|
160
|
+
for (const [key, value] of Object.entries(theme.colors)) {
|
|
161
|
+
if (key === "extra" && value && typeof value === "object") {
|
|
162
|
+
for (const [ek, ev] of Object.entries(value)) {
|
|
163
|
+
vars[colorExtraVarName(ek)] = ev;
|
|
164
|
+
}
|
|
165
|
+
} else if (key !== "extra") {
|
|
166
|
+
vars[colorVarName(key)] = value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const [key, value] of Object.entries(theme.spacing)) {
|
|
170
|
+
vars[spacingVarName(key)] = value;
|
|
171
|
+
}
|
|
172
|
+
for (const [key, value] of Object.entries(theme.typography)) {
|
|
173
|
+
vars[typographyVarName(key)] = value;
|
|
174
|
+
}
|
|
175
|
+
for (const [key, value] of Object.entries(theme.radius)) {
|
|
176
|
+
vars[radiusVarName(key)] = value;
|
|
177
|
+
}
|
|
178
|
+
for (const [key, value] of Object.entries(theme.shadows)) {
|
|
179
|
+
vars[shadowVarName(key)] = value;
|
|
180
|
+
}
|
|
181
|
+
const sorted = {};
|
|
182
|
+
for (const key of Object.keys(vars).sort()) {
|
|
183
|
+
sorted[key] = vars[key];
|
|
184
|
+
}
|
|
185
|
+
return sorted;
|
|
186
|
+
}
|
|
187
|
+
function themeToCssDeclarationBlock(theme, opts) {
|
|
188
|
+
const selector = opts?.selector ?? ":root";
|
|
189
|
+
const vars = themeToCssVariables(theme);
|
|
190
|
+
const body = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
191
|
+
return `${selector} {
|
|
192
|
+
${body}
|
|
193
|
+
}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/presets.ts
|
|
197
|
+
var sharedTypography = {
|
|
198
|
+
fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
199
|
+
fontSizeBase: "16px",
|
|
200
|
+
lineHeightBase: "1.55",
|
|
201
|
+
fontWeightNormal: "400",
|
|
202
|
+
fontWeightStrong: "700"
|
|
203
|
+
};
|
|
204
|
+
var sharedSpacing = {
|
|
205
|
+
xs: "4px",
|
|
206
|
+
sm: "8px",
|
|
207
|
+
md: "12px",
|
|
208
|
+
lg: "18px",
|
|
209
|
+
xl: "24px"
|
|
210
|
+
};
|
|
211
|
+
var sharedRadius = {
|
|
212
|
+
sm: "6px",
|
|
213
|
+
md: "10px",
|
|
214
|
+
lg: "16px"
|
|
215
|
+
};
|
|
2
216
|
var defaultTheme = {
|
|
3
217
|
name: "default",
|
|
4
218
|
colors: {
|
|
5
219
|
background: "#ffffff",
|
|
6
220
|
foreground: "#111827",
|
|
7
221
|
primary: "#2563eb",
|
|
8
|
-
muted: "#6b7280"
|
|
222
|
+
muted: "#6b7280",
|
|
223
|
+
border: "rgba(17, 24, 39, 0.12)",
|
|
224
|
+
panel: "rgba(17, 24, 39, 0.04)",
|
|
225
|
+
danger: "#dc2626",
|
|
226
|
+
success: "#16a34a",
|
|
227
|
+
warning: "#d97706"
|
|
228
|
+
},
|
|
229
|
+
spacing: { ...sharedSpacing },
|
|
230
|
+
typography: { ...sharedTypography },
|
|
231
|
+
radius: { ...sharedRadius },
|
|
232
|
+
shadows: {
|
|
233
|
+
sm: "0 1px 2px rgba(0, 0, 0, 0.06)",
|
|
234
|
+
md: "0 8px 24px rgba(17, 24, 39, 0.12)",
|
|
235
|
+
lg: "0 18px 50px rgba(17, 24, 39, 0.14)"
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
var lightTheme = {
|
|
239
|
+
name: "light",
|
|
240
|
+
colors: {
|
|
241
|
+
background: "#f7f8ff",
|
|
242
|
+
foreground: "rgba(17, 24, 39, 0.92)",
|
|
243
|
+
primary: "#2563eb",
|
|
244
|
+
muted: "rgba(17, 24, 39, 0.64)",
|
|
245
|
+
border: "rgba(17, 24, 39, 0.12)",
|
|
246
|
+
panel: "rgba(17, 24, 39, 0.04)",
|
|
247
|
+
danger: "#e11d48",
|
|
248
|
+
success: "#059669",
|
|
249
|
+
warning: "#d97706",
|
|
250
|
+
extra: {
|
|
251
|
+
accent: "#22d3ee",
|
|
252
|
+
"panel-strong": "rgba(17, 24, 39, 0.06)"
|
|
253
|
+
}
|
|
9
254
|
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
255
|
+
spacing: { ...sharedSpacing },
|
|
256
|
+
typography: { ...sharedTypography },
|
|
257
|
+
radius: { ...sharedRadius },
|
|
258
|
+
shadows: {
|
|
259
|
+
sm: "0 1px 2px rgba(17, 24, 39, 0.08)",
|
|
260
|
+
md: "0 8px 24px rgba(17, 24, 39, 0.1)",
|
|
261
|
+
lg: "0 18px 50px rgba(17, 24, 39, 0.14)"
|
|
14
262
|
}
|
|
15
263
|
};
|
|
264
|
+
var darkTheme = {
|
|
265
|
+
name: "dark",
|
|
266
|
+
colors: {
|
|
267
|
+
background: "#0b1020",
|
|
268
|
+
foreground: "rgba(255, 255, 255, 0.92)",
|
|
269
|
+
primary: "#7c3aed",
|
|
270
|
+
muted: "rgba(255, 255, 255, 0.68)",
|
|
271
|
+
border: "rgba(255, 255, 255, 0.14)",
|
|
272
|
+
panel: "rgba(255, 255, 255, 0.08)",
|
|
273
|
+
danger: "#fb7185",
|
|
274
|
+
success: "#34d399",
|
|
275
|
+
warning: "#fbbf24",
|
|
276
|
+
extra: {
|
|
277
|
+
accent: "#22d3ee",
|
|
278
|
+
"panel-strong": "rgba(255, 255, 255, 0.12)"
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
spacing: { ...sharedSpacing },
|
|
282
|
+
typography: { ...sharedTypography },
|
|
283
|
+
radius: { ...sharedRadius },
|
|
284
|
+
shadows: {
|
|
285
|
+
sm: "0 1px 2px rgba(0, 0, 0, 0.2)",
|
|
286
|
+
md: "0 12px 32px rgba(0, 0, 0, 0.28)",
|
|
287
|
+
lg: "0 18px 50px rgba(0, 0, 0, 0.35)"
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var brandThemeOverrides = {
|
|
291
|
+
name: "brand",
|
|
292
|
+
colors: {
|
|
293
|
+
primary: "#7c3aed",
|
|
294
|
+
extra: {
|
|
295
|
+
accent: "#22d3ee"
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var brandTheme = mergeThemes(darkTheme, brandThemeOverrides);
|
|
300
|
+
var PRESETS = {
|
|
301
|
+
default: defaultTheme,
|
|
302
|
+
light: lightTheme,
|
|
303
|
+
dark: darkTheme,
|
|
304
|
+
brand: brandTheme
|
|
305
|
+
};
|
|
306
|
+
function getPresetTheme(preset) {
|
|
307
|
+
return PRESETS[preset];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/catalog.ts
|
|
311
|
+
var COLOR_DESCRIPTIONS = {
|
|
312
|
+
background: "Page and shell background",
|
|
313
|
+
foreground: "Primary text color",
|
|
314
|
+
primary: "Brand and interactive accent",
|
|
315
|
+
muted: "Secondary text and hints",
|
|
316
|
+
border: "Borders and dividers",
|
|
317
|
+
panel: "Card and panel surfaces",
|
|
318
|
+
danger: "Errors and high-risk states",
|
|
319
|
+
success: "Success and positive feedback",
|
|
320
|
+
warning: "Warnings and caution states"
|
|
321
|
+
};
|
|
322
|
+
var SPACING_DESCRIPTIONS = {
|
|
323
|
+
xs: "Extra-small spacing",
|
|
324
|
+
sm: "Small spacing",
|
|
325
|
+
md: "Medium spacing",
|
|
326
|
+
lg: "Large spacing",
|
|
327
|
+
xl: "Extra-large spacing"
|
|
328
|
+
};
|
|
329
|
+
var TYPOGRAPHY_DESCRIPTIONS = {
|
|
330
|
+
fontFamily: "Base font stack",
|
|
331
|
+
fontSizeBase: "Base font size",
|
|
332
|
+
lineHeightBase: "Base line height",
|
|
333
|
+
fontWeightNormal: "Normal text weight",
|
|
334
|
+
fontWeightStrong: "Headings and emphasis weight"
|
|
335
|
+
};
|
|
336
|
+
var RADIUS_DESCRIPTIONS = {
|
|
337
|
+
sm: "Small corner radius",
|
|
338
|
+
md: "Medium corner radius",
|
|
339
|
+
lg: "Large corner radius"
|
|
340
|
+
};
|
|
341
|
+
var SHADOW_DESCRIPTIONS = {
|
|
342
|
+
sm: "Small elevation shadow",
|
|
343
|
+
md: "Medium elevation shadow",
|
|
344
|
+
lg: "Large elevation shadow"
|
|
345
|
+
};
|
|
346
|
+
function buildThemeCatalog() {
|
|
347
|
+
const entries = [];
|
|
348
|
+
for (const key of REQUIRED_COLOR_KEYS) {
|
|
349
|
+
entries.push({
|
|
350
|
+
tokenPath: `colors.${key}`,
|
|
351
|
+
cssVariable: colorVarName(key),
|
|
352
|
+
type: "color",
|
|
353
|
+
required: true,
|
|
354
|
+
description: COLOR_DESCRIPTIONS[key] ?? `Color token: ${key}`
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
entries.push({
|
|
358
|
+
tokenPath: "colors.extra.*",
|
|
359
|
+
cssVariable: "--lk-color-extra-{key}",
|
|
360
|
+
type: "color-extra",
|
|
361
|
+
required: false,
|
|
362
|
+
description: "Optional extension colors (non-stable until 1.0)"
|
|
363
|
+
});
|
|
364
|
+
for (const key of REQUIRED_SPACING_KEYS) {
|
|
365
|
+
entries.push({
|
|
366
|
+
tokenPath: `spacing.${key}`,
|
|
367
|
+
cssVariable: spacingVarName(key),
|
|
368
|
+
type: "spacing",
|
|
369
|
+
required: true,
|
|
370
|
+
description: SPACING_DESCRIPTIONS[key] ?? `Spacing token: ${key}`
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
for (const key of REQUIRED_TYPOGRAPHY_KEYS) {
|
|
374
|
+
entries.push({
|
|
375
|
+
tokenPath: `typography.${key}`,
|
|
376
|
+
cssVariable: typographyVarName(key),
|
|
377
|
+
type: "typography",
|
|
378
|
+
required: true,
|
|
379
|
+
description: TYPOGRAPHY_DESCRIPTIONS[key] ?? `Typography token: ${key}`
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
for (const key of REQUIRED_RADIUS_KEYS) {
|
|
383
|
+
entries.push({
|
|
384
|
+
tokenPath: `radius.${key}`,
|
|
385
|
+
cssVariable: radiusVarName(key),
|
|
386
|
+
type: "radius",
|
|
387
|
+
required: true,
|
|
388
|
+
description: RADIUS_DESCRIPTIONS[key] ?? `Radius token: ${key}`
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
for (const key of REQUIRED_SHADOW_KEYS) {
|
|
392
|
+
entries.push({
|
|
393
|
+
tokenPath: `shadows.${key}`,
|
|
394
|
+
cssVariable: shadowVarName(key),
|
|
395
|
+
type: "shadow",
|
|
396
|
+
required: true,
|
|
397
|
+
description: SHADOW_DESCRIPTIONS[key] ?? `Shadow token: ${key}`
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return entries;
|
|
401
|
+
}
|
|
16
402
|
export {
|
|
17
|
-
|
|
403
|
+
REQUIRED_COLOR_KEYS,
|
|
404
|
+
REQUIRED_RADIUS_KEYS,
|
|
405
|
+
REQUIRED_SHADOW_KEYS,
|
|
406
|
+
REQUIRED_SPACING_KEYS,
|
|
407
|
+
REQUIRED_TYPOGRAPHY_KEYS,
|
|
408
|
+
brandTheme,
|
|
409
|
+
brandThemeOverrides,
|
|
410
|
+
buildThemeCatalog,
|
|
411
|
+
colorExtraVarName,
|
|
412
|
+
colorVarName,
|
|
413
|
+
darkTheme,
|
|
414
|
+
defaultTheme,
|
|
415
|
+
getPresetTheme,
|
|
416
|
+
lightTheme,
|
|
417
|
+
mergeThemes,
|
|
418
|
+
radiusVarName,
|
|
419
|
+
shadowVarName,
|
|
420
|
+
spacingVarName,
|
|
421
|
+
themeToCssDeclarationBlock,
|
|
422
|
+
themeToCssVariables,
|
|
423
|
+
tokenKeyToKebab,
|
|
424
|
+
typographyVarName,
|
|
425
|
+
validateTheme
|
|
18
426
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/themes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Theme primitives and tokens for LessonKit.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,17 +27,23 @@
|
|
|
27
27
|
"types": "./dist/index.d.ts",
|
|
28
28
|
"import": "./dist/index.js",
|
|
29
29
|
"require": "./dist/index.cjs"
|
|
30
|
-
}
|
|
30
|
+
},
|
|
31
|
+
"./theme-contract.v1.json": "./theme-contract.v1.json",
|
|
32
|
+
"./theme-catalog.v1.json": "./theme-catalog.v1.json",
|
|
33
|
+
"./base.css": "./base.css"
|
|
31
34
|
},
|
|
32
35
|
"files": [
|
|
33
|
-
"dist"
|
|
36
|
+
"dist",
|
|
37
|
+
"theme-contract.v1.json",
|
|
38
|
+
"theme-catalog.v1.json",
|
|
39
|
+
"base.css"
|
|
34
40
|
],
|
|
35
41
|
"scripts": {
|
|
36
42
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
37
43
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
38
44
|
"prepublishOnly": "npm run build",
|
|
39
45
|
"typecheck": "tsc -p tsconfig.json",
|
|
40
|
-
"test": "vitest run
|
|
46
|
+
"test": "vitest run",
|
|
41
47
|
"test:coverage": "vitest run --coverage --passWithNoTests=false",
|
|
42
48
|
"lint": "echo \"(no lint configured yet)\""
|
|
43
49
|
},
|