@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.
@@ -1,11 +1,23 @@
1
- import { heroui } from "@heroui/react";
1
+ import { heroui, HeroUIPluginConfig } from "@heroui/react";
2
2
 
3
- export const ThemePlugin: any = heroui({
4
- layout: {},
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
- dark: {},
8
- doge_light: {
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
- wallet_kit_light: {
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
+ // });
@@ -1,5 +1,6 @@
1
1
  @import "tailwindcss";
2
2
 
3
- @plugin "./plugin";
3
+ @plugin "./plugin.ts";
4
4
 
5
5
  @source "../../../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx,mjs}";
6
+ @source "../components/**/*.{js,ts,jsx,tsx,mjs}";
@@ -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
+ }