Last active
January 17, 2025 12:07
-
-
Save TKasperczyk/29f0ccf13a25e105259e05c2b3f49470 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script lang="ts"> | |
| import { Button } from "$shadcn/button"; | |
| import { Popover, PopoverContent, PopoverTrigger } from "$shadcn/popover"; | |
| import Check from "lucide-svelte/icons/check"; | |
| import Palette from "lucide-svelte/icons/palette"; | |
| import Moon from "lucide-svelte/icons/moon"; | |
| import Sun from "lucide-svelte/icons/sun"; | |
| import { onMount } from "svelte"; | |
| import { colorThemes } from "$lib/config/theme"; | |
| type ColorMode = "base" | "dark"; | |
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |
| const themeColors = Object.entries(colorThemes).map(([name, _]) => ({ | |
| name: `${name[0].toUpperCase()}${name.slice(1)}`, | |
| value: name | |
| })) as { name: string; value: keyof typeof colorThemes }[]; | |
| type Theme = keyof typeof colorThemes; | |
| let currentThemeColor = $state<Theme>("default"); | |
| let currentColorMode = $state<ColorMode>("dark"); | |
| function setThemeColor(color: Theme) { | |
| currentThemeColor = color; | |
| applyTheme(color, currentColorMode); | |
| } | |
| function toggleDarkMode() { | |
| currentColorMode = currentColorMode === "dark" ? "base" : "dark"; | |
| applyTheme(currentThemeColor, currentColorMode); | |
| } | |
| function toCssVariableName(key: string): string { | |
| return `--${key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)}`; | |
| } | |
| function handleLocalStorageError(error: unknown) { | |
| if (error instanceof DOMException && error.name === "QuotaExceededError") { | |
| console.warn("localStorage quota exceeded. Unable to save theme preference."); | |
| } else if (error instanceof DOMException && error.name === "SecurityError") { | |
| console.warn("localStorage access denied due to security settings."); | |
| } else { | |
| console.warn("Failed to access localStorage:", JSON.stringify(error)); | |
| } | |
| } | |
| function applyTheme(theme: Theme, colorMode: ColorMode) { | |
| const root = document.documentElement; | |
| const themeColors = { | |
| ...colorThemes[theme]["base"], | |
| ...(colorMode === "dark" ? colorThemes[theme]["dark"] : {}) | |
| }; | |
| for (const [key, value] of Object.entries(themeColors)) { | |
| const cssVariableName = toCssVariableName(key); | |
| root.style.setProperty(cssVariableName, value); | |
| } | |
| if (colorMode === "dark") { | |
| root.classList.add("dark"); | |
| } else { | |
| root.classList.remove("dark"); | |
| } | |
| // Save the theme preference | |
| try { | |
| localStorage.setItem("ATLAS_theme", theme); | |
| localStorage.setItem("ATLAS_darkMode", colorMode); | |
| } catch (error) { | |
| handleLocalStorageError(error); | |
| } | |
| } | |
| onMount(() => { | |
| try { | |
| // Load saved theme preference | |
| const savedTheme = (localStorage.getItem("ATLAS_theme") || "default") as Theme; | |
| const savedColorMode = (localStorage.getItem("ATLAS_darkMode") || "dark") as ColorMode; | |
| currentColorMode = savedColorMode; | |
| currentThemeColor = savedTheme; | |
| applyTheme(savedTheme, savedColorMode); | |
| } catch (error) { | |
| handleLocalStorageError(error); | |
| } | |
| }); | |
| </script> | |
| <Popover> | |
| <PopoverTrigger> | |
| {#snippet child({ props })} | |
| <Button variant="outline" size="icon" class="w-10 h-10 rounded-full" type="button" { ...props }> | |
| <Palette class="h-4 w-4" /> | |
| <span class="sr-only">Select theme color</span> | |
| </Button> | |
| {/snippet} | |
| </PopoverTrigger> | |
| <PopoverContent class="w-56"> | |
| <div class="grid gap-4"> | |
| <h4 class="font-medium leading-none">Choose theme color</h4> | |
| <div class="grid gap-2"> | |
| {#each themeColors as color} | |
| <Button | |
| variant="ghost" | |
| class="w-full justify-start" | |
| type="button" | |
| onclick={() => setThemeColor(color.value)} | |
| aria-pressed={currentThemeColor === color.value} | |
| > | |
| <div | |
| class="w-4 h-4 rounded-full mr-2" | |
| style="background-color: hsl({colorThemes[color.value][currentColorMode].primary})" | |
| ></div> | |
| {color.name} | |
| <div | |
| class="mr-2 h-4 w-4 opacity-0 transition-opacity ml-auto" | |
| class:opacity-100={currentThemeColor === color.value} | |
| > | |
| <Check class="h-4 w-4" /> | |
| </div> | |
| </Button> | |
| {/each} | |
| <Button | |
| variant="ghost" | |
| onclick={toggleDarkMode} | |
| class="w-full justify-start" | |
| type="button" | |
| > | |
| {#if currentColorMode === "dark"} | |
| <Sun class="h-4 w-4 mr-2" /> | |
| Light Mode | |
| {:else} | |
| <Moon class="h-4 w-4 mr-2" /> | |
| Dark Mode | |
| {/if} | |
| </Button> | |
| </div> | |
| </div> | |
| </PopoverContent> | |
| </Popover> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const commonBase = { | |
| error: "0 84% 60%", | |
| warning: "40 100% 45%", | |
| success: "142 76% 36%", | |
| // ... | |
| }; | |
| const commonDark = { | |
| // ... | |
| }; | |
| const defaultBaseTheme = { | |
| ...commonBase, | |
| background: "0 0% 100%", | |
| foreground: "20 14.3% 4.1%", | |
| card: "24.6 40% 95%", | |
| // ... | |
| }; | |
| const defaultDarkTheme = { | |
| ...commonDark, | |
| background: "20 14.3% 4.1%", | |
| // ... | |
| }; | |
| function createTheme( | |
| baseOverrides: Partial<typeof defaultBaseTheme>, | |
| darkOverrides: Partial<typeof defaultDarkTheme> | |
| ) { | |
| return { | |
| base: { | |
| ...defaultBaseTheme, | |
| ...baseOverrides | |
| }, | |
| dark: { | |
| ...defaultDarkTheme, | |
| ...darkOverrides | |
| } | |
| }; | |
| } | |
| export const colorThemes = { | |
| default: createTheme({}, {}), | |
| magenta: createTheme( | |
| { | |
| primary: "346.8 77.2% 49.8%", | |
| ring: "346.8 77.2% 49.8%", | |
| card: "346.8 20% 95%" | |
| }, | |
| { | |
| primary: "346.8 77.2% 49.8%", | |
| card: "346.8 10% 10%", | |
| ring: "346.8 77.2% 49.8%" | |
| } | |
| ), | |
| green: createTheme( | |
| { | |
| primary: "142.1 76.2% 36.3%", | |
| ring: "142.1 76.2% 36.3%", | |
| card: "142.4 20% 95%" | |
| }, | |
| { | |
| primary: "142.1 70.6% 45.3%", | |
| card: "142.4 5% 10%", | |
| ring: "142.4 71.8% 29.2%" | |
| } | |
| ), | |
| // ... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment