@silvery/theme 0.3.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.
Files changed (47) hide show
  1. package/package.json +36 -0
  2. package/src/ThemeContext.tsx +62 -0
  3. package/src/alias.ts +94 -0
  4. package/src/auto-generate.ts +126 -0
  5. package/src/builder.ts +218 -0
  6. package/src/cli.ts +275 -0
  7. package/src/color.ts +142 -0
  8. package/src/contrast.ts +75 -0
  9. package/src/css.ts +51 -0
  10. package/src/derive.ts +167 -0
  11. package/src/detect.ts +263 -0
  12. package/src/export/base16.ts +64 -0
  13. package/src/generate.ts +79 -0
  14. package/src/generators.ts +255 -0
  15. package/src/import/base16.ts +150 -0
  16. package/src/import/types.ts +47 -0
  17. package/src/index.ts +2 -0
  18. package/src/palettes/ayu.ts +92 -0
  19. package/src/palettes/catppuccin.ts +118 -0
  20. package/src/palettes/dracula.ts +34 -0
  21. package/src/palettes/edge.ts +63 -0
  22. package/src/palettes/everforest.ts +63 -0
  23. package/src/palettes/gruvbox.ts +62 -0
  24. package/src/palettes/horizon.ts +35 -0
  25. package/src/palettes/index.ts +293 -0
  26. package/src/palettes/kanagawa.ts +91 -0
  27. package/src/palettes/material.ts +64 -0
  28. package/src/palettes/modus.ts +63 -0
  29. package/src/palettes/monokai.ts +64 -0
  30. package/src/palettes/moonfly.ts +35 -0
  31. package/src/palettes/nightfly.ts +35 -0
  32. package/src/palettes/nightfox.ts +63 -0
  33. package/src/palettes/nord.ts +34 -0
  34. package/src/palettes/one-dark.ts +34 -0
  35. package/src/palettes/oxocarbon.ts +63 -0
  36. package/src/palettes/palenight.ts +36 -0
  37. package/src/palettes/rose-pine.ts +90 -0
  38. package/src/palettes/snazzy.ts +35 -0
  39. package/src/palettes/solarized.ts +62 -0
  40. package/src/palettes/sonokai.ts +35 -0
  41. package/src/palettes/tokyo-night.ts +90 -0
  42. package/src/resolve.ts +44 -0
  43. package/src/state.ts +58 -0
  44. package/src/theme.ts +148 -0
  45. package/src/types.ts +223 -0
  46. package/src/validate-theme.ts +100 -0
  47. package/src/validate.ts +56 -0
package/src/theme.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @silvery/theme — Universal color themes for any platform.
3
+ *
4
+ * Two-layer architecture:
5
+ * Layer 1: ColorPalette (22 terminal colors — universal pivot format)
6
+ * Layer 2: Theme (33 semantic tokens — what UI apps consume)
7
+ *
8
+ * Pipeline: Palette generators → ColorPalette → deriveTheme() → Theme
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createTheme, catppuccinMocha, resolveThemeColor } from "@silvery/theme"
13
+ *
14
+ * const theme = createTheme().preset('catppuccin-mocha').build()
15
+ * const color = resolveThemeColor("$primary", theme) // → "#F9E2AF"
16
+ * ```
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+
21
+ // React integration
22
+ export { ThemeProvider, useTheme } from "./ThemeContext"
23
+ export type { ThemeProviderProps } from "./ThemeContext"
24
+
25
+ // Core types
26
+ export type { Theme, ColorPalette, HueName, AnsiPrimary, AnsiColorName } from "./types"
27
+ export { COLOR_PALETTE_FIELDS } from "./types"
28
+
29
+ // Derivation
30
+ export { deriveTheme } from "./derive"
31
+
32
+ // Color utilities
33
+ export {
34
+ blend,
35
+ brighten,
36
+ darken,
37
+ contrastFg,
38
+ desaturate,
39
+ complement,
40
+ hexToRgb,
41
+ rgbToHex,
42
+ hexToHsl,
43
+ hslToHex,
44
+ rgbToHsl,
45
+ } from "./color"
46
+ export type { HSL } from "./color"
47
+
48
+ // Token resolution
49
+ export { resolveThemeColor } from "./resolve"
50
+
51
+ // ANSI 16 theme generation
52
+ export { generateTheme } from "./generate"
53
+
54
+ // Builder API
55
+ export { createTheme, quickTheme, presetTheme } from "./builder"
56
+
57
+ // Palette generators
58
+ export { fromBase16, fromColors, fromPreset } from "./generators"
59
+
60
+ // Active theme state (side-effectful)
61
+ export { setActiveTheme, getActiveTheme, pushContextTheme, popContextTheme } from "./state"
62
+
63
+ // Validation
64
+ export { validateColorPalette } from "./validate"
65
+ export type { ValidationResult } from "./validate"
66
+ export { validateTheme, THEME_TOKEN_KEYS } from "./validate-theme"
67
+ export type { ThemeValidationResult } from "./validate-theme"
68
+
69
+ // Contrast checking
70
+ export { checkContrast } from "./contrast"
71
+ export type { ContrastResult } from "./contrast"
72
+
73
+ // Token aliasing
74
+ export { resolveAliases, resolveTokenAlias } from "./alias"
75
+
76
+ // CSS variables export
77
+ export { themeToCSSVars } from "./css"
78
+
79
+ // Auto-generate themes from a single color
80
+ export { autoGenerateTheme } from "./auto-generate"
81
+
82
+ // Base16 import/export
83
+ export { importBase16 } from "./import/base16"
84
+ export { exportBase16 } from "./export/base16"
85
+ export type { Base16Scheme } from "./import/types"
86
+
87
+ // Terminal detection
88
+ export { detectTerminalPalette, detectTheme } from "./detect"
89
+ export type { DetectedPalette, DetectThemeOptions } from "./detect"
90
+
91
+ // Built-in themes (pre-derived)
92
+ export {
93
+ ansi16DarkTheme,
94
+ ansi16LightTheme,
95
+ defaultDarkTheme,
96
+ defaultLightTheme,
97
+ builtinThemes,
98
+ getThemeByName,
99
+ } from "./palettes/index"
100
+
101
+ // Built-in palettes (45 palettes from 15 theme families)
102
+ export {
103
+ builtinPalettes,
104
+ getPaletteByName,
105
+ catppuccinMocha,
106
+ catppuccinFrappe,
107
+ catppuccinMacchiato,
108
+ catppuccinLatte,
109
+ nord,
110
+ dracula,
111
+ oneDark,
112
+ solarizedDark,
113
+ solarizedLight,
114
+ gruvboxDark,
115
+ gruvboxLight,
116
+ tokyoNight,
117
+ tokyoNightStorm,
118
+ tokyoNightDay,
119
+ rosePine,
120
+ rosePineMoon,
121
+ rosePineDawn,
122
+ kanagawaWave,
123
+ kanagawaDragon,
124
+ kanagawaLotus,
125
+ everforestDark,
126
+ everforestLight,
127
+ nightfox,
128
+ dawnfox,
129
+ monokai,
130
+ monokaiPro,
131
+ snazzy,
132
+ materialDark,
133
+ materialLight,
134
+ palenight,
135
+ ayuDark,
136
+ ayuMirage,
137
+ ayuLight,
138
+ horizon,
139
+ moonfly,
140
+ nightfly,
141
+ oxocarbonDark,
142
+ oxocarbonLight,
143
+ sonokai,
144
+ edgeDark,
145
+ edgeLight,
146
+ modusVivendi,
147
+ modusOperandi,
148
+ } from "./palettes/index"
package/src/types.ts ADDED
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Core type definitions for the swatch theme system.
3
+ *
4
+ * Two-layer architecture:
5
+ * Layer 1: ColorPalette — 22 terminal colors (what palette generators produce)
6
+ * Layer 2: Theme — 33 semantic tokens (what UI apps consume)
7
+ *
8
+ * Pipeline: Palette generators → ColorPalette (22) → deriveTheme() → Theme (33)
9
+ */
10
+
11
+ // ============================================================================
12
+ // ColorPalette — The 22-Color Terminal Standard
13
+ // ============================================================================
14
+
15
+ /**
16
+ * The 22-color format every modern terminal emulator uses
17
+ * (Ghostty, Kitty, Alacritty, iTerm2, WezTerm).
18
+ *
19
+ * 16 ANSI palette colors + 6 special colors = universal pivot format.
20
+ * All fields are required hex strings (#RRGGBB).
21
+ */
22
+ export interface ColorPalette {
23
+ name?: string
24
+ dark?: boolean
25
+
26
+ // ── 16 ANSI palette ────────────────────────────────────────────
27
+ /** ANSI 0 — normal black */
28
+ black: string
29
+ /** ANSI 1 — normal red */
30
+ red: string
31
+ /** ANSI 2 — normal green */
32
+ green: string
33
+ /** ANSI 3 — normal yellow */
34
+ yellow: string
35
+ /** ANSI 4 — normal blue */
36
+ blue: string
37
+ /** ANSI 5 — normal magenta */
38
+ magenta: string
39
+ /** ANSI 6 — normal cyan */
40
+ cyan: string
41
+ /** ANSI 7 — normal white */
42
+ white: string
43
+ /** ANSI 8 — bright black */
44
+ brightBlack: string
45
+ /** ANSI 9 — bright red */
46
+ brightRed: string
47
+ /** ANSI 10 — bright green */
48
+ brightGreen: string
49
+ /** ANSI 11 — bright yellow */
50
+ brightYellow: string
51
+ /** ANSI 12 — bright blue */
52
+ brightBlue: string
53
+ /** ANSI 13 — bright magenta */
54
+ brightMagenta: string
55
+ /** ANSI 14 — bright cyan */
56
+ brightCyan: string
57
+ /** ANSI 15 — bright white */
58
+ brightWhite: string
59
+
60
+ // ── 6 special colors ────────────────────────────────────────────
61
+ /** Default text color */
62
+ foreground: string
63
+ /** Default background color */
64
+ background: string
65
+ /** Cursor block/line color */
66
+ cursorColor: string
67
+ /** Text rendered under the cursor */
68
+ cursorText: string
69
+ /** Background color of selected text */
70
+ selectionBackground: string
71
+ /** Text color of selected text */
72
+ selectionForeground: string
73
+ }
74
+
75
+ /** All 22 color field names on ColorPalette. */
76
+ export const COLOR_PALETTE_FIELDS = [
77
+ "black",
78
+ "red",
79
+ "green",
80
+ "yellow",
81
+ "blue",
82
+ "magenta",
83
+ "cyan",
84
+ "white",
85
+ "brightBlack",
86
+ "brightRed",
87
+ "brightGreen",
88
+ "brightYellow",
89
+ "brightBlue",
90
+ "brightMagenta",
91
+ "brightCyan",
92
+ "brightWhite",
93
+ "foreground",
94
+ "background",
95
+ "cursorColor",
96
+ "cursorText",
97
+ "selectionBackground",
98
+ "selectionForeground",
99
+ ] as const
100
+
101
+ /** Name of one of the 16 ANSI palette colors. */
102
+ export type AnsiColorName =
103
+ | "black"
104
+ | "red"
105
+ | "green"
106
+ | "yellow"
107
+ | "blue"
108
+ | "magenta"
109
+ | "cyan"
110
+ | "white"
111
+ | "brightBlack"
112
+ | "brightRed"
113
+ | "brightGreen"
114
+ | "brightYellow"
115
+ | "brightBlue"
116
+ | "brightMagenta"
117
+ | "brightCyan"
118
+ | "brightWhite"
119
+
120
+ // ============================================================================
121
+ // Theme — 33 Semantic Tokens for UI Consumption
122
+ // ============================================================================
123
+
124
+ /**
125
+ * Semantic color token map (33 tokens + palette).
126
+ *
127
+ * Two pairing conventions:
128
+ * Surface pairs: `$name` = text, `$name-bg` = background
129
+ * (muted, surface, popover, inverse, cursor, selection)
130
+ * Accent pairs: `$name` = area bg, `$name-fg` = text on area
131
+ * (primary, secondary, accent, error, warning, success, info)
132
+ *
133
+ * Components reference tokens with a `$` prefix (e.g. `color="$primary"`).
134
+ * All property names are lowercase, no hyphens, no camelCase.
135
+ */
136
+ export interface Theme {
137
+ /** Human-readable theme name */
138
+ name: string
139
+
140
+ // ── Root pair ───────────────────────────────────────────────────
141
+ /** Default background */
142
+ bg: string
143
+ /** Default text */
144
+ fg: string
145
+
146
+ // ── 6 surface pairs (base = text, *bg = background) ─────────────
147
+ /** Secondary/muted text (~70% contrast) */
148
+ muted: string
149
+ /** Muted area background (hover state) */
150
+ mutedbg: string
151
+ /** Text on elevated surface */
152
+ surface: string
153
+ /** Elevated content area background */
154
+ surfacebg: string
155
+ /** Text on floating content */
156
+ popover: string
157
+ /** Floating content background (popover, dropdown) */
158
+ popoverbg: string
159
+ /** Text on chrome area */
160
+ inverse: string
161
+ /** Chrome area (status/title bar) */
162
+ inversebg: string
163
+ /** Text under cursor */
164
+ cursor: string
165
+ /** Cursor color */
166
+ cursorbg: string
167
+ /** Text on selected items */
168
+ selection: string
169
+ /** Selected items background */
170
+ selectionbg: string
171
+
172
+ // ── 7 accent pairs (base = area bg, *fg = text on area) ─────────
173
+ /** Brand accent area */
174
+ primary: string
175
+ /** Text on primary accent area */
176
+ primaryfg: string
177
+ /** Alternate accent area */
178
+ secondary: string
179
+ /** Text on secondary accent area */
180
+ secondaryfg: string
181
+ /** Attention/pop accent area */
182
+ accent: string
183
+ /** Text on accent area */
184
+ accentfg: string
185
+ /** Error/destructive area */
186
+ error: string
187
+ /** Text on error area */
188
+ errorfg: string
189
+ /** Warning/caution area */
190
+ warning: string
191
+ /** Text on warning area */
192
+ warningfg: string
193
+ /** Success/positive area */
194
+ success: string
195
+ /** Text on success area */
196
+ successfg: string
197
+ /** Neutral info area */
198
+ info: string
199
+ /** Text on info area */
200
+ infofg: string
201
+
202
+ // ── 5 standalone tokens ─────────────────────────────────────────
203
+ /** Structural dividers, borders */
204
+ border: string
205
+ /** Interactive control borders (inputs, buttons) */
206
+ inputborder: string
207
+ /** Focus border (always blue) */
208
+ focusborder: string
209
+ /** Hyperlinks */
210
+ link: string
211
+ /** Disabled/placeholder text (~50% contrast) */
212
+ disabledfg: string
213
+
214
+ // ── 16 palette passthrough ──────────────────────────────────────
215
+ /** 16 ANSI colors ($color0–$color15) */
216
+ palette: string[]
217
+ }
218
+
219
+ /** Supported primary colors for ANSI 16 theme generation. */
220
+ export type AnsiPrimary = "yellow" | "cyan" | "magenta" | "green" | "red" | "blue" | "white"
221
+
222
+ /** Accent hue name — the 8 hue names for palette generators. */
223
+ export type HueName = "red" | "orange" | "yellow" | "green" | "teal" | "blue" | "purple" | "pink"
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Theme validation — checks that all required semantic tokens are present.
3
+ *
4
+ * Complements validateColorPalette() which validates the lower-level
5
+ * ColorPalette. This validates the derived Theme object.
6
+ */
7
+
8
+ /** All 33 required semantic token keys on Theme (excludes `name` and `palette`). */
9
+ export const THEME_TOKEN_KEYS: readonly string[] = [
10
+ // Root pair
11
+ "bg",
12
+ "fg",
13
+ // 6 surface pairs (base = text, *bg = background)
14
+ "muted",
15
+ "mutedbg",
16
+ "surface",
17
+ "surfacebg",
18
+ "popover",
19
+ "popoverbg",
20
+ "inverse",
21
+ "inversebg",
22
+ "cursor",
23
+ "cursorbg",
24
+ "selection",
25
+ "selectionbg",
26
+ // 7 accent pairs (base = area bg, *fg = text on area)
27
+ "primary",
28
+ "primaryfg",
29
+ "secondary",
30
+ "secondaryfg",
31
+ "accent",
32
+ "accentfg",
33
+ "error",
34
+ "errorfg",
35
+ "warning",
36
+ "warningfg",
37
+ "success",
38
+ "successfg",
39
+ "info",
40
+ "infofg",
41
+ // 5 standalone tokens
42
+ "border",
43
+ "inputborder",
44
+ "focusborder",
45
+ "link",
46
+ "disabledfg",
47
+ ] as const
48
+
49
+ /** Result of theme validation. */
50
+ export interface ThemeValidationResult {
51
+ /** Whether the theme has all required tokens. */
52
+ valid: boolean
53
+ /** Token keys that are required but missing or empty. */
54
+ missing: string[]
55
+ /** Token keys that exist on the object but are not recognized theme tokens. */
56
+ extra: string[]
57
+ }
58
+
59
+ /** All recognized keys on Theme (tokens + metadata). */
60
+ const ALL_KNOWN_KEYS = new Set([...THEME_TOKEN_KEYS, "name", "palette"])
61
+
62
+ /**
63
+ * Validate a Theme object — check that all required tokens are present.
64
+ *
65
+ * @param theme - The theme object to validate
66
+ * @returns Validation result with missing and extra token lists
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const result = validateTheme(myTheme)
71
+ * if (!result.valid) {
72
+ * console.log("Missing tokens:", result.missing)
73
+ * }
74
+ * ```
75
+ */
76
+ export function validateTheme(theme: Record<string, unknown>): ThemeValidationResult {
77
+ const missing: string[] = []
78
+ const extra: string[] = []
79
+
80
+ // Check for missing or empty required tokens
81
+ for (const key of THEME_TOKEN_KEYS) {
82
+ const val = theme[key]
83
+ if (val === undefined || val === null || val === "") {
84
+ missing.push(key)
85
+ }
86
+ }
87
+
88
+ // Check for unrecognized keys (exclude prototype properties)
89
+ for (const key of Object.keys(theme)) {
90
+ if (!ALL_KNOWN_KEYS.has(key)) {
91
+ extra.push(key)
92
+ }
93
+ }
94
+
95
+ return {
96
+ valid: missing.length === 0,
97
+ missing,
98
+ extra,
99
+ }
100
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Palette validation — checks ColorPalette fields and contrast.
3
+ */
4
+
5
+ import { hexToRgb } from "./color"
6
+ import { COLOR_PALETTE_FIELDS, type ColorPalette } from "./types"
7
+
8
+ /** Validation result from validateColorPalette(). */
9
+ export interface ValidationResult {
10
+ valid: boolean
11
+ errors: string[]
12
+ warnings: string[]
13
+ }
14
+
15
+ /**
16
+ * Validate a ColorPalette.
17
+ *
18
+ * Checks:
19
+ * - All 22 color fields are present and non-empty hex strings
20
+ * - Warns on low-contrast foreground/background combinations
21
+ */
22
+ export function validateColorPalette(p: ColorPalette): ValidationResult {
23
+ const errors: string[] = []
24
+ const warnings: string[] = []
25
+
26
+ // Required color fields
27
+ for (const field of COLOR_PALETTE_FIELDS) {
28
+ const val = p[field]
29
+ if (!val || typeof val !== "string") {
30
+ errors.push(`${field} is required and must be a non-empty string`)
31
+ }
32
+ }
33
+
34
+ // Contrast warnings (only for hex colors)
35
+ if (p.foreground && p.background) {
36
+ const fgRgb = hexToRgb(p.foreground)
37
+ const bgRgb = hexToRgb(p.background)
38
+ if (fgRgb && bgRgb) {
39
+ const fgSum = fgRgb[0] + fgRgb[1] + fgRgb[2]
40
+ const bgSum = bgRgb[0] + bgRgb[1] + bgRgb[2]
41
+ const fgIsLight = fgSum > 384
42
+ const bgIsLight = bgSum > 384
43
+ if (fgIsLight === bgIsLight) {
44
+ warnings.push(
45
+ `Low contrast: foreground (${p.foreground}) and background (${p.background}) have similar lightness`,
46
+ )
47
+ }
48
+ }
49
+ }
50
+
51
+ return {
52
+ valid: errors.length === 0,
53
+ errors,
54
+ warnings,
55
+ }
56
+ }