@kyro-cms/admin 0.2.10 → 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/README.md +46 -272
- package/package.json +32 -7
- package/src/blocks/examples/sample-block-2.tsx +27 -0
- package/src/blocks/examples/sample-block.tsx +26 -0
- package/src/blocks/index.ts +14 -0
- package/src/blocks/registry.ts +38 -0
- package/src/blocks/types.ts +23 -0
- package/src/components/Admin.tsx +1 -1
- package/src/components/ApiKeysManager.tsx +1 -1
- package/src/components/AuditLogsPage.tsx +1 -1
- package/src/components/AutoForm.tsx +2 -2
- package/src/components/BrandingHub.tsx +1 -1
- package/src/components/CreateView.tsx +1 -1
- package/src/components/DetailView.tsx +1 -1
- package/src/components/DeveloperCenter.tsx +1 -1
- package/src/components/EnhancedListView.tsx +1 -1
- package/src/components/ListView.tsx +1 -1
- package/src/components/LoginPage.tsx +1 -1
- package/src/components/MediaGallery.tsx +1 -1
- package/src/components/UserManagement.tsx +1 -1
- package/src/components/WebhookManager.tsx +2 -2
- package/src/components/fields/RelationshipBlockField.tsx +1 -1
- package/src/components/fields/RelationshipField.tsx +1 -1
- package/src/components/fields/UploadField.tsx +1 -6
- package/src/components/ui/CommandPalette.tsx +1 -1
- package/src/fields/examples/sample-field-2.tsx +30 -0
- package/src/fields/examples/sample-field.tsx +30 -0
- package/src/fields/index.ts +33 -0
- package/src/fields/registry.tsx +46 -0
- package/src/fields/types.ts +24 -0
- package/src/hooks/data.ts +116 -0
- package/src/hooks/examples/sample-hook-2.ts +13 -0
- package/src/hooks/examples/sample-hook.ts +12 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/lifecycle.ts +81 -0
- package/src/hooks/types.ts +40 -0
- package/src/index.ts +78 -0
- package/src/integration.ts +52 -0
- package/src/pages/api/[collection]/[id]/publish.ts +2 -2
- package/src/pages/api/[collection]/[id]/unpublish.ts +2 -2
- package/src/pages/api/[collection]/[id]/versions.ts +1 -1
- package/src/pages/api/[collection]/[id].ts +2 -2
- package/src/pages/api/[collection]/index.ts +2 -2
- package/src/pages/api/collections.ts +1 -1
- package/src/pages/api/globals/[slug].ts +2 -2
- package/src/pages/api/graphql.ts +3 -3
- package/src/pages/api/media/folders.ts +1 -1
- package/src/pages/api/media/index.ts +1 -1
- package/src/pages/api/media/resize.ts +1 -1
- package/src/pages/api/slug-availability.ts +2 -2
- package/src/pages/api/storage-config.ts +1 -1
- package/src/pages/api/storage-status.ts +1 -1
- package/src/pages/api/upload.ts +1 -1
- package/src/plugins/examples/sample-plugin-2.ts +21 -0
- package/src/plugins/examples/sample-plugin.ts +21 -0
- package/src/plugins/index.ts +10 -0
- package/src/plugins/registry.ts +36 -0
- package/src/plugins/types.ts +22 -0
- package/src/theme/ThemeProvider.tsx +238 -0
- package/src/theme/index.ts +20 -0
- package/src/theme/tokens.ts +222 -0
- package/src/components/Modal.tsx +0 -206
- package/src/components/index.ts +0 -29
- package/src/env.ts +0 -20
- package/src/lib/i18n.tsx +0 -353
- package/src/lib/validation.ts +0 -250
- package/src/pages/api/globals/[slug]/test.ts +0 -171
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIRoute } from "astro";
|
|
2
|
-
import { dataStore } from "
|
|
3
|
-
import { globals } from "
|
|
2
|
+
import { dataStore } from "../../../lib/dataStore";
|
|
3
|
+
import { globals } from "../../../lib/config";
|
|
4
4
|
|
|
5
5
|
export const GET: APIRoute = async ({ params }) => {
|
|
6
6
|
const slug = params.slug as string;
|
package/src/pages/api/graphql.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { APIRoute } from "astro";
|
|
2
|
-
import { executeGraphQL } from "
|
|
3
|
-
import { dataStore } from "
|
|
4
|
-
import { collections } from "
|
|
2
|
+
import { executeGraphQL } from "../../lib/graphql/schema";
|
|
3
|
+
import { dataStore } from "../../lib/dataStore";
|
|
4
|
+
import { collections } from "../../lib/config";
|
|
5
5
|
|
|
6
6
|
dataStore.initialize(collections);
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIRoute } from "astro";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
|
-
import { getStorageConfig } from "
|
|
3
|
+
import { getStorageConfig } from "../../../lib/storage";
|
|
4
4
|
import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3";
|
|
5
5
|
|
|
6
6
|
function isCloudProvider(provider: string): boolean {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIRoute } from "astro";
|
|
2
2
|
import { getMediaService, type MediaItem } from "../../../lib/MediaService";
|
|
3
|
-
import { constructMediaUrl, getStorageConfig } from "
|
|
3
|
+
import { constructMediaUrl, getStorageConfig } from "../../../lib/storage";
|
|
4
4
|
|
|
5
5
|
export const GET: APIRoute = async ({ url }) => {
|
|
6
6
|
let mediaService: any = null;
|
|
@@ -5,7 +5,7 @@ import fs from "fs/promises";
|
|
|
5
5
|
import fsSync from "fs";
|
|
6
6
|
import https from "https";
|
|
7
7
|
import { createHash } from "crypto";
|
|
8
|
-
import { getStorageConfig } from "
|
|
8
|
+
import { getStorageConfig } from "../../../lib/storage";
|
|
9
9
|
|
|
10
10
|
// Cache configuration
|
|
11
11
|
const CACHE_BASE = path.join(process.cwd(), ".cache", "kyro-media", "resize");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIRoute } from "astro";
|
|
2
|
-
import { dataStore } from "
|
|
3
|
-
import { collections } from "
|
|
2
|
+
import { dataStore } from "../../lib/dataStore";
|
|
3
|
+
import { collections } from "../../lib/config";
|
|
4
4
|
|
|
5
5
|
dataStore.initialize(collections);
|
|
6
6
|
|
package/src/pages/api/upload.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIRoute } from "astro";
|
|
2
2
|
import { MediaService, type Dialect } from "@kyro-cms/core";
|
|
3
|
-
import { getDatabaseConfig, runMigrations } from "
|
|
3
|
+
import { getDatabaseConfig, runMigrations } from "../../lib/db";
|
|
4
4
|
|
|
5
5
|
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
|
|
6
6
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { KyroPlugin } from "../types.js";
|
|
2
|
+
import { registerPlugin } from "../registry.js";
|
|
3
|
+
|
|
4
|
+
// Second MVP plugin demonstrating beforeDeploy hook usage
|
|
5
|
+
const samplePlugin2: KyroPlugin = {
|
|
6
|
+
name: "sample-plugin-2",
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
description: "Second MVP plugin demonstrating beforeDeploy hook",
|
|
9
|
+
hooks: {
|
|
10
|
+
beforeDeploy: (ctx) => {
|
|
11
|
+
// Lightweight side-effect; in real plugins, you could validate config, migrations, etc.
|
|
12
|
+
void ctx;
|
|
13
|
+
console.log("[Kyro Admin] sample-plugin-2 beforeDeploy");
|
|
14
|
+
return { success: true };
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
registerPlugin(samplePlugin2);
|
|
20
|
+
|
|
21
|
+
export default samplePlugin2;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { KyroPlugin } from "../types.js";
|
|
2
|
+
import { registerPlugin } from "../registry.js";
|
|
3
|
+
|
|
4
|
+
// Simple MVP plugin demonstrating registration and an onAdminReady hook
|
|
5
|
+
const samplePlugin: KyroPlugin = {
|
|
6
|
+
name: "sample-plugin",
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
description: "A tiny sample plugin to demonstrate the extensibility surface",
|
|
9
|
+
hooks: {
|
|
10
|
+
onAdminReady: () => {
|
|
11
|
+
// Lightweight side-effect; in real plugins this could mount UI or register editors
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.log("[ Kyro Admin ] sample-plugin: onAdminReady executed");
|
|
14
|
+
return;
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
registerPlugin(samplePlugin);
|
|
20
|
+
|
|
21
|
+
export default samplePlugin;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
registerPlugin,
|
|
3
|
+
unregisterPlugin,
|
|
4
|
+
getPlugin,
|
|
5
|
+
getPlugins,
|
|
6
|
+
getPluginsWithHook,
|
|
7
|
+
} from "./registry.ts";
|
|
8
|
+
export type { KyroPlugin } from "./types.ts";
|
|
9
|
+
export { default as samplePlugin } from "./examples/sample-plugin";
|
|
10
|
+
export { default as samplePlugin2 } from "./examples/sample-plugin-2.ts";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { KyroPlugin } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
const plugins: Map<string, KyroPlugin> = new Map();
|
|
4
|
+
|
|
5
|
+
export function registerPlugin(plugin: KyroPlugin): void {
|
|
6
|
+
if (!plugin.name || typeof plugin.name !== "string") {
|
|
7
|
+
throw new Error("Plugin must have a valid name");
|
|
8
|
+
}
|
|
9
|
+
if (plugins.has(plugin.name)) {
|
|
10
|
+
console.warn(`Plugin "${plugin.name}" is already registered. Overwriting.`);
|
|
11
|
+
}
|
|
12
|
+
plugins.set(plugin.name, plugin);
|
|
13
|
+
if (plugin.apply) {
|
|
14
|
+
plugin.apply({});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function unregisterPlugin(name: string): void {
|
|
19
|
+
plugins.delete(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getPlugin(name: string): KyroPlugin | undefined {
|
|
23
|
+
return plugins.get(name);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getPlugins(): KyroPlugin[] {
|
|
27
|
+
return Array.from(plugins.values());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getPluginsWithHook<
|
|
31
|
+
K extends keyof NonNullable<KyroPlugin["hooks"]>,
|
|
32
|
+
>(hookName: K): KyroPlugin[] {
|
|
33
|
+
return Array.from(plugins.values()).filter(
|
|
34
|
+
(p) => p.hooks && typeof p.hooks[hookName] === "function",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AdminContext, HookResult } from "../hooks/types.js";
|
|
2
|
+
|
|
3
|
+
export interface KyroPlugin {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
apply?: (config: Record<string, unknown>) => void;
|
|
8
|
+
hooks?: {
|
|
9
|
+
onAdminReady?: (
|
|
10
|
+
ctx: AdminContext,
|
|
11
|
+
) => void | HookResult | Promise<void | HookResult>;
|
|
12
|
+
beforeDeploy?: (
|
|
13
|
+
ctx: AdminContext,
|
|
14
|
+
) => void | HookResult | Promise<void | HookResult>;
|
|
15
|
+
afterDeploy?: (
|
|
16
|
+
ctx: AdminContext,
|
|
17
|
+
result: HookResult,
|
|
18
|
+
) => void | Promise<void>;
|
|
19
|
+
beforeRender?: (ctx: AdminContext) => void;
|
|
20
|
+
afterRender?: (ctx: AdminContext) => void;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useState,
|
|
5
|
+
useEffect,
|
|
6
|
+
useCallback,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
import type { KyroTheme } from "./tokens.js";
|
|
10
|
+
import { LIGHT_THEME, DARK_THEME, mergeThemes } from "./tokens.js";
|
|
11
|
+
|
|
12
|
+
export type ThemeMode = "light" | "dark" | "system";
|
|
13
|
+
|
|
14
|
+
interface ThemeContextValue {
|
|
15
|
+
mode: ThemeMode;
|
|
16
|
+
theme: KyroTheme;
|
|
17
|
+
lightTheme: KyroTheme;
|
|
18
|
+
darkTheme: KyroTheme;
|
|
19
|
+
setMode: (mode: ThemeMode) => void;
|
|
20
|
+
updateTheme: (overrides: Partial<KyroTheme>) => void;
|
|
21
|
+
getCssVar: (key: string) => string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
25
|
+
|
|
26
|
+
export function useTheme() {
|
|
27
|
+
const context = useContext(ThemeContext);
|
|
28
|
+
if (!context) {
|
|
29
|
+
return {
|
|
30
|
+
mode: "light" as ThemeMode,
|
|
31
|
+
theme: LIGHT_THEME,
|
|
32
|
+
lightTheme: LIGHT_THEME,
|
|
33
|
+
darkTheme: DARK_THEME,
|
|
34
|
+
setMode: () => {},
|
|
35
|
+
updateTheme: () => {},
|
|
36
|
+
getCssVar: (key: string) => `var(--kyro-${key})`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return context;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ThemeProviderProps {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
defaultMode?: ThemeMode;
|
|
45
|
+
light?: Partial<KyroTheme>;
|
|
46
|
+
dark?: Partial<KyroTheme>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function applyThemeToDOM(config: KyroTheme) {
|
|
50
|
+
const root = document.documentElement;
|
|
51
|
+
if (!root) return;
|
|
52
|
+
|
|
53
|
+
// Apply colors
|
|
54
|
+
if (config.colors) {
|
|
55
|
+
Object.entries(config.colors).forEach(([key, value]) => {
|
|
56
|
+
if (value) {
|
|
57
|
+
root.style.setProperty(`--kyro-${key}`, value);
|
|
58
|
+
root.style.setProperty(
|
|
59
|
+
`--kyro-${key}-light`,
|
|
60
|
+
adjustBrightness(value, 0.9),
|
|
61
|
+
);
|
|
62
|
+
root.style.setProperty(
|
|
63
|
+
`--kyro-${key}-dark`,
|
|
64
|
+
adjustBrightness(value, 0.8),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Apply typography
|
|
71
|
+
if (config.typography) {
|
|
72
|
+
if (config.typography.fontFamily) {
|
|
73
|
+
root.style.setProperty(
|
|
74
|
+
"--kyro-font-family",
|
|
75
|
+
config.typography.fontFamily,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (config.typography.fontFamilyMono) {
|
|
79
|
+
root.style.setProperty(
|
|
80
|
+
"--kyro-font-mono",
|
|
81
|
+
config.typography.fontFamilyMono,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Apply spacing
|
|
87
|
+
if (config.spacing) {
|
|
88
|
+
Object.entries(config.spacing).forEach(([key, value]) => {
|
|
89
|
+
if (value) root.style.setProperty(`--kyro-spacing-${key}`, value);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Apply border radius
|
|
94
|
+
if (config.borderRadius) {
|
|
95
|
+
Object.entries(config.borderRadius).forEach(([key, value]) => {
|
|
96
|
+
if (value) root.style.setProperty(`--kyro-radius-${key}`, value);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Apply shadows
|
|
101
|
+
if (config.shadows) {
|
|
102
|
+
Object.entries(config.shadows).forEach(([key, value]) => {
|
|
103
|
+
if (value) root.style.setProperty(`--kyro-shadow-${key}`, value);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Apply block theme overrides
|
|
108
|
+
if (config.blocks) {
|
|
109
|
+
if (config.blocks.card) {
|
|
110
|
+
Object.entries(config.blocks.card).forEach(([key, value]) => {
|
|
111
|
+
if (value) root.style.setProperty(`--kyro-block-card-${key}`, value);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (config.blocks.hero?.background) {
|
|
115
|
+
root.style.setProperty(
|
|
116
|
+
"--kyro-block-hero-bg",
|
|
117
|
+
config.blocks.hero.background,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (config.blocks.code) {
|
|
121
|
+
Object.entries(config.blocks.code).forEach(([key, value]) => {
|
|
122
|
+
if (value) root.style.setProperty(`--kyro-block-code-${key}`, value);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Apply field theme overrides
|
|
128
|
+
if (config.fields) {
|
|
129
|
+
if (config.fields.input) {
|
|
130
|
+
Object.entries(config.fields.input).forEach(([key, value]) => {
|
|
131
|
+
if (value) root.style.setProperty(`--kyro-field-input-${key}`, value);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (config.fields.upload) {
|
|
135
|
+
if (config.fields.upload.dropzoneBackground) {
|
|
136
|
+
root.style.setProperty(
|
|
137
|
+
"--kyro-field-upload-dropzone-bg",
|
|
138
|
+
config.fields.upload.dropzoneBackground,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function adjustBrightness(hex: string, factor: number): string {
|
|
146
|
+
if (!hex.startsWith("#")) return hex;
|
|
147
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
148
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
149
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
150
|
+
const adjust = (c: number) =>
|
|
151
|
+
Math.round(c * factor)
|
|
152
|
+
.toString(16)
|
|
153
|
+
.padStart(2, "0");
|
|
154
|
+
return `#${adjust(r)}${adjust(g)}${adjust(b)}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function ThemeProvider({
|
|
158
|
+
children,
|
|
159
|
+
defaultMode = "light",
|
|
160
|
+
light: lightOverrides,
|
|
161
|
+
dark: darkOverrides,
|
|
162
|
+
}: ThemeProviderProps) {
|
|
163
|
+
const [mode, setMode] = useState<ThemeMode>(defaultMode);
|
|
164
|
+
const [baseLight, setBaseLight] = useState<Partial<KyroTheme>>(
|
|
165
|
+
lightOverrides || {},
|
|
166
|
+
);
|
|
167
|
+
const [baseDark, setBaseDark] = useState<Partial<KyroTheme>>(
|
|
168
|
+
darkOverrides || {},
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const lightTheme = mergeThemes(LIGHT_THEME, baseLight);
|
|
172
|
+
const darkTheme = mergeThemes(DARK_THEME, baseDark);
|
|
173
|
+
|
|
174
|
+
const getResolvedTheme = useCallback((): KyroTheme => {
|
|
175
|
+
if (mode === "system") {
|
|
176
|
+
if (typeof window !== "undefined") {
|
|
177
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
178
|
+
? darkTheme
|
|
179
|
+
: lightTheme;
|
|
180
|
+
}
|
|
181
|
+
return lightTheme;
|
|
182
|
+
}
|
|
183
|
+
return mode === "dark" ? darkTheme : lightTheme;
|
|
184
|
+
}, [mode, lightTheme, darkTheme]);
|
|
185
|
+
|
|
186
|
+
const [theme, setTheme] = useState<KyroTheme>(getResolvedTheme());
|
|
187
|
+
|
|
188
|
+
// Apply theme on mode/customization change
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const resolved = getResolvedTheme();
|
|
191
|
+
setTheme(resolved);
|
|
192
|
+
applyThemeToDOM(resolved);
|
|
193
|
+
}, [getResolvedTheme]);
|
|
194
|
+
|
|
195
|
+
// Handle system theme changes
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (mode !== "system") return;
|
|
198
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
199
|
+
const handler = () => {
|
|
200
|
+
const resolved = getResolvedTheme();
|
|
201
|
+
setTheme(resolved);
|
|
202
|
+
applyThemeToDOM(resolved);
|
|
203
|
+
};
|
|
204
|
+
mediaQuery.addEventListener("change", handler);
|
|
205
|
+
return () => mediaQuery.removeEventListener("change", handler);
|
|
206
|
+
}, [mode, getResolvedTheme]);
|
|
207
|
+
|
|
208
|
+
const updateTheme = useCallback((overrides: Partial<KyroTheme>) => {
|
|
209
|
+
setBaseLight((prev) => ({ ...prev, ...overrides }));
|
|
210
|
+
setBaseDark((prev) => ({ ...prev, ...overrides }));
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
const getCssVar = useCallback((key: string) => `var(--kyro-${key})`, []);
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<ThemeContext.Provider
|
|
217
|
+
value={{
|
|
218
|
+
mode,
|
|
219
|
+
theme,
|
|
220
|
+
lightTheme,
|
|
221
|
+
darkTheme,
|
|
222
|
+
setMode,
|
|
223
|
+
updateTheme,
|
|
224
|
+
getCssVar,
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
{children}
|
|
228
|
+
</ThemeContext.Provider>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const LightThemeProvider = (
|
|
233
|
+
props: Omit<ThemeProviderProps, "defaultMode">,
|
|
234
|
+
) => <ThemeProvider defaultMode="light" {...props} />;
|
|
235
|
+
|
|
236
|
+
export const DarkThemeProvider = (
|
|
237
|
+
props: Omit<ThemeProviderProps, "defaultMode">,
|
|
238
|
+
) => <ThemeProvider defaultMode="dark" {...props} />;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ThemeProvider,
|
|
3
|
+
LightThemeProvider,
|
|
4
|
+
DarkThemeProvider,
|
|
5
|
+
useTheme,
|
|
6
|
+
} from "./ThemeProvider.tsx";
|
|
7
|
+
export type { ThemeMode } from "./ThemeProvider.tsx";
|
|
8
|
+
export {
|
|
9
|
+
LIGHT_THEME,
|
|
10
|
+
DARK_THEME,
|
|
11
|
+
mergeThemes,
|
|
12
|
+
type KyroTheme,
|
|
13
|
+
type ThemeColors,
|
|
14
|
+
type ThemeTypography,
|
|
15
|
+
type ThemeSpacing,
|
|
16
|
+
type ThemeRadius,
|
|
17
|
+
type ThemeShadows,
|
|
18
|
+
type BlockThemeOverrides,
|
|
19
|
+
type FieldThemeOverrides,
|
|
20
|
+
} from "./tokens.ts";
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
export interface ThemeColors {
|
|
2
|
+
primary?: string;
|
|
3
|
+
secondary?: string;
|
|
4
|
+
background?: string;
|
|
5
|
+
surface?: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
textMuted?: string;
|
|
8
|
+
border?: string;
|
|
9
|
+
success?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
warning?: string;
|
|
12
|
+
info?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ThemeTypography {
|
|
16
|
+
fontFamily?: string;
|
|
17
|
+
fontFamilyMono?: string;
|
|
18
|
+
fontSize?: {
|
|
19
|
+
xs?: string;
|
|
20
|
+
sm?: string;
|
|
21
|
+
md?: string;
|
|
22
|
+
lg?: string;
|
|
23
|
+
xl?: string;
|
|
24
|
+
"2xl"?: string;
|
|
25
|
+
"3xl"?: string;
|
|
26
|
+
"4xl"?: string;
|
|
27
|
+
};
|
|
28
|
+
fontWeight?: {
|
|
29
|
+
normal?: number;
|
|
30
|
+
medium?: number;
|
|
31
|
+
semibold?: number;
|
|
32
|
+
bold?: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ThemeSpacing {
|
|
37
|
+
xs?: string;
|
|
38
|
+
sm?: string;
|
|
39
|
+
md?: string;
|
|
40
|
+
lg?: string;
|
|
41
|
+
xl?: string;
|
|
42
|
+
"2xl"?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ThemeRadius {
|
|
46
|
+
sm?: string;
|
|
47
|
+
md?: string;
|
|
48
|
+
lg?: string;
|
|
49
|
+
full?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ThemeShadows {
|
|
53
|
+
sm?: string;
|
|
54
|
+
md?: string;
|
|
55
|
+
lg?: string;
|
|
56
|
+
xl?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface BlockThemeOverrides {
|
|
60
|
+
card?: {
|
|
61
|
+
background?: string;
|
|
62
|
+
borderRadius?: string;
|
|
63
|
+
padding?: string;
|
|
64
|
+
shadow?: string;
|
|
65
|
+
};
|
|
66
|
+
hero?: {
|
|
67
|
+
background?: string;
|
|
68
|
+
textAlign?: "left" | "center" | "right";
|
|
69
|
+
};
|
|
70
|
+
gallery?: {
|
|
71
|
+
gridColumns?: number;
|
|
72
|
+
gap?: string;
|
|
73
|
+
};
|
|
74
|
+
code?: {
|
|
75
|
+
fontFamily?: string;
|
|
76
|
+
fontSize?: string;
|
|
77
|
+
background?: string;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface FieldThemeOverrides {
|
|
82
|
+
input?: {
|
|
83
|
+
background?: string;
|
|
84
|
+
border?: string;
|
|
85
|
+
borderRadius?: string;
|
|
86
|
+
padding?: string;
|
|
87
|
+
focusRing?: string;
|
|
88
|
+
};
|
|
89
|
+
select?: {
|
|
90
|
+
background?: string;
|
|
91
|
+
border?: string;
|
|
92
|
+
};
|
|
93
|
+
textarea?: {
|
|
94
|
+
background?: string;
|
|
95
|
+
minHeight?: string;
|
|
96
|
+
};
|
|
97
|
+
richText?: {
|
|
98
|
+
toolbarBackground?: string;
|
|
99
|
+
buttonActive?: string;
|
|
100
|
+
};
|
|
101
|
+
upload?: {
|
|
102
|
+
dropzoneBackground?: string;
|
|
103
|
+
dropzoneActive?: string;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface KyroTheme {
|
|
108
|
+
id?: string;
|
|
109
|
+
label?: string;
|
|
110
|
+
colors?: ThemeColors;
|
|
111
|
+
typography?: ThemeTypography;
|
|
112
|
+
spacing?: ThemeSpacing;
|
|
113
|
+
borderRadius?: ThemeRadius;
|
|
114
|
+
shadows?: ThemeShadows;
|
|
115
|
+
blocks?: BlockThemeOverrides;
|
|
116
|
+
fields?: FieldThemeOverrides;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const LIGHT_THEME: KyroTheme = {
|
|
120
|
+
id: "light",
|
|
121
|
+
label: "Light",
|
|
122
|
+
colors: {
|
|
123
|
+
primary: "#3b82f6",
|
|
124
|
+
secondary: "#8b5cf6",
|
|
125
|
+
background: "#f8fafc",
|
|
126
|
+
surface: "#ffffff",
|
|
127
|
+
text: "#0f172a",
|
|
128
|
+
textMuted: "#64748b",
|
|
129
|
+
border: "#e2e8f0",
|
|
130
|
+
success: "#22c55e",
|
|
131
|
+
error: "#ef4444",
|
|
132
|
+
warning: "#f59e0b",
|
|
133
|
+
info: "#06b6d4",
|
|
134
|
+
},
|
|
135
|
+
typography: {
|
|
136
|
+
fontFamily: "'Inter', system-ui, -apple-system, sans-serif",
|
|
137
|
+
fontFamilyMono: "'Fira Code', 'Cascadia Code', monospace",
|
|
138
|
+
},
|
|
139
|
+
spacing: {
|
|
140
|
+
xs: "0.25rem",
|
|
141
|
+
sm: "0.5rem",
|
|
142
|
+
md: "1rem",
|
|
143
|
+
lg: "1.5rem",
|
|
144
|
+
xl: "2rem",
|
|
145
|
+
"2xl": "3rem",
|
|
146
|
+
},
|
|
147
|
+
borderRadius: {
|
|
148
|
+
sm: "0.25rem",
|
|
149
|
+
md: "0.5rem",
|
|
150
|
+
lg: "0.75rem",
|
|
151
|
+
full: "9999px",
|
|
152
|
+
},
|
|
153
|
+
shadows: {
|
|
154
|
+
sm: "0 1px 2px rgba(0,0,0,0.05)",
|
|
155
|
+
md: "0 4px 6px -1px rgba(0,0,0,0.1)",
|
|
156
|
+
lg: "0 10px 15px -3px rgba(0,0,0,0.1)",
|
|
157
|
+
xl: "0 20px 25px -5px rgba(0,0,0,0.1)",
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const DARK_THEME: KyroTheme = {
|
|
162
|
+
id: "dark",
|
|
163
|
+
label: "Dark",
|
|
164
|
+
colors: {
|
|
165
|
+
primary: "#60a5fa",
|
|
166
|
+
secondary: "#a78bfa",
|
|
167
|
+
background: "#0b1222",
|
|
168
|
+
surface: "#1e293b",
|
|
169
|
+
text: "#f1f5f9",
|
|
170
|
+
textMuted: "#94a3b8",
|
|
171
|
+
border: "#334155",
|
|
172
|
+
success: "#4ade80",
|
|
173
|
+
error: "#f87171",
|
|
174
|
+
warning: "#fbbf24",
|
|
175
|
+
info: "#22d3ee",
|
|
176
|
+
},
|
|
177
|
+
typography: {
|
|
178
|
+
fontFamily: "'Inter', system-ui, -apple-system, sans-serif",
|
|
179
|
+
fontFamilyMono: "'Fira Code', 'Cascadia Code', monospace",
|
|
180
|
+
},
|
|
181
|
+
spacing: {
|
|
182
|
+
xs: "0.25rem",
|
|
183
|
+
sm: "0.5rem",
|
|
184
|
+
md: "1rem",
|
|
185
|
+
lg: "1.5rem",
|
|
186
|
+
xl: "2rem",
|
|
187
|
+
"2xl": "3rem",
|
|
188
|
+
},
|
|
189
|
+
borderRadius: {
|
|
190
|
+
sm: "0.25rem",
|
|
191
|
+
md: "0.5rem",
|
|
192
|
+
lg: "0.75rem",
|
|
193
|
+
full: "9999px",
|
|
194
|
+
},
|
|
195
|
+
shadows: {
|
|
196
|
+
sm: "0 1px 2px rgba(0,0,0,0.3)",
|
|
197
|
+
md: "0 4px 6px -1px rgba(0,0,0,0.4)",
|
|
198
|
+
lg: "0 10px 15px -3px rgba(0,0,0,0.4)",
|
|
199
|
+
xl: "0 20px 25px -5px rgba(0,0,0,0.5)",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export function mergeThemes(
|
|
204
|
+
base: KyroTheme,
|
|
205
|
+
overrides: Partial<KyroTheme>,
|
|
206
|
+
): KyroTheme {
|
|
207
|
+
return {
|
|
208
|
+
...base,
|
|
209
|
+
...overrides,
|
|
210
|
+
colors: { ...base.colors, ...overrides.colors },
|
|
211
|
+
typography: { ...base.typography, ...overrides.typography },
|
|
212
|
+
spacing: { ...base.spacing, ...overrides.spacing },
|
|
213
|
+
borderRadius: { ...base.borderRadius, ...overrides.borderRadius },
|
|
214
|
+
shadows: { ...base.shadows, ...overrides.shadows },
|
|
215
|
+
blocks: base.blocks
|
|
216
|
+
? { ...base.blocks, ...overrides.blocks }
|
|
217
|
+
: overrides.blocks,
|
|
218
|
+
fields: base.fields
|
|
219
|
+
? { ...base.fields, ...overrides.fields }
|
|
220
|
+
: overrides.fields,
|
|
221
|
+
};
|
|
222
|
+
}
|