import * as React from 'react'; import { css } from '@emotion/react'; import { TinyColor } from '@ctrl/tinycolor'; import { HomeOutlined, FolderOutlined, BellOutlined, QuestionCircleOutlined, } from '@ant-design/icons'; import useLocale from '../../../../hooks/useLocale'; import useSiteToken from '../../../../hooks/useSiteToken'; import { Typography, Layout, Menu, Breadcrumb, MenuProps, Space, ConfigProvider, Card, Form, Radio, theme, Button, } from 'antd'; import ThemePicker, { THEME } from './ThemePicker'; import ColorPicker from './ColorPicker'; import RadiusPicker from './RadiusPicker'; import Group from '../Group'; import BackgroundImage from './BackgroundImage'; import { getClosetColor, DEFAULT_COLOR, getAvatarURL, PINK_COLOR } from './colorUtil'; const { Header, Content, Sider } = Layout; const TokenChecker = () => { if (process.env.NODE_ENV !== 'production') { console.log('Demo Token:', theme.useToken()); } return null; }; // ============================= Theme ============================= const locales = { cn: { themeTitle: '定制主题,随心所欲', themeDesc: 'Ant Design 5.0 开放更多样式算法,让你定制主题更简单', customizeTheme: '定制主题', myTheme: '我的主题', titlePrimaryColor: '主色', titleBorderRadius: '圆角', titleCompact: '宽松度', default: '默认', compact: '紧凑', titleTheme: '主题', light: '亮色', dark: '暗黑', toDef: '深度定制', toUse: '去使用', }, en: { themeTitle: 'Flexible theme customization', themeDesc: 'Ant Design 5.0 enable extendable algorithm, make custom theme easier', customizeTheme: 'Customize Theme', myTheme: 'My Theme', titlePrimaryColor: 'Primary Color', titleBorderRadius: 'Border Radius', titleCompact: 'Compact', titleTheme: 'Theme', default: 'Default', compact: 'Compact', light: 'Light', dark: 'Dark', toDef: 'More', toUse: 'Apply', }, }; // ============================= Style ============================= const useStyle = () => { const { token } = useSiteToken(); return { demo: css` overflow: hidden; background: rgba(240, 242, 245, 0.25); backdrop-filter: blur(50px); box-shadow: 0 2px 10px 2px rgba(0, 0, 0, 0.1); transition: all ${token.motionDurationSlow}; `, otherDemo: css` backdrop-filter: blur(10px); background: rgba(247, 247, 247, 0.5); `, darkDemo: css` background: #000; `, larkDemo: css` // background: #f7f7f7; background: rgba(240, 242, 245, 0.65); `, comicDemo: css` // background: #ffe4e6; background: rgba(240, 242, 245, 0.65); `, menu: css` margin-left: auto; `, darkSideMenu: css``, header: css` display: flex; align-items: center; border-bottom: 1px solid ${token.colorSplit}; padding-inline: ${token.paddingLG}px !important; height: ${token.controlHeightLG * 1.2}px; line-height: ${token.controlHeightLG * 1.2}px; `, headerDark: css` border-bottom-color: rgba(255, 255, 255, 0.1); `, avatar: css` width: ${token.controlHeight}px; height: ${token.controlHeight}px; border-radius: 100%; background: rgba(240, 240, 240, 0.75); `, avatarDark: css` background: rgba(200, 200, 200, 0.3); `, logo: css` display: flex; align-items: center; column-gap: ${token.padding}px; h1 { font-weight: 400; font-size: 16px; line-height: 1.5; } `, logoImg: css` width: 30px; height: 30px; overflow: hidden; img { width: 30px; height: 30px; vertical-align: top; } `, logoImgPureColor: css` img { transform: translateX(-30px); } `, transBg: css` background: transparent !important; `, form: css` width: 800px; margin: 0 auto; `, }; }; interface PickerProps { title: React.ReactNode; } // ========================== Menu Config ========================== const subMenuItems: MenuProps['items'] = [ { key: `Design Values`, label: `Design Values`, }, { key: `Global Styles`, label: `Global Styles`, }, { key: `Themes`, label: `Themes`, }, { key: `DesignPatterns`, label: `Design Patterns`, }, ]; const sideMenuItems: MenuProps['items'] = [ { key: `Design`, label: `Design`, icon: , children: subMenuItems, }, { key: `Development`, label: `Development`, icon: , }, ]; // ============================= Theme ============================= function getTitleColor(colorPrimary: string, isLight?: boolean) { if (!isLight) { return '#FFF'; } const color = new TinyColor(colorPrimary); const closestColor = getClosetColor(colorPrimary); switch (closestColor) { case DEFAULT_COLOR: case PINK_COLOR: case '#F2BD27': return undefined; default: return color.toHsl().l < 0.7 ? '#FFF' : undefined; } } interface ThemeData { themeType: THEME; colorPrimary: string; borderRadius: number; compact: 'default' | 'compact'; } const ThemeDefault: ThemeData = { themeType: 'default', colorPrimary: '#1677FF', borderRadius: 6, compact: 'default', }; const ThemesInfo: Record> = { default: {}, dark: { borderRadius: 2, }, lark: { colorPrimary: '#00B96B', borderRadius: 4, }, comic: { colorPrimary: PINK_COLOR, borderRadius: 16, }, }; export default function Theme() { const style = useStyle(); const { token } = useSiteToken(); const [locale] = useLocale(locales); const [themeData, setThemeData] = React.useState(ThemeDefault); const onThemeChange = (_: Partial, nextThemeData: ThemeData) => { setThemeData(nextThemeData); }; const { compact, themeType, ...themeToken } = themeData; const isLight = themeType !== 'dark'; const [form] = Form.useForm(); // const algorithmFn = isLight ? theme.defaultAlgorithm : theme.darkAlgorithm; const algorithmFn = React.useMemo(() => { const algorithms = [isLight ? theme.defaultAlgorithm : theme.darkAlgorithm]; if (compact === 'compact') { algorithms.push(theme.compactAlgorithm); } return algorithms; }, [isLight, compact]); // ================================ Themes ================================ React.useEffect(() => { const mergedData = { ...ThemeDefault, themeType, ...ThemesInfo[themeType], } as any; setThemeData(mergedData); form.setFieldsValue(mergedData); }, [themeType]); // ================================ Tokens ================================ const closestColor = getClosetColor(themeData.colorPrimary); const [backgroundColor, avatarColor] = React.useMemo(() => { let bgColor = 'transparent'; const mapToken = theme.defaultAlgorithm({ ...theme.defaultConfig.token, colorPrimary: themeData.colorPrimary, }); if (themeType === 'dark') { bgColor = '#393F4A'; } else if (closestColor === DEFAULT_COLOR) { bgColor = '#F5F8FF'; } else { bgColor = mapToken.colorPrimaryHover; } return [bgColor, mapToken.colorPrimaryBgHover]; }, [themeType, closestColor, themeData.colorPrimary]); const logoColor = React.useMemo(() => { const hsl = new TinyColor(themeData.colorPrimary).toHsl(); hsl.l = Math.min(hsl.l, 0.7); return new TinyColor(hsl).toHexString(); }, [themeData.colorPrimary]); // ================================ Render ================================ const themeNode = ( {/* Logo */} Ant Design 5.0 }>Design Themes {locale.customizeTheme} {locale.toDef} {locale.toUse} } > {locale.default} {locale.compact} ); const posStyle: React.CSSProperties = { position: 'absolute', }; return ( {/* >>>>>> Default <<<<<< */} {/* Image Left Top */} {/* Image Right Bottom */} {/* >>>>>> Dark <<<<<< */} {/* Image Left Top */} {/* Image Right Bottom */} {/* >>>>>> Background Image <<<<<< */} > } > {themeNode} ); }