Ant Design
的自定义主题,对于刚入手的时候感觉真是一脸蒙圈,那今天给它梳理倒腾下;
1、自定义主题要点
整体样式变化,主要两个部分:
1.1、Design Token
https://ant.design/docs/react/customize-theme-cn#theme
官方介绍一大堆,咱们粗暴点就当作key=>value
配置内容来看和理解!
大体分为四块配置项:
分类 | 涉及配置项 | |
---|---|---|
通用/基本样式 | token | 可查阅SeedToken、MapToken、AliasToken |
组件样式 | components | 查阅各个组件最底下的主题变量(Design Token) 内容 |
样式算法 | algorithm | 这块其实就算UI库内部自动帮你换算不同“等级”颜色color值 |
扩展配置 | inherit、cssVar、hashed | 这块应该很少会去改它,做主题切换的时候建议cssVar 开启 |
1.2、全局配置 ConfigProvider 组件
https://ant.design/components/config-provider-cn
import React from 'react';
import { ConfigProvider } from 'antd';
// ...
const Demo: React.FC = () => (
<ConfigProvider componentSize={"middle"}>
// 界面组件
</ConfigProvider>
);
export default Demo;
这块涉及到主题样式的主要是componentSize
配置项和组件配置
2、实战
以下做个实验性案例,不要纠结细节哈!
2.1、实验环境
- next: 14.1.0
- react:^18
- antd:^5.14.1
√ Would you like to use TypeScript? ... Yes
√ Would you like to use ESLint? ... No
√ Would you like to use Tailwind CSS? ... No
√ Would you like to use `src/` directory? ... No
√ Would you like to use App Router? (recommended) ... Yes
√ Would you like to customize the default import alias (@/*)? ... Yes
√ What import alias would you like configured? ... @/*
2.2、目录结构
| - app
|- page.tsx
|- theme
|- default
|- index.ts
|- custom
|- index.ts
|- index.ts
|- themeCenter.ts
2.3、相关文件编写
2.3.1、page.tsx
主要方便实验展示
"use client";
import React, { useState } from "react";
import {
SearchOutlined,
AppstoreOutlined,
MailOutlined,
SettingOutlined,
} from "@ant-design/icons";
import { Button, Flex, Tooltip, Menu, Pagination, Divider } from "antd";
import ThemeComponents from "@/theme";
const items: any = [
{
label: "Navigation One",
key: "mail",
icon: <MailOutlined />,
},
];
const App: React.FC = () => {
const [theme, setTheme] = useState("default");
return (
<>
<Flex gap="small">
<Button type="primary" onClick={() => setTheme("default")}>
主题1
</Button>
<Button type="primary" onClick={() => setTheme("custom")}>
主题2
</Button>
</Flex>
<Divider />
<ThemeComponents theme={theme} dark={'light'}>
<Flex gap="small" vertical>
<Flex wrap="wrap" gap="small">
<Button type="primary" shape="circle">
A
</Button>
<Button type="primary" icon={<SearchOutlined />}>
Search
</Button>
<Tooltip title="search">
<Button shape="circle" icon={<SearchOutlined />} />
</Tooltip>
<Button icon={<SearchOutlined />}>Search</Button>
</Flex>
<Menu mode="horizontal" items={items} selectedKeys={["mail"]} />
<Pagination defaultCurrent={1} total={50} />
</Flex>
</ThemeComponents>
</>
);
};
export default App;
2.3.1、ThemeComponents
// 这里仅演示主题切换,其他业务逻辑~~~自己探索哈!
import React, { useEffect, useState } from "react";
import { ConfigProvider } from "antd";
import { ThemeModeEnum } from "@/theme/themeCenter.d";
import DefaultTheme from "./default";
import CustomTheme from "./custom";
type Props = {
theme: string;
dark?: boolean,
children: React.ReactNode;
};
const ThemeMap = {
default: DefaultTheme,
custom: CustomTheme,
};
const ThemeComponents = ({ theme = "default", dark, children }: Props) => {
theme = theme ? theme : "default";
const [themeCenter, setThemeCenter] = useState(new ThemeMap[theme]());
const [darkTheme, setDarkTheme] = useState(dark);
useEffect(() => {
console.log("theme:", theme);
setThemeCenter(new ThemeMap[theme]());
}, [theme]);
useEffect(() => {
console.log("darkTheme:", dark);
if(themeCenter){
themeCenter.ThemeMode = dark;
setDarkTheme(dark);
}
}, [dark]);
return (
<ConfigProvider {...themeCenter?.ThemeConfigProvider}>
{children}
</ConfigProvider>
);
};
export default ThemeComponents;
2.3.1、Theme管理
这部分主要涉及两个部分:基础主题类
和继承主题类
继承主题类
这部分主要用于不同主题样式的具体化配置
按主题目录区分,方便主题做其他更复杂的扩展预留空间
// 案例file: theme/default/index.ts
import ThemeCenter from "../themeCenter"
class ThemeConfiguration extends ThemeCenter
{
// 这是父级ThemeCenter下放的初始化方法,主题初始化在这里进行
// 除_initThemeConfig方法外,其他的可自行定义
protected _initThemeConfig(){
// 设置主题色
this.ThemeColor = '#FF5C00';
// 设置基础主题样式Token
this.setThemeAllToken({
fontSize: 14,
colorLink: '#1890ff',
}, 'token')
// 设置组件样式Token
this.LayoutComponentsToken();
this.MenuComponentsToken();
// ConfigProvider组件默认配置
this.setThemeConfigProvider({
componentSize: 'small'
})
}
protected LayoutComponentsToken(){
this.setThemeComponentsToken('Layout',{
headerBg: '#fff',
headerColor: '#333',
headerHeight: 35,
headerPadding: '0 16px 0 0',
lightSiderBg: 'transparent',
siderBg: '#fff',
bodyBg: 'transparent',
// footerBg: '#f2f3f5',
// footerPadding: '24px 0'
});
}
protected MenuComponentsToken(){
// @link https://ant.design/components/menu-cn#%E4%B8%BB%E9%A2%98%E5%8F%98%E9%87%8Fdesign-token
this.setThemeComponentsToken('Menu',{
collapsedWidth: 46
// itemBg: "rgba(255,255,255, .85)",
// darkItemBg: 'var(--ant-layout-sider-bg)',
// darkItemColor: 'rgba(0,0,0, .65)',
// darkItemDisabledColor: 'rgba(0,0,0, .25)',
// darkItemHoverBg: 'rgba(255,92,0, .65)',
// darkItemHoverColor: '#fff',
// darkPopupBg: '#181818',
// darkSubMenuItemBg: 'var(--ant-layout-sider-bg)',
})
}
}
export default ThemeConfiguration;
基础主题类
// file: /theme/themeCenter.ts
import type {
ThemeConfig,
ThemeComponentsConfig,
ThemeConfigProviderProps
} from "./themeCenter.d"
import { ThemeModeEnum } from "./themeCenter.d"
import {theme} from "antd";
class ThemeCenter {
private themeName = "default";
private themeColor:string = '#FF5C00';
private themeMode:ThemeModeEnum = ThemeModeEnum.AUTO;
/**
* 明暗算法配置
* @var {object} _algorithm
*/
private _algorithm = {
light: theme.defaultAlgorithm,
dark: theme.darkAlgorithm
}
private _lightAlgorithm = this._algorithm['light'];
private _darkAlgorithm = this._algorithm['dark'];
/**
* 自定义主题配置
* @link https://ant.design/docs/react/customize-theme-cn#theme
* @var {ThemeConfig} _customThemeToken
*/
private _customThemeToken:ThemeConfig = {
token: {},
// 继承上层 ConfigProvider 中配置的主题
inherit: true,
algorithm: this._algorithm['light'],
components: {},
// 开启 CSS 变量,参考使用 CSS 变量
// @link https://ant.design/docs/react/css-variables-cn#api
cssVar: {
prefix: 'bogoo',
key: 'theme',
},
// 组件 class Hash 值
hashed: true,
}
/**
* 自定义ConfigProvider组件配置
*
* @var {ThemeConfigProviderProps} _customConfigProvider
*/
private _customConfigProvider:ThemeConfigProviderProps = {
componentSize: undefined,
theme: this._customThemeToken
}
constructor() {this._initThemeConfig();}
protected _initThemeConfig(){}
/**获取主题名称*/
public get ThemeName(){return this.themeName;}
/**获取当前主题色*/
public get ThemeColor(){return this.themeColor;}
public set ThemeColor(color: string){
this.themeColor = color;
this.setThemeAllToken({colorPrimary: color}, 'token')
}
/**获取明暗色系名称*/
public get ThemeMode(){return this.themeMode;}
/**设置明暗主题色配置*/
public set ThemeMode(mode: ThemeModeEnum){
this.themeMode = mode;
let _algorithm: any = this._lightAlgorithm;
if (mode === ThemeModeEnum.AUTO) {
// _algorithm = this._darkAlgorithm;
}else{
_algorithm = mode===ThemeModeEnum.LIGHT ? this._lightAlgorithm : this._darkAlgorithm;
}
this.setThemeAllToken({algorithm: _algorithm});
}
/**主题Token配置*/
public get ThemeToken(){return this._customThemeToken;}
/**
* 设置主题Token配置
*
* @param {ThemeConfig|ThemeComponentsConfig} token 全局主题token或组件token
* @param {'token'|'algorithm'|'components'} field 可选,若指定配置名称,则仅更新对应配置
*
* @return {ThemeConfig}
*/
public setThemeAllToken(
token: ThemeConfig|ThemeComponentsConfig,
field?:'token'|'algorithm'|'components'
){
let _token:any = {};
let _updateToken = token;
if (field){
if (!['token','algorithm','components'].includes(field))return this._customThemeToken;
if (_updateToken instanceof Array){
// @ts-ignore
_token[field] = this._customThemeToken[field].concat(_updateToken)
}else if(typeof _updateToken === 'object'){
_token[field] = Object.assign({}, this._customThemeToken[field]||{}, _updateToken)
}else{
_token[field] = _updateToken
}
}else{
_token = _updateToken;
}
console.log('_token:', _token)
Object.assign(this._customThemeToken, _token);
return this._customThemeToken;
}
/**
* 组件主题Token配置
*
* @param {string} componentsName 组件名称(首字母大小)
* @param {ThemeComponentsConfig} token 主题样式配置
*
* @return {void}
*/
public setThemeComponentsToken(componentsName:string, token: ThemeComponentsConfig){
this.setThemeAllToken({
// @ts-ignore
[componentsName]: Object.assign({} ,this._customThemeToken?.components[componentsName]||undefined, token)
}, 'components')
}
/**ConfigProvider组件配置*/
public get ThemeConfigProvider(){return this._customConfigProvider;}
public setThemeConfigProvider(config:ThemeConfigProviderProps){
Object.assign(this._customConfigProvider, config);
}
}
export default ThemeCenter;
补充
themeCenter.d.ts
import type {
ThemeConfig as AntdThemeConfig,
ComponentsConfig as AntdComponentsConfig,
} from "antd/es/config-provider/context";
import type {SizeType} from "antd/es/config-provider/SizeContext";
import type {ReactNode} from "react";
export enum ThemeModeEnum {
AUTO = 'auto',
DARK = 'dark',
LIGHT = 'light'
}
export interface ThemeConfigProviderProps {
componentSize?: SizeType;
theme?:AntdThemeConfig;
}
export interface ThemeConfig extends AntdThemeConfig {}
export interface ThemeComponentsConfig extends AntdComponentsConfig {}
没啦!学废了嘛??!!!