Skip to content

feat(ui): add dark mode with persistent theme toggle - #21

Open
danieljecu wants to merge 1 commit into
mainfrom
feat/dark-mode
Open

feat(ui): add dark mode with persistent theme toggle#21
danieljecu wants to merge 1 commit into
mainfrom
feat/dark-mode

Conversation

@danieljecu

Copy link
Copy Markdown
Owner

What

Adds full dark mode support to the UI, building on the theme unification from #20:

  • Mode toggle — moon/sun icon button in the header; choice persists to localStorage, first visit follows the OS prefers-color-scheme.
  • Mode-aware theme (ui/src/styles/theme.ts) — every semantic token (pageBg, surface, border, textPrimary, textSecondary, cardHeader) now has light and dark variants; getTheme(mode) builds the MUI theme and attaches colors to it.
  • Context-driven styling — emotion styled components switched from static Theme.colors imports to ${({ theme }) => theme.colors.X} callbacks (typed via new emotion.d.ts), so all borders, surfaces and card headers flip with the mode. Inline MUI Card style borders moved to sx + borderColor: "divider".
  • Provider wiringColorModeContext + useColorMode() in app-provider.tsx; CssBaseline now owns body background/text (hardcoded values removed from index.css); toasts follow the mode.
  • Edge cases — the @reach/dialog login/register modal ships a hardcoded white background, themed via inline style (wins the cascade); the drag-highlight green darkens in dark mode so text stays readable.

Why

Follow-up to the contrast fixes in #20: the app only had a light palette, and the styling was statically bound to it, making dark mode impossible without this refactor.

Verified

  • Toggled both directions in the browser: body flips #f4f6f8#10161a, preference survives reload, login modal renders a proper dark surface with visible outlined fields; an open dialog restyles live on toggle.
  • tsc --noEmit clean.
  • Jest: 1 test passes as before; two suites fail on a pre-existing config issue (duplicate jest config + unresolved constants/navigation imports) — confirmed identical on a clean tree, unrelated to this change.

Reviewer notes

  • .claude/launch.json is dev-tooling config for browser-preview verification; drop it from the PR if unwanted.
  • header-bar/styles.ts intentionally keeps a static navyDark import — it's a brand constant identical in both modes.

🤖 Generated with Claude Code

- theme.ts: light/dark variants for all semantic tokens (pageBg, surface,
  border, text, cardHeader), exposed via getTheme(mode); colors attached
  to the MUI theme so emotion styled components read them from context
- app-provider: ColorModeContext + useColorMode hook, persists choice to
  localStorage, defaults to OS prefers-color-scheme; CssBaseline now owns
  body background/text (removed hardcoded values from index.css)
- header: moon/sun IconButton to toggle mode
- styled files: switched static Theme.colors imports to theme-context
  callbacks so borders, card headers and surfaces flip with the mode;
  inline Card style borders moved to sx with borderColor: divider
- login: reach-dialog surface themed inline (its stylesheet hardcodes
  white); drag highlight uses dark green in dark mode for readable text
- launch.json: dev server config for browser preview verification

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant