Skip to content

fix(theming): allow three states in useTheme#656

Open
AugustinMauroy wants to merge 5 commits intomainfrom
fix-theme-toggle
Open

fix(theming): allow three states in useTheme#656
AugustinMauroy wants to merge 5 commits intomainfrom
fix-theme-toggle

Conversation

@AugustinMauroy
Copy link
Member

Description

refracto the way that we handle theming

Validation

Manual test on safari & chrome

Related Issues

Close #642

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run node --run test and all tests passed.
  • I have check code formatting with node --run format & node --run lint.
  • I've covered new added functionality with unit tests if necessary.

Copilot AI review requested due to automatic review settings March 7, 2026 23:07
@AugustinMauroy AugustinMauroy requested a review from a team as a code owner March 7, 2026 23:07
@vercel
Copy link

vercel bot commented Mar 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api-docs-tooling Ready Ready Preview Mar 20, 2026 9:13pm

Request Review

@codecov
Copy link

codecov bot commented Mar 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.84%. Comparing base (9122dbe) to head (68bdbd3).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #656   +/-   ##
=======================================
  Coverage   75.84%   75.84%           
=======================================
  Files         149      149           
  Lines       13682    13682           
  Branches     1040     1040           
=======================================
  Hits        10377    10377           
  Misses       3299     3299           
  Partials        6        6           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link

github-actions bot commented Mar 7, 2026

orama-db Generator

File Base Head Diff
orama-db.json 8.05 MB 8.05 MB +1.63 KB (+0.02%)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the web generator UI theming to track a theme preference (including system) and keep the applied document theme synced with OS color-scheme changes, updating the NavBar integration accordingly.

Changes:

  • Reworked useTheme to support system | light | dark preferences, with localStorage persistence and system-change listeners.
  • Updated NavBar to pass the new theme preference API through to ThemeToggle.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/generators/web/ui/hooks/useTheme.mjs Introduces theme preference model (system/light/dark), storage helpers, and OS theme change syncing.
src/generators/web/ui/components/NavBar.jsx Switches NavBar theme toggle wiring to the new useTheme return values/props.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ovflowd
Copy link
Member

ovflowd commented Mar 8, 2026

refracto the way that we handle theming

Could you please elaborate more why this PR is necessary? Why does it need to be refactored? Is this fixing any specific bug? 👀

@AugustinMauroy
Copy link
Member Author

refracto the way that we handle theming

Could you please elaborate more why this PR is necessary? Why does it need to be refactored? Is this fixing any specific bug? 👀

In the linked issue we realize that on safari theme didn't change on web generator when using the dropdown so after some thinkering I found this solution

@avivkeller avivkeller changed the title refracto: theming fix(theming): allow three states in useTheme Mar 8, 2026
Copy link
Member

@ovflowd ovflowd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be getting crazy but this hook is much bigger than I genuinely imagine we need for a simple useTheme hook 🤔

@AugustinMauroy
Copy link
Member Author

I might be getting crazy but this hook is much bigger than I genuinely imagine we need for a simple useTheme hook 🤔

