@tomo-inc/tomo-ui 0.0.8 → 0.0.10
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/package.json +5 -3
- package/project.json +19 -1
- package/src/App.tsx +134 -14
- package/src/components/index.ts +1 -0
- package/src/components/modal/index.tsx +9 -12
- package/src/components/passcodeInput/index.tsx +81 -0
- package/src/components/toast/index.ts +1 -2
- package/src/index.ts +1 -0
- package/src/style.css +1 -2
- package/src/tailwind/plugin.ts +195 -144
- package/src/tailwind/tailwind.css +2 -1
- package/src/tailwind/theme-to-css.ts +256 -0
- package/src/theme-context.tsx +47 -0
- package/src/theme-provider.tsx +32 -6
- package/src/types.ts +7 -0
- package/src/components/modal/modal-body.tsx +0 -10
- package/src/components/modal/modal-content.tsx +0 -17
- package/src/components/modal/modal-context.tsx +0 -12
- package/src/components/modal/modal-footer.tsx +0 -12
- package/src/components/modal/modal-header.tsx +0 -28
- package/src/components/modal/modal.tsx +0 -152
- package/src/components/modal/use-disclosure.ts +0 -46
package/src/tailwind/plugin.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import { heroui } from "@heroui/react";
|
|
1
|
+
import { heroui, HeroUIPluginConfig } from "@heroui/react";
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
|
|
3
|
+
export const baseConfig: HeroUIPluginConfig = {
|
|
4
|
+
// Don't change this prefix, some css not working
|
|
5
|
+
prefix: "heroui",
|
|
6
|
+
// prefix: "tomoui",
|
|
5
7
|
themes: {
|
|
6
|
-
light: {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
// light: {
|
|
9
|
+
// colors: {
|
|
10
|
+
// foreground: "#000",
|
|
11
|
+
// background: "#FFF",
|
|
12
|
+
// content1: "#FCFCFD",
|
|
13
|
+
// primary: {
|
|
14
|
+
// ...generateColorScale("#fe3c9c"),
|
|
15
|
+
// DEFAULT: "#fe3c9c",
|
|
16
|
+
// foreground: "#fff",
|
|
17
|
+
// },
|
|
18
|
+
// },
|
|
19
|
+
// },
|
|
20
|
+
light: {
|
|
9
21
|
colors: {
|
|
10
22
|
foreground: "#12122A",
|
|
11
23
|
background: "#FFF",
|
|
@@ -34,144 +46,183 @@ export const ThemePlugin: any = heroui({
|
|
|
34
46
|
t5: "#EEC41F",
|
|
35
47
|
} as any,
|
|
36
48
|
},
|
|
37
|
-
|
|
38
|
-
colors: {
|
|
39
|
-
foreground: "#12122A",
|
|
40
|
-
background: "#FFF",
|
|
41
|
-
content1: "#FCFCFD",
|
|
42
|
-
primary: {
|
|
43
|
-
50: "#FFFBEA",
|
|
44
|
-
100: "#FFF3C4",
|
|
45
|
-
200: "#FCE588",
|
|
46
|
-
300: "#FADB5F",
|
|
47
|
-
400: "#F7C948",
|
|
48
|
-
500: "#FCD436",
|
|
49
|
-
600: "#F0B429",
|
|
50
|
-
700: "#DE911D",
|
|
51
|
-
800: "#CB6E17",
|
|
52
|
-
900: "#B44D12",
|
|
53
|
-
DEFAULT: "#FCD436",
|
|
54
|
-
},
|
|
55
|
-
danger: "#FF5A5A",
|
|
56
|
-
warning: "#FFAD32",
|
|
57
|
-
success: "#079455",
|
|
58
|
-
t1: "#12122A",
|
|
59
|
-
t2: "#616184",
|
|
60
|
-
t3: "#8989AB",
|
|
61
|
-
t4: "#C1C0D8",
|
|
62
|
-
t5: "#EEC41F",
|
|
63
|
-
} as any,
|
|
64
|
-
},
|
|
65
|
-
mydoge: {
|
|
66
|
-
colors: {
|
|
67
|
-
primary: {
|
|
68
|
-
"50": "#fffef7",
|
|
69
|
-
"100": "#fffceb",
|
|
70
|
-
"200": "#fff9d5",
|
|
71
|
-
"300": "#fff5b8",
|
|
72
|
-
"400": "#feef8a",
|
|
73
|
-
"500": "#FED70B",
|
|
74
|
-
"600": "#e5c009",
|
|
75
|
-
"700": "#bfa007",
|
|
76
|
-
"800": "#998006",
|
|
77
|
-
"900": "#736005",
|
|
78
|
-
foreground: "#000",
|
|
79
|
-
DEFAULT: "#FED70B",
|
|
80
|
-
},
|
|
81
|
-
secondary: {
|
|
82
|
-
"50": "#f5f3ff",
|
|
83
|
-
"100": "#ede9fe",
|
|
84
|
-
"200": "#ddd6fe",
|
|
85
|
-
"300": "#c4b5fd",
|
|
86
|
-
"400": "#a78bfa",
|
|
87
|
-
"500": "#8b5cf6",
|
|
88
|
-
"600": "#7c3aed",
|
|
89
|
-
"700": "#6d28d9",
|
|
90
|
-
"800": "#5b21b6",
|
|
91
|
-
"900": "#4c1d95",
|
|
92
|
-
foreground: "#fff",
|
|
93
|
-
DEFAULT: "#8b5cf6",
|
|
94
|
-
},
|
|
95
|
-
default: {
|
|
96
|
-
"50": "#0e0e0e",
|
|
97
|
-
"100": "#1c1c1c",
|
|
98
|
-
"200": "#2a2a2a",
|
|
99
|
-
"300": "#383838",
|
|
100
|
-
"400": "#464646",
|
|
101
|
-
"500": "#6b6b6b",
|
|
102
|
-
"600": "#909090",
|
|
103
|
-
"700": "#b5b5b5",
|
|
104
|
-
"800": "#dadada",
|
|
105
|
-
"900": "#ffffff",
|
|
106
|
-
foreground: "#ffffff",
|
|
107
|
-
DEFAULT: "#464646",
|
|
108
|
-
},
|
|
109
|
-
success: {
|
|
110
|
-
"50": "#f0fdf4",
|
|
111
|
-
"100": "#dcfce7",
|
|
112
|
-
"200": "#bbf7d0",
|
|
113
|
-
"300": "#86efac",
|
|
114
|
-
"400": "#4ade80",
|
|
115
|
-
"500": "#10b981",
|
|
116
|
-
"600": "#059669",
|
|
117
|
-
"700": "#047857",
|
|
118
|
-
"800": "#065f46",
|
|
119
|
-
"900": "#064e3b",
|
|
120
|
-
foreground: "#fff",
|
|
121
|
-
DEFAULT: "#10b981",
|
|
122
|
-
},
|
|
123
|
-
warning: {
|
|
124
|
-
"50": "#fffbeb",
|
|
125
|
-
"100": "#fef3c7",
|
|
126
|
-
"200": "#fde68a",
|
|
127
|
-
"300": "#fcd34d",
|
|
128
|
-
"400": "#fbbf24",
|
|
129
|
-
"500": "#f59e0b",
|
|
130
|
-
"600": "#d97706",
|
|
131
|
-
"700": "#b45309",
|
|
132
|
-
"800": "#92400e",
|
|
133
|
-
"900": "#78350f",
|
|
134
|
-
foreground: "#000",
|
|
135
|
-
DEFAULT: "#f59e0b",
|
|
136
|
-
},
|
|
137
|
-
danger: {
|
|
138
|
-
"50": "#fef2f2",
|
|
139
|
-
"100": "#fee2e2",
|
|
140
|
-
"200": "#fecaca",
|
|
141
|
-
"300": "#fca5a5",
|
|
142
|
-
"400": "#f87171",
|
|
143
|
-
"500": "#ef4444",
|
|
144
|
-
"600": "#dc2626",
|
|
145
|
-
"700": "#b91c1c",
|
|
146
|
-
"800": "#991b1b",
|
|
147
|
-
"900": "#7f1d1d",
|
|
148
|
-
foreground: "#fff",
|
|
149
|
-
DEFAULT: "#ef4444",
|
|
150
|
-
},
|
|
151
|
-
background: "#000000",
|
|
152
|
-
foreground: "#ffffff",
|
|
153
|
-
content1: {
|
|
154
|
-
DEFAULT: "#1A1A1A",
|
|
155
|
-
foreground: "#fff",
|
|
156
|
-
},
|
|
157
|
-
content2: {
|
|
158
|
-
DEFAULT: "#242424",
|
|
159
|
-
foreground: "#fff",
|
|
160
|
-
},
|
|
161
|
-
content3: {
|
|
162
|
-
DEFAULT: "#464646",
|
|
163
|
-
foreground: "#fff",
|
|
164
|
-
},
|
|
165
|
-
content4: {
|
|
166
|
-
DEFAULT: "#666666",
|
|
167
|
-
foreground: "#fff",
|
|
168
|
-
},
|
|
169
|
-
focus: "#FED70B",
|
|
170
|
-
overlay: "#000000",
|
|
171
|
-
divider: "#464646",
|
|
172
|
-
},
|
|
173
|
-
},
|
|
49
|
+
dark: {},
|
|
174
50
|
},
|
|
175
|
-
}
|
|
51
|
+
};
|
|
176
52
|
|
|
53
|
+
export const ThemePlugin = heroui(baseConfig) as any;
|
|
177
54
|
export default ThemePlugin;
|
|
55
|
+
|
|
56
|
+
// export const ThemePlugin: any = heroui({
|
|
57
|
+
// layout: {},
|
|
58
|
+
// themes: {
|
|
59
|
+
// light: {
|
|
60
|
+
// colors: {
|
|
61
|
+
// foreground: "#12122A",
|
|
62
|
+
// background: "#FFF",
|
|
63
|
+
// content1: "#FCFCFD",
|
|
64
|
+
// primary: {
|
|
65
|
+
// 50: "#FFFBEA",
|
|
66
|
+
// 100: "#FFF3C4",
|
|
67
|
+
// 200: "#FCE588",
|
|
68
|
+
// 300: "#FADB5F",
|
|
69
|
+
// 400: "#F7C948",
|
|
70
|
+
// 500: "#FCD436",
|
|
71
|
+
// 600: "#F0B429",
|
|
72
|
+
// 700: "#DE911D",
|
|
73
|
+
// 800: "#CB6E17",
|
|
74
|
+
// 900: "#B44D12",
|
|
75
|
+
// DEFAULT: "#FCD436",
|
|
76
|
+
// },
|
|
77
|
+
// danger: "#FF5A5A",
|
|
78
|
+
// warning: "#FFAD32",
|
|
79
|
+
// success: "#079455",
|
|
80
|
+
// // Remove no required colors
|
|
81
|
+
// // t1: "#12122A",
|
|
82
|
+
// // t2: "#616184",
|
|
83
|
+
// // t3: "#8989AB",
|
|
84
|
+
// // t4: "#C1C0D8",
|
|
85
|
+
// // t5: "#EEC41F",
|
|
86
|
+
// },
|
|
87
|
+
// },
|
|
88
|
+
// // doge_light: {
|
|
89
|
+
// // colors: {
|
|
90
|
+
// // foreground: "#12122A",
|
|
91
|
+
// // background: "#FFF",
|
|
92
|
+
// // content1: "#FCFCFD",
|
|
93
|
+
// // primary: {
|
|
94
|
+
// // 50: "#FFFBEA",
|
|
95
|
+
// // 100: "#FFF3C4",
|
|
96
|
+
// // 200: "#FCE588",
|
|
97
|
+
// // 300: "#FADB5F",
|
|
98
|
+
// // 400: "#F7C948",
|
|
99
|
+
// // 500: "#FCD436",
|
|
100
|
+
// // 600: "#F0B429",
|
|
101
|
+
// // 700: "#DE911D",
|
|
102
|
+
// // 800: "#CB6E17",
|
|
103
|
+
// // 900: "#B44D12",
|
|
104
|
+
// // foreground: "#12122A",
|
|
105
|
+
// // DEFAULT: "#FCD436",
|
|
106
|
+
// // },
|
|
107
|
+
// // danger: "#FF5A5A",
|
|
108
|
+
// // warning: "#FFAD32",
|
|
109
|
+
// // success: "#079455",
|
|
110
|
+
// // t1: "#12122A",
|
|
111
|
+
// // t2: "#616184",
|
|
112
|
+
// // t3: "#8989AB",
|
|
113
|
+
// // t4: "#C1C0D8",
|
|
114
|
+
// // t5: "#EEC41F",
|
|
115
|
+
// // } as any,
|
|
116
|
+
// // },
|
|
117
|
+
|
|
118
|
+
// // mydoge: {
|
|
119
|
+
// // colors: {
|
|
120
|
+
// // primary: {
|
|
121
|
+
// // "50": "#fffef7",
|
|
122
|
+
// // "100": "#fffceb",
|
|
123
|
+
// // "200": "#fff9d5",
|
|
124
|
+
// // "300": "#fff5b8",
|
|
125
|
+
// // "400": "#feef8a",
|
|
126
|
+
// // "500": "#FED70B",
|
|
127
|
+
// // "600": "#e5c009",
|
|
128
|
+
// // "700": "#bfa007",
|
|
129
|
+
// // "800": "#998006",
|
|
130
|
+
// // "900": "#736005",
|
|
131
|
+
// // foreground: "#000",
|
|
132
|
+
// // DEFAULT: "#FED70B",
|
|
133
|
+
// // },
|
|
134
|
+
// // secondary: {
|
|
135
|
+
// // "50": "#f5f3ff",
|
|
136
|
+
// // "100": "#ede9fe",
|
|
137
|
+
// // "200": "#ddd6fe",
|
|
138
|
+
// // "300": "#c4b5fd",
|
|
139
|
+
// // "400": "#a78bfa",
|
|
140
|
+
// // "500": "#8b5cf6",
|
|
141
|
+
// // "600": "#7c3aed",
|
|
142
|
+
// // "700": "#6d28d9",
|
|
143
|
+
// // "800": "#5b21b6",
|
|
144
|
+
// // "900": "#4c1d95",
|
|
145
|
+
// // foreground: "#fff",
|
|
146
|
+
// // DEFAULT: "#8b5cf6",
|
|
147
|
+
// // },
|
|
148
|
+
// // default: {
|
|
149
|
+
// // "50": "#0e0e0e",
|
|
150
|
+
// // "100": "#1c1c1c",
|
|
151
|
+
// // "200": "#2a2a2a",
|
|
152
|
+
// // "300": "#383838",
|
|
153
|
+
// // "400": "#464646",
|
|
154
|
+
// // "500": "#6b6b6b",
|
|
155
|
+
// // "600": "#909090",
|
|
156
|
+
// // "700": "#b5b5b5",
|
|
157
|
+
// // "800": "#dadada",
|
|
158
|
+
// // "900": "#ffffff",
|
|
159
|
+
// // foreground: "#ffffff",
|
|
160
|
+
// // DEFAULT: "#464646",
|
|
161
|
+
// // },
|
|
162
|
+
// // success: {
|
|
163
|
+
// // "50": "#f0fdf4",
|
|
164
|
+
// // "100": "#dcfce7",
|
|
165
|
+
// // "200": "#bbf7d0",
|
|
166
|
+
// // "300": "#86efac",
|
|
167
|
+
// // "400": "#4ade80",
|
|
168
|
+
// // "500": "#10b981",
|
|
169
|
+
// // "600": "#059669",
|
|
170
|
+
// // "700": "#047857",
|
|
171
|
+
// // "800": "#065f46",
|
|
172
|
+
// // "900": "#064e3b",
|
|
173
|
+
// // foreground: "#fff",
|
|
174
|
+
// // DEFAULT: "#10b981",
|
|
175
|
+
// // },
|
|
176
|
+
// // warning: {
|
|
177
|
+
// // "50": "#fffbeb",
|
|
178
|
+
// // "100": "#fef3c7",
|
|
179
|
+
// // "200": "#fde68a",
|
|
180
|
+
// // "300": "#fcd34d",
|
|
181
|
+
// // "400": "#fbbf24",
|
|
182
|
+
// // "500": "#f59e0b",
|
|
183
|
+
// // "600": "#d97706",
|
|
184
|
+
// // "700": "#b45309",
|
|
185
|
+
// // "800": "#92400e",
|
|
186
|
+
// // "900": "#78350f",
|
|
187
|
+
// // foreground: "#000",
|
|
188
|
+
// // DEFAULT: "#f59e0b",
|
|
189
|
+
// // },
|
|
190
|
+
// // danger: {
|
|
191
|
+
// // "50": "#fef2f2",
|
|
192
|
+
// // "100": "#fee2e2",
|
|
193
|
+
// // "200": "#fecaca",
|
|
194
|
+
// // "300": "#fca5a5",
|
|
195
|
+
// // "400": "#f87171",
|
|
196
|
+
// // "500": "#ef4444",
|
|
197
|
+
// // "600": "#dc2626",
|
|
198
|
+
// // "700": "#b91c1c",
|
|
199
|
+
// // "800": "#991b1b",
|
|
200
|
+
// // "900": "#7f1d1d",
|
|
201
|
+
// // foreground: "#fff",
|
|
202
|
+
// // DEFAULT: "#ef4444",
|
|
203
|
+
// // },
|
|
204
|
+
// // background: "#000000",
|
|
205
|
+
// // foreground: "#ffffff",
|
|
206
|
+
// // content1: {
|
|
207
|
+
// // DEFAULT: "#1A1A1A",
|
|
208
|
+
// // foreground: "#fff",
|
|
209
|
+
// // },
|
|
210
|
+
// // content2: {
|
|
211
|
+
// // DEFAULT: "#242424",
|
|
212
|
+
// // foreground: "#fff",
|
|
213
|
+
// // },
|
|
214
|
+
// // content3: {
|
|
215
|
+
// // DEFAULT: "#464646",
|
|
216
|
+
// // foreground: "#fff",
|
|
217
|
+
// // },
|
|
218
|
+
// // content4: {
|
|
219
|
+
// // DEFAULT: "#666666",
|
|
220
|
+
// // foreground: "#fff",
|
|
221
|
+
// // },
|
|
222
|
+
// // focus: "#FED70B",
|
|
223
|
+
// // overlay: "#000000",
|
|
224
|
+
// // divider: "#464646",
|
|
225
|
+
// // },
|
|
226
|
+
// // },
|
|
227
|
+
// },
|
|
228
|
+
// });
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { ColorScale, HeroUIPluginConfig, LayoutTheme } from "@heroui/theme";
|
|
2
|
+
|
|
3
|
+
import Color from "color";
|
|
4
|
+
import * as flat from "flat";
|
|
5
|
+
import { baseConfig } from "./plugin";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert camelCase to kebab-case
|
|
9
|
+
* Matches heroui's kebabCase utility
|
|
10
|
+
*/
|
|
11
|
+
function toKebabCase(str: string): string {
|
|
12
|
+
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Remove DEFAULT keys (e.g., "primary-DEFAULT" -> "primary")
|
|
17
|
+
* This matches heroui plugin's removeDefaultKeys function
|
|
18
|
+
*/
|
|
19
|
+
function removeDefaultKeys(obj: Record<string, unknown>): Record<string, unknown> {
|
|
20
|
+
const newObj: Record<string, unknown> = {};
|
|
21
|
+
|
|
22
|
+
for (const key in obj) {
|
|
23
|
+
if (key.endsWith("-DEFAULT")) {
|
|
24
|
+
newObj[key.replace("-DEFAULT", "")] = obj[key];
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
newObj[key] = obj[key];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return newObj;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Flatten nested theme objects to `a-b-c` keys.
|
|
35
|
+
* Uses the same logic as heroui plugin's flattenThemeObject
|
|
36
|
+
*/
|
|
37
|
+
function flattenThemeObject(obj: Record<string, unknown>): Record<string, unknown> {
|
|
38
|
+
return removeDefaultKeys(
|
|
39
|
+
flat.flatten(obj, {
|
|
40
|
+
safe: true,
|
|
41
|
+
delimiter: "-",
|
|
42
|
+
}) as Record<string, unknown>,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Convert color string to HSL format (h s% l%)
|
|
48
|
+
* Matches heroui plugin's color conversion logic
|
|
49
|
+
*/
|
|
50
|
+
function toHslString(colorValue: string): string | null {
|
|
51
|
+
try {
|
|
52
|
+
const parsedColor = Color(colorValue).hsl().round(2).array();
|
|
53
|
+
const [h, s, l] = parsedColor;
|
|
54
|
+
|
|
55
|
+
return `${h} ${s}% ${l}%`;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate CSS variable name from prefix and color key
|
|
63
|
+
* This matches the naming convention used by heroui() plugin
|
|
64
|
+
*/
|
|
65
|
+
function generateCSSVariableName(prefix: string, colorKey: string): string {
|
|
66
|
+
return `--${prefix}-${colorKey}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Process layout theme and convert to CSS variables
|
|
71
|
+
* Matches heroui plugin's layout processing logic
|
|
72
|
+
*
|
|
73
|
+
* @param layout - Layout theme object
|
|
74
|
+
* @param prefix - CSS variable prefix
|
|
75
|
+
* @returns Array of CSS variable strings
|
|
76
|
+
*/
|
|
77
|
+
function processLayoutTheme(layout: LayoutTheme, prefix: string): string[] {
|
|
78
|
+
const cssVars: string[] = [];
|
|
79
|
+
|
|
80
|
+
if (!layout) return cssVars;
|
|
81
|
+
|
|
82
|
+
// Convert keys to kebab-case (matching heroui's mapKeys logic)
|
|
83
|
+
const kebabLayout: Record<string, unknown> = {};
|
|
84
|
+
|
|
85
|
+
for (const [key, value] of Object.entries(layout)) {
|
|
86
|
+
kebabLayout[toKebabCase(key)] = value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Flatten nested layout objects
|
|
90
|
+
const flatLayout = flat.flatten(kebabLayout, {
|
|
91
|
+
safe: true,
|
|
92
|
+
delimiter: "-",
|
|
93
|
+
}) as Record<string, unknown>;
|
|
94
|
+
|
|
95
|
+
// Process each layout value
|
|
96
|
+
for (const [key, value] of Object.entries(flatLayout)) {
|
|
97
|
+
if (!value) continue;
|
|
98
|
+
|
|
99
|
+
const layoutVariablePrefix = `--${prefix}-${key}`;
|
|
100
|
+
|
|
101
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
102
|
+
// Handle nested objects (e.g., boxShadow: { small: "...", medium: "..." })
|
|
103
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
104
|
+
if (!nestedValue) continue;
|
|
105
|
+
|
|
106
|
+
const nestedLayoutVariable = `${layoutVariablePrefix}-${nestedKey}`;
|
|
107
|
+
|
|
108
|
+
cssVars.push(` ${nestedLayoutVariable}: ${nestedValue};`);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
// Handle singular values (e.g., disabledOpacity: 0.5)
|
|
112
|
+
// Special handling for opacity values: 0.5 -> .5
|
|
113
|
+
let formattedValue = value;
|
|
114
|
+
|
|
115
|
+
if (layoutVariablePrefix.includes("opacity") && typeof value === "number") {
|
|
116
|
+
formattedValue = value.toString().replace(/^0\./, ".");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
cssVars.push(` ${layoutVariablePrefix}: ${formattedValue};`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return cssVars;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Convert heroui theme configuration to CSS variables string
|
|
128
|
+
*
|
|
129
|
+
* This function takes a standard HeroUI theme config (same format as heroui({}))
|
|
130
|
+
* and converts it to CSS variables that can override the base theme.
|
|
131
|
+
*
|
|
132
|
+
* The base theme is automatically generated by heroui() plugin at build time.
|
|
133
|
+
* This function uses the same processing logic as heroui plugin:
|
|
134
|
+
* - Uses flat() to flatten nested objects
|
|
135
|
+
* - Uses Color() to convert colors to HSL
|
|
136
|
+
* - Generates CSS variables with the same naming convention
|
|
137
|
+
*
|
|
138
|
+
* @param config - HeroUI theme configuration object (same format as heroui({}))
|
|
139
|
+
* @param scopeId - Optional unique ID to scope CSS variables to a specific element
|
|
140
|
+
* @returns CSS variables as string that can be injected into CSS to override base theme
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* const config: HeroUIPluginConfig = {
|
|
144
|
+
* themes: {
|
|
145
|
+
* light: {
|
|
146
|
+
* colors: {
|
|
147
|
+
* primary: "#ff0000"
|
|
148
|
+
* }
|
|
149
|
+
* }
|
|
150
|
+
* }
|
|
151
|
+
* };
|
|
152
|
+
* const css = themeConfigToCSS(config);
|
|
153
|
+
* // Returns: ":root { --heroui-primary: 0 100% 50%; }"
|
|
154
|
+
* const css = themeConfigToCSS(config, "unique-id");
|
|
155
|
+
* // Returns: "#unique-id [class=\"light\"] { --heroui-primary: 0 100% 50%; }"
|
|
156
|
+
*/
|
|
157
|
+
export function themeConfigToCSS(config: HeroUIPluginConfig, scopeId?: string): string {
|
|
158
|
+
const prefix = baseConfig.prefix || "tomo"; // is Fixed
|
|
159
|
+
const themes = config.themes || {};
|
|
160
|
+
const cssBlocks: string[] = [];
|
|
161
|
+
|
|
162
|
+
// Process global layout (applies to all themes)
|
|
163
|
+
const globalLayoutVars = config.layout ? processLayoutTheme(config.layout, prefix) : [];
|
|
164
|
+
|
|
165
|
+
// Build scope selector prefix if scopeId is provided
|
|
166
|
+
// CSS variables are scoped to the element with the given ID
|
|
167
|
+
const scopeSelector = scopeId ? `#${scopeId.replace(/:/g, "\\:")}` : "";
|
|
168
|
+
|
|
169
|
+
// Process each theme (matching heroui plugin's resolveConfig logic)
|
|
170
|
+
for (const [themeName, themeConfig] of Object.entries(themes)) {
|
|
171
|
+
const cssVars: string[] = [];
|
|
172
|
+
|
|
173
|
+
// Process colors
|
|
174
|
+
if (themeConfig?.colors) {
|
|
175
|
+
// Flatten colors using the same method as heroui plugin
|
|
176
|
+
const flatColors = flattenThemeObject(themeConfig.colors as Record<string, ColorScale>);
|
|
177
|
+
|
|
178
|
+
// Extract colors -> HSL triplet values (matching heroui plugin)
|
|
179
|
+
for (const [colorKey, colorValue] of Object.entries(flatColors)) {
|
|
180
|
+
if (!colorValue) continue;
|
|
181
|
+
|
|
182
|
+
const value = toHslString(colorValue as string);
|
|
183
|
+
|
|
184
|
+
if (value) {
|
|
185
|
+
const varName = generateCSSVariableName(prefix, colorKey);
|
|
186
|
+
|
|
187
|
+
cssVars.push(` ${varName}: ${value};`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Process theme-specific layout (overrides global layout)
|
|
193
|
+
if (themeConfig?.layout) {
|
|
194
|
+
const themeLayoutVars = processLayoutTheme(themeConfig.layout, prefix);
|
|
195
|
+
|
|
196
|
+
cssVars.push(...themeLayoutVars);
|
|
197
|
+
} else if (globalLayoutVars.length > 0) {
|
|
198
|
+
// Apply global layout if no theme-specific layout
|
|
199
|
+
cssVars.push(...globalLayoutVars);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (cssVars.length > 0) {
|
|
203
|
+
// Scope CSS variables to the element with the given ID when class matches
|
|
204
|
+
// Each TomoUIProvider instance has its own scoped div with class attribute
|
|
205
|
+
const selector = scopeId ? `${scopeSelector}[class="${themeName}"]` : `[class="${themeName}"]`;
|
|
206
|
+
|
|
207
|
+
cssBlocks.push(`${selector} {${cssVars.join("")}}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// If no themes but global layout exists, add it to :root or scoped element
|
|
212
|
+
if (cssBlocks.length === 0 && globalLayoutVars.length > 0) {
|
|
213
|
+
const rootSelector = scopeId ? scopeSelector : ":root";
|
|
214
|
+
|
|
215
|
+
cssBlocks.push(`${rootSelector} {${globalLayoutVars.join("")}}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return cssBlocks.join("\n");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Generate a color scale from a base color
|
|
223
|
+
* @param baseColor - The base color to generate the color scale from
|
|
224
|
+
* @returns A record of color scale values
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* const colorScale = generateColorScale("#000000");
|
|
228
|
+
* // Returns: { "50": "#000000", "100": "#000000", "200": "#000000", "300": "#000000", "400": "#000000", "500": "#000000", "600": "#000000", "700": "#000000", "800": "#000000", "900": "#000000", "950": "#000000" }
|
|
229
|
+
*/
|
|
230
|
+
export function generateColorScale(baseColor: string) {
|
|
231
|
+
const base = Color(baseColor);
|
|
232
|
+
|
|
233
|
+
const steps = {
|
|
234
|
+
"50": 0.45,
|
|
235
|
+
"100": 0.35,
|
|
236
|
+
"200": 0.25,
|
|
237
|
+
"300": 0.15,
|
|
238
|
+
"400": 0.07,
|
|
239
|
+
"500": 0,
|
|
240
|
+
"600": -0.07,
|
|
241
|
+
"700": -0.15,
|
|
242
|
+
"800": -0.25,
|
|
243
|
+
"900": -0.35,
|
|
244
|
+
"950": -0.45,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const scale: Record<string, string> = {};
|
|
248
|
+
|
|
249
|
+
Object.keys(steps).forEach((key) => {
|
|
250
|
+
const amount = steps[key as keyof typeof steps];
|
|
251
|
+
|
|
252
|
+
scale[key] = amount >= 0 ? base.lighten(amount).hex() : base.darken(Math.abs(amount)).hex();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return scale;
|
|
256
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createContext, useCallback, useContext, useState, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
type Theme = string;
|
|
4
|
+
|
|
5
|
+
interface ThemeContextType {
|
|
6
|
+
theme: Theme;
|
|
7
|
+
setTheme: (theme: Theme) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
11
|
+
|
|
12
|
+
interface ThemeContextProviderProps {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
defaultTheme?: Theme;
|
|
15
|
+
forcedTheme?: Theme;
|
|
16
|
+
themes?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ThemeContextProvider({ children, defaultTheme = "light", forcedTheme }: ThemeContextProviderProps) {
|
|
20
|
+
const [theme, setThemeState] = useState<Theme>(forcedTheme || defaultTheme);
|
|
21
|
+
|
|
22
|
+
const setTheme = useCallback(
|
|
23
|
+
(newTheme: Theme) => {
|
|
24
|
+
if (!forcedTheme) {
|
|
25
|
+
setThemeState(newTheme);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
[forcedTheme],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const value: ThemeContextType = {
|
|
32
|
+
theme: forcedTheme || theme,
|
|
33
|
+
setTheme,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useTheme(): ThemeContextType {
|
|
40
|
+
const context = useContext(ThemeContext);
|
|
41
|
+
|
|
42
|
+
if (context === undefined) {
|
|
43
|
+
throw new Error("useTheme must be used within a ThemeContextProvider");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return context;
|
|
47
|
+
}
|