sprucing the app up a little
This commit is contained in:
parent
57ab2d9324
commit
13e5727497
20 changed files with 537 additions and 111 deletions
|
@ -19,4 +19,10 @@
|
|||
&:has(:focus-visible) {
|
||||
border-color: var(--info);
|
||||
}
|
||||
}
|
||||
|
||||
.hue {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
}
|
|
@ -1,69 +1,115 @@
|
|||
import { Component, createEffect, createMemo, createResource, For, Setter } from "solid-js";
|
||||
import { Accessor, Component, createContext, createEffect, createMemo, createResource, For, ParentComponent, Setter, Show, Suspense, useContext } from "solid-js";
|
||||
import css from './colorschemepicker.module.css';
|
||||
import { CgDarkMode } from "solid-icons/cg";
|
||||
import { action, cache, useAction } from "@solidjs/router";
|
||||
import { action, createAsyncStore, query, useAction } from "@solidjs/router";
|
||||
import { useSession } from "vinxi/http";
|
||||
import { createStore, reconcile, ReconcileOptions, SetStoreFunction } from "solid-js/store";
|
||||
|
||||
export enum ColorScheme {
|
||||
Auto = 'light dark',
|
||||
Light = 'light',
|
||||
Dark = 'dark',
|
||||
}
|
||||
type ColorSchemeKey = keyof typeof ColorScheme;
|
||||
|
||||
const colorSchemeKeys: readonly ColorSchemeKey[] = ['Auto', 'Light', 'Dark'] as const;
|
||||
const colorSchemes = Object.entries(ColorScheme) as readonly [keyof typeof ColorScheme, ColorScheme][];
|
||||
|
||||
interface ColorSchemePickerProps {
|
||||
value?: Setter<ColorScheme>;
|
||||
export interface State {
|
||||
colorScheme: ColorScheme;
|
||||
hue: number;
|
||||
}
|
||||
|
||||
const getSession = async () => {
|
||||
'use server';
|
||||
|
||||
console.log('what? why? how???', process.env.SESSION_SECRET);
|
||||
|
||||
return useSession<{ colorScheme: ColorSchemeKey }>({
|
||||
return useSession<State>({
|
||||
password: process.env.SESSION_SECRET!,
|
||||
});
|
||||
};
|
||||
|
||||
export const getColorScheme = cache(async () => {
|
||||
export const getState = query(async () => {
|
||||
'use server';
|
||||
|
||||
const session = await getSession();
|
||||
|
||||
return session.data.colorScheme;
|
||||
return session.data;
|
||||
}, 'color-scheme');
|
||||
|
||||
const setColorScheme = action(async (colorScheme: ColorSchemeKey) => {
|
||||
const setState = action(async (state: State) => {
|
||||
'use server';
|
||||
|
||||
const session = await getSession();
|
||||
await session.update({ colorScheme });
|
||||
await session.update(state);
|
||||
}, 'color-scheme');
|
||||
|
||||
export const ColorSchemePicker: Component<ColorSchemePickerProps> = (props) => {
|
||||
const [value, { mutate }] = createResource<ColorSchemeKey>(() => getColorScheme(), { initialValue: 'Auto' });
|
||||
const updateStore = useAction(setColorScheme);
|
||||
interface ThemeContextType {
|
||||
readonly theme: State;
|
||||
setColorScheme(colorScheme: ColorScheme): void;
|
||||
setHue(hue: number): void;
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
props.value?.(ColorScheme[value()]);
|
||||
});
|
||||
const ThemeContext = createContext<ThemeContextType>();
|
||||
|
||||
return <label class={css.picker} aria-label="Color scheme picker">
|
||||
<CgDarkMode />
|
||||
const useStore = () => useContext(ThemeContext)!;
|
||||
|
||||
<select name="color-scheme-picker" onInput={(e) => {
|
||||
if (e.target.value !== value()) {
|
||||
const nextValue = (e.target.value ?? 'Auto') as ColorSchemeKey;
|
||||
export const useTheme = () => {
|
||||
const { theme } = useContext(ThemeContext) ?? {};
|
||||
|
||||
mutate(nextValue);
|
||||
updateStore(nextValue);
|
||||
}
|
||||
}}>
|
||||
<For each={colorSchemeKeys}>{
|
||||
(v) => <option value={v} selected={v === value()}>{v}</option>
|
||||
}</For>
|
||||
</select>
|
||||
</label>;
|
||||
if (theme === undefined) {
|
||||
throw new Error('useColorScheme is called outside a <ColorSchemeProvider />');
|
||||
}
|
||||
|
||||
return theme;
|
||||
};
|
||||
|
||||
export const ThemeProvider: ParentComponent = (props) => {
|
||||
const [state, { mutate }] = createResource<State>(() => getState(), { deferStream: true, initialValue: { colorScheme: ColorScheme.Auto, hue: 0 } });
|
||||
const updateState = useAction(setState);
|
||||
|
||||
return <Suspense>
|
||||
<Show when={state()}>{state => {
|
||||
const [store, setStore] = createStore(state());
|
||||
|
||||
createEffect(() => {
|
||||
setStore(state());
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
console.log({ ...store })
|
||||
});
|
||||
|
||||
return <ThemeContext.Provider value={{
|
||||
get theme() { return store; },
|
||||
setColorScheme(colorScheme: ColorScheme) { updateState(mutate(prev => ({ ...prev, colorScheme }))) },
|
||||
setHue(hue: number) { updateState(mutate(prev => ({ ...prev, hue }))) },
|
||||
}}>
|
||||
{props.children}
|
||||
</ThemeContext.Provider>;
|
||||
}}</Show>
|
||||
</Suspense>;
|
||||
};
|
||||
|
||||
export const ColorSchemePicker: Component = (props) => {
|
||||
const { theme, setColorScheme, setHue } = useStore();
|
||||
|
||||
return <>
|
||||
<label class={css.picker} aria-label="Color scheme picker">
|
||||
<CgDarkMode />
|
||||
|
||||
<select name="color-scheme-picker" onInput={(e) => {
|
||||
if (e.target.value !== theme.colorScheme) {
|
||||
const nextValue = (e.target.value ?? ColorScheme.Auto) as ColorScheme;
|
||||
|
||||
setColorScheme(nextValue);
|
||||
}
|
||||
}}>
|
||||
<For each={colorSchemes}>{
|
||||
([label, value]) => <option value={value} selected={value === theme.colorScheme}>{label}</option>
|
||||
}</For>
|
||||
</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