refactor colorpicker component
This commit is contained in:
parent
55ae8902a8
commit
57bb6ec2d9
7 changed files with 50 additions and 49 deletions
82
src/features/theme/context.tsx
Normal file
82
src/features/theme/context.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { action, query, useAction } from "@solidjs/router";
|
||||
import { createContext, createEffect, createResource, ParentComponent, Show, Suspense, useContext } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { useSession } from "vinxi/http";
|
||||
|
||||
|
||||
export enum ColorScheme {
|
||||
Auto = 'light dark',
|
||||
Light = 'light',
|
||||
Dark = 'dark',
|
||||
}
|
||||
|
||||
export interface State {
|
||||
colorScheme: ColorScheme;
|
||||
hue: number;
|
||||
}
|
||||
|
||||
const getSession = async () => {
|
||||
'use server';
|
||||
|
||||
return useSession<State>({
|
||||
password: process.env.SESSION_SECRET!,
|
||||
});
|
||||
};
|
||||
|
||||
export const getState = query(async () => {
|
||||
'use server';
|
||||
|
||||
const session = await getSession();
|
||||
|
||||
return session.data;
|
||||
}, 'color-scheme');
|
||||
|
||||
const setState = action(async (state: State) => {
|
||||
'use server';
|
||||
|
||||
const session = await getSession();
|
||||
await session.update(prev => ({ ...prev, ...state }));
|
||||
}, 'color-scheme');
|
||||
|
||||
interface ThemeContextType {
|
||||
readonly theme: State;
|
||||
setColorScheme(colorScheme: ColorScheme): void;
|
||||
setHue(hue: number): void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType>();
|
||||
|
||||
export const useStore = () => useContext(ThemeContext)!;
|
||||
|
||||
export const useTheme = () => {
|
||||
const ctx = useContext(ThemeContext);
|
||||
|
||||
if (ctx === undefined) {
|
||||
throw new Error('useColorScheme is called outside a <ColorSchemeProvider />');
|
||||
}
|
||||
|
||||
return ctx.theme;
|
||||
};
|
||||
|
||||
export const ThemeProvider: ParentComponent = (props) => {
|
||||
const [state, { mutate }] = createResource<State>(() => getState(), { deferStream: true });
|
||||
const updateState = useAction(setState);
|
||||
|
||||
return <Suspense>
|
||||
<Show when={state()}>{state => {
|
||||
const [store, setStore] = createStore(state());
|
||||
|
||||
createEffect(() => {
|
||||
setStore(state());
|
||||
});
|
||||
|
||||
return <ThemeContext.Provider value={{
|
||||
get theme() { return store; },
|
||||
setColorScheme(colorScheme: ColorScheme) { updateState(mutate(prev => ({ colorScheme, hue: prev?.hue ?? 0 }))) },
|
||||
setHue(hue: number) { updateState(mutate(prev => ({ hue, colorScheme: prev?.colorScheme ?? ColorScheme.Auto }))) },
|
||||
}}>
|
||||
{props.children}
|
||||
</ThemeContext.Provider>;
|
||||
}}</Show>
|
||||
</Suspense>;
|
||||
};
|
2
src/features/theme/index.ts
Normal file
2
src/features/theme/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { useTheme, getState, ThemeProvider, ColorScheme } from './context';
|
||||
export { ColorSchemePicker } from './picker';
|
9
src/features/theme/picker.module.css
Normal file
9
src/features/theme/picker.module.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.picker {
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.hue {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
}
|
41
src/features/theme/picker.tsx
Normal file
41
src/features/theme/picker.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { WiMoonAltFirstQuarter, WiMoonAltFull, WiMoonAltNew } from "solid-icons/wi";
|
||||
import { Component, createEffect, createSignal, Match, Switch } from "solid-js";
|
||||
import { Select } from "~/components/select";
|
||||
import { ColorScheme, useStore } from "./context";
|
||||
import css from './picker.module.css';
|
||||
|
||||
const colorSchemes: Record<ColorScheme, keyof typeof ColorScheme> = Object.fromEntries(Object.entries(ColorScheme).map(([k, v]) => [v, k] as const)) as any;
|
||||
|
||||
export const ColorSchemePicker: Component = (props) => {
|
||||
const { theme, setColorScheme, setHue } = useStore();
|
||||
const [scheme, setScheme] = createSignal<ColorScheme>(theme.colorScheme);
|
||||
|
||||
createEffect(() => {
|
||||
const next = scheme();
|
||||
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
|
||||
setColorScheme(next);
|
||||
});
|
||||
|
||||
return <>
|
||||
<label aria-label="Color scheme picker">
|
||||
<Select id="color-scheme-picker" class={css.picker} value={theme.colorScheme} setValue={setScheme} values={colorSchemes}>{
|
||||
(k, v) => <>
|
||||
<Switch>
|
||||
<Match when={k === ColorScheme.Auto}><WiMoonAltFirstQuarter /></Match>
|
||||
<Match when={k === ColorScheme.Light}><WiMoonAltNew /></Match>
|
||||
<Match when={k === ColorScheme.Dark}><WiMoonAltFull /></Match>
|
||||
</Switch>
|
||||
{v}
|
||||
</>
|
||||
}</Select>
|
||||
</label>
|
||||
|
||||
<label class={css.hue} aria-label="Hue slider">
|
||||
<input type="range" min="0" max="360" value={theme.hue} onInput={e => setHue(e.target.valueAsNumber)} />
|
||||
</label>
|
||||
</>;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue