feat: add design-system theme provider with light/dark modes (Closes #30) - #43
Conversation
…-labs#30) Codify the navy/gold/terracotta identity system into a ThemeProvider context with accessible light and dark modes persisted per user. Theme logic lives in src/lib/theme.ts (storage, resolution, system listening, no-FOUC script); ThemeToggle now consumes useTheme. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@DammyAji is attempting to deploy a commit to the Meshack Yaro's projects Team on Vercel. A member of the Team first needs to authorize it. |
This is a strong, well-scoped PR that centralizes theme logic, adds a ThemeProvider + hook, refactors the toggle, and adds solid unit tests. Overall I consider it near-ready for merge after addressing a few targeted, actionable items (mainly around runtime-safety, small accessibility details, and one clarity/test gap). Risk: low-to-medium — mostly UI plumbing with good test coverage; main checks are runtime/SSR-safety and accessibility. Critical, diff-anchored issues to fix before merge src/lib/theme.ts — getStoredTheme: guard against server-side execution and absent localStorage (window undefined) and ensure function never throws; please ensure top-level calls are safe during SSR (e.g., wrap localStorage access in typeof window !== 'undefined' checks). Possible improvements (actionable suggestions) src/components/ThemeToggle.tsx — accessibility: add a clear accessible label (aria-label, or visually-hidden text) describing the control (e.g., "Toggle color theme") in addition to aria-pressed so screen-reader users understand the current state and action. Also ensure the role and keyboard behavior (Enter/Space) are correct. Small nits / style suggestions (non-blocking) src/lib/theme.ts — export naming: ensure exported constants (THEME_STORAGE_KEY) are documented or re-exported in a central place if other modules will reference them. Get this done and it'll be approved and ready to merge. |
|
@meshackyaro Please check now and review. Thank you. |
Check my last review and fix the critical, diff-anchored issues before I can approve and merge |
Address review feedback: guard localStorage/matchMedia against server-side execution, add a legacy matchMedia addListener fallback, stop the OS-preference listener once the user makes an explicit choice, and document the no-FOUC script wiring. Adds unit tests for SSR safety and the legacy-listener path. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
@meshackyaro I actually worked on it but did not push. I just realized, and i have done that so please kindly review. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
meshackyaro
left a comment
There was a problem hiding this comment.
Approved — this looks great and is ready to merge.
Nice work — the ThemeProvider cleanly centralizes theme logic, removes the duplicated no-FOUC script, persists user choice correctly, and the refactor of ThemeToggle is non-invasive and more accessible. Tests cover the core behaviors (storage, resolution, DOM application, and subscription lifecycle), README/TODO updates document the design, and the implementation follows the repo’s existing Context/provider conventions. Low risk, well-scoped, and improves maintainability.
I’m happy to merge this as-is — go ahead when CI is green.
Summary
Closes #30
Design-System Theme Provider (navy/gold/terracotta) with Dark Mode
This PR implements the Design-System Theme Provider for the GuildWorkman web app, codifying the navy/gold/terracotta identity system into a single, accessible theme provider with light and dark modes persisted per user.
Description
The core objective of issue #30 is to:
Before this PR, the navy/gold/terracotta colour tokens and their dark-mode overrides already lived in
src/app/globals.css(Tailwind v4@theme inline+data-themeoverrides), but the logic around them was scattered:ThemeTogglecomponent read/wrotelocalStorageand stampeddata-themeon<html>directly, with no central state.This PR introduces a dedicated theme layer that follows the project's existing conventions (mirroring the
NotificationProviderContext pattern used for the Transaction Notification Center), centralising every decision about the theme system in one place.What changed
New files
src/lib/theme.ts— the single source of truth for the theme system:Themetype ("light" | "dark") andTHEME_STORAGE_KEYisThemetype guard for untrusted values (localStorage, inline script)getStoredTheme/setStoredTheme— guarded, never-throwing per-user persistencegetSystemTheme— OS preference viaprefers-color-scheme(safe fallback)applyTheme— stamps<html data-theme>+ setscolor-schemeso native controls (scrollbars, selects, date pickers) match the themelistenForSystemTheme— subscribes to OS preference changes, returns an unsubscriberesolveTheme— stored choice wins, otherwise the OS preferencethemeScript— the shared no-FOUC paint-time script used by the root layoutsrc/components/theme/ThemeProvider.tsx— React Context provider exposingtheme,resolvedTheme,setTheme, andtoggleThemevia theuseTheme()hook. A stored per-user choice wins; otherwise the OS preference is followed live until the user makes an explicit choice.src/components/theme/index.ts— barrel export (matches thenotifications/index.tsconvention).src/lib/test/theme.test.ts— 14 unit tests covering storage (including corrupted-storage recovery), theme resolution, DOM application, system-preference subscription/unsubscription, and the no-FOUC script.Edited files
src/components/ThemeToggle.tsx— refactored to consumeuseTheme()(visuals unchanged), with an addedaria-pressedfor accessibility.src/app/layout.tsx— wraps the app in<ThemeProvider>and imports the no-FOUC script from@/lib/theme(single source of truth instead of a duplicated inline string).README.md— design-system section updated to document the new provider architecture.TODO.md— implementation checklist and architectural decisions documented, per the issue's requirement to document new dependencies/decisions.Tasks (from issue #30)
layout.tsx, the Context provider pattern, and component conventions.ThemeProvider+useTheme+ refactoredThemeToggle..github/workflows/ci.ymlusesactions/setup-nodewithcache: npm.npm run lintandnpm run typecheck— both pass (0 errors).npm run buildto verify production bundling — production build succeeds (18 routes).Acceptance Criteria
tsc --noEmitis clean (CI runs it and blocks on failure).Verification
npx tsc --noEmitnpx eslint .npm testnpm run buildgit merge-tree --write-tree upstream/dev HEADArchitectural Decisions
NotificationProviderpattern),react-icons(already a dependency), and the existing Tailwind v4 token setup.src/app/globals.css;src/lib/theme.tsowns the logic around them.theme) — the same mechanism the previous toggle used, so existing stored choices keep working. No auth/session exists yet, so browser-local is the right scope; a per-account key can be layered on later.color-schemeis set so native controls adapt, focus rings useoutline-gold, and the toggle exposesaria-label+aria-pressed.themeScript(now imported from@/lib/theme) paints the correct theme before React hydrates; the provider only keeps React state in sync.prefers-color-schemelive; choosing persists and stops following.Links
Closes #30