iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0
Modern Web

設計系統 - Design System系列 第 26

[Day 26] Design System - Theme 深淺模式

  • 分享至 

  • xImage
  •  

本系列文章會在筆者的部落格繼續連載!Design System 101 感謝大家的閱讀!

前言

深淺主題已經在前端社群風靡多年,小至個人部落格大至世界級客戶端應用 (Github),都有深淺色的切換功能,提供使用者可以自由地切換主題。

而要讓一個網站可以無痛的切換深淺色,最主要的原則就是在開發時遵循 Design Token,在先前 Token to CSS 的章節提到過其好處,如果色票沒有列在 Token 內則需要在應用層的 CSS 中去處理。

本篇會介紹如何透過 useTheme 去控制應用的深淺主題。

Warm Up

Design Token (Materal Design)

在最初的一開始我們介紹 Token 這個概念,透過 System Token (alias.json) 的 light 與 dark 分別指向不同的 Ref Token (base.json),這一步讓我們有能力可以讓同一個 System Token 的 alias 根據某些條件轉換成不同的值。

Normalized CSS

接著再透過 Design Token 建置時,將 :root 拆分成 light 與 dark 到 normalized.css,並在網頁一開始載入該 CSS ,最後透過 data-theme 去控制要使用哪種主題色。

html[data-theme='light'],
.tocino-light {
  --tocino-sys-color-primary: #6750a4;
  // ... All System Token (淺色)
}

:root {
  --tocino-sys-color-primary: #d0bcff;
  // ... All System Token (深色)
}

接下來要實作的就是如何提供一個功能讓使用者可以切換 data-theme 的值,也就是本篇要介紹的 useTheme

useTheme

描述

useTheme 要做的事情很簡單,就是控制 data-theme 的值。

API

理想上我們希望當使用 useTheme 時可以讓它可以讓我們更新 data-theme 以及知道當前的 theme

import { useTheme } from '@tocino-ui/core/hooks';

export default () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <div>
      <span>Current Theme: {theme}</span>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};

參數

名稱 型別 初始值 描述
initialTheme string - 初始的主題

回傳 API

名稱 型別 初始值 描述
theme string dark 當前的主題
toggleTheme () => void - 切換主題
setTheme React.SetStateAction<string> - 設定主題

實作

首先是 useTheme 的實作,會先透過 getTheme 取得 localStorage 的上次存取的 Theme, 如果沒有值,就將深色主題作為預設值,並且 mounting 的時候透過 useEffect 中去更新 data-theme 的值。最後將 theme, toggleThemesetTheme 這三個 API 透過 useMemo 傳遞給使用者。

themeManager 主要負責 localStorage 的存取與透過 documentElement.setAttribute 去更新 data-theme 的值。

/**
 * ====== CONST ======
 */
const THEME_KEY = 'tocino-theme';
const THEME = {
  LIGHT: 'light',
  DARK: 'dark',
  SYSTEM: 'system',
};

/**
 * ====== THEME MANAGER ======
 */

const themeManager = {
  getTheme: () => {
    return window?.localStorage.getItem(THEME_KEY) ?? THEME.DARK;
  },
  setTheme: (theme) => {
    document?.documentElement.setAttribute('data-theme', theme);
    window?.localStorage.setItem(THEME_KEY, theme);
  },
};

/**
 * ====== `useTheme` ======
 */

function useTheme(initialTheme) {
  const [theme, setTheme] = useState(() => {
    return initialTheme ?? themeManager.getTheme(); // 取得 Default Theme
  });

  useEffect(() => {
    themeManager.setTheme(theme);
  }, [theme]);

  const toggleTheme = useCallback((theme) => {
    setTheme((prev) => (prev === THEME.DARK ? THEME.LIGHT : THEME.DARK));
  }, []);

  return useMemo(
    () => ({
      theme,
      toggleTheme,
      setTheme,
    }),
    [theme, toggleTheme],
  );
}

Image

Sandbox Playground

可以看到上面引入了我們先前做的 Button 組件,並且透過 useTheme 去控制 data-theme 的值,這樣就可以讓我們的應用自由地切換深淺色主題。

小結

明天見!


上一篇
[Day 25] Design System - 文檔建置
下一篇
[Day 27] Design System - i18n
系列文
設計系統 - Design System30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言