向 Next.js 项目添加 Sass 主题的最简单方法

需要克服的挑战

尽管 Sass 功能强大,可以编写更简洁、更模块化的 CSS,但它在运行时主题方面存在局限性,因为 Sass 代码是在浏览器呈现之前编译的。这意味着**纯 Sass 无法在运行时动态切换主题**。但是,通过将 CSS 变量与 Sass 相结合,我们可以用最少的额外努力实现运行时主题。

在 Next.js 中设置主题上下文

上下文提供程序将跨组件管理主题状态。此处的示例代码是为 Next.js 14 项目编写的,但它适用于任何 React 设置。

// ThemeProvider.tsx
'use client'
import React, { createContext, FC, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
import './ThemeProvider.scss';

type Theme = 'dark' | 'light';

type ContextReturnType = {
    theme: Theme;
    toggleTheme: () => void;
};

type Props = {
    children: React.ReactNode;
};

const ThemeContext = createContext({
    theme: 'dark',
    toggleTheme: () => {},
});

const ThemeProvider: FC = ({ children }) => {
    const [theme, setTheme] = useState(() => {
        if (typeof window !== 'undefined') {
            const savedTheme = localStorage.getItem('theme');
            return (savedTheme || 'dark') as Theme;
        }
        return 'dark';
    });

    const [isLoading, setIsLoading] = useState(true);

    const toggleTheme = useCallback(() => {
        setTheme(prevTheme => {
            const nextTheme = prevTheme === 'light' ? 'dark' : 'light';
            localStorage.setItem('theme', nextTheme);
            return nextTheme;
        });
    }, []);

    useLayoutEffect(() => {
        document.documentElement.className = theme;
        setIsLoading(false);
    }, [theme]);

    const contextValue = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);

    if (isLoading) {
        return null;
    }

    return {children};
};

export const useThemeContext = () => useContext(ThemeContext);
export default ThemeProvider;

主题存储在状态中并保存到“localStorage”,因此用户的偏好在会话之间保持不变。“toggleTheme”函数在浅色和深色主题之间切换并更新“localStorage”。我们使用“useLayoutEffect”将主题类应用于 HTML 元素,使我们能够在 Sass 中基于此类设置样式。

使用 Sass 定义主题

接下来,让我们使用 Sass 定义主题样式。我们将创建两个 mixin,即“Text”和“Background”,以处理文本和背景的颜色,但您可以根据需要拥有任意数量的颜色。

// theme.scss
@mixin Text($primary, $secondary) {
    --text-primary: #{$primary};
    --text-secondary: #{$secondary};
}

@mixin Background($primary, $secondary) {
    --background-primary: #{$primary};
    --background-secondary: #{$secondary};
}

.dark {
    --theme: dark;
    @include Text('#F9F9F9', '#C6C6C6');
    @include Background('#0D0E10', '#1E1F24');
}

.light {
    --theme: light;
    @include Text('#1C1C1C', '#4A4A4A');
    @include Background('#FFFFFF', '#f2f7ff');
}

`Text` 和 `Background` 混合类让我们能够轻松地为每个主题应用配色方案。`.dark` 和 `.light` 类为每个主题定义 CSS 变量。`--theme` 属性让我们能够识别 Sass 样式中哪个主题处于活动状态。您可以使用它来应用特定于主题的样式。

在 Sass 中使用 CSS 变量

为了使我们的主题值在 Sass 中可用,让我们创建一个将 CSS 变量转换为 Sass 变量的辅助函数。

// mixins.scss
@function theme($color-name) {
    @return var(--#{$color-name});
}

`theme` 函数通过名称检索 CSS 变量值,允许您在任何想要访问主题相关样式的 Sass 文件中使用 `theme('text-primary')`。

将主题应用到组件

现在,让我们将这些主题变量应用于组件。以下是使用主题相关颜色设置按钮组件样式的方法。

// Button.module.scss
@import 'mixins.scss';

.button {
    color: theme('text-primary');
    background-color: theme('background-primary');
}

“按钮”组件现在使用特定于主题的颜色作为其文本和背景,自动适应活动主题。

结论

按照这些步骤,您可以轻松地在 Next.js 项目中实现动态主题。

您可以在我的网站上找到更多我撰写的文章。