Skip to content

Commit eee16ad

Browse files
author
Javed Hussain
committed
Added these support
1 parent b545c5f commit eee16ad

File tree

6 files changed

+455
-3
lines changed

6 files changed

+455
-3
lines changed

src/lib/components/feature/settings/SettingsForm.svelte

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import * as Tabs from '$ui/tabs';
66
import { Checkbox } from '$ui/checkbox';
77
import { configStore } from '$stores/config.svelte';
8+
import { themeStore } from '$lib/stores/theme.svelte';
9+
import { themes } from '$lib/config/themes';
810
import Calendar from '@lucide/svelte/icons/calendar';
911
import Currency from '@lucide/svelte/icons/currency';
1012
import Earth from '@lucide/svelte/icons/earth';
1113
import Languages from '@lucide/svelte/icons/languages';
12-
import PaintBucket from '@lucide/svelte/icons/paint-bucket';
14+
import Palette from '@lucide/svelte/icons/palette';
1315
import RulerDimensionLine from '@lucide/svelte/icons/ruler-dimension-line';
1416
import SubmitButton from '$appui/SubmitButton.svelte';
1517
import { toast } from 'svelte-sonner';
@@ -48,6 +50,7 @@
4850
currency: z.string().min(1, 'Currency is required'),
4951
unitOfDistance: z.enum(['kilometer', 'mile']),
5052
unitOfVolume: z.enum(['liter', 'gallon']),
53+
theme: z.string().default('light'),
5154
customCss: z.string().optional(),
5255
featureFuelLog: z.boolean().default(true),
5356
featureMaintenance: z.boolean().default(true),
@@ -64,9 +67,17 @@
6467
onUpdated: async ({ form: f }) => {
6568
if (f.valid) {
6669
processing = true;
70+
71+
// Handle theme change
72+
if (f.data.theme) {
73+
themeStore.setTheme(f.data.theme as any);
74+
}
75+
6776
const updatedConfig = localConfig.map((item) => {
6877
if (item.key in f.data) {
6978
const value = f.data[item.key as keyof typeof f.data];
79+
// Skip theme as it's not in config table
80+
if (item.key === 'theme') return item;
7081
// Convert boolean values to strings for storage
7182
const stringValue = typeof value === 'boolean' ? String(value) : value;
7283
return { ...item, value: stringValue };
@@ -112,6 +123,8 @@
112123
configData[item.key] = item.value || '';
113124
}
114125
});
126+
// Add current theme to form data
127+
configData.theme = themeStore.theme;
115128
console.log('Setting form data:', configData);
116129
formData.set(configData);
117130
}
@@ -200,7 +213,7 @@
200213
<Select.Root bind:value={$formData.unitOfVolume} type="single">
201214
<Select.Trigger {...props} class="w-full">
202215
<div class="flex items-center justify-start">
203-
<PaintBucket class="mr-2 h-4 w-4" />
216+
<Currency class="mr-2 h-4 w-4" />
204217
{uovOptions.find((opt) => opt.value === $formData.unitOfVolume)?.label ||
205218
'Select unit system'}
206219
</div>
@@ -253,7 +266,37 @@
253266
<!-- Interface Tab -->
254267
<Tabs.Content value="interface" class="space-y-6">
255268
<fieldset class="flex flex-col gap-6" disabled={processing}>
256-
<!-- Date Format -->
269+
<!-- Theme -->
270+
<Form.Field {form} name="theme" class="w-full">
271+
<Form.Control>
272+
{#snippet children({ props })}
273+
<FormLabel description="Choose your preferred theme">Theme</FormLabel>
274+
<Select.Root bind:value={$formData.theme} type="single">
275+
<Select.Trigger {...props} class="w-full">
276+
<div class="flex items-center justify-start">
277+
<Palette class="mr-2 h-4 w-4" />
278+
{themes[$formData.theme]?.label || 'Select theme'}
279+
</div>
280+
</Select.Trigger>
281+
<Select.Content>
282+
{#each Object.values(themes) as theme (theme.name)}
283+
<Select.Item value={theme.name}>
284+
<div class="flex items-center gap-2">
285+
<div
286+
class="border-foreground/20 h-3 w-3 rounded border"
287+
style="background-color: {theme.colors?.primary || '#000'}"
288+
></div>
289+
{theme.label}
290+
</div>
291+
</Select.Item>
292+
{/each}
293+
</Select.Content>
294+
</Select.Root>
295+
{/snippet}
296+
</Form.Control>
297+
<Form.FieldErrors />
298+
</Form.Field>
299+
<!-- Custom CSS -->
257300
<Form.Field {form} name="customCss" class="w-full">
258301
<Form.Control>
259302
{#snippet children({ props })}

src/lib/config/themes.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import type { ThemeConfig } from '$lib/types/theme';
2+
3+
export const themes: Record<string, ThemeConfig> = {
4+
slate: {
5+
name: 'slate',
6+
label: 'Slate',
7+
active: false,
8+
colors: {
9+
primary: 'oklch(0.356 0.011 252.894)',
10+
primaryForeground: 'oklch(0.985 0 0)',
11+
ring: 'oklch(0.356 0.011 252.894)'
12+
},
13+
darkColors: {
14+
primary: 'oklch(0.876 0.015 252.894)',
15+
primaryForeground: 'oklch(0.145 0.001 247.858)',
16+
ring: 'oklch(0.876 0.015 252.894)'
17+
}
18+
},
19+
stone: {
20+
name: 'stone',
21+
label: 'Stone',
22+
active: false,
23+
colors: {
24+
primary: 'oklch(0.38 0.005 60)',
25+
primaryForeground: 'oklch(0.985 0 0)',
26+
ring: 'oklch(0.38 0.005 60)'
27+
},
28+
darkColors: {
29+
primary: 'oklch(0.88 0.005 60)',
30+
primaryForeground: 'oklch(0.145 0 0)',
31+
ring: 'oklch(0.88 0.005 60)'
32+
}
33+
},
34+
red: {
35+
name: 'red',
36+
label: 'Red',
37+
active: false,
38+
colors: {
39+
primary: 'oklch(0.577 0.245 27.325)',
40+
primaryForeground: 'oklch(0.985 0 0)',
41+
ring: 'oklch(0.577 0.245 27.325)'
42+
},
43+
darkColors: {
44+
primary: 'oklch(0.677 0.245 27.325)',
45+
primaryForeground: 'oklch(0.985 0 0)',
46+
ring: 'oklch(0.677 0.245 27.325)'
47+
}
48+
},
49+
rose: {
50+
name: 'rose',
51+
label: 'Rose',
52+
active: false,
53+
colors: {
54+
primary: 'oklch(0.643 0.181 19.301)',
55+
primaryForeground: 'oklch(0.985 0 0)',
56+
ring: 'oklch(0.643 0.181 19.301)'
57+
},
58+
darkColors: {
59+
primary: 'oklch(0.743 0.181 19.301)',
60+
primaryForeground: 'oklch(0.985 0 0)',
61+
ring: 'oklch(0.743 0.181 19.301)'
62+
}
63+
},
64+
blue: {
65+
name: 'blue',
66+
label: 'Blue',
67+
active: false,
68+
colors: {
69+
primary: 'oklch(0.488 0.243 264.376)',
70+
primaryForeground: 'oklch(0.985 0 0)',
71+
ring: 'oklch(0.488 0.243 264.376)'
72+
},
73+
darkColors: {
74+
primary: 'oklch(0.588 0.243 264.376)',
75+
primaryForeground: 'oklch(0.985 0 0)',
76+
ring: 'oklch(0.588 0.243 264.376)'
77+
}
78+
},
79+
green: {
80+
name: 'green',
81+
label: 'Green',
82+
active: false,
83+
colors: {
84+
primary: 'oklch(0.54 0.194 142.495)',
85+
primaryForeground: 'oklch(0.985 0 0)',
86+
ring: 'oklch(0.54 0.194 142.495)'
87+
},
88+
darkColors: {
89+
primary: 'oklch(0.64 0.194 142.495)',
90+
primaryForeground: 'oklch(0.985 0 0)',
91+
ring: 'oklch(0.64 0.194 142.495)'
92+
}
93+
},
94+
purple: {
95+
name: 'purple',
96+
label: 'Purple',
97+
active: false,
98+
colors: {
99+
primary: 'oklch(0.589 0.25 292.514)',
100+
primaryForeground: 'oklch(0.985 0 0)',
101+
ring: 'oklch(0.589 0.25 292.514)'
102+
},
103+
darkColors: {
104+
primary: 'oklch(0.689 0.25 292.514)',
105+
primaryForeground: 'oklch(0.985 0 0)',
106+
ring: 'oklch(0.689 0.25 292.514)'
107+
}
108+
},
109+
orange: {
110+
name: 'orange',
111+
label: 'Orange',
112+
active: false,
113+
colors: {
114+
primary: 'oklch(0.662 0.218 46.415)',
115+
primaryForeground: 'oklch(0.985 0 0)',
116+
ring: 'oklch(0.662 0.218 46.415)'
117+
},
118+
darkColors: {
119+
primary: 'oklch(0.762 0.218 46.415)',
120+
primaryForeground: 'oklch(0.985 0 0)',
121+
ring: 'oklch(0.762 0.218 46.415)'
122+
}
123+
},
124+
yellow: {
125+
name: 'yellow',
126+
label: 'Yellow',
127+
active: false,
128+
colors: {
129+
primary: 'oklch(0.776 0.173 91.935)',
130+
primaryForeground: 'oklch(0.205 0 0)',
131+
ring: 'oklch(0.776 0.173 91.935)'
132+
},
133+
darkColors: {
134+
primary: 'oklch(0.876 0.173 91.935)',
135+
primaryForeground: 'oklch(0.145 0 0)',
136+
ring: 'oklch(0.876 0.173 91.935)'
137+
}
138+
},
139+
teal: {
140+
name: 'teal',
141+
label: 'Teal',
142+
active: false,
143+
colors: {
144+
primary: 'oklch(0.559 0.151 180.735)',
145+
primaryForeground: 'oklch(0.985 0 0)',
146+
ring: 'oklch(0.559 0.151 180.735)'
147+
},
148+
darkColors: {
149+
primary: 'oklch(0.659 0.151 180.735)',
150+
primaryForeground: 'oklch(0.985 0 0)',
151+
ring: 'oklch(0.659 0.151 180.735)'
152+
}
153+
},
154+
indigo: {
155+
name: 'indigo',
156+
label: 'Indigo',
157+
active: false,
158+
colors: {
159+
primary: 'oklch(0.482 0.198 272.314)',
160+
primaryForeground: 'oklch(0.985 0 0)',
161+
ring: 'oklch(0.482 0.198 272.314)'
162+
},
163+
darkColors: {
164+
primary: 'oklch(0.582 0.198 272.314)',
165+
primaryForeground: 'oklch(0.985 0 0)',
166+
ring: 'oklch(0.582 0.198 272.314)'
167+
}
168+
},
169+
pink: {
170+
name: 'pink',
171+
label: 'Pink',
172+
active: false,
173+
colors: {
174+
primary: 'oklch(0.671 0.221 349.761)',
175+
primaryForeground: 'oklch(0.985 0 0)',
176+
ring: 'oklch(0.671 0.221 349.761)'
177+
},
178+
darkColors: {
179+
primary: 'oklch(0.771 0.221 349.761)',
180+
primaryForeground: 'oklch(0.985 0 0)',
181+
ring: 'oklch(0.771 0.221 349.761)'
182+
}
183+
}
184+
};
185+
186+
export const themesList = Object.values(themes);

src/lib/stores/theme.svelte.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { getContext, setContext } from 'svelte';
2+
import { type ThemeName, type ThemeConfig } from '$lib/types/theme';
3+
import { themes } from '$lib/config/themes';
4+
import { getStoredTheme, saveTheme, applyThemeColors, setThemeClass } from '$lib/utils/theme';
5+
6+
const THEME_KEY = Symbol('theme');
7+
8+
interface ThemeStore {
9+
theme: ThemeName;
10+
setTheme: (name: ThemeName) => void;
11+
getThemes: () => ThemeConfig[];
12+
getActiveTheme: () => ThemeConfig | undefined;
13+
initializeTheme: () => void;
14+
}
15+
16+
function createThemeStore(): ThemeStore {
17+
let theme = $state<ThemeName>('light');
18+
let initialized = false;
19+
20+
function applyTheme(name: ThemeName) {
21+
const themeConfig = themes[name];
22+
if (themeConfig) {
23+
applyThemeColors(themeConfig.colors, themeConfig.darkColors);
24+
setThemeClass(name);
25+
}
26+
}
27+
28+
function initializeTheme() {
29+
if (initialized || typeof window === 'undefined') return;
30+
31+
const storedTheme = getStoredTheme();
32+
if (storedTheme && storedTheme in themes) {
33+
theme = storedTheme;
34+
} else {
35+
theme = 'light';
36+
}
37+
38+
applyTheme(theme);
39+
initialized = true;
40+
}
41+
42+
function setTheme(name: ThemeName) {
43+
if (name in themes) {
44+
theme = name;
45+
applyTheme(name);
46+
saveTheme(name);
47+
}
48+
}
49+
50+
function getThemes(): ThemeConfig[] {
51+
return Object.values(themes);
52+
}
53+
54+
function getActiveTheme(): ThemeConfig | undefined {
55+
return themes[theme];
56+
}
57+
58+
return {
59+
get theme() {
60+
return theme;
61+
},
62+
set theme(value: ThemeName) {
63+
setTheme(value);
64+
},
65+
setTheme,
66+
getThemes,
67+
getActiveTheme,
68+
initializeTheme
69+
};
70+
}
71+
72+
export const themeStore = createThemeStore();
73+
74+
// Context helpers for components
75+
export function setThemeContext(store: ThemeStore) {
76+
return setContext(THEME_KEY, store);
77+
}
78+
79+
export function getThemeContext(): ThemeStore {
80+
return getContext<ThemeStore>(THEME_KEY);
81+
}

0 commit comments

Comments
 (0)