@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 CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm](https://img.shields.io/npm/v/@lessonkit/themes.svg)](https://www.npmjs.com/package/@lessonkit/themes)
5
5
  [![License](https://img.shields.io/github/license/eddiethedean/lessonkit)](../../LICENSE)
6
6
 
7
- Theme primitives for LessonKit.
7
+ Design tokens and theme utilities for LessonKit.
8
8
 
9
9
  ## Install
10
10
 
@@ -12,8 +12,41 @@ Theme primitives for LessonKit.
12
12
  npm install @lessonkit/themes
13
13
  ```
14
14
 
15
- ## Included (0.3.0)
15
+ ## API (0.4.0)
16
16
 
17
- - `defaultTheme`
18
- - `LessonkitTheme` type
17
+ ### Types
19
18
 
19
+ - `LessonkitThemeV1` — full token schema v1
20
+ - `LessonkitTheme` — alias for `LessonkitThemeV1`
21
+ - `PartialLessonkitThemeV1` — partial overrides for merge / `ThemeProvider`
22
+
23
+ ### Presets
24
+
25
+ - `defaultTheme`, `lightTheme`, `darkTheme`, `brandTheme`, `brandThemeOverrides`
26
+ - `getPresetTheme(preset)` — `default` | `light` | `dark` | `brand` (full themes for catalog/validation)
27
+
28
+ In `ThemeProvider`, `preset="default"` uses the mode palette only; `preset="brand"` merges `brandThemeOverrides` onto the active mode (see [`docs/THEMING.md`](../../docs/THEMING.md)).
29
+
30
+ ### Utilities
31
+
32
+ - `validateTheme(input)` — validate unknown input
33
+ - `mergeThemes(base, ...overrides)` — deep merge, last writer wins
34
+ - `themeToCssVariables(theme)` — flat `--lk-*` map (sorted keys)
35
+ - `themeToCssDeclarationBlock(theme)` — `:root { ... }` text
36
+ - `buildThemeCatalog()` — enumerable token metadata
37
+
38
+ ### Machine-readable exports
39
+
40
+ ```json
41
+ {
42
+ "imports": {
43
+ "@lessonkit/themes/theme-contract.v1.json": "./theme-contract.v1.json",
44
+ "@lessonkit/themes/theme-catalog.v1.json": "./theme-catalog.v1.json",
45
+ "@lessonkit/themes/base.css": "./base.css"
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Docs
51
+
52
+ See [`docs/THEMING.md`](../../docs/THEMING.md) for the CSS variable contract and override rules.
package/base.css ADDED
@@ -0,0 +1,30 @@
1
+ /*
2
+ Optional LessonKit base styles — uses only --lk-* CSS variables.
3
+ Import: @import "@lessonkit/themes/base.css";
4
+ */
5
+
6
+ .lk-panel {
7
+ border: 1px solid var(--lk-color-border);
8
+ border-radius: var(--lk-radius-lg);
9
+ padding: var(--lk-space-lg);
10
+ background: var(--lk-color-panel);
11
+ box-shadow: var(--lk-shadow-lg);
12
+ }
13
+
14
+ .lk-button {
15
+ appearance: none;
16
+ border: 1px solid var(--lk-color-border);
17
+ background: var(--lk-color-panel);
18
+ color: var(--lk-color-foreground);
19
+ border-radius: var(--lk-radius-md);
20
+ padding: var(--lk-space-sm) var(--lk-space-md);
21
+ font-family: var(--lk-font-family);
22
+ font-size: var(--lk-font-size-base);
23
+ font-weight: var(--lk-font-weight-strong);
24
+ cursor: pointer;
25
+ }
26
+
27
+ .lk-button:disabled {
28
+ opacity: 0.55;
29
+ cursor: not-allowed;
30
+ }
package/dist/index.cjs CHANGED
@@ -20,24 +20,456 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- defaultTheme: () => defaultTheme
23
+ REQUIRED_COLOR_KEYS: () => REQUIRED_COLOR_KEYS,
24
+ REQUIRED_RADIUS_KEYS: () => REQUIRED_RADIUS_KEYS,
25
+ REQUIRED_SHADOW_KEYS: () => REQUIRED_SHADOW_KEYS,
26
+ REQUIRED_SPACING_KEYS: () => REQUIRED_SPACING_KEYS,
27
+ REQUIRED_TYPOGRAPHY_KEYS: () => REQUIRED_TYPOGRAPHY_KEYS,
28
+ brandTheme: () => brandTheme,
29
+ brandThemeOverrides: () => brandThemeOverrides,
30
+ buildThemeCatalog: () => buildThemeCatalog,
31
+ colorExtraVarName: () => colorExtraVarName,
32
+ colorVarName: () => colorVarName,
33
+ darkTheme: () => darkTheme,
34
+ defaultTheme: () => defaultTheme,
35
+ getPresetTheme: () => getPresetTheme,
36
+ lightTheme: () => lightTheme,
37
+ mergeThemes: () => mergeThemes,
38
+ radiusVarName: () => radiusVarName,
39
+ shadowVarName: () => shadowVarName,
40
+ spacingVarName: () => spacingVarName,
41
+ themeToCssDeclarationBlock: () => themeToCssDeclarationBlock,
42
+ themeToCssVariables: () => themeToCssVariables,
43
+ tokenKeyToKebab: () => tokenKeyToKebab,
44
+ typographyVarName: () => typographyVarName,
45
+ validateTheme: () => validateTheme
24
46
  });
25
47
  module.exports = __toCommonJS(index_exports);
48
+
49
+ // src/schema.ts
50
+ var COLOR_KEYS = [
51
+ "background",
52
+ "foreground",
53
+ "primary",
54
+ "muted",
55
+ "border",
56
+ "panel",
57
+ "danger",
58
+ "success",
59
+ "warning"
60
+ ];
61
+ var SPACING_KEYS = ["xs", "sm", "md", "lg", "xl"];
62
+ var TYPOGRAPHY_KEYS = [
63
+ "fontFamily",
64
+ "fontSizeBase",
65
+ "lineHeightBase",
66
+ "fontWeightNormal",
67
+ "fontWeightStrong"
68
+ ];
69
+ var RADIUS_KEYS = ["sm", "md", "lg"];
70
+ var SHADOW_KEYS = ["sm", "md", "lg"];
71
+ function isNonEmptyString(v) {
72
+ return typeof v === "string" && v.trim().length > 0;
73
+ }
74
+ function validateRequiredGroup(group, value, keys, issues) {
75
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
76
+ issues.push({ path: group, message: "must be an object" });
77
+ return null;
78
+ }
79
+ const obj = value;
80
+ const out = {};
81
+ for (const key of keys) {
82
+ const v = obj[key];
83
+ if (!isNonEmptyString(v)) {
84
+ issues.push({ path: `${group}.${key}`, message: "required non-empty string" });
85
+ } else {
86
+ out[key] = v;
87
+ }
88
+ }
89
+ return out;
90
+ }
91
+ function validateColorsExtra(value, issues) {
92
+ if (value === void 0) return void 0;
93
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
94
+ issues.push({ path: "colors.extra", message: "must be an object when provided" });
95
+ return void 0;
96
+ }
97
+ const extra = {};
98
+ for (const [k, v] of Object.entries(value)) {
99
+ if (!isNonEmptyString(v)) {
100
+ issues.push({ path: `colors.extra.${k}`, message: "must be a non-empty string" });
101
+ } else {
102
+ extra[k] = v;
103
+ }
104
+ }
105
+ return extra;
106
+ }
107
+ function validateTheme(input) {
108
+ const issues = [];
109
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
110
+ return { ok: false, issues: [{ path: "", message: "theme must be an object" }] };
111
+ }
112
+ const raw = input;
113
+ if (!isNonEmptyString(raw.name)) {
114
+ issues.push({ path: "name", message: "required non-empty string" });
115
+ }
116
+ const colorsBase = validateRequiredGroup("colors", raw.colors, COLOR_KEYS, issues);
117
+ let colorsExtra;
118
+ if (raw.colors !== null && typeof raw.colors === "object" && !Array.isArray(raw.colors)) {
119
+ colorsExtra = validateColorsExtra(raw.colors.extra, issues);
120
+ }
121
+ const spacing = validateRequiredGroup("spacing", raw.spacing, SPACING_KEYS, issues);
122
+ const typography = validateRequiredGroup("typography", raw.typography, TYPOGRAPHY_KEYS, issues);
123
+ const radius = validateRequiredGroup("radius", raw.radius, RADIUS_KEYS, issues);
124
+ const shadows = validateRequiredGroup("shadows", raw.shadows, SHADOW_KEYS, issues);
125
+ if (issues.length > 0) {
126
+ return { ok: false, issues };
127
+ }
128
+ const colors = { ...colorsBase };
129
+ if (colorsExtra && Object.keys(colorsExtra).length > 0) {
130
+ colors.extra = colorsExtra;
131
+ }
132
+ return {
133
+ ok: true,
134
+ theme: {
135
+ name: raw.name,
136
+ colors,
137
+ spacing,
138
+ typography,
139
+ radius,
140
+ shadows
141
+ }
142
+ };
143
+ }
144
+ var REQUIRED_COLOR_KEYS = COLOR_KEYS;
145
+ var REQUIRED_SPACING_KEYS = SPACING_KEYS;
146
+ var REQUIRED_TYPOGRAPHY_KEYS = TYPOGRAPHY_KEYS;
147
+ var REQUIRED_RADIUS_KEYS = RADIUS_KEYS;
148
+ var REQUIRED_SHADOW_KEYS = SHADOW_KEYS;
149
+
150
+ // src/merge.ts
151
+ function mergeExtra(base, override) {
152
+ if (!base && !override) return void 0;
153
+ return { ...base, ...override };
154
+ }
155
+ function mergeColors(base, override) {
156
+ if (!override) return base;
157
+ const { extra: overrideExtra, ...rest } = override;
158
+ const merged = { ...base, ...rest };
159
+ const extra = mergeExtra(base.extra, overrideExtra);
160
+ if (extra) merged.extra = extra;
161
+ else delete merged.extra;
162
+ return merged;
163
+ }
164
+ function mergeGroup(base, override) {
165
+ if (!override) return base;
166
+ return { ...base, ...override };
167
+ }
168
+ function mergeThemes(base, ...overrides) {
169
+ let result = { ...base };
170
+ for (const o of overrides) {
171
+ if (!o) continue;
172
+ result = {
173
+ name: o.name ?? result.name,
174
+ colors: mergeColors(result.colors, o.colors),
175
+ spacing: mergeGroup(result.spacing, o.spacing),
176
+ typography: mergeGroup(result.typography, o.typography),
177
+ radius: mergeGroup(result.radius, o.radius),
178
+ shadows: mergeGroup(result.shadows, o.shadows)
179
+ };
180
+ }
181
+ return result;
182
+ }
183
+
184
+ // src/cssVariables.ts
185
+ function tokenKeyToKebab(key) {
186
+ return key.replace(/([A-Z])/g, "-$1").toLowerCase();
187
+ }
188
+ function colorVarName(key) {
189
+ return `--lk-color-${tokenKeyToKebab(key)}`;
190
+ }
191
+ function colorExtraVarName(key) {
192
+ return `--lk-color-extra-${tokenKeyToKebab(key)}`;
193
+ }
194
+ function spacingVarName(key) {
195
+ return `--lk-space-${key}`;
196
+ }
197
+ function typographyVarName(key) {
198
+ return `--lk-${tokenKeyToKebab(key)}`;
199
+ }
200
+ function radiusVarName(key) {
201
+ return `--lk-radius-${key}`;
202
+ }
203
+ function shadowVarName(key) {
204
+ return `--lk-shadow-${key}`;
205
+ }
206
+ function themeToCssVariables(theme) {
207
+ const vars = {};
208
+ for (const [key, value] of Object.entries(theme.colors)) {
209
+ if (key === "extra" && value && typeof value === "object") {
210
+ for (const [ek, ev] of Object.entries(value)) {
211
+ vars[colorExtraVarName(ek)] = ev;
212
+ }
213
+ } else if (key !== "extra") {
214
+ vars[colorVarName(key)] = value;
215
+ }
216
+ }
217
+ for (const [key, value] of Object.entries(theme.spacing)) {
218
+ vars[spacingVarName(key)] = value;
219
+ }
220
+ for (const [key, value] of Object.entries(theme.typography)) {
221
+ vars[typographyVarName(key)] = value;
222
+ }
223
+ for (const [key, value] of Object.entries(theme.radius)) {
224
+ vars[radiusVarName(key)] = value;
225
+ }
226
+ for (const [key, value] of Object.entries(theme.shadows)) {
227
+ vars[shadowVarName(key)] = value;
228
+ }
229
+ const sorted = {};
230
+ for (const key of Object.keys(vars).sort()) {
231
+ sorted[key] = vars[key];
232
+ }
233
+ return sorted;
234
+ }
235
+ function themeToCssDeclarationBlock(theme, opts) {
236
+ const selector = opts?.selector ?? ":root";
237
+ const vars = themeToCssVariables(theme);
238
+ const body = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`).join("\n");
239
+ return `${selector} {
240
+ ${body}
241
+ }`;
242
+ }
243
+
244
+ // src/presets.ts
245
+ var sharedTypography = {
246
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
247
+ fontSizeBase: "16px",
248
+ lineHeightBase: "1.55",
249
+ fontWeightNormal: "400",
250
+ fontWeightStrong: "700"
251
+ };
252
+ var sharedSpacing = {
253
+ xs: "4px",
254
+ sm: "8px",
255
+ md: "12px",
256
+ lg: "18px",
257
+ xl: "24px"
258
+ };
259
+ var sharedRadius = {
260
+ sm: "6px",
261
+ md: "10px",
262
+ lg: "16px"
263
+ };
26
264
  var defaultTheme = {
27
265
  name: "default",
28
266
  colors: {
29
267
  background: "#ffffff",
30
268
  foreground: "#111827",
31
269
  primary: "#2563eb",
32
- muted: "#6b7280"
270
+ muted: "#6b7280",
271
+ border: "rgba(17, 24, 39, 0.12)",
272
+ panel: "rgba(17, 24, 39, 0.04)",
273
+ danger: "#dc2626",
274
+ success: "#16a34a",
275
+ warning: "#d97706"
33
276
  },
34
- radius: {
35
- sm: "6px",
36
- md: "10px",
37
- lg: "14px"
277
+ spacing: { ...sharedSpacing },
278
+ typography: { ...sharedTypography },
279
+ radius: { ...sharedRadius },
280
+ shadows: {
281
+ sm: "0 1px 2px rgba(0, 0, 0, 0.06)",
282
+ md: "0 8px 24px rgba(17, 24, 39, 0.12)",
283
+ lg: "0 18px 50px rgba(17, 24, 39, 0.14)"
38
284
  }
39
285
  };
286
+ var lightTheme = {
287
+ name: "light",
288
+ colors: {
289
+ background: "#f7f8ff",
290
+ foreground: "rgba(17, 24, 39, 0.92)",
291
+ primary: "#2563eb",
292
+ muted: "rgba(17, 24, 39, 0.64)",
293
+ border: "rgba(17, 24, 39, 0.12)",
294
+ panel: "rgba(17, 24, 39, 0.04)",
295
+ danger: "#e11d48",
296
+ success: "#059669",
297
+ warning: "#d97706",
298
+ extra: {
299
+ accent: "#22d3ee",
300
+ "panel-strong": "rgba(17, 24, 39, 0.06)"
301
+ }
302
+ },
303
+ spacing: { ...sharedSpacing },
304
+ typography: { ...sharedTypography },
305
+ radius: { ...sharedRadius },
306
+ shadows: {
307
+ sm: "0 1px 2px rgba(17, 24, 39, 0.08)",
308
+ md: "0 8px 24px rgba(17, 24, 39, 0.1)",
309
+ lg: "0 18px 50px rgba(17, 24, 39, 0.14)"
310
+ }
311
+ };
312
+ var darkTheme = {
313
+ name: "dark",
314
+ colors: {
315
+ background: "#0b1020",
316
+ foreground: "rgba(255, 255, 255, 0.92)",
317
+ primary: "#7c3aed",
318
+ muted: "rgba(255, 255, 255, 0.68)",
319
+ border: "rgba(255, 255, 255, 0.14)",
320
+ panel: "rgba(255, 255, 255, 0.08)",
321
+ danger: "#fb7185",
322
+ success: "#34d399",
323
+ warning: "#fbbf24",
324
+ extra: {
325
+ accent: "#22d3ee",
326
+ "panel-strong": "rgba(255, 255, 255, 0.12)"
327
+ }
328
+ },
329
+ spacing: { ...sharedSpacing },
330
+ typography: { ...sharedTypography },
331
+ radius: { ...sharedRadius },
332
+ shadows: {
333
+ sm: "0 1px 2px rgba(0, 0, 0, 0.2)",
334
+ md: "0 12px 32px rgba(0, 0, 0, 0.28)",
335
+ lg: "0 18px 50px rgba(0, 0, 0, 0.35)"
336
+ }
337
+ };
338
+ var brandThemeOverrides = {
339
+ name: "brand",
340
+ colors: {
341
+ primary: "#7c3aed",
342
+ extra: {
343
+ accent: "#22d3ee"
344
+ }
345
+ }
346
+ };
347
+ var brandTheme = mergeThemes(darkTheme, brandThemeOverrides);
348
+ var PRESETS = {
349
+ default: defaultTheme,
350
+ light: lightTheme,
351
+ dark: darkTheme,
352
+ brand: brandTheme
353
+ };
354
+ function getPresetTheme(preset) {
355
+ return PRESETS[preset];
356
+ }
357
+
358
+ // src/catalog.ts
359
+ var COLOR_DESCRIPTIONS = {
360
+ background: "Page and shell background",
361
+ foreground: "Primary text color",
362
+ primary: "Brand and interactive accent",
363
+ muted: "Secondary text and hints",
364
+ border: "Borders and dividers",
365
+ panel: "Card and panel surfaces",
366
+ danger: "Errors and high-risk states",
367
+ success: "Success and positive feedback",
368
+ warning: "Warnings and caution states"
369
+ };
370
+ var SPACING_DESCRIPTIONS = {
371
+ xs: "Extra-small spacing",
372
+ sm: "Small spacing",
373
+ md: "Medium spacing",
374
+ lg: "Large spacing",
375
+ xl: "Extra-large spacing"
376
+ };
377
+ var TYPOGRAPHY_DESCRIPTIONS = {
378
+ fontFamily: "Base font stack",
379
+ fontSizeBase: "Base font size",
380
+ lineHeightBase: "Base line height",
381
+ fontWeightNormal: "Normal text weight",
382
+ fontWeightStrong: "Headings and emphasis weight"
383
+ };
384
+ var RADIUS_DESCRIPTIONS = {
385
+ sm: "Small corner radius",
386
+ md: "Medium corner radius",
387
+ lg: "Large corner radius"
388
+ };
389
+ var SHADOW_DESCRIPTIONS = {
390
+ sm: "Small elevation shadow",
391
+ md: "Medium elevation shadow",
392
+ lg: "Large elevation shadow"
393
+ };
394
+ function buildThemeCatalog() {
395
+ const entries = [];
396
+ for (const key of REQUIRED_COLOR_KEYS) {
397
+ entries.push({
398
+ tokenPath: `colors.${key}`,
399
+ cssVariable: colorVarName(key),
400
+ type: "color",
401
+ required: true,
402
+ description: COLOR_DESCRIPTIONS[key] ?? `Color token: ${key}`
403
+ });
404
+ }
405
+ entries.push({
406
+ tokenPath: "colors.extra.*",
407
+ cssVariable: "--lk-color-extra-{key}",
408
+ type: "color-extra",
409
+ required: false,
410
+ description: "Optional extension colors (non-stable until 1.0)"
411
+ });
412
+ for (const key of REQUIRED_SPACING_KEYS) {
413
+ entries.push({
414
+ tokenPath: `spacing.${key}`,
415
+ cssVariable: spacingVarName(key),
416
+ type: "spacing",
417
+ required: true,
418
+ description: SPACING_DESCRIPTIONS[key] ?? `Spacing token: ${key}`
419
+ });
420
+ }
421
+ for (const key of REQUIRED_TYPOGRAPHY_KEYS) {
422
+ entries.push({
423
+ tokenPath: `typography.${key}`,
424
+ cssVariable: typographyVarName(key),
425
+ type: "typography",
426
+ required: true,
427
+ description: TYPOGRAPHY_DESCRIPTIONS[key] ?? `Typography token: ${key}`
428
+ });
429
+ }
430
+ for (const key of REQUIRED_RADIUS_KEYS) {
431
+ entries.push({
432
+ tokenPath: `radius.${key}`,
433
+ cssVariable: radiusVarName(key),
434
+ type: "radius",
435
+ required: true,
436
+ description: RADIUS_DESCRIPTIONS[key] ?? `Radius token: ${key}`
437
+ });
438
+ }
439
+ for (const key of REQUIRED_SHADOW_KEYS) {
440
+ entries.push({
441
+ tokenPath: `shadows.${key}`,
442
+ cssVariable: shadowVarName(key),
443
+ type: "shadow",
444
+ required: true,
445
+ description: SHADOW_DESCRIPTIONS[key] ?? `Shadow token: ${key}`
446
+ });
447
+ }
448
+ return entries;
449
+ }
40
450
  // Annotate the CommonJS export names for ESM import in node:
41
451
  0 && (module.exports = {
42
- defaultTheme
452
+ REQUIRED_COLOR_KEYS,
453
+ REQUIRED_RADIUS_KEYS,
454
+ REQUIRED_SHADOW_KEYS,
455
+ REQUIRED_SPACING_KEYS,
456
+ REQUIRED_TYPOGRAPHY_KEYS,
457
+ brandTheme,
458
+ brandThemeOverrides,
459
+ buildThemeCatalog,
460
+ colorExtraVarName,
461
+ colorVarName,
462
+ darkTheme,
463
+ defaultTheme,
464
+ getPresetTheme,
465
+ lightTheme,
466
+ mergeThemes,
467
+ radiusVarName,
468
+ shadowVarName,
469
+ spacingVarName,
470
+ themeToCssDeclarationBlock,
471
+ themeToCssVariables,
472
+ tokenKeyToKebab,
473
+ typographyVarName,
474
+ validateTheme
43
475
  });
package/dist/index.d.cts CHANGED
@@ -1,17 +1,96 @@
1
- type LessonkitTheme = {
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
- background?: string;
5
- foreground?: string;
6
- primary?: string;
7
- muted?: string;
8
- };
9
- radius?: {
10
- sm?: string;
11
- md?: string;
12
- lg?: string;
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
- declare const defaultTheme: LessonkitTheme;
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 };