what if we use next-themes ? (FYI it's only use react api)

@avivkeller
Copy link
Member

avivkeller commented Mar 19, 2026

I might be getting crazy but this hook is much bigger than I genuinely imagine we need for a simple useTheme hook 🤔

It's a bit complex as it is, sure, but it can be simplified. For instance, the following achieves the same result (Note this removes the legacy addListener support, which you may want to add back):

import { useState, useEffect, useCallback } from 'react';

/** @returns {'dark'|'light'} The current OS-level color scheme. */
const getSystemTheme = () =>
  matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

/**
 * Applies a theme to the document root.
 * Resolves 'system' to the actual OS preference before applying.
 * @param {'system'|'light'|'dark'} pref - The theme preference.
 */
const applyTheme = (pref) => {
  const theme = pref === 'system' ? getSystemTheme() : pref;
  document.documentElement.setAttribute('data-theme', theme);
  document.documentElement.style.colorScheme = theme;
};

/**
 * Applies the system theme to the document root.
 */
const applySystemTheme = () => applyTheme('system');

/**
 * React hook for managing theme preference.
 * Persists the choice to localStorage and listens for OS theme changes
 * when set to 'system'.
 * @returns {['system'|'light'|'dark', (next: 'system'|'light'|'dark') => void]}
 */
export const useTheme = () => {
  // Read stored preference once on mount; default to 'system'.
  const [pref, setPref] = useState(() => {
    return localStorage.getItem('theme') || 'system';
  });

  // Apply theme on every preference change, and if 'system',
  // also listen for OS-level color scheme changes.
  useEffect(() => {
    applyTheme(pref);

    if (pref !== 'system') return;

    const mql = matchMedia('(prefers-color-scheme: dark)');
    mql.addEventListener('change', applySystemTheme);
    return () => mql.removeEventListener('change', applySystemTheme);
  }, [pref]);

  /** Updates the preference in both React state and localStorage. */
  const setTheme = useCallback((next) => {
    setPref(next);
    localStorage.setItem('theme', next);
  }, []);

  return [pref, setTheme];
};

Co-Authored-By: Aviv Keller <me@aviv.sh>
Co-Authored-By: Aviv Keller <me@aviv.sh>
Comment on lines +32 to 35
if (typeof window === 'undefined') {
return 'system';
}

Copy link
Member

@avivkeller avivkeller Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the if?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because node/rolldown was trying to execute the local storage on the building time

[21:00:19.647] DEBUG (generators): Starting "web"
[21:00:24.138] ERROR: localStorage is not defined
ReferenceError: localStorage is not defined
    at eval (eval at chunkedRequire (file:///home/runner/work/doc-kit/doc-kit/src/generators/web/utils/chunks.mjs:26:23), <anonymous>:13659:3)
    at D$1 (eval at chunkedRequire (file:///home/runner/work/doc-kit/doc-kit/src/generators/web/utils/chunks.mjs:26:23), <anonymous>:222:34)
    at h (eval at chunkedRequire (file:///home/runner/work/doc-kit/doc-kit/src/generators/web/utils/chunks.mjs:26:23), <anonymous>:61:45)
    at d (eval at chunkedRequire (file:///home/runner/work/doc-kit/doc-kit/src/generators/web/utils/chunks.mjs:26:23), <anonymous>:57:18)
    at useTheme (eval at chunkedRequire (file:///home/runner/work/doc-kit/doc-kit/src/generators/web/utils/chunks.mjs:26:23), <anonymous>:13658:26)
    at Object.NavBar_default (eval at chunkedRequire (file:///home/runner/work/doc-kit/doc-kit/src/generators/web/utils/chunks.mjs:26:23), <anonymous>:13908:48)
    at N (/home/runner/work/doc-kit/doc-kit/node_modules/preact-render-to-string/dist/commonjs.js:1:5307)
    at N (/home/runner/work/doc-kit/doc-kit/node_modules/preact-render-to-string/dist/commonjs.js:1:4474)
    at N (/home/runner/work/doc-kit/doc-kit/node_modules/preact-render-to-string/dist/commonjs.js:1:6041)
    at N (/home/runner/work/doc-kit/doc-kit/node_modules/preact-render-to-string/dist/commonjs.js:1:6041)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably exit the hook early with a SERVER codepath.

if (SERVER) return ['system', ()=>{}]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be better ways to systematically omit something to run from a server code-path. 😅

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"use client" work ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"use client" work ?

No, but any if statement if (CLIENT) or if (SERVER) will only run the code in the block on the client/server, respectively (with code elimination)

Comment on lines +56 to +58
if (typeof window !== 'undefined') {
localStorage.setItem('theme', next);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

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.

Theme Toggle Not Working

5 participants