@stackwright/themes 0.3.1-alpha.4 → 0.3.1
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/CHANGELOG.md +20 -0
- package/dist/index.d.mts +13 -13
- package/dist/index.d.ts +13 -13
- package/dist/index.js +57 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +58 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/ThemeProvider.tsx +5 -4
- package/src/themeLoader.ts +54 -66
- package/src/types.ts +58 -58
- package/test/setup.ts +1 -0
- package/test/themeLoader.test.ts +174 -0
- package/tsconfig.json +2 -17
- /package/{.continue/rules/CONTINUE.md → AGENTS.md} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @stackwright/themes
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- dc2db25: Adding null checks to core
|
|
8
|
+
- bd7cd6e: Internal packagename refactor.
|
|
9
|
+
- ca71410: Core testing implemented
|
|
10
|
+
- 51dbbc9: Refactor types out of core into own package.
|
|
11
|
+
- f195337: Adding test dependencies to all packages.
|
|
12
|
+
- 5ff20a6: Fixing mixed compilation tooling (tsup/tsc) to only tsup
|
|
13
|
+
- 46df7ac: Documentation updates
|
|
14
|
+
- e4fbf2f: Update all dependencies
|
|
15
|
+
- cc761ce: More version updates
|
|
16
|
+
|
|
17
|
+
## 0.3.1-alpha.5
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- dc2db25: Adding null checks to core
|
|
22
|
+
|
|
3
23
|
## 0.3.1-alpha.4
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -15,12 +15,12 @@ interface ThemeConfig {
|
|
|
15
15
|
};
|
|
16
16
|
backgroundImage?: {
|
|
17
17
|
url: string;
|
|
18
|
-
repeat?:
|
|
19
|
-
size?:
|
|
18
|
+
repeat?: "repeat" | "repeat-x" | "repeat-y" | "no-repeat";
|
|
19
|
+
size?: "auto" | "cover" | "contain" | string;
|
|
20
20
|
position?: string;
|
|
21
|
-
attachment?:
|
|
21
|
+
attachment?: "scroll" | "fixed" | "local";
|
|
22
22
|
scale?: number;
|
|
23
|
-
animation?:
|
|
23
|
+
animation?: "drift" | "float" | "shimmer" | "shimmer-float" | "none";
|
|
24
24
|
customAnimation?: string;
|
|
25
25
|
};
|
|
26
26
|
typography: {
|
|
@@ -34,8 +34,8 @@ interface ThemeConfig {
|
|
|
34
34
|
base: string;
|
|
35
35
|
lg: string;
|
|
36
36
|
xl: string;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
"2xl": string;
|
|
38
|
+
"3xl": string;
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
41
|
spacing: {
|
|
@@ -44,13 +44,13 @@ interface ThemeConfig {
|
|
|
44
44
|
md: string;
|
|
45
45
|
lg: string;
|
|
46
46
|
xl: string;
|
|
47
|
-
|
|
47
|
+
"2xl": string;
|
|
48
48
|
};
|
|
49
|
-
components
|
|
50
|
-
button
|
|
51
|
-
card
|
|
52
|
-
header
|
|
53
|
-
footer
|
|
49
|
+
components?: {
|
|
50
|
+
button?: ComponentStyle;
|
|
51
|
+
card?: ComponentStyle;
|
|
52
|
+
header?: ComponentStyle;
|
|
53
|
+
footer?: ComponentStyle;
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
interface ComponentStyle {
|
|
@@ -68,7 +68,7 @@ interface Theme extends ThemeConfig {
|
|
|
68
68
|
|
|
69
69
|
interface ThemeContextType {
|
|
70
70
|
theme: Theme;
|
|
71
|
-
setTheme
|
|
71
|
+
setTheme: (theme: Theme) => void;
|
|
72
72
|
}
|
|
73
73
|
interface ThemeProviderProps {
|
|
74
74
|
theme: Theme;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,12 +15,12 @@ interface ThemeConfig {
|
|
|
15
15
|
};
|
|
16
16
|
backgroundImage?: {
|
|
17
17
|
url: string;
|
|
18
|
-
repeat?:
|
|
19
|
-
size?:
|
|
18
|
+
repeat?: "repeat" | "repeat-x" | "repeat-y" | "no-repeat";
|
|
19
|
+
size?: "auto" | "cover" | "contain" | string;
|
|
20
20
|
position?: string;
|
|
21
|
-
attachment?:
|
|
21
|
+
attachment?: "scroll" | "fixed" | "local";
|
|
22
22
|
scale?: number;
|
|
23
|
-
animation?:
|
|
23
|
+
animation?: "drift" | "float" | "shimmer" | "shimmer-float" | "none";
|
|
24
24
|
customAnimation?: string;
|
|
25
25
|
};
|
|
26
26
|
typography: {
|
|
@@ -34,8 +34,8 @@ interface ThemeConfig {
|
|
|
34
34
|
base: string;
|
|
35
35
|
lg: string;
|
|
36
36
|
xl: string;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
"2xl": string;
|
|
38
|
+
"3xl": string;
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
41
|
spacing: {
|
|
@@ -44,13 +44,13 @@ interface ThemeConfig {
|
|
|
44
44
|
md: string;
|
|
45
45
|
lg: string;
|
|
46
46
|
xl: string;
|
|
47
|
-
|
|
47
|
+
"2xl": string;
|
|
48
48
|
};
|
|
49
|
-
components
|
|
50
|
-
button
|
|
51
|
-
card
|
|
52
|
-
header
|
|
53
|
-
footer
|
|
49
|
+
components?: {
|
|
50
|
+
button?: ComponentStyle;
|
|
51
|
+
card?: ComponentStyle;
|
|
52
|
+
header?: ComponentStyle;
|
|
53
|
+
footer?: ComponentStyle;
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
interface ComponentStyle {
|
|
@@ -68,7 +68,7 @@ interface Theme extends ThemeConfig {
|
|
|
68
68
|
|
|
69
69
|
interface ThemeContextType {
|
|
70
70
|
theme: Theme;
|
|
71
|
-
setTheme
|
|
71
|
+
setTheme: (theme: Theme) => void;
|
|
72
72
|
}
|
|
73
73
|
interface ThemeProviderProps {
|
|
74
74
|
theme: Theme;
|
package/dist/index.js
CHANGED
|
@@ -40,8 +40,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
40
40
|
var import_react = require("react");
|
|
41
41
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
42
42
|
var ThemeContext = (0, import_react.createContext)(void 0);
|
|
43
|
-
var ThemeProvider = ({ theme, children }) => {
|
|
44
|
-
|
|
43
|
+
var ThemeProvider = ({ theme: initialTheme, children }) => {
|
|
44
|
+
const [theme, setTheme] = (0, import_react.useState)(initialTheme);
|
|
45
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value: { theme, setTheme }, children });
|
|
45
46
|
};
|
|
46
47
|
var useTheme = () => {
|
|
47
48
|
const context = (0, import_react.useContext)(ThemeContext);
|
|
@@ -85,80 +86,68 @@ var ThemeLoader = class {
|
|
|
85
86
|
static getEmbeddedTheme(name) {
|
|
86
87
|
const themes = {
|
|
87
88
|
corporate: `
|
|
89
|
+
id: "corporate"
|
|
88
90
|
name: "Corporate"
|
|
91
|
+
description: "A professional amber-toned corporate theme"
|
|
89
92
|
colors:
|
|
90
|
-
primary:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
50: "#f8fafc"
|
|
98
|
-
100: "#f1f5f9"
|
|
99
|
-
700: "#334155"
|
|
100
|
-
800: "#1e293b"
|
|
101
|
-
900: "#0f172a"
|
|
102
|
-
text:
|
|
103
|
-
primary: "#1f2937"
|
|
104
|
-
secondary: "#6b7280"
|
|
105
|
-
inverse: "#ffffff"
|
|
106
|
-
|
|
107
|
-
spacing:
|
|
108
|
-
section: "py-20"
|
|
109
|
-
container: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
|
110
|
-
|
|
93
|
+
primary: "#f59e0b"
|
|
94
|
+
secondary: "#334155"
|
|
95
|
+
accent: "#d97706"
|
|
96
|
+
background: "#f8fafc"
|
|
97
|
+
surface: "#ffffff"
|
|
98
|
+
text: "#1f2937"
|
|
99
|
+
textSecondary: "#6b7280"
|
|
111
100
|
typography:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
101
|
+
fontFamily:
|
|
102
|
+
primary: "Roboto, sans-serif"
|
|
103
|
+
secondary: "Roboto, sans-serif"
|
|
104
|
+
scale:
|
|
105
|
+
xs: "0.75rem"
|
|
106
|
+
sm: "0.875rem"
|
|
107
|
+
base: "1rem"
|
|
108
|
+
lg: "1.125rem"
|
|
109
|
+
xl: "1.25rem"
|
|
110
|
+
2xl: "1.5rem"
|
|
111
|
+
3xl: "1.875rem"
|
|
112
|
+
spacing:
|
|
113
|
+
xs: "0.5rem"
|
|
114
|
+
sm: "0.75rem"
|
|
115
|
+
md: "1rem"
|
|
116
|
+
lg: "1.5rem"
|
|
117
|
+
xl: "2rem"
|
|
118
|
+
2xl: "3rem"
|
|
124
119
|
`,
|
|
125
120
|
soft: `
|
|
121
|
+
id: "soft"
|
|
126
122
|
name: "Soft"
|
|
123
|
+
description: "A gentle pink-toned soft theme"
|
|
127
124
|
colors:
|
|
128
|
-
primary:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
50: "#fefefe"
|
|
136
|
-
100: "#f9fafb"
|
|
137
|
-
700: "#6b7280"
|
|
138
|
-
800: "#4b5563"
|
|
139
|
-
900: "#374151"
|
|
140
|
-
text:
|
|
141
|
-
primary: "#374151"
|
|
142
|
-
secondary: "#9ca3af"
|
|
143
|
-
inverse: "#ffffff"
|
|
144
|
-
|
|
145
|
-
spacing:
|
|
146
|
-
section: "py-16"
|
|
147
|
-
container: "max-w-6xl mx-auto px-6 sm:px-8 lg:px-10"
|
|
148
|
-
|
|
125
|
+
primary: "#ec4899"
|
|
126
|
+
secondary: "#6b7280"
|
|
127
|
+
accent: "#db2777"
|
|
128
|
+
background: "#f9fafb"
|
|
129
|
+
surface: "#ffffff"
|
|
130
|
+
text: "#374151"
|
|
131
|
+
textSecondary: "#9ca3af"
|
|
149
132
|
typography:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
133
|
+
fontFamily:
|
|
134
|
+
primary: "Roboto, sans-serif"
|
|
135
|
+
secondary: "Roboto, sans-serif"
|
|
136
|
+
scale:
|
|
137
|
+
xs: "0.75rem"
|
|
138
|
+
sm: "0.875rem"
|
|
139
|
+
base: "1rem"
|
|
140
|
+
lg: "1.125rem"
|
|
141
|
+
xl: "1.25rem"
|
|
142
|
+
2xl: "1.5rem"
|
|
143
|
+
3xl: "1.875rem"
|
|
144
|
+
spacing:
|
|
145
|
+
xs: "0.5rem"
|
|
146
|
+
sm: "0.75rem"
|
|
147
|
+
md: "1rem"
|
|
148
|
+
lg: "1.5rem"
|
|
149
|
+
xl: "2rem"
|
|
150
|
+
2xl: "3rem"
|
|
162
151
|
`
|
|
163
152
|
};
|
|
164
153
|
if (!themes[name]) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/ThemeProvider.tsx","../src/themeLoader.ts"],"sourcesContent":["export * from './types';\nexport * from './ThemeProvider';\nexport * from './themeLoader';\nexport type { ThemeConfig, Theme, ComponentStyle } from './types';","import React, { createContext, useContext, ReactNode } from 'react';\nimport { Theme } from './types';\n\ninterface ThemeContextType {\n theme: Theme;\n setTheme
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/ThemeProvider.tsx","../src/themeLoader.ts"],"sourcesContent":["export * from './types';\nexport * from './ThemeProvider';\nexport * from './themeLoader';\nexport type { ThemeConfig, Theme, ComponentStyle } from './types';","import React, { createContext, useContext, useState, ReactNode } from 'react';\nimport { Theme } from './types';\n\ninterface ThemeContextType {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\ninterface ThemeProviderProps {\n theme: Theme;\n children: ReactNode;\n}\n\nexport const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme: initialTheme, children }) => {\n const [theme, setTheme] = useState<Theme>(initialTheme);\n return (\n <ThemeContext.Provider value={{ theme, setTheme }}>\n {children}\n </ThemeContext.Provider>\n );\n};\n\nexport const useTheme = (): ThemeContextType => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n};","import * as yaml from 'js-yaml';\nimport { Theme } from './types';\n\nexport class ThemeLoader {\n private static themes: Map<string, Theme> = new Map();\n\n static loadThemeFromYaml(yamlContent: string): Theme {\n try {\n const theme = yaml.load(yamlContent) as Theme;\n this.themes.set(theme.name, theme);\n return theme;\n } catch (error) {\n throw new Error(`Failed to parse theme YAML: ${error}`);\n }\n }\n\n static loadThemeFromFile(themeName: string): Theme {\n // In a real implementation, this would load from the file system\n // For now, we'll embed the themes\n const themeData = this.getEmbeddedTheme(themeName);\n return this.loadThemeFromYaml(themeData);\n }\n\n static getTheme(name: string): Theme | undefined {\n return this.themes.get(name);\n }\n\n static getAllThemes(): Theme[] {\n return Array.from(this.themes.values());\n }\n\n static registerCustomTheme(theme: Theme): void {\n this.themes.set(theme.name, theme);\n console.log(`🎨 Registered custom theme: ${theme.name}`);\n }\n\n static loadCustomTheme(theme: Theme): Theme {\n this.registerCustomTheme(theme);\n return theme;\n }\n\n private static getEmbeddedTheme(name: string): string {\n const themes: Record<string, string> = {\n corporate: `\nid: \"corporate\"\nname: \"Corporate\"\ndescription: \"A professional amber-toned corporate theme\"\ncolors:\n primary: \"#f59e0b\"\n secondary: \"#334155\"\n accent: \"#d97706\"\n background: \"#f8fafc\"\n surface: \"#ffffff\"\n text: \"#1f2937\"\n textSecondary: \"#6b7280\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n soft: `\nid: \"soft\"\nname: \"Soft\"\ndescription: \"A gentle pink-toned soft theme\"\ncolors:\n primary: \"#ec4899\"\n secondary: \"#6b7280\"\n accent: \"#db2777\"\n background: \"#f9fafb\"\n surface: \"#ffffff\"\n text: \"#374151\"\n textSecondary: \"#9ca3af\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`\n };\n\n if (!themes[name]) {\n throw new Error(`Theme '${name}' not found`);\n }\n\n return themes[name];\n }\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAsE;AAkBlE;AAVJ,IAAM,mBAAe,4BAA4C,MAAS;AAOnE,IAAM,gBAA8C,CAAC,EAAE,OAAO,cAAc,SAAS,MAAM;AAChG,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAgB,YAAY;AACtD,SACE,4CAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,OAAO,SAAS,GAC7C,UACH;AAEJ;AAEO,IAAM,WAAW,MAAwB;AAC9C,QAAM,cAAU,yBAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;;;AC9BA,WAAsB;AAGf,IAAM,cAAN,MAAkB;AAAA,EACvB,OAAe,SAA6B,oBAAI,IAAI;AAAA,EAEpD,OAAO,kBAAkB,aAA4B;AACnD,QAAI;AACF,YAAM,QAAa,UAAK,WAAW;AACnC,WAAK,OAAO,IAAI,MAAM,MAAM,KAAK;AACjC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,+BAA+B,KAAK,EAAE;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,OAAO,kBAAkB,WAA0B;AAGjD,UAAM,YAAY,KAAK,iBAAiB,SAAS;AACjD,WAAO,KAAK,kBAAkB,SAAS;AAAA,EACzC;AAAA,EAEA,OAAO,SAAS,MAAiC;AAC/C,WAAO,KAAK,OAAO,IAAI,IAAI;AAAA,EAC7B;AAAA,EAEA,OAAO,eAAwB;AAC7B,WAAO,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC;AAAA,EACxC;AAAA,EAEA,OAAO,oBAAoB,OAAoB;AAC7C,SAAK,OAAO,IAAI,MAAM,MAAM,KAAK;AACjC,YAAQ,IAAI,sCAA+B,MAAM,IAAI,EAAE;AAAA,EACzD;AAAA,EAEA,OAAO,gBAAgB,OAAqB;AAC1C,SAAK,oBAAoB,KAAK;AAC9B,WAAO;AAAA,EACT;AAAA,EAEA,OAAe,iBAAiB,MAAsB;AACpD,UAAM,SAAiC;AAAA,MACrC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAgCX,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgCR;AAEA,QAAI,CAAC,OAAO,IAAI,GAAG;AACjB,YAAM,IAAI,MAAM,UAAU,IAAI,aAAa;AAAA,IAC7C;AAEA,WAAO,OAAO,IAAI;AAAA,EACpB;AACF;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/ThemeProvider.tsx
|
|
2
|
-
import { createContext, useContext } from "react";
|
|
2
|
+
import { createContext, useContext, useState } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
var ThemeContext = createContext(void 0);
|
|
5
|
-
var ThemeProvider = ({ theme, children }) => {
|
|
6
|
-
|
|
5
|
+
var ThemeProvider = ({ theme: initialTheme, children }) => {
|
|
6
|
+
const [theme, setTheme] = useState(initialTheme);
|
|
7
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, setTheme }, children });
|
|
7
8
|
};
|
|
8
9
|
var useTheme = () => {
|
|
9
10
|
const context = useContext(ThemeContext);
|
|
@@ -47,80 +48,68 @@ var ThemeLoader = class {
|
|
|
47
48
|
static getEmbeddedTheme(name) {
|
|
48
49
|
const themes = {
|
|
49
50
|
corporate: `
|
|
51
|
+
id: "corporate"
|
|
50
52
|
name: "Corporate"
|
|
53
|
+
description: "A professional amber-toned corporate theme"
|
|
51
54
|
colors:
|
|
52
|
-
primary:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
50: "#f8fafc"
|
|
60
|
-
100: "#f1f5f9"
|
|
61
|
-
700: "#334155"
|
|
62
|
-
800: "#1e293b"
|
|
63
|
-
900: "#0f172a"
|
|
64
|
-
text:
|
|
65
|
-
primary: "#1f2937"
|
|
66
|
-
secondary: "#6b7280"
|
|
67
|
-
inverse: "#ffffff"
|
|
68
|
-
|
|
69
|
-
spacing:
|
|
70
|
-
section: "py-20"
|
|
71
|
-
container: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
|
72
|
-
|
|
55
|
+
primary: "#f59e0b"
|
|
56
|
+
secondary: "#334155"
|
|
57
|
+
accent: "#d97706"
|
|
58
|
+
background: "#f8fafc"
|
|
59
|
+
surface: "#ffffff"
|
|
60
|
+
text: "#1f2937"
|
|
61
|
+
textSecondary: "#6b7280"
|
|
73
62
|
typography:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
63
|
+
fontFamily:
|
|
64
|
+
primary: "Roboto, sans-serif"
|
|
65
|
+
secondary: "Roboto, sans-serif"
|
|
66
|
+
scale:
|
|
67
|
+
xs: "0.75rem"
|
|
68
|
+
sm: "0.875rem"
|
|
69
|
+
base: "1rem"
|
|
70
|
+
lg: "1.125rem"
|
|
71
|
+
xl: "1.25rem"
|
|
72
|
+
2xl: "1.5rem"
|
|
73
|
+
3xl: "1.875rem"
|
|
74
|
+
spacing:
|
|
75
|
+
xs: "0.5rem"
|
|
76
|
+
sm: "0.75rem"
|
|
77
|
+
md: "1rem"
|
|
78
|
+
lg: "1.5rem"
|
|
79
|
+
xl: "2rem"
|
|
80
|
+
2xl: "3rem"
|
|
86
81
|
`,
|
|
87
82
|
soft: `
|
|
83
|
+
id: "soft"
|
|
88
84
|
name: "Soft"
|
|
85
|
+
description: "A gentle pink-toned soft theme"
|
|
89
86
|
colors:
|
|
90
|
-
primary:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
50: "#fefefe"
|
|
98
|
-
100: "#f9fafb"
|
|
99
|
-
700: "#6b7280"
|
|
100
|
-
800: "#4b5563"
|
|
101
|
-
900: "#374151"
|
|
102
|
-
text:
|
|
103
|
-
primary: "#374151"
|
|
104
|
-
secondary: "#9ca3af"
|
|
105
|
-
inverse: "#ffffff"
|
|
106
|
-
|
|
107
|
-
spacing:
|
|
108
|
-
section: "py-16"
|
|
109
|
-
container: "max-w-6xl mx-auto px-6 sm:px-8 lg:px-10"
|
|
110
|
-
|
|
87
|
+
primary: "#ec4899"
|
|
88
|
+
secondary: "#6b7280"
|
|
89
|
+
accent: "#db2777"
|
|
90
|
+
background: "#f9fafb"
|
|
91
|
+
surface: "#ffffff"
|
|
92
|
+
text: "#374151"
|
|
93
|
+
textSecondary: "#9ca3af"
|
|
111
94
|
typography:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
95
|
+
fontFamily:
|
|
96
|
+
primary: "Roboto, sans-serif"
|
|
97
|
+
secondary: "Roboto, sans-serif"
|
|
98
|
+
scale:
|
|
99
|
+
xs: "0.75rem"
|
|
100
|
+
sm: "0.875rem"
|
|
101
|
+
base: "1rem"
|
|
102
|
+
lg: "1.125rem"
|
|
103
|
+
xl: "1.25rem"
|
|
104
|
+
2xl: "1.5rem"
|
|
105
|
+
3xl: "1.875rem"
|
|
106
|
+
spacing:
|
|
107
|
+
xs: "0.5rem"
|
|
108
|
+
sm: "0.75rem"
|
|
109
|
+
md: "1rem"
|
|
110
|
+
lg: "1.5rem"
|
|
111
|
+
xl: "2rem"
|
|
112
|
+
2xl: "3rem"
|
|
124
113
|
`
|
|
125
114
|
};
|
|
126
115
|
if (!themes[name]) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ThemeProvider.tsx","../src/themeLoader.ts"],"sourcesContent":["import React, { createContext, useContext, ReactNode } from 'react';\nimport { Theme } from './types';\n\ninterface ThemeContextType {\n theme: Theme;\n setTheme
|
|
1
|
+
{"version":3,"sources":["../src/ThemeProvider.tsx","../src/themeLoader.ts"],"sourcesContent":["import React, { createContext, useContext, useState, ReactNode } from 'react';\nimport { Theme } from './types';\n\ninterface ThemeContextType {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\ninterface ThemeProviderProps {\n theme: Theme;\n children: ReactNode;\n}\n\nexport const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme: initialTheme, children }) => {\n const [theme, setTheme] = useState<Theme>(initialTheme);\n return (\n <ThemeContext.Provider value={{ theme, setTheme }}>\n {children}\n </ThemeContext.Provider>\n );\n};\n\nexport const useTheme = (): ThemeContextType => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n};","import * as yaml from 'js-yaml';\nimport { Theme } from './types';\n\nexport class ThemeLoader {\n private static themes: Map<string, Theme> = new Map();\n\n static loadThemeFromYaml(yamlContent: string): Theme {\n try {\n const theme = yaml.load(yamlContent) as Theme;\n this.themes.set(theme.name, theme);\n return theme;\n } catch (error) {\n throw new Error(`Failed to parse theme YAML: ${error}`);\n }\n }\n\n static loadThemeFromFile(themeName: string): Theme {\n // In a real implementation, this would load from the file system\n // For now, we'll embed the themes\n const themeData = this.getEmbeddedTheme(themeName);\n return this.loadThemeFromYaml(themeData);\n }\n\n static getTheme(name: string): Theme | undefined {\n return this.themes.get(name);\n }\n\n static getAllThemes(): Theme[] {\n return Array.from(this.themes.values());\n }\n\n static registerCustomTheme(theme: Theme): void {\n this.themes.set(theme.name, theme);\n console.log(`🎨 Registered custom theme: ${theme.name}`);\n }\n\n static loadCustomTheme(theme: Theme): Theme {\n this.registerCustomTheme(theme);\n return theme;\n }\n\n private static getEmbeddedTheme(name: string): string {\n const themes: Record<string, string> = {\n corporate: `\nid: \"corporate\"\nname: \"Corporate\"\ndescription: \"A professional amber-toned corporate theme\"\ncolors:\n primary: \"#f59e0b\"\n secondary: \"#334155\"\n accent: \"#d97706\"\n background: \"#f8fafc\"\n surface: \"#ffffff\"\n text: \"#1f2937\"\n textSecondary: \"#6b7280\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n soft: `\nid: \"soft\"\nname: \"Soft\"\ndescription: \"A gentle pink-toned soft theme\"\ncolors:\n primary: \"#ec4899\"\n secondary: \"#6b7280\"\n accent: \"#db2777\"\n background: \"#f9fafb\"\n surface: \"#ffffff\"\n text: \"#374151\"\n textSecondary: \"#9ca3af\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`\n };\n\n if (!themes[name]) {\n throw new Error(`Theme '${name}' not found`);\n }\n\n return themes[name];\n }\n}"],"mappings":";AAAA,SAAgB,eAAe,YAAY,gBAA2B;AAkBlE;AAVJ,IAAM,eAAe,cAA4C,MAAS;AAOnE,IAAM,gBAA8C,CAAC,EAAE,OAAO,cAAc,SAAS,MAAM;AAChG,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAgB,YAAY;AACtD,SACE,oBAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,OAAO,SAAS,GAC7C,UACH;AAEJ;AAEO,IAAM,WAAW,MAAwB;AAC9C,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;;;AC9BA,YAAY,UAAU;AAGf,IAAM,cAAN,MAAkB;AAAA,EACvB,OAAe,SAA6B,oBAAI,IAAI;AAAA,EAEpD,OAAO,kBAAkB,aAA4B;AACnD,QAAI;AACF,YAAM,QAAa,UAAK,WAAW;AACnC,WAAK,OAAO,IAAI,MAAM,MAAM,KAAK;AACjC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,+BAA+B,KAAK,EAAE;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,OAAO,kBAAkB,WAA0B;AAGjD,UAAM,YAAY,KAAK,iBAAiB,SAAS;AACjD,WAAO,KAAK,kBAAkB,SAAS;AAAA,EACzC;AAAA,EAEA,OAAO,SAAS,MAAiC;AAC/C,WAAO,KAAK,OAAO,IAAI,IAAI;AAAA,EAC7B;AAAA,EAEA,OAAO,eAAwB;AAC7B,WAAO,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC;AAAA,EACxC;AAAA,EAEA,OAAO,oBAAoB,OAAoB;AAC7C,SAAK,OAAO,IAAI,MAAM,MAAM,KAAK;AACjC,YAAQ,IAAI,sCAA+B,MAAM,IAAI,EAAE;AAAA,EACzD;AAAA,EAEA,OAAO,gBAAgB,OAAqB;AAC1C,SAAK,oBAAoB,KAAK;AAC9B,WAAO;AAAA,EACT;AAAA,EAEA,OAAe,iBAAiB,MAAsB;AACpD,UAAM,SAAiC;AAAA,MACrC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAgCX,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgCR;AAEA,QAAI,CAAC,OAAO,IAAI,GAAG;AACjB,YAAM,IAAI,MAAM,UAAU,IAAI,aAAa;AAAA,IAC7C;AAEA,WAAO,OAAO,IAAI;AAAA,EACpB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackwright/themes",
|
|
3
|
-
"version": "0.3.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsup",
|
|
19
|
-
"dev": "tsup --watch"
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"test": "vitest",
|
|
21
|
+
"test:run": "vitest run"
|
|
20
22
|
}
|
|
21
23
|
}
|
package/src/ThemeProvider.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React, { createContext, useContext, ReactNode } from 'react';
|
|
1
|
+
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
|
2
2
|
import { Theme } from './types';
|
|
3
3
|
|
|
4
4
|
interface ThemeContextType {
|
|
5
5
|
theme: Theme;
|
|
6
|
-
setTheme
|
|
6
|
+
setTheme: (theme: Theme) => void;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
@@ -13,9 +13,10 @@ interface ThemeProviderProps {
|
|
|
13
13
|
children: ReactNode;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme, children }) => {
|
|
16
|
+
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme: initialTheme, children }) => {
|
|
17
|
+
const [theme, setTheme] = useState<Theme>(initialTheme);
|
|
17
18
|
return (
|
|
18
|
-
<ThemeContext.Provider value={{ theme }}>
|
|
19
|
+
<ThemeContext.Provider value={{ theme, setTheme }}>
|
|
19
20
|
{children}
|
|
20
21
|
</ThemeContext.Provider>
|
|
21
22
|
);
|
package/src/themeLoader.ts
CHANGED
|
@@ -42,80 +42,68 @@ export class ThemeLoader {
|
|
|
42
42
|
private static getEmbeddedTheme(name: string): string {
|
|
43
43
|
const themes: Record<string, string> = {
|
|
44
44
|
corporate: `
|
|
45
|
+
id: "corporate"
|
|
45
46
|
name: "Corporate"
|
|
47
|
+
description: "A professional amber-toned corporate theme"
|
|
46
48
|
colors:
|
|
47
|
-
primary:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
50: "#f8fafc"
|
|
55
|
-
100: "#f1f5f9"
|
|
56
|
-
700: "#334155"
|
|
57
|
-
800: "#1e293b"
|
|
58
|
-
900: "#0f172a"
|
|
59
|
-
text:
|
|
60
|
-
primary: "#1f2937"
|
|
61
|
-
secondary: "#6b7280"
|
|
62
|
-
inverse: "#ffffff"
|
|
63
|
-
|
|
64
|
-
spacing:
|
|
65
|
-
section: "py-20"
|
|
66
|
-
container: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
|
67
|
-
|
|
49
|
+
primary: "#f59e0b"
|
|
50
|
+
secondary: "#334155"
|
|
51
|
+
accent: "#d97706"
|
|
52
|
+
background: "#f8fafc"
|
|
53
|
+
surface: "#ffffff"
|
|
54
|
+
text: "#1f2937"
|
|
55
|
+
textSecondary: "#6b7280"
|
|
68
56
|
typography:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
57
|
+
fontFamily:
|
|
58
|
+
primary: "Roboto, sans-serif"
|
|
59
|
+
secondary: "Roboto, sans-serif"
|
|
60
|
+
scale:
|
|
61
|
+
xs: "0.75rem"
|
|
62
|
+
sm: "0.875rem"
|
|
63
|
+
base: "1rem"
|
|
64
|
+
lg: "1.125rem"
|
|
65
|
+
xl: "1.25rem"
|
|
66
|
+
2xl: "1.5rem"
|
|
67
|
+
3xl: "1.875rem"
|
|
68
|
+
spacing:
|
|
69
|
+
xs: "0.5rem"
|
|
70
|
+
sm: "0.75rem"
|
|
71
|
+
md: "1rem"
|
|
72
|
+
lg: "1.5rem"
|
|
73
|
+
xl: "2rem"
|
|
74
|
+
2xl: "3rem"
|
|
81
75
|
`,
|
|
82
76
|
soft: `
|
|
77
|
+
id: "soft"
|
|
83
78
|
name: "Soft"
|
|
79
|
+
description: "A gentle pink-toned soft theme"
|
|
84
80
|
colors:
|
|
85
|
-
primary:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
50: "#fefefe"
|
|
93
|
-
100: "#f9fafb"
|
|
94
|
-
700: "#6b7280"
|
|
95
|
-
800: "#4b5563"
|
|
96
|
-
900: "#374151"
|
|
97
|
-
text:
|
|
98
|
-
primary: "#374151"
|
|
99
|
-
secondary: "#9ca3af"
|
|
100
|
-
inverse: "#ffffff"
|
|
101
|
-
|
|
102
|
-
spacing:
|
|
103
|
-
section: "py-16"
|
|
104
|
-
container: "max-w-6xl mx-auto px-6 sm:px-8 lg:px-10"
|
|
105
|
-
|
|
81
|
+
primary: "#ec4899"
|
|
82
|
+
secondary: "#6b7280"
|
|
83
|
+
accent: "#db2777"
|
|
84
|
+
background: "#f9fafb"
|
|
85
|
+
surface: "#ffffff"
|
|
86
|
+
text: "#374151"
|
|
87
|
+
textSecondary: "#9ca3af"
|
|
106
88
|
typography:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
89
|
+
fontFamily:
|
|
90
|
+
primary: "Roboto, sans-serif"
|
|
91
|
+
secondary: "Roboto, sans-serif"
|
|
92
|
+
scale:
|
|
93
|
+
xs: "0.75rem"
|
|
94
|
+
sm: "0.875rem"
|
|
95
|
+
base: "1rem"
|
|
96
|
+
lg: "1.125rem"
|
|
97
|
+
xl: "1.25rem"
|
|
98
|
+
2xl: "1.5rem"
|
|
99
|
+
3xl: "1.875rem"
|
|
100
|
+
spacing:
|
|
101
|
+
xs: "0.5rem"
|
|
102
|
+
sm: "0.75rem"
|
|
103
|
+
md: "1rem"
|
|
104
|
+
lg: "1.5rem"
|
|
105
|
+
xl: "2rem"
|
|
106
|
+
2xl: "3rem"
|
|
119
107
|
`
|
|
120
108
|
};
|
|
121
109
|
|
package/src/types.ts
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
1
|
export interface ThemeConfig {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
backgroundImage?: {
|
|
15
|
-
url: string;
|
|
16
|
-
repeat?: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat';
|
|
17
|
-
size?: 'auto' | 'cover' | 'contain' | string;
|
|
18
|
-
position?: string;
|
|
19
|
-
attachment?: 'scroll' | 'fixed' | 'local';
|
|
20
|
-
scale?: number;
|
|
21
|
-
animation?: 'drift' | 'float' | 'shimmer' | 'shimmer-float' | 'none';
|
|
22
|
-
customAnimation?: string;
|
|
23
|
-
};
|
|
24
|
-
typography: {
|
|
25
|
-
fontFamily: {
|
|
26
|
-
primary: string;
|
|
27
|
-
secondary: string;
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
colors: {
|
|
6
|
+
primary: string;
|
|
7
|
+
secondary: string;
|
|
8
|
+
accent: string;
|
|
9
|
+
background: string;
|
|
10
|
+
surface: string;
|
|
11
|
+
text: string;
|
|
12
|
+
textSecondary: string;
|
|
28
13
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
14
|
+
backgroundImage?: {
|
|
15
|
+
url: string;
|
|
16
|
+
repeat?: "repeat" | "repeat-x" | "repeat-y" | "no-repeat";
|
|
17
|
+
size?: "auto" | "cover" | "contain" | string;
|
|
18
|
+
position?: string;
|
|
19
|
+
attachment?: "scroll" | "fixed" | "local";
|
|
20
|
+
scale?: number;
|
|
21
|
+
animation?: "drift" | "float" | "shimmer" | "shimmer-float" | "none";
|
|
22
|
+
customAnimation?: string;
|
|
23
|
+
};
|
|
24
|
+
typography: {
|
|
25
|
+
fontFamily: {
|
|
26
|
+
primary: string;
|
|
27
|
+
secondary: string;
|
|
28
|
+
};
|
|
29
|
+
scale: {
|
|
30
|
+
xs: string;
|
|
31
|
+
sm: string;
|
|
32
|
+
base: string;
|
|
33
|
+
lg: string;
|
|
34
|
+
xl: string;
|
|
35
|
+
"2xl": string;
|
|
36
|
+
"3xl": string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
spacing: {
|
|
40
|
+
xs: string;
|
|
41
|
+
sm: string;
|
|
42
|
+
md: string;
|
|
43
|
+
lg: string;
|
|
44
|
+
xl: string;
|
|
45
|
+
"2xl": string;
|
|
46
|
+
};
|
|
47
|
+
components?: {
|
|
48
|
+
button?: ComponentStyle;
|
|
49
|
+
card?: ComponentStyle;
|
|
50
|
+
header?: ComponentStyle;
|
|
51
|
+
footer?: ComponentStyle;
|
|
37
52
|
};
|
|
38
|
-
};
|
|
39
|
-
spacing: {
|
|
40
|
-
xs: string;
|
|
41
|
-
sm: string;
|
|
42
|
-
md: string;
|
|
43
|
-
lg: string;
|
|
44
|
-
xl: string;
|
|
45
|
-
'2xl': string;
|
|
46
|
-
};
|
|
47
|
-
components: {
|
|
48
|
-
button: ComponentStyle;
|
|
49
|
-
card: ComponentStyle;
|
|
50
|
-
header: ComponentStyle;
|
|
51
|
-
footer: ComponentStyle;
|
|
52
|
-
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export interface ComponentStyle {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
base?: string;
|
|
57
|
+
primary?: string;
|
|
58
|
+
secondary?: string;
|
|
59
|
+
outline?: string;
|
|
60
|
+
shadow?: string;
|
|
61
|
+
nav?: string;
|
|
62
|
+
text?: string;
|
|
63
|
+
[key: string]: string | undefined;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export interface Theme extends ThemeConfig {}
|
|
66
|
+
export interface Theme extends ThemeConfig {}
|
package/test/setup.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Vitest setup for @stackwright/themes
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { ThemeLoader } from '../src/themeLoader';
|
|
3
|
+
import type { Theme } from '../src/types';
|
|
4
|
+
|
|
5
|
+
// The ThemeLoader uses a static Map, so we need to be careful about state
|
|
6
|
+
// leaking between tests. We work around this by using unique theme names.
|
|
7
|
+
|
|
8
|
+
const makeCustomTheme = (overrides: Partial<Theme> = {}): Theme => ({
|
|
9
|
+
id: 'test-theme',
|
|
10
|
+
name: 'Test Theme',
|
|
11
|
+
description: 'A theme for testing',
|
|
12
|
+
colors: {
|
|
13
|
+
primary: '#aabbcc',
|
|
14
|
+
secondary: '#112233',
|
|
15
|
+
accent: '#ccbbaa',
|
|
16
|
+
background: '#ffffff',
|
|
17
|
+
surface: '#f0f0f0',
|
|
18
|
+
text: '#000000',
|
|
19
|
+
textSecondary: '#666666',
|
|
20
|
+
},
|
|
21
|
+
typography: {
|
|
22
|
+
fontFamily: { primary: 'Arial, sans-serif', secondary: 'Georgia, serif' },
|
|
23
|
+
scale: {
|
|
24
|
+
xs: '0.75rem', sm: '0.875rem', base: '1rem',
|
|
25
|
+
lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
spacing: {
|
|
29
|
+
xs: '0.5rem', sm: '0.75rem', md: '1rem',
|
|
30
|
+
lg: '1.5rem', xl: '2rem', '2xl': '3rem',
|
|
31
|
+
},
|
|
32
|
+
...overrides,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('ThemeLoader.loadThemeFromFile', () => {
|
|
36
|
+
it('loads the corporate theme and returns a valid ThemeConfig', () => {
|
|
37
|
+
const theme = ThemeLoader.loadThemeFromFile('corporate');
|
|
38
|
+
expect(theme).toBeDefined();
|
|
39
|
+
expect(theme.name).toBe('Corporate');
|
|
40
|
+
expect(theme.id).toBe('corporate');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('corporate theme has flat hex string colors (not nested objects)', () => {
|
|
44
|
+
const theme = ThemeLoader.loadThemeFromFile('corporate');
|
|
45
|
+
expect(typeof theme.colors.primary).toBe('string');
|
|
46
|
+
expect(theme.colors.primary).toMatch(/^#[0-9a-fA-F]{6}$/);
|
|
47
|
+
expect(typeof theme.colors.secondary).toBe('string');
|
|
48
|
+
expect(typeof theme.colors.background).toBe('string');
|
|
49
|
+
expect(typeof theme.colors.surface).toBe('string');
|
|
50
|
+
expect(typeof theme.colors.text).toBe('string');
|
|
51
|
+
expect(typeof theme.colors.textSecondary).toBe('string');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('loads the soft theme and returns a valid ThemeConfig', () => {
|
|
55
|
+
const theme = ThemeLoader.loadThemeFromFile('soft');
|
|
56
|
+
expect(theme).toBeDefined();
|
|
57
|
+
expect(theme.name).toBe('Soft');
|
|
58
|
+
expect(theme.id).toBe('soft');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('soft theme has flat hex string colors', () => {
|
|
62
|
+
const theme = ThemeLoader.loadThemeFromFile('soft');
|
|
63
|
+
expect(typeof theme.colors.primary).toBe('string');
|
|
64
|
+
expect(theme.colors.primary).toMatch(/^#[0-9a-fA-F]{6}$/);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('throws for an unknown theme name', () => {
|
|
68
|
+
expect(() => ThemeLoader.loadThemeFromFile('nonexistent')).toThrow(
|
|
69
|
+
"Theme 'nonexistent' not found"
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('loaded themes have required typography and spacing fields', () => {
|
|
74
|
+
const theme = ThemeLoader.loadThemeFromFile('corporate');
|
|
75
|
+
expect(theme.typography).toBeDefined();
|
|
76
|
+
expect(theme.typography.fontFamily.primary).toBeTruthy();
|
|
77
|
+
expect(theme.spacing).toBeDefined();
|
|
78
|
+
expect(theme.spacing.md).toBeTruthy();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('ThemeLoader.loadThemeFromYaml', () => {
|
|
83
|
+
it('parses valid YAML and returns a theme', () => {
|
|
84
|
+
const yaml = `
|
|
85
|
+
id: "yaml-test"
|
|
86
|
+
name: "YAML Test"
|
|
87
|
+
description: "Parsed from YAML"
|
|
88
|
+
colors:
|
|
89
|
+
primary: "#ff0000"
|
|
90
|
+
secondary: "#00ff00"
|
|
91
|
+
accent: "#0000ff"
|
|
92
|
+
background: "#ffffff"
|
|
93
|
+
surface: "#eeeeee"
|
|
94
|
+
text: "#111111"
|
|
95
|
+
textSecondary: "#555555"
|
|
96
|
+
typography:
|
|
97
|
+
fontFamily:
|
|
98
|
+
primary: "Roboto, sans-serif"
|
|
99
|
+
secondary: "Roboto, sans-serif"
|
|
100
|
+
scale:
|
|
101
|
+
xs: "0.75rem"
|
|
102
|
+
sm: "0.875rem"
|
|
103
|
+
base: "1rem"
|
|
104
|
+
lg: "1.125rem"
|
|
105
|
+
xl: "1.25rem"
|
|
106
|
+
2xl: "1.5rem"
|
|
107
|
+
3xl: "1.875rem"
|
|
108
|
+
spacing:
|
|
109
|
+
xs: "0.5rem"
|
|
110
|
+
sm: "0.75rem"
|
|
111
|
+
md: "1rem"
|
|
112
|
+
lg: "1.5rem"
|
|
113
|
+
xl: "2rem"
|
|
114
|
+
2xl: "3rem"
|
|
115
|
+
`;
|
|
116
|
+
const theme = ThemeLoader.loadThemeFromYaml(yaml);
|
|
117
|
+
expect(theme.name).toBe('YAML Test');
|
|
118
|
+
expect(theme.colors.primary).toBe('#ff0000');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('throws for invalid YAML syntax', () => {
|
|
122
|
+
expect(() =>
|
|
123
|
+
ThemeLoader.loadThemeFromYaml('{ invalid: yaml: content: :')
|
|
124
|
+
).toThrow('Failed to parse theme YAML');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('ThemeLoader.registerCustomTheme / getTheme', () => {
|
|
129
|
+
it('registers a custom theme and retrieves it by name', () => {
|
|
130
|
+
const custom = makeCustomTheme({ name: 'My Custom Theme A' });
|
|
131
|
+
ThemeLoader.registerCustomTheme(custom);
|
|
132
|
+
const retrieved = ThemeLoader.getTheme('My Custom Theme A');
|
|
133
|
+
expect(retrieved).toBeDefined();
|
|
134
|
+
expect(retrieved?.colors.primary).toBe('#aabbcc');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('getTheme returns undefined for an unregistered name', () => {
|
|
138
|
+
expect(ThemeLoader.getTheme('__does_not_exist__')).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('overwriting a theme by name replaces it', () => {
|
|
142
|
+
const v1 = makeCustomTheme({ name: 'Overwrite Test', colors: { ...makeCustomTheme().colors, primary: '#111111' } });
|
|
143
|
+
const v2 = makeCustomTheme({ name: 'Overwrite Test', colors: { ...makeCustomTheme().colors, primary: '#222222' } });
|
|
144
|
+
ThemeLoader.registerCustomTheme(v1);
|
|
145
|
+
ThemeLoader.registerCustomTheme(v2);
|
|
146
|
+
expect(ThemeLoader.getTheme('Overwrite Test')?.colors.primary).toBe('#222222');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('ThemeLoader.loadCustomTheme', () => {
|
|
151
|
+
it('registers the theme and returns it', () => {
|
|
152
|
+
const custom = makeCustomTheme({ name: 'LoadCustom Test' });
|
|
153
|
+
const returned = ThemeLoader.loadCustomTheme(custom);
|
|
154
|
+
expect(returned).toBe(custom);
|
|
155
|
+
expect(ThemeLoader.getTheme('LoadCustom Test')).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('ThemeLoader.getAllThemes', () => {
|
|
160
|
+
it('returns an array (may include themes registered by other tests)', () => {
|
|
161
|
+
// Load a known theme to ensure at least one is present
|
|
162
|
+
ThemeLoader.loadThemeFromFile('corporate');
|
|
163
|
+
const all = ThemeLoader.getAllThemes();
|
|
164
|
+
expect(Array.isArray(all)).toBe(true);
|
|
165
|
+
expect(all.length).toBeGreaterThan(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('includes a custom theme after registration', () => {
|
|
169
|
+
const uniqueName = `AllThemes-${Date.now()}`;
|
|
170
|
+
ThemeLoader.registerCustomTheme(makeCustomTheme({ name: uniqueName }));
|
|
171
|
+
const names = ThemeLoader.getAllThemes().map(t => t.name);
|
|
172
|
+
expect(names).toContain(uniqueName);
|
|
173
|
+
});
|
|
174
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
+
"extends": "../tsconfig.base.json",
|
|
2
3
|
"compilerOptions": {
|
|
3
|
-
"target": "es2022",
|
|
4
|
-
"lib": ["dom", "dom.iterable", "es6"],
|
|
5
|
-
"allowJs": true,
|
|
6
|
-
"skipLibCheck": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"allowSyntheticDefaultImports": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"moduleResolution": "node",
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"isolatedModules": true,
|
|
14
|
-
"noEmit": false,
|
|
15
|
-
"jsx": "react-jsx",
|
|
16
|
-
"module": "commonjs",
|
|
17
4
|
"outDir": "./dist",
|
|
18
|
-
"rootDir": "./src"
|
|
19
|
-
"declaration": true,
|
|
20
|
-
"declarationMap": true
|
|
5
|
+
"rootDir": "./src"
|
|
21
6
|
},
|
|
22
7
|
"include": [
|
|
23
8
|
"src/**/*"
|
|
File without changes
|