Skip to content

Commit

Permalink
feat: theme switcher
Browse files Browse the repository at this point in the history
  • Loading branch information
riipandi committed Dec 6, 2024
1 parent 68d5cbf commit 3abfba8
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 9 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@kobalte/core": "^0.13.7",
"@nanostores/persistent": "^0.10.2",
"@nanostores/solid": "^0.5.0",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.1",
"@tanstack/solid-table": "^8.20.5",
"@tauri-apps/api": "^2",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/assets/styles/scrollbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

/* Set scrollbar width and height for Webkit browsers */
.custom-scrollbar::-webkit-scrollbar {
width: 12px; /* Fixed width */
height: 12px;
width: 11px; /* Fixed width */
height: 11px;
}

/* Track background for Webkit browsers */
Expand Down
86 changes: 86 additions & 0 deletions src/components/theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { ConfigColorMode } from '@kobalte/core/color-mode'
import { useColorMode } from '@kobalte/core/color-mode'
import * as Lucide from 'lucide-solid'
import { type JSX, createSignal, onMount } from 'solid-js'
import { Select } from '#/components/base-ui/select'
import { clx } from '#/libs/utils'

interface ThemeOption {
value: ConfigColorMode
label: string
icon: (iconClass: string) => JSX.Element
}

const THEME_OPTIONS: ThemeOption[] = [
{
value: 'light',
label: 'Light',
icon: (iconClass: string) => <Lucide.Sun class={iconClass} />,
},
{
value: 'dark',
label: 'Dark',
icon: (iconClass: string) => <Lucide.Moon class={iconClass} />,
},
{
value: 'system',
label: 'System',
icon: (iconClass: string) => <Lucide.Monitor class={iconClass} />,
},
]

export function ThemeSelector() {
const { colorMode, setColorMode } = useColorMode()
const [selectedTheme, setSelectedTheme] = createSignal<ThemeOption>()

onMount(() => {
// Use MaybeConfigColorMode to get value from custom storage
setSelectedTheme(THEME_OPTIONS.find((option) => option.value === colorMode()))
})

const handleThemeChange = (opts: ThemeOption | null) => {
if (opts) {
setSelectedTheme(opts)
setColorMode(opts.value)
}
}

return (
<Select<ThemeOption>
options={THEME_OPTIONS}
optionValue="value"
optionTextValue="label"
value={selectedTheme() ?? THEME_OPTIONS[0]}
onChange={handleThemeChange}
gutter={8}
sameWidth={false}
placement="bottom-end"
itemComponent={(props) => (
<Select.Item
item={props.item}
class={clx(
props.item.rawValue.value === colorMode() ? 'bg-gray-100 dark:bg-gray-700' : '',
'flex cursor-default items-center space-x-2 px-3 py-1 text-sm outline-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-700'
)}
>
{props.item.rawValue.icon('h-4 w-4')}
<Select.ItemLabel>{props.item.rawValue.label}</Select.ItemLabel>
</Select.Item>
)}
>
<Select.Trigger
aria-label="toggle color mode"
class="flex cursor-pointer items-center justify-center rounded-md p-2 text-gray-700 transition hover:bg-gray-100 hover:text-gray-800 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-200"
>
<Select.Value<ThemeOption>>
{(state) => state.selectedOption().icon('h-5 w-5')}
</Select.Value>
</Select.Trigger>
<Select.Portal>
<Select.Content class="z-50 select-none rounded border border-gray-300 bg-white p-1 shadow-md dark:border-none dark:bg-gray-800 dark:text-gray-300 dark:shadow-none">
<Select.Listbox />
</Select.Content>
</Select.Portal>
</Select>
)
}
25 changes: 23 additions & 2 deletions src/layouts/root-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import { ComponentProps } from 'solid-js'
import { ColorModeProvider, ColorModeScript } from '@kobalte/core'
import { MetaProvider } from '@solidjs/meta'
import { ComponentProps, Suspense } from 'solid-js'
import { clx } from '#/libs/utils'

interface RootLayoutProps extends ComponentProps<'div'> {}

const PageLoader = () => {
return (
<div class="size-full min-h-screen bg-background">
<div class="flex h-full items-center justify-center">
<h1 class="text-foreground">Loading...</h1>
</div>
</div>
)
}

const RootLayout = (props: RootLayoutProps) => {
return <div class={clx('root-layout', props.class)}>{props.children}</div>
return (
<MetaProvider>
<Suspense fallback={<PageLoader />}>
<ColorModeProvider>
<div class={clx('root-layout', props.class)}>{props.children}</div>
</ColorModeProvider>
<ColorModeScript storageType="localStorage" />
</Suspense>
</MetaProvider>
)
}

export default RootLayout
3 changes: 2 additions & 1 deletion src/stores/ui.store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MaybeConfigColorMode } from '@kobalte/core/color-mode'
import { persistentMap } from '@nanostores/persistent'
import { storeDecode, storeEncode } from '#/libs/utils'

export type Theme = 'dark' | 'light' | 'system'
export type Theme = MaybeConfigColorMode

type UIStore = {
sidebar: 'expanded' | 'collapsed'
Expand Down
10 changes: 6 additions & 4 deletions src/views/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Link } from '#/components/link'
import { resetUiState, saveUiState, uiStore } from '#/stores/ui.store'

import viteLogo from '/vite.svg'
import { ThemeSelector } from '#/components/theme'
import solidLogo from '../assets/images/solid.svg'

type Quotes = {
Expand Down Expand Up @@ -47,18 +48,19 @@ export default function Component() {
}

return (
<div class="page-wrapper custom-scrollbar">
<div class="mx-auto flex h-full max-w-2xl flex-col items-center justify-center p-8">
<div class="page-wrapper custom-scrollbar flex size-full items-center">
<div class="mx-auto h-auto max-w-xl flex-1">
<header class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-4">
<Link href="https://vite.dev" class="transition-transform hover:scale-110" newTab>
<img src={viteLogo} class="h-8 w-8" alt="Vite logo" />
<img src={viteLogo} class="size-8" alt="Vite logo" />
</Link>
<Link href="https://solidjs.com" class="transition-transform hover:scale-110" newTab>
<img src={solidLogo} class="h-8 w-8" alt="Solid logo" />
<img src={solidLogo} class="size-8" alt="Solid logo" />
</Link>
<h1 class="font-bold text-2xl text-gray-900 dark:text-white">Daily Quotes</h1>
</div>
<ThemeSelector />
</header>

<main class="w-full flex-1 space-y-4">
Expand Down

0 comments on commit 3abfba8

Please sign in to comment.