Watch
0
0
Fork
You've already forked hyperhive
0

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:
iris 2026-08-29 11:11:39 +02:00
commit 9720bdfad0
16 changed files with 230 additions and 487 deletions

View 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]);
}