@moontra/moonui 2.4.13 → 2.5.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/src/lib/theme.tsx CHANGED
@@ -1,398 +1,262 @@
1
- import * as React from 'react';
2
- import { createContext, useContext, useState, useMemo, ReactNode } from 'react';
1
+ "use client";
3
2
 
4
3
  /**
5
- * Tema türleri için temel renk paletini tanımlayan tip
4
+ * MoonUI ThemeProvider CALISIR tema sistemi.
5
+ *
6
+ * ONCEKI HALI BOZUKTU: eski ThemeProvider yalnizca bir React context tasiyordu;
7
+ * DOM'a hicbir sey yazmiyordu (setProperty/classList/useEffect yoktu) ve
8
+ * createCssVariables() export edilmis ama HIC CAGRILMAMISTI. Yani <ThemeProvider>
9
+ * gorsel olarak no-op'tu. Ustelik token isimleri (--color-primary) ve deger formati
10
+ * (tam `hsl(...)` string'i) component'lerin okudugu sozlesmeyle uyusmuyordu:
11
+ * Tailwind preset'i renkleri `hsl(var(--primary) / <alpha-value>)` diye bagliyor,
12
+ * dolayisiyla tam hsl(...) yazilsaydi `hsl(hsl(...))` olusur ve renk kirilirdi.
13
+ *
14
+ * BU SURUM:
15
+ * - tokens.css ile AYNI sozlesmeyi kullanir: --primary, --card-foreground, --radius
16
+ * ve ham HSL ucusu ("217.2 91.2% 59.8%").
17
+ * - Token'lari gercekten <html> uzerine inline custom property olarak YAZAR.
18
+ * - 'default' preset'te ve ozel token yokken HICBIR SEY YAZMAZ; statik tokens.css
19
+ * gecerli kalir. Bu, "provider'i mount etmek gorunumu degistirmez" garantisini
20
+ * YAPISAL olarak verir.
21
+ * - Karanlik mod next-themes ile uyumlu: <html>'deki `dark` sinifini MutationObserver
22
+ * ile IZLER ve preset'in dark varyantini uygular. `dark` sinifini kendisi YAZMAZ —
23
+ * o isi tema kutuphanenize birakir, cakisma olmaz.
6
24
  */
7
- export type ThemeColors = {
8
- primary: string;
9
- secondary: string;
10
- success: string;
11
- warning: string;
12
- error: string;
13
- background: string;
14
- foreground: string;
15
- card: string;
16
- cardForeground: string;
17
- border: string;
18
- muted: string;
19
- mutedForeground: string;
20
- accent: string;
21
- accentForeground: string;
22
- };
23
25
 
24
- /**
25
- * Tema yapılandırma tipi
26
- */
27
- export type ThemeConfig = {
28
- colors: ThemeColors;
29
- borderRadius: {
30
- sm: string;
31
- md: string;
32
- lg: string;
33
- full: string;
34
- };
35
- fontSizes: {
36
- xs: string;
37
- sm: string;
38
- base: string;
39
- lg: string;
40
- xl: string;
41
- '2xl': string;
42
- '3xl': string;
43
- };
44
- spacing: {
45
- 1: string;
46
- 2: string;
47
- 4: string;
48
- 6: string;
49
- 8: string;
50
- };
51
- // Kullanıcıların kendi özel değişkenlerini ekleyebilmesi için
52
- extensions?: Record<string, Record<string, string> | string>;
53
- };
26
+ import * as React from "react";
27
+ import {
28
+ themePresets,
29
+ type ThemePresetName,
30
+ type ThemePresetDefinition,
31
+ type ThemeTokens,
32
+ } from "./theme-presets";
54
33
 
55
- /**
56
- * Tema hazır şablonları
57
- */
58
- export type ThemePreset = 'default' | 'modern' | 'rounded' | 'minimal';
34
+ export { themePresets };
35
+ export type { ThemePresetName, ThemePresetDefinition, ThemeTokens };
59
36
 
60
- /**
61
- * Varsayılan açık tema
62
- */
63
- // Modern teması için değerler
64
- const modernTheme: Partial<ThemeConfig> = {
65
- borderRadius: {
66
- sm: '0.5rem',
67
- md: '0.75rem',
68
- lg: '1rem',
69
- full: '9999px',
70
- },
71
- colors: {
72
- primary: 'hsl(250, 95%, 64%)',
73
- secondary: 'hsl(250, 70%, 80%)',
74
- success: 'hsl(142, 71%, 45%)',
75
- warning: 'hsl(38, 92%, 50%)',
76
- error: 'hsl(0, 84%, 60%)',
77
- background: 'hsl(0, 0%, 100%)',
78
- foreground: 'hsl(221, 39%, 11%)',
79
- card: 'hsl(0, 0%, 100%)',
80
- cardForeground: 'hsl(221, 39%, 11%)',
81
- border: 'hsl(210, 20%, 90%)',
82
- muted: 'hsl(210, 20%, 96%)',
83
- mutedForeground: 'hsl(215, 16%, 47%)',
84
- accent: 'hsl(328, 85%, 70%)',
85
- accentForeground: 'hsl(210, 20%, 98%)',
86
- }
87
- };
37
+ /** camelCase token adi -> CSS custom property. cardForeground -> --card-foreground,
38
+ * chart1 -> --chart-1 */
39
+ const toCssVar = (key: string): string =>
40
+ "--" +
41
+ key
42
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
43
+ .replace(/([a-zA-Z])(\d)/g, "$1-$2")
44
+ .toLowerCase();
88
45
 
89
- // Yuvarlak teması için değerler
90
- const roundedTheme = {
91
- borderRadius: {
92
- sm: '1rem',
93
- md: '1.25rem',
94
- lg: '1.5rem',
95
- full: '9999px',
96
- }
97
- };
46
+ /** Bu provider'in <html>'e yazdigi property'lerin listesi burada tutulur; temizlik
47
+ * yalnizca kendi yazdiklarimizi siler, sayfanin diger inline degiskenlerine dokunmaz. */
48
+ const OWNED_ATTR = "data-moonui-theme-vars";
98
49
 
99
- // Minimal teması için değerler
100
- const minimalTheme: Partial<ThemeConfig> = {
101
- borderRadius: {
102
- sm: '0.125rem',
103
- md: '0.25rem',
104
- lg: '0.375rem',
105
- full: '9999px',
106
- },
107
- colors: {
108
- primary: 'hsl(220, 14%, 40%)',
109
- secondary: 'hsl(220, 9%, 60%)',
110
- success: 'hsl(142, 71%, 45%)',
111
- warning: 'hsl(38, 92%, 50%)',
112
- error: 'hsl(0, 84%, 60%)',
113
- background: 'hsl(0, 0%, 100%)',
114
- foreground: 'hsl(221, 39%, 11%)',
115
- card: 'hsl(0, 0%, 100%)',
116
- cardForeground: 'hsl(221, 39%, 11%)',
117
- border: 'hsl(220, 13%, 91%)',
118
- muted: 'hsl(220, 14%, 96%)',
119
- mutedForeground: 'hsl(215, 16%, 47%)',
120
- accent: 'hsl(210, 20%, 96%)',
121
- accentForeground: 'hsl(221, 39%, 11%)',
122
- }
123
- };
50
+ export type ColorMode = "light" | "dark";
124
51
 
125
- export const defaultLightTheme: ThemeConfig = {
126
- colors: {
127
- primary: 'hsl(221, 83%, 53%)',
128
- secondary: 'hsl(215, 20%, 65%)',
129
- success: 'hsl(142, 71%, 45%)',
130
- warning: 'hsl(38, 92%, 50%)',
131
- error: 'hsl(0, 84%, 60%)',
132
- background: 'hsl(0, 0%, 100%)',
133
- foreground: 'hsl(221, 39%, 11%)',
134
- card: 'hsl(0, 0%, 100%)',
135
- cardForeground: 'hsl(221, 39%, 11%)',
136
- border: 'hsl(210, 20%, 90%)',
137
- muted: 'hsl(210, 20%, 96%)',
138
- mutedForeground: 'hsl(215, 16%, 47%)',
139
- accent: 'hsl(210, 20%, 96%)',
140
- accentForeground: 'hsl(221, 39%, 11%)',
141
- },
142
- borderRadius: {
143
- sm: '0.25rem',
144
- md: '0.375rem',
145
- lg: '0.5rem',
146
- full: '9999px',
147
- },
148
- fontSizes: {
149
- xs: '0.75rem',
150
- sm: '0.875rem',
151
- base: '1rem',
152
- lg: '1.125rem',
153
- xl: '1.25rem',
154
- '2xl': '1.5rem',
155
- '3xl': '1.875rem',
156
- },
157
- spacing: {
158
- 1: '0.25rem',
159
- 2: '0.5rem',
160
- 4: '1rem',
161
- 6: '1.5rem',
162
- 8: '2rem',
163
- },
164
- };
165
-
166
- /**
167
- * Varsayılan koyu tema
168
- */
169
- export const defaultDarkTheme: ThemeConfig = {
170
- ...defaultLightTheme,
171
- colors: {
172
- primary: 'hsl(221, 83%, 53%)',
173
- secondary: 'hsl(215, 20%, 65%)',
174
- success: 'hsl(142, 71%, 45%)',
175
- warning: 'hsl(38, 92%, 50%)',
176
- error: 'hsl(0, 84%, 60%)',
177
- background: 'hsl(221, 39%, 11%)',
178
- foreground: 'hsl(210, 20%, 98%)',
179
- card: 'hsl(220, 26%, 14%)',
180
- cardForeground: 'hsl(210, 20%, 98%)',
181
- border: 'hsl(215, 28%, 17%)',
182
- muted: 'hsl(215, 27%, 16%)',
183
- mutedForeground: 'hsl(215, 16%, 57%)',
184
- accent: 'hsl(215, 27%, 16%)',
185
- accentForeground: 'hsl(210, 20%, 98%)',
186
- },
187
- };
188
-
189
- /**
190
- * Tema bağlamı için tip
191
- */
192
- export interface ThemeContextType {
193
- theme: ThemeConfig;
194
- colorMode: 'light' | 'dark';
195
- setColorMode: (mode: 'light' | 'dark') => void;
196
- updateTheme: (config: Partial<ThemeConfig>) => void;
52
+ export interface ThemeContextValue {
53
+ preset: ThemePresetName;
54
+ setPreset: (preset: ThemePresetName) => void;
55
+ /** Tum preset tanimlari — tema secici UI'lari icin (isim, aciklama, kategori) */
56
+ presets: Record<ThemePresetName, ThemePresetDefinition>;
57
+ /** Gecerli renk modu (<html>.dark sinifindan okunur) */
58
+ colorMode: ColorMode;
59
+ /** Preset uzerine binen serbest override'lar, ornek: { primary: "270 90% 60%" } */
60
+ overrides: Partial<ThemeTokens> & Record<string, string>;
61
+ setOverrides: (o: Partial<ThemeTokens> & Record<string, string>) => void;
62
+ /** Su an DOM'a yazilmis efektif token'lar; hicbir sey yazilmadiysa null */
63
+ appliedTokens: Record<string, string> | null;
197
64
  }
198
65
 
199
- // Tema bağlamı
200
- const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
66
+ const ThemeContext = React.createContext<ThemeContextValue | null>(null);
201
67
 
202
- // Tema sağlayıcı props
203
68
  export interface ThemeProviderProps {
204
- children: ReactNode;
205
- defaultColorMode?: 'light' | 'dark';
206
- preset?: ThemePreset;
207
- lightTheme?: Partial<ThemeConfig>;
208
- darkTheme?: Partial<ThemeConfig>;
209
- extensions?: Record<string, Record<string, string> | string>;
69
+ children: React.ReactNode;
70
+ /** Baslangic preset'i. Varsayilan "default" = statik tokens.css, hicbir sey yazilmaz */
71
+ defaultPreset?: ThemePresetName;
72
+ /** Kontrollu kullanim: verilirse ic state yerine bu gecerlidir */
73
+ preset?: ThemePresetName;
74
+ overrides?: Partial<ThemeTokens> & Record<string, string>;
75
+ /** Secimi localStorage'a yaz/oku. Varsayilan false (opt-in) */
76
+ persist?: boolean;
77
+ storageKey?: string;
210
78
  }
211
79
 
212
- /**
213
- * Tema önayarını getir
214
- */
215
- const getPresetTheme = (preset: ThemePreset): Partial<ThemeConfig> => {
216
- switch (preset) {
217
- case 'modern':
218
- return modernTheme;
219
- case 'rounded':
220
- return roundedTheme;
221
- case 'minimal':
222
- return minimalTheme;
223
- default:
224
- return {};
225
- }
226
- };
80
+ const readColorMode = (): ColorMode =>
81
+ typeof document !== "undefined" &&
82
+ document.documentElement.classList.contains("dark")
83
+ ? "dark"
84
+ : "light";
227
85
 
228
- /**
229
- * MoonUI tema sağlayıcısı
230
- */
231
86
  export const ThemeProvider = ({
232
87
  children,
233
- defaultColorMode = 'light',
234
- preset = 'default',
235
- lightTheme = {},
236
- darkTheme = {},
237
- extensions = {},
88
+ defaultPreset = "default",
89
+ preset: controlledPreset,
90
+ overrides: controlledOverrides,
91
+ persist = false,
92
+ storageKey = "moonui-theme-preset",
238
93
  }: ThemeProviderProps) => {
239
- // Renk modu state'i
240
- const [colorMode, setColorMode] = useState<'light' | 'dark'>(defaultColorMode);
241
-
242
- // Preset tema değerlerini al
243
- const presetThemeValues = getPresetTheme(preset);
94
+ const [uncontrolledPreset, setUncontrolledPreset] =
95
+ React.useState<ThemePresetName>(defaultPreset);
96
+ const [uncontrolledOverrides, setUncontrolledOverrides] = React.useState<
97
+ Partial<ThemeTokens> & Record<string, string>
98
+ >({});
99
+ // SSR ile ayni ilk render icin daima "light" ile basla; gercegi effect'te oku.
100
+ const [colorMode, setColorMode] = React.useState<ColorMode>("light");
101
+ const [appliedTokens, setAppliedTokens] = React.useState<Record<
102
+ string,
103
+ string
104
+ > | null>(null);
244
105
 
245
- // Özelleştirilmiş temalar
246
- const [customLightTheme, setCustomLightTheme] = useState<ThemeConfig>({
247
- ...defaultLightTheme,
248
- ...presetThemeValues,
249
- ...lightTheme,
250
- colors: {
251
- ...defaultLightTheme.colors,
252
- ...(presetThemeValues.colors || {}),
253
- ...(lightTheme.colors || {}),
254
- },
255
- extensions: extensions,
256
- });
257
-
258
- const [customDarkTheme, setCustomDarkTheme] = useState<ThemeConfig>({
259
- ...defaultDarkTheme,
260
- ...presetThemeValues,
261
- ...darkTheme,
262
- colors: {
263
- ...defaultDarkTheme.colors,
264
- ...(presetThemeValues.colors || {}),
265
- ...(darkTheme.colors || {}),
266
- },
267
- extensions: extensions,
268
- });
106
+ const preset = controlledPreset ?? uncontrolledPreset;
107
+ const overrides = controlledOverrides ?? uncontrolledOverrides;
269
108
 
270
- // Aktif tema
271
- const theme = useMemo(() => {
272
- return colorMode === 'light' ? customLightTheme : customDarkTheme;
273
- }, [colorMode, customLightTheme, customDarkTheme]);
109
+ // Kaydedilmis preset + ozel token'lari geri yukle. Ozel token'lar da saklanir;
110
+ // aksi halde kullanicinin olusturdugu tema sayfa yenilenince kaybolurdu.
111
+ React.useEffect(() => {
112
+ if (!persist) return;
113
+ try {
114
+ if (!controlledPreset) {
115
+ const saved = window.localStorage.getItem(storageKey);
116
+ if (saved && saved in themePresets) {
117
+ setUncontrolledPreset(saved as ThemePresetName);
118
+ }
119
+ }
120
+ if (!controlledOverrides) {
121
+ const savedOverrides = window.localStorage.getItem(
122
+ `${storageKey}-overrides`
123
+ );
124
+ if (savedOverrides) {
125
+ const parsed = JSON.parse(savedOverrides);
126
+ if (parsed && typeof parsed === "object") {
127
+ setUncontrolledOverrides(parsed);
128
+ }
129
+ }
130
+ }
131
+ } catch {
132
+ /* private mode / bozuk JSON — sessizce gec */
133
+ }
134
+ }, [persist, storageKey, controlledPreset, controlledOverrides]);
274
135
 
275
- // Tema güncelleme fonksiyonu
276
- const updateTheme = (config: Partial<ThemeConfig>) => {
277
- if (colorMode === 'light') {
278
- setCustomLightTheme(prev => ({
279
- ...prev,
280
- ...config,
281
- colors: {
282
- ...prev.colors,
283
- ...(config.colors || {}),
284
- },
285
- extensions: {
286
- ...(prev.extensions || {}),
287
- ...(config.extensions || {}),
288
- },
289
- }));
290
- } else {
291
- setCustomDarkTheme(prev => ({
292
- ...prev,
293
- ...config,
294
- colors: {
295
- ...prev.colors,
296
- ...(config.colors || {}),
297
- },
298
- extensions: {
299
- ...(prev.extensions || {}),
300
- ...(config.extensions || {}),
301
- },
302
- }));
136
+ // Ozel token'lari sakla (kontrolsuz kullanimda)
137
+ React.useEffect(() => {
138
+ if (!persist || controlledOverrides) return;
139
+ try {
140
+ const key = `${storageKey}-overrides`;
141
+ if (Object.keys(uncontrolledOverrides).length === 0) {
142
+ window.localStorage.removeItem(key);
143
+ } else {
144
+ window.localStorage.setItem(key, JSON.stringify(uncontrolledOverrides));
145
+ }
146
+ } catch {
147
+ /* yok say */
303
148
  }
304
- };
305
-
306
- // Tema değiştirme fonksiyonu
307
- const setThemePreset = (preset: ThemePreset) => {
308
- const presetValues = getPresetTheme(preset);
309
-
310
- updateTheme({
311
- ...presetValues,
312
- colors: presetValues.colors as ThemeColors,
313
- });
314
- };
149
+ }, [persist, storageKey, uncontrolledOverrides, controlledOverrides]);
315
150
 
316
- const value = {
317
- theme,
318
- colorMode,
319
- setColorMode,
320
- updateTheme,
321
- setThemePreset,
322
- };
151
+ // next-themes gibi kutuphaneler <html>'deki `dark` sinifini degistirir; izle.
152
+ React.useEffect(() => {
153
+ setColorMode(readColorMode());
154
+ const el = document.documentElement;
155
+ const observer = new MutationObserver(() => setColorMode(readColorMode()));
156
+ observer.observe(el, { attributes: true, attributeFilter: ["class"] });
157
+ return () => observer.disconnect();
158
+ }, []);
323
159
 
324
- return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
325
- };
160
+ // Asil is: token'lari DOM'a yaz.
161
+ React.useEffect(() => {
162
+ const root = document.documentElement;
326
163
 
327
- /**
328
- * Tema bağlamı için tip
329
- */
330
- export interface ThemeContextType {
331
- theme: ThemeConfig;
332
- colorMode: 'light' | 'dark';
333
- setColorMode: (mode: 'light' | 'dark') => void;
334
- updateTheme: (config: Partial<ThemeConfig>) => void;
335
- setThemePreset: (preset: ThemePreset) => void;
336
- }
164
+ const previous = root.getAttribute(OWNED_ATTR);
165
+ if (previous) {
166
+ for (const name of previous.split(",")) root.style.removeProperty(name);
167
+ root.removeAttribute(OWNED_ATTR);
168
+ }
337
169
 
338
- /**
339
- * Tema kullanma kancası
340
- */
341
- export const useTheme = (): ThemeContextType => {
342
- const context = useContext(ThemeContext);
343
-
344
- if (context === undefined) {
345
- throw new Error('useTheme must be used within a ThemeProvider');
346
- }
347
-
348
- return context;
349
- };
170
+ const hasOverrides = Object.keys(overrides).length > 0;
350
171
 
351
- /**
352
- * CSS değişkenlerini tema konfigürasyonundan oluşturma fonksiyonu
353
- */
354
- export const createCssVariables = (theme: ThemeConfig): Record<string, string> => {
355
- const variables: Record<string, string> = {};
356
-
357
- // Renkleri CSS değişkenlerine dönüştür
358
- Object.entries(theme.colors).forEach(([key, value]) => {
359
- variables[`--color-${key}`] = value;
360
- });
361
-
362
- // Border radius değerlerini CSS değişkenlerine dönüştür
363
- Object.entries(theme.borderRadius).forEach(([key, value]) => {
364
- variables[`--radius-${key}`] = value;
365
- });
366
-
367
- // Font boyutlarını CSS değişkenlerine dönüştür
368
- Object.entries(theme.fontSizes).forEach(([key, value]) => {
369
- variables[`--font-size-${key}`] = value;
370
- });
371
-
372
- // Boşluk değerlerini CSS değişkenlerine dönüştür
373
- Object.entries(theme.spacing).forEach(([key, value]) => {
374
- variables[`--space-${key}`] = value;
375
- });
376
-
377
- // Özel uzantı değişkenlerini CSS'e dönüştür
378
- if (theme.extensions) {
379
- Object.entries(theme.extensions).forEach(([categoryKey, categoryValue]) => {
380
- if (typeof categoryValue === 'object' && categoryValue !== null) {
381
- Object.entries(categoryValue as Record<string, string>).forEach(([key, value]) => {
382
- variables[`--${categoryKey}-${key}`] = value;
383
- });
384
- } else if (typeof categoryValue === 'string') {
385
- variables[`--${categoryKey}`] = categoryValue;
172
+ // SIFIR-DELTA: varsayilan preset + override yok => hicbir sey yazma.
173
+ if (preset === "default" && !hasOverrides) {
174
+ setAppliedTokens(null);
175
+ return;
176
+ }
177
+
178
+ const def = themePresets[preset] ?? themePresets.default;
179
+ const effective: Record<string, string> = {
180
+ ...(colorMode === "dark" ? def.dark : def.light),
181
+ radius: `${def.radius}rem`,
182
+ ...overrides,
183
+ };
184
+
185
+ const written: string[] = [];
186
+ for (const [key, value] of Object.entries(effective)) {
187
+ if (value == null || value === "") continue;
188
+ // "--" ile baslayan anahtarlar oldugu gibi gecer (ornek: "--brand-600")
189
+ const cssVar = key.startsWith("--") ? key : toCssVar(key);
190
+ root.style.setProperty(cssVar, value);
191
+ written.push(cssVar);
192
+ }
193
+ root.setAttribute(OWNED_ATTR, written.join(","));
194
+ setAppliedTokens(effective);
195
+
196
+ return () => {
197
+ for (const name of written) root.style.removeProperty(name);
198
+ root.removeAttribute(OWNED_ATTR);
199
+ };
200
+ }, [preset, overrides, colorMode]);
201
+
202
+ const setPreset = React.useCallback(
203
+ (next: ThemePresetName) => {
204
+ setUncontrolledPreset(next);
205
+ if (persist) {
206
+ try {
207
+ window.localStorage.setItem(storageKey, next);
208
+ } catch {
209
+ /* yok say */
210
+ }
386
211
  }
387
- });
212
+ },
213
+ [persist, storageKey]
214
+ );
215
+
216
+ const value = React.useMemo<ThemeContextValue>(
217
+ () => ({
218
+ preset,
219
+ setPreset,
220
+ presets: themePresets,
221
+ colorMode,
222
+ overrides,
223
+ setOverrides: setUncontrolledOverrides,
224
+ appliedTokens,
225
+ }),
226
+ [preset, setPreset, colorMode, overrides, appliedTokens]
227
+ );
228
+
229
+ return (
230
+ <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
231
+ );
232
+ };
233
+
234
+ export const useTheme = (): ThemeContextValue => {
235
+ const ctx = React.useContext(ThemeContext);
236
+ if (!ctx) {
237
+ throw new Error("useTheme, <ThemeProvider> icinde cagrilmalidir.");
388
238
  }
389
-
390
- return variables;
239
+ return ctx;
391
240
  };
392
241
 
393
242
  /**
394
- * CSS sınıflarını tema değerlerine göre üretme yardımcı fonksiyonu
243
+ * Bir preset'i CSS metnine cevirir — tema secicilerdeki "disa aktar" icin.
244
+ * Uretilen blok dogrudan globals.css'e yapistirilabilir.
395
245
  */
396
- export const getCssClass = (themeKey: keyof ThemeConfig, value: string): string => {
397
- return `var(--${themeKey}-${value})`;
246
+ export const themeToCss = (
247
+ preset: ThemePresetName,
248
+ { selector = ":root", darkSelector = ".dark" } = {}
249
+ ): string => {
250
+ const def = themePresets[preset] ?? themePresets.default;
251
+ const block = (tokens: ThemeTokens, radius?: string) =>
252
+ Object.entries(tokens)
253
+ .filter(([, v]) => v)
254
+ .map(([k, v]) => ` ${toCssVar(k)}: ${v};`)
255
+ .concat(radius ? [` --radius: ${radius};`] : [])
256
+ .join("\n");
257
+
258
+ return (
259
+ `${selector} {\n${block(def.light, `${def.radius}rem`)}\n}\n\n` +
260
+ `${darkSelector} {\n${block(def.dark)}\n}\n`
261
+ );
398
262
  };