@tailwind-styled/theme 2.0.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/dist/index.cjs +180 -0
- package/dist/index.d.cts +181 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +151 -0
- package/package.json +27 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ThemeRegistry: () => ThemeRegistry,
|
|
24
|
+
compileDesignTokens: () => compileDesignTokens,
|
|
25
|
+
createMultiTheme: () => createMultiTheme,
|
|
26
|
+
createTheme: () => createTheme,
|
|
27
|
+
defineThemeContract: () => defineThemeContract
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
function defineThemeContract(shape) {
|
|
31
|
+
const vars = {};
|
|
32
|
+
for (const group in shape) {
|
|
33
|
+
;
|
|
34
|
+
vars[group] = {};
|
|
35
|
+
for (const token in shape[group]) {
|
|
36
|
+
;
|
|
37
|
+
vars[group][token] = `var(--${group}-${token})`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { _contract: shape, _vars: vars };
|
|
41
|
+
}
|
|
42
|
+
function createTheme(contract, name, values, asRoot = false) {
|
|
43
|
+
const flatVars = {};
|
|
44
|
+
const cssLines = [];
|
|
45
|
+
for (const group in values) {
|
|
46
|
+
for (const token in values[group]) {
|
|
47
|
+
const varName = `--${group}-${token}`;
|
|
48
|
+
const value = values[group][token];
|
|
49
|
+
flatVars[varName] = value;
|
|
50
|
+
cssLines.push(` ${varName}: ${value};`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const selector = asRoot ? ":root" : `[data-theme="${name}"]`;
|
|
54
|
+
const css = `${selector} {
|
|
55
|
+
${cssLines.join("\n")}
|
|
56
|
+
}`;
|
|
57
|
+
return {
|
|
58
|
+
name,
|
|
59
|
+
contract,
|
|
60
|
+
values,
|
|
61
|
+
css,
|
|
62
|
+
vars: flatVars,
|
|
63
|
+
selector
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
var ThemeRegistry = class {
|
|
67
|
+
constructor() {
|
|
68
|
+
this.themes = /* @__PURE__ */ new Map();
|
|
69
|
+
this.defaultTheme = null;
|
|
70
|
+
}
|
|
71
|
+
/** Register a theme */
|
|
72
|
+
register(theme, isDefault = false) {
|
|
73
|
+
this.themes.set(theme.name, theme);
|
|
74
|
+
if (isDefault || !this.defaultTheme) {
|
|
75
|
+
this.defaultTheme = theme.name;
|
|
76
|
+
}
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/** Get a theme by name */
|
|
80
|
+
get(name) {
|
|
81
|
+
return this.themes.get(name);
|
|
82
|
+
}
|
|
83
|
+
/** Get all theme names */
|
|
84
|
+
names() {
|
|
85
|
+
return Array.from(this.themes.keys());
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Generate combined CSS for all themes.
|
|
89
|
+
* Inject into <head> or a .css file.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // In globals.css or layout.tsx
|
|
93
|
+
* const css = registry.generateCss()
|
|
94
|
+
*/
|
|
95
|
+
generateCss() {
|
|
96
|
+
return Array.from(this.themes.values()).map((t) => t.css).join("\n\n");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get the CSS for a specific theme only.
|
|
100
|
+
*/
|
|
101
|
+
getThemeCss(name) {
|
|
102
|
+
var _a, _b;
|
|
103
|
+
return (_b = (_a = this.themes.get(name)) == null ? void 0 : _a.css) != null ? _b : null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Inject all theme CSS into document <head> (browser only).
|
|
107
|
+
* Call once on app init.
|
|
108
|
+
*/
|
|
109
|
+
inject(styleId = "__tw_themes") {
|
|
110
|
+
if (typeof document === "undefined") return;
|
|
111
|
+
let style = document.getElementById(styleId);
|
|
112
|
+
if (!style) {
|
|
113
|
+
style = document.createElement("style");
|
|
114
|
+
style.id = styleId;
|
|
115
|
+
document.head.appendChild(style);
|
|
116
|
+
}
|
|
117
|
+
style.textContent = this.generateCss();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Switch active theme by setting data-theme on <html>.
|
|
121
|
+
*/
|
|
122
|
+
apply(name, target = document.documentElement) {
|
|
123
|
+
if (typeof document === "undefined") return;
|
|
124
|
+
if (!this.themes.has(name)) {
|
|
125
|
+
console.warn(`[tailwind-styled-v4] Theme "${name}" not registered.`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
target.dataset.theme = name;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get current active theme name from data-theme attribute.
|
|
132
|
+
*/
|
|
133
|
+
current(target = document.documentElement) {
|
|
134
|
+
var _a;
|
|
135
|
+
if (typeof document === "undefined") return this.defaultTheme;
|
|
136
|
+
return (_a = target.dataset.theme) != null ? _a : this.defaultTheme;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function createMultiTheme(config) {
|
|
140
|
+
var _a;
|
|
141
|
+
const registry = new ThemeRegistry();
|
|
142
|
+
const light = createTheme(config.contract, "light", config.light, true);
|
|
143
|
+
const dark = createTheme(config.contract, "dark", config.dark, false);
|
|
144
|
+
registry.register(light, true);
|
|
145
|
+
registry.register(dark);
|
|
146
|
+
for (const [name, values] of Object.entries((_a = config.extras) != null ? _a : {})) {
|
|
147
|
+
registry.register(createTheme(config.contract, name, values));
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
registry,
|
|
151
|
+
vars: config.contract._vars,
|
|
152
|
+
light,
|
|
153
|
+
dark
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function compileDesignTokens(tokens, prefix = "") {
|
|
157
|
+
const vars = [];
|
|
158
|
+
function flatten(obj, path) {
|
|
159
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
160
|
+
const varPath = path ? `${path}-${key}` : key;
|
|
161
|
+
if (typeof value === "string") {
|
|
162
|
+
vars.push(` --${varPath}: ${value};`);
|
|
163
|
+
} else {
|
|
164
|
+
flatten(value, varPath);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
flatten(tokens, prefix);
|
|
169
|
+
return `:root {
|
|
170
|
+
${vars.join("\n")}
|
|
171
|
+
}`;
|
|
172
|
+
}
|
|
173
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
174
|
+
0 && (module.exports = {
|
|
175
|
+
ThemeRegistry,
|
|
176
|
+
compileDesignTokens,
|
|
177
|
+
createMultiTheme,
|
|
178
|
+
createTheme,
|
|
179
|
+
defineThemeContract
|
|
180
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tailwind-styled-v4 — Multi-Theme Engine
|
|
3
|
+
*
|
|
4
|
+
* Enterprise-grade theming. Support light/dark/brand themes dengan
|
|
5
|
+
* CSS variables. Zero runtime overhead — themes di-resolve via CSS.
|
|
6
|
+
*
|
|
7
|
+
* Fitur:
|
|
8
|
+
* - Multiple named themes (light, dark, brand, high-contrast)
|
|
9
|
+
* - CSS variable output (Tailwind v4 compatible)
|
|
10
|
+
* - Theme contract (TypeScript-safe — missing tokens = TS error)
|
|
11
|
+
* - Per-component theme override
|
|
12
|
+
* - White-label ready
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // 1. Define contract
|
|
16
|
+
* const contract = defineThemeContract({
|
|
17
|
+
* colors: { bg: "", fg: "", primary: "", muted: "" },
|
|
18
|
+
* font: { sans: "", mono: "" },
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* // 2. Create themes
|
|
22
|
+
* const lightTheme = createTheme(contract, "light", {
|
|
23
|
+
* colors: { bg: "#ffffff", fg: "#09090b", primary: "#3b82f6", muted: "#71717a" },
|
|
24
|
+
* font: { sans: "InterVariable, sans-serif", mono: "JetBrains Mono, monospace" },
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* const darkTheme = createTheme(contract, "dark", {
|
|
28
|
+
* colors: { bg: "#09090b", fg: "#fafafa", primary: "#60a5fa", muted: "#a1a1aa" },
|
|
29
|
+
* font: { sans: "InterVariable, sans-serif", mono: "JetBrains Mono, monospace" },
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* // 3. Use tokens in components
|
|
33
|
+
* const Card = tw.div`bg-[var(--colors-bg)] text-[var(--colors-fg)] p-6`
|
|
34
|
+
*
|
|
35
|
+
* // 4. Apply in layout
|
|
36
|
+
* // <html data-theme="dark"> or inject CSS
|
|
37
|
+
*/
|
|
38
|
+
type TokenMap = Record<string, Record<string, string>>;
|
|
39
|
+
interface ThemeContract<T extends TokenMap> {
|
|
40
|
+
_contract: T;
|
|
41
|
+
_vars: ThemeVars<T>;
|
|
42
|
+
}
|
|
43
|
+
type ThemeVars<T extends TokenMap> = {
|
|
44
|
+
[Group in keyof T]: {
|
|
45
|
+
[Token in keyof T[Group]]: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
interface Theme<T extends TokenMap> {
|
|
49
|
+
name: string;
|
|
50
|
+
contract: ThemeContract<T>;
|
|
51
|
+
values: T;
|
|
52
|
+
/** CSS string to inject (`:root` or `[data-theme="name"]`) */
|
|
53
|
+
css: string;
|
|
54
|
+
/** CSS variables as a flat record */
|
|
55
|
+
vars: Record<string, string>;
|
|
56
|
+
/** Apply this theme to an element via data attribute */
|
|
57
|
+
selector: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Define the shape of your theme. All themes must satisfy this contract.
|
|
61
|
+
* Returns typed CSS variable references for use in tw components.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* const contract = defineThemeContract({
|
|
65
|
+
* colors: { bg: "", fg: "", primary: "" },
|
|
66
|
+
* font: { sans: "" },
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* // Use in components:
|
|
70
|
+
* const Card = tw.div`bg-[${contract._vars.colors.bg}]`
|
|
71
|
+
* // → tw.div`bg-[var(--colors-bg)]`
|
|
72
|
+
*/
|
|
73
|
+
declare function defineThemeContract<T extends TokenMap>(shape: T): ThemeContract<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Create a typed theme that satisfies a contract.
|
|
76
|
+
*
|
|
77
|
+
* @param contract - Theme contract from defineThemeContract()
|
|
78
|
+
* @param name - Theme name ("light", "dark", "brand", etc.)
|
|
79
|
+
* @param values - Token values (TypeScript enforces completeness)
|
|
80
|
+
* @param asRoot - If true, use :root selector. Default: false (uses [data-theme])
|
|
81
|
+
*/
|
|
82
|
+
declare function createTheme<T extends TokenMap>(contract: ThemeContract<T>, name: string, values: T, asRoot?: boolean): Theme<T>;
|
|
83
|
+
declare class ThemeRegistry {
|
|
84
|
+
private themes;
|
|
85
|
+
private defaultTheme;
|
|
86
|
+
/** Register a theme */
|
|
87
|
+
register<T extends TokenMap>(theme: Theme<T>, isDefault?: boolean): this;
|
|
88
|
+
/** Get a theme by name */
|
|
89
|
+
get(name: string): Theme<any> | undefined;
|
|
90
|
+
/** Get all theme names */
|
|
91
|
+
names(): string[];
|
|
92
|
+
/**
|
|
93
|
+
* Generate combined CSS for all themes.
|
|
94
|
+
* Inject into <head> or a .css file.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // In globals.css or layout.tsx
|
|
98
|
+
* const css = registry.generateCss()
|
|
99
|
+
*/
|
|
100
|
+
generateCss(): string;
|
|
101
|
+
/**
|
|
102
|
+
* Get the CSS for a specific theme only.
|
|
103
|
+
*/
|
|
104
|
+
getThemeCss(name: string): string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Inject all theme CSS into document <head> (browser only).
|
|
107
|
+
* Call once on app init.
|
|
108
|
+
*/
|
|
109
|
+
inject(styleId?: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Switch active theme by setting data-theme on <html>.
|
|
112
|
+
*/
|
|
113
|
+
apply(name: string, target?: HTMLElement): void;
|
|
114
|
+
/**
|
|
115
|
+
* Get current active theme name from data-theme attribute.
|
|
116
|
+
*/
|
|
117
|
+
current(target?: HTMLElement): string | null;
|
|
118
|
+
}
|
|
119
|
+
interface MultiThemeConfig<T extends TokenMap> {
|
|
120
|
+
contract: ThemeContract<T>;
|
|
121
|
+
light: T;
|
|
122
|
+
dark: T;
|
|
123
|
+
/** Additional named themes (brand, high-contrast, etc.) */
|
|
124
|
+
extras?: Record<string, T>;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create a ThemeRegistry with light/dark + optional extras in one call.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* const { registry, vars } = createMultiTheme({
|
|
131
|
+
* contract: defineThemeContract({
|
|
132
|
+
* colors: { bg: "", fg: "", primary: "", border: "" }
|
|
133
|
+
* }),
|
|
134
|
+
* light: {
|
|
135
|
+
* colors: { bg: "#fff", fg: "#09090b", primary: "#3b82f6", border: "#e5e7eb" }
|
|
136
|
+
* },
|
|
137
|
+
* dark: {
|
|
138
|
+
* colors: { bg: "#09090b", fg: "#fafafa", primary: "#60a5fa", border: "#27272a" }
|
|
139
|
+
* },
|
|
140
|
+
* })
|
|
141
|
+
*
|
|
142
|
+
* // Inject CSS:
|
|
143
|
+
* registry.inject()
|
|
144
|
+
*
|
|
145
|
+
* // Use tokens in components:
|
|
146
|
+
* const Card = tw.div`bg-[${vars.colors.bg}] text-[${vars.colors.fg}]`
|
|
147
|
+
*/
|
|
148
|
+
declare function createMultiTheme<T extends TokenMap>(config: MultiThemeConfig<T>): {
|
|
149
|
+
registry: ThemeRegistry;
|
|
150
|
+
vars: ThemeVars<T>;
|
|
151
|
+
light: Theme<T>;
|
|
152
|
+
dark: Theme<T>;
|
|
153
|
+
};
|
|
154
|
+
interface DesignTokens {
|
|
155
|
+
[path: string]: string | DesignTokens;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Flatten nested design token object into CSS variables.
|
|
159
|
+
* Supports Figma-style nested tokens.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* compileDesignTokens({
|
|
163
|
+
* color: {
|
|
164
|
+
* brand: { primary: "#3b82f6", secondary: "#6366f1" },
|
|
165
|
+
* neutral: { 50: "#fafafa", 900: "#09090b" }
|
|
166
|
+
* },
|
|
167
|
+
* spacing: { base: "4px", lg: "16px" }
|
|
168
|
+
* })
|
|
169
|
+
* →
|
|
170
|
+
* :root {
|
|
171
|
+
* --color-brand-primary: #3b82f6;
|
|
172
|
+
* --color-brand-secondary: #6366f1;
|
|
173
|
+
* --color-neutral-50: #fafafa;
|
|
174
|
+
* --color-neutral-900: #09090b;
|
|
175
|
+
* --spacing-base: 4px;
|
|
176
|
+
* --spacing-lg: 16px;
|
|
177
|
+
* }
|
|
178
|
+
*/
|
|
179
|
+
declare function compileDesignTokens(tokens: DesignTokens, prefix?: string): string;
|
|
180
|
+
|
|
181
|
+
export { type DesignTokens, type MultiThemeConfig, type Theme, type ThemeContract, ThemeRegistry, type ThemeVars, type TokenMap, compileDesignTokens, createMultiTheme, createTheme, defineThemeContract };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tailwind-styled-v4 — Multi-Theme Engine
|
|
3
|
+
*
|
|
4
|
+
* Enterprise-grade theming. Support light/dark/brand themes dengan
|
|
5
|
+
* CSS variables. Zero runtime overhead — themes di-resolve via CSS.
|
|
6
|
+
*
|
|
7
|
+
* Fitur:
|
|
8
|
+
* - Multiple named themes (light, dark, brand, high-contrast)
|
|
9
|
+
* - CSS variable output (Tailwind v4 compatible)
|
|
10
|
+
* - Theme contract (TypeScript-safe — missing tokens = TS error)
|
|
11
|
+
* - Per-component theme override
|
|
12
|
+
* - White-label ready
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // 1. Define contract
|
|
16
|
+
* const contract = defineThemeContract({
|
|
17
|
+
* colors: { bg: "", fg: "", primary: "", muted: "" },
|
|
18
|
+
* font: { sans: "", mono: "" },
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* // 2. Create themes
|
|
22
|
+
* const lightTheme = createTheme(contract, "light", {
|
|
23
|
+
* colors: { bg: "#ffffff", fg: "#09090b", primary: "#3b82f6", muted: "#71717a" },
|
|
24
|
+
* font: { sans: "InterVariable, sans-serif", mono: "JetBrains Mono, monospace" },
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* const darkTheme = createTheme(contract, "dark", {
|
|
28
|
+
* colors: { bg: "#09090b", fg: "#fafafa", primary: "#60a5fa", muted: "#a1a1aa" },
|
|
29
|
+
* font: { sans: "InterVariable, sans-serif", mono: "JetBrains Mono, monospace" },
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* // 3. Use tokens in components
|
|
33
|
+
* const Card = tw.div`bg-[var(--colors-bg)] text-[var(--colors-fg)] p-6`
|
|
34
|
+
*
|
|
35
|
+
* // 4. Apply in layout
|
|
36
|
+
* // <html data-theme="dark"> or inject CSS
|
|
37
|
+
*/
|
|
38
|
+
type TokenMap = Record<string, Record<string, string>>;
|
|
39
|
+
interface ThemeContract<T extends TokenMap> {
|
|
40
|
+
_contract: T;
|
|
41
|
+
_vars: ThemeVars<T>;
|
|
42
|
+
}
|
|
43
|
+
type ThemeVars<T extends TokenMap> = {
|
|
44
|
+
[Group in keyof T]: {
|
|
45
|
+
[Token in keyof T[Group]]: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
interface Theme<T extends TokenMap> {
|
|
49
|
+
name: string;
|
|
50
|
+
contract: ThemeContract<T>;
|
|
51
|
+
values: T;
|
|
52
|
+
/** CSS string to inject (`:root` or `[data-theme="name"]`) */
|
|
53
|
+
css: string;
|
|
54
|
+
/** CSS variables as a flat record */
|
|
55
|
+
vars: Record<string, string>;
|
|
56
|
+
/** Apply this theme to an element via data attribute */
|
|
57
|
+
selector: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Define the shape of your theme. All themes must satisfy this contract.
|
|
61
|
+
* Returns typed CSS variable references for use in tw components.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* const contract = defineThemeContract({
|
|
65
|
+
* colors: { bg: "", fg: "", primary: "" },
|
|
66
|
+
* font: { sans: "" },
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* // Use in components:
|
|
70
|
+
* const Card = tw.div`bg-[${contract._vars.colors.bg}]`
|
|
71
|
+
* // → tw.div`bg-[var(--colors-bg)]`
|
|
72
|
+
*/
|
|
73
|
+
declare function defineThemeContract<T extends TokenMap>(shape: T): ThemeContract<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Create a typed theme that satisfies a contract.
|
|
76
|
+
*
|
|
77
|
+
* @param contract - Theme contract from defineThemeContract()
|
|
78
|
+
* @param name - Theme name ("light", "dark", "brand", etc.)
|
|
79
|
+
* @param values - Token values (TypeScript enforces completeness)
|
|
80
|
+
* @param asRoot - If true, use :root selector. Default: false (uses [data-theme])
|
|
81
|
+
*/
|
|
82
|
+
declare function createTheme<T extends TokenMap>(contract: ThemeContract<T>, name: string, values: T, asRoot?: boolean): Theme<T>;
|
|
83
|
+
declare class ThemeRegistry {
|
|
84
|
+
private themes;
|
|
85
|
+
private defaultTheme;
|
|
86
|
+
/** Register a theme */
|
|
87
|
+
register<T extends TokenMap>(theme: Theme<T>, isDefault?: boolean): this;
|
|
88
|
+
/** Get a theme by name */
|
|
89
|
+
get(name: string): Theme<any> | undefined;
|
|
90
|
+
/** Get all theme names */
|
|
91
|
+
names(): string[];
|
|
92
|
+
/**
|
|
93
|
+
* Generate combined CSS for all themes.
|
|
94
|
+
* Inject into <head> or a .css file.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // In globals.css or layout.tsx
|
|
98
|
+
* const css = registry.generateCss()
|
|
99
|
+
*/
|
|
100
|
+
generateCss(): string;
|
|
101
|
+
/**
|
|
102
|
+
* Get the CSS for a specific theme only.
|
|
103
|
+
*/
|
|
104
|
+
getThemeCss(name: string): string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Inject all theme CSS into document <head> (browser only).
|
|
107
|
+
* Call once on app init.
|
|
108
|
+
*/
|
|
109
|
+
inject(styleId?: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Switch active theme by setting data-theme on <html>.
|
|
112
|
+
*/
|
|
113
|
+
apply(name: string, target?: HTMLElement): void;
|
|
114
|
+
/**
|
|
115
|
+
* Get current active theme name from data-theme attribute.
|
|
116
|
+
*/
|
|
117
|
+
current(target?: HTMLElement): string | null;
|
|
118
|
+
}
|
|
119
|
+
interface MultiThemeConfig<T extends TokenMap> {
|
|
120
|
+
contract: ThemeContract<T>;
|
|
121
|
+
light: T;
|
|
122
|
+
dark: T;
|
|
123
|
+
/** Additional named themes (brand, high-contrast, etc.) */
|
|
124
|
+
extras?: Record<string, T>;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create a ThemeRegistry with light/dark + optional extras in one call.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* const { registry, vars } = createMultiTheme({
|
|
131
|
+
* contract: defineThemeContract({
|
|
132
|
+
* colors: { bg: "", fg: "", primary: "", border: "" }
|
|
133
|
+
* }),
|
|
134
|
+
* light: {
|
|
135
|
+
* colors: { bg: "#fff", fg: "#09090b", primary: "#3b82f6", border: "#e5e7eb" }
|
|
136
|
+
* },
|
|
137
|
+
* dark: {
|
|
138
|
+
* colors: { bg: "#09090b", fg: "#fafafa", primary: "#60a5fa", border: "#27272a" }
|
|
139
|
+
* },
|
|
140
|
+
* })
|
|
141
|
+
*
|
|
142
|
+
* // Inject CSS:
|
|
143
|
+
* registry.inject()
|
|
144
|
+
*
|
|
145
|
+
* // Use tokens in components:
|
|
146
|
+
* const Card = tw.div`bg-[${vars.colors.bg}] text-[${vars.colors.fg}]`
|
|
147
|
+
*/
|
|
148
|
+
declare function createMultiTheme<T extends TokenMap>(config: MultiThemeConfig<T>): {
|
|
149
|
+
registry: ThemeRegistry;
|
|
150
|
+
vars: ThemeVars<T>;
|
|
151
|
+
light: Theme<T>;
|
|
152
|
+
dark: Theme<T>;
|
|
153
|
+
};
|
|
154
|
+
interface DesignTokens {
|
|
155
|
+
[path: string]: string | DesignTokens;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Flatten nested design token object into CSS variables.
|
|
159
|
+
* Supports Figma-style nested tokens.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* compileDesignTokens({
|
|
163
|
+
* color: {
|
|
164
|
+
* brand: { primary: "#3b82f6", secondary: "#6366f1" },
|
|
165
|
+
* neutral: { 50: "#fafafa", 900: "#09090b" }
|
|
166
|
+
* },
|
|
167
|
+
* spacing: { base: "4px", lg: "16px" }
|
|
168
|
+
* })
|
|
169
|
+
* →
|
|
170
|
+
* :root {
|
|
171
|
+
* --color-brand-primary: #3b82f6;
|
|
172
|
+
* --color-brand-secondary: #6366f1;
|
|
173
|
+
* --color-neutral-50: #fafafa;
|
|
174
|
+
* --color-neutral-900: #09090b;
|
|
175
|
+
* --spacing-base: 4px;
|
|
176
|
+
* --spacing-lg: 16px;
|
|
177
|
+
* }
|
|
178
|
+
*/
|
|
179
|
+
declare function compileDesignTokens(tokens: DesignTokens, prefix?: string): string;
|
|
180
|
+
|
|
181
|
+
export { type DesignTokens, type MultiThemeConfig, type Theme, type ThemeContract, ThemeRegistry, type ThemeVars, type TokenMap, compileDesignTokens, createMultiTheme, createTheme, defineThemeContract };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function defineThemeContract(shape) {
|
|
3
|
+
const vars = {};
|
|
4
|
+
for (const group in shape) {
|
|
5
|
+
;
|
|
6
|
+
vars[group] = {};
|
|
7
|
+
for (const token in shape[group]) {
|
|
8
|
+
;
|
|
9
|
+
vars[group][token] = `var(--${group}-${token})`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return { _contract: shape, _vars: vars };
|
|
13
|
+
}
|
|
14
|
+
function createTheme(contract, name, values, asRoot = false) {
|
|
15
|
+
const flatVars = {};
|
|
16
|
+
const cssLines = [];
|
|
17
|
+
for (const group in values) {
|
|
18
|
+
for (const token in values[group]) {
|
|
19
|
+
const varName = `--${group}-${token}`;
|
|
20
|
+
const value = values[group][token];
|
|
21
|
+
flatVars[varName] = value;
|
|
22
|
+
cssLines.push(` ${varName}: ${value};`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const selector = asRoot ? ":root" : `[data-theme="${name}"]`;
|
|
26
|
+
const css = `${selector} {
|
|
27
|
+
${cssLines.join("\n")}
|
|
28
|
+
}`;
|
|
29
|
+
return {
|
|
30
|
+
name,
|
|
31
|
+
contract,
|
|
32
|
+
values,
|
|
33
|
+
css,
|
|
34
|
+
vars: flatVars,
|
|
35
|
+
selector
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
var ThemeRegistry = class {
|
|
39
|
+
constructor() {
|
|
40
|
+
this.themes = /* @__PURE__ */ new Map();
|
|
41
|
+
this.defaultTheme = null;
|
|
42
|
+
}
|
|
43
|
+
/** Register a theme */
|
|
44
|
+
register(theme, isDefault = false) {
|
|
45
|
+
this.themes.set(theme.name, theme);
|
|
46
|
+
if (isDefault || !this.defaultTheme) {
|
|
47
|
+
this.defaultTheme = theme.name;
|
|
48
|
+
}
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/** Get a theme by name */
|
|
52
|
+
get(name) {
|
|
53
|
+
return this.themes.get(name);
|
|
54
|
+
}
|
|
55
|
+
/** Get all theme names */
|
|
56
|
+
names() {
|
|
57
|
+
return Array.from(this.themes.keys());
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Generate combined CSS for all themes.
|
|
61
|
+
* Inject into <head> or a .css file.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // In globals.css or layout.tsx
|
|
65
|
+
* const css = registry.generateCss()
|
|
66
|
+
*/
|
|
67
|
+
generateCss() {
|
|
68
|
+
return Array.from(this.themes.values()).map((t) => t.css).join("\n\n");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the CSS for a specific theme only.
|
|
72
|
+
*/
|
|
73
|
+
getThemeCss(name) {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
return (_b = (_a = this.themes.get(name)) == null ? void 0 : _a.css) != null ? _b : null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Inject all theme CSS into document <head> (browser only).
|
|
79
|
+
* Call once on app init.
|
|
80
|
+
*/
|
|
81
|
+
inject(styleId = "__tw_themes") {
|
|
82
|
+
if (typeof document === "undefined") return;
|
|
83
|
+
let style = document.getElementById(styleId);
|
|
84
|
+
if (!style) {
|
|
85
|
+
style = document.createElement("style");
|
|
86
|
+
style.id = styleId;
|
|
87
|
+
document.head.appendChild(style);
|
|
88
|
+
}
|
|
89
|
+
style.textContent = this.generateCss();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Switch active theme by setting data-theme on <html>.
|
|
93
|
+
*/
|
|
94
|
+
apply(name, target = document.documentElement) {
|
|
95
|
+
if (typeof document === "undefined") return;
|
|
96
|
+
if (!this.themes.has(name)) {
|
|
97
|
+
console.warn(`[tailwind-styled-v4] Theme "${name}" not registered.`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
target.dataset.theme = name;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get current active theme name from data-theme attribute.
|
|
104
|
+
*/
|
|
105
|
+
current(target = document.documentElement) {
|
|
106
|
+
var _a;
|
|
107
|
+
if (typeof document === "undefined") return this.defaultTheme;
|
|
108
|
+
return (_a = target.dataset.theme) != null ? _a : this.defaultTheme;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
function createMultiTheme(config) {
|
|
112
|
+
var _a;
|
|
113
|
+
const registry = new ThemeRegistry();
|
|
114
|
+
const light = createTheme(config.contract, "light", config.light, true);
|
|
115
|
+
const dark = createTheme(config.contract, "dark", config.dark, false);
|
|
116
|
+
registry.register(light, true);
|
|
117
|
+
registry.register(dark);
|
|
118
|
+
for (const [name, values] of Object.entries((_a = config.extras) != null ? _a : {})) {
|
|
119
|
+
registry.register(createTheme(config.contract, name, values));
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
registry,
|
|
123
|
+
vars: config.contract._vars,
|
|
124
|
+
light,
|
|
125
|
+
dark
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function compileDesignTokens(tokens, prefix = "") {
|
|
129
|
+
const vars = [];
|
|
130
|
+
function flatten(obj, path) {
|
|
131
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
132
|
+
const varPath = path ? `${path}-${key}` : key;
|
|
133
|
+
if (typeof value === "string") {
|
|
134
|
+
vars.push(` --${varPath}: ${value};`);
|
|
135
|
+
} else {
|
|
136
|
+
flatten(value, varPath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
flatten(tokens, prefix);
|
|
141
|
+
return `:root {
|
|
142
|
+
${vars.join("\n")}
|
|
143
|
+
}`;
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
ThemeRegistry,
|
|
147
|
+
compileDesignTokens,
|
|
148
|
+
createMultiTheme,
|
|
149
|
+
createTheme,
|
|
150
|
+
defineThemeContract
|
|
151
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tailwind-styled/theme",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Enterprise multi-theme engine for tailwind-styled-v4 — CSS variable output, TypeScript-safe contracts",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": ["dist"],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --out-dir dist --clean",
|
|
21
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --out-dir dist --watch"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8",
|
|
25
|
+
"typescript": "^5"
|
|
26
|
+
}
|
|
27
|
+
}
|