settings: move the theme/motion panel into @hive/shared, add motion to agent
Review feedback on this PR (mara): "i think the component should be shared. motion setting is missing." Both addressed: - `settings-storage.ts` (generic localStorage hook), `theme-apply.ts`, `motion-apply.ts`, and `SettingsMenu.tsx`/`.css` all move from swarm-ui's `lib/`/`shell/` into `@hive/shared/src/settings/` — agent's previous local copies are deleted outright rather than kept as a second implementation. One component, `Badge` trigger everywhere (already used elsewhere in swarm-ui, so not a new visual language there either) — storage keys stay caller-owned (`themeKey`/ `motionKey` props + matching `useApplyThemeOverride`/ `useApplyMotionOverride` calls at each package's single mount point) so agent and swarm-ui keep fully independent, non-colliding persisted settings. - Agent's settings menu now includes the motion row, matching swarm-ui's. No animation in the agent package is gated behind `data-motion` yet — same as when swarm-ui first built this plumbing ahead of having a consumer — so it's currently inert there, ready for whenever agent grows a motion-guarded animation. - swarm-ui's own theme default flips to `'dark'` as part of this move (`theme-apply.ts`'s new default), superseding PR #3715 — that PR becomes redundant once this lands and will be closed rather than merged, to avoid the two colliding on the same file. Verified end-to-end with real screenshots on both pages: shared component renders identically (Badge trigger, theme+motion rows, dark default) on agent's mock server and a static rebuild of swarm-ui's dist.
This commit is contained in:
parent
b28e8f1c4e
commit
9720bdfad0
16 changed files with 230 additions and 487 deletions
40
frontend/packages/shared/src/settings/theme-apply.ts
Normal file
40
frontend/packages/shared/src/settings/theme-apply.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Applies a stored theme override by setting `data-theme="light"|"dark"`
|
||||
// on `<html>`, cleared when the override is "system" (letting the
|
||||
// `prefers-color-scheme` media query in `./colors.css` resume control).
|
||||
// No palette values live here — `colors.css`'s own
|
||||
// `:root[data-theme='light']` / `:root[data-theme='dark']` blocks are
|
||||
// what actually apply the colours.
|
||||
//
|
||||
// Callers own their own storage key (kept in their own small constant,
|
||||
// not derived here) so two pages never silently share one browser-local
|
||||
// setting by accident. `fallback` defaults to `'dark'`: `prefers-color-
|
||||
// scheme` has no real "unset" value the way `prefers-reduced-motion`
|
||||
// does, so a browser with no explicit dark-mode toggle reports `light`
|
||||
// — `'system'` as a default silently reads as light-by-default for most
|
||||
// first-time visitors (mara: "light mode seems to be default"). Still
|
||||
// fully overridable per caller if a page ever wants a different default.
|
||||
import { useEffect } from 'preact/hooks';
|
||||
import { useLocalSetting } from './settings-storage.js';
|
||||
|
||||
export type ThemeOverride = 'system' | 'light' | 'dark';
|
||||
|
||||
export function useThemeOverride(key: string, fallback: ThemeOverride = 'dark') {
|
||||
return useLocalSetting<ThemeOverride>(key, fallback);
|
||||
}
|
||||
|
||||
// Mounted once, high in the tree — one mount point applies the override
|
||||
// regardless of what else is rendering, and `useLocalSetting`'s
|
||||
// same-tab subscription means `SettingsMenu` changing the value re-runs
|
||||
// this effect without either component needing to know about the other
|
||||
// directly.
|
||||
export function useApplyThemeOverride(key: string, fallback: ThemeOverride = 'dark'): void {
|
||||
const [override] = useThemeOverride(key, fallback);
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (override === 'system') {
|
||||
delete root.dataset.theme;
|
||||
} else {
|
||||
root.dataset.theme = override;
|
||||
}
|
||||
}, [override]);
|
||||
}
|
||||
Loading…
Reference in a new issue