ant-design/.dumi/pages/index/components/Theme/index.tsx
二货爱吃白萝卜 cbcfd38ca7
docs: v5 site upgrade (#38328)
* build: try to use dumi as doc tool

* docs: migrate demo structure to dumi way

* refactor: use type export & import

* docs: migrate demo previewer to dumi

* docs: create empty layout & components

* docs: apply custom rehype plugin

* docs: create empty extra pages

* docs: Add Banner component

* chore: move theme tsconfig.json

* docs: home page init

* docs: migrate header (#37896)

* docs: header

* docs: update

* docs: home init

* clean up

* test: fix site lint

* chore: tsc ignore demo

* chore: dumi demo migrate script

* chore: cards

* docs: home layout

* docs: Update locale logic

* docs: fix getLink logic

* chore: fix ci (#37899)

* chore: fix ci

* ci: remove check-ts-demo

* ci: preview build

* test: ignore demo.tsx

* chore: update script

* test: update snapshot

* test: update node and image test

* chore: add .surgeignore

* docs: layout providers (#37908)

* docs: add components sidebar (#37923)

* docs: sidebar

* docs: update docs title

* docs: update design doc

* chore: code clean

* docs: handle changelog page

* docs: add title

* docs: add subtitle

* docs: active header nav

* chore: code clean

* docs: overview

* chore: code clean

* docs: update intl (#37918)

* docs: update intl

* chore: code clean

* docs: update favicons

* chore: update testPathIgnorePatterns

* chore: code clean

* chore: code clean

* chore: copy 404.html (#37996)

* docs: Home page theme picker

* chore: Update migrate script

* docs: home page update

* docs: theme editor style

* docs: theme lang

* chore: update migrate.js

* docs: fix demo (#38094)

* chore: update migrate.js

* docs: update md

* docs: update demo

* test: fix snapshot

* chore: move debug to code attr in migrate script

* chore: update md

Co-authored-by: PeachScript <scdzwyxst@gmail.com>

* feat: overview page

* feat: Migrate `404` page (#38118)

* feat: migrate IconSearch component (#37916)

* feat<site/IconSearch>: copy IconDisplay from site to .dumi

* feat<site/IconSearch>: change docs of icon

* feat<site/IconSearch>: tweak

* feat<site/IconSearch>: use useIntl instead of injectIntl

* feat<site/IconSearch>: fix ts type error

* feat<site/IconSearch>: use intl.formatMessage to render text

* docs: Adjust home btn sizw

* docs: Update doc

* feat: v5 site overview page (#38131)

* feat: site

* fix: fix

* feat: v5 site overview page

* fix: fix path

* fix: fix

* fix: fix

* docs: fix margin logic

* feat: v5 site change-log page (#38137)

* feat: v5 site change-log page (#38162)

* docs: site redirect to home pag

* docs: theme picker

* docs: use react-intl from dumi (#38183)

* docs: Theme Picker

* docs: update dumi config

* docs: home back fix

* docs: picker colorful

* docs: locale of it

* docs: update components desc

* docs: site of links

* docs: update components list

* docs: update desc

* feat: Migrate `DemoWrapper` component (#38166)

* feat: Migrate `DemoWrapper` component

* feat: remove invalid comments and add comment for `key` prop

* docs: FloatButton pure panel

* chore: update demo

* chore: update dumi config

* Revert "chore: update demo"

This reverts commit 028265d3ba.

* chore: test logic adjust to support cnpm modules

* chore: add locale alias

* docs: /index to /

* docs: add locale redirect head script

* chore: adjust compact

* docs: fix missing token

* feat: compact switch

* chore: code clean

* docs: update home

* docs: fix radius token

* docs: hash of it

* chore: adjust home page

* docs: Add background map

* docs: site theme bac logic

* docs: avatar

* docs: update logo color

* docs: home banner

* docs: adjust tour size

* docs: purepanl update

* docs: transfooter

* docs: update banner gif

* docs: content (#38361)

* docs: title & EditButton

* docs: content

* chore: fix toc

* docs: resource page

* docs: transform resource data from hast

* docs: filename & Resource Card

* chore: enable prerender

* chore: remove less

* docs: toc style

* chore: fix lint

* docs: fix Layout page

* docs: fix CP page

* chore: update demos

* docs: workaround for export dynamic html

* chore: enable demo eslint

* docs: table style

* fix: header shadow

* chore: update snapshot

* fix: toc style

* docs: add title

* docs: Adjust site

* feat: helmet

* docs: site css

* fix: description

* feat: toc debug

* docs: update config-provider

* feat: use colorPanel

* fix: colorPanel value

* feat: anchor ink ball style

* feat: apply theme editor

* fix: code block style

* chore: update demo

* chore: fix lint

* chore: code clean

* chore: update snapshot

* feat: ts2js

* chore: description

* docs: site ready for ssr

includes:
- move client render logic to useEffect in site theme
- extract antd cssinjs to a single css file like bisheng
- workaround to support react@18 pipeableStream for emotion

* chore: bump testing lib

* docs: font size of title

* chore: remove react-sortable-hoc

* chore: update snapshot

* chore: update script

Co-authored-by: PeachScript <scdzwyxst@gmail.com>
Co-authored-by: MadCcc <1075746765@qq.com>
Co-authored-by: zqran <uuxnet@gmail.com>
Co-authored-by: TrickyPi <530257315@qq.com>
Co-authored-by: lijianan <574980606@qq.com>
2022-11-09 12:28:04 +08:00

521 lines
14 KiB
TypeScript

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,
Input,
InputNumber,
Radio,
theme,
} from 'antd';
import ThemePicker, { THEME } from './ThemePicker';
import ColorPicker from './ColorPicker';
import RadiusPicker from './RadiusPicker';
import Group from '../Group';
import BackgroundImage from './BackgroundImage';
import { PRESET_COLORS, getClosetColor, DEFAULT_COLOR, getAvatarURL } from './colorUtil';
const { Header, Content, Sider } = Layout;
const TokenChecker = () => {
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: '暗黑',
},
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',
},
};
// ============================= 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: green;
`,
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;
`,
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;
`,
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);
}
`,
side: css``,
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: <FolderOutlined />,
children: subMenuItems,
},
{
key: `Development`,
label: `Development`,
icon: <FolderOutlined />,
},
];
// ============================= 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 '#FB7299':
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<THEME, Partial<ThemeData>> = {
default: {},
dark: {
borderRadius: 2,
},
lark: {
colorPrimary: '#00B96B',
borderRadius: 4,
},
comic: {
colorPrimary: '#fb7299',
borderRadius: 16,
},
};
export default function Theme() {
const style = useStyle();
const { token } = useSiteToken();
const [locale] = useLocale(locales);
const [themeData, setThemeData] = React.useState<ThemeData>(ThemeDefault);
const onThemeChange = (_: Partial<ThemeData>, 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 = (
<ConfigProvider
theme={{
token: {
...themeToken,
},
hashed: true,
algorithm: algorithmFn,
components: {
Layout: isLight
? {
colorBgHeader: 'transparent',
colorBgBody: 'transparent',
}
: {
// colorBgBody: 'transparent',
},
Menu: isLight
? {
colorItemBg: 'transparent',
colorSubItemBg: 'transparent',
colorActiveBarWidth: 0,
}
: {},
},
}}
>
<TokenChecker />
<div
css={[
style.demo,
isLight && closestColor !== DEFAULT_COLOR && style.otherDemo,
!isLight && style.darkDemo,
]}
style={{ borderRadius: themeData.borderRadius }}
>
<Layout>
<Header css={style.header}>
{/* Logo */}
<div css={style.logo}>
<div css={[style.logoImg, closestColor !== DEFAULT_COLOR && style.logoImgPureColor]}>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
style={{
filter:
closestColor === DEFAULT_COLOR
? undefined
: `drop-shadow(30px 0 0 ${logoColor})`,
}}
/>
</div>
<h1>Ant Design 5.0</h1>
</div>
<Space css={style.menu} size="middle">
<BellOutlined />
<QuestionCircleOutlined />
<div
css={[style.avatar, themeType === 'dark' && style.avatarDark]}
style={{
backgroundColor: avatarColor,
backgroundImage: `url(${getAvatarURL(closestColor)})`,
backgroundSize: 'cover',
boxShadow: `0 0 2px rgba(0, 0, 0, 0.2)`,
}}
/>
</Space>
</Header>
<Layout>
<Sider width={200} className="site-layout-background">
<Menu
mode="inline"
css={style.side}
selectedKeys={['Themes']}
openKeys={['Design']}
style={{ height: '100%', borderRight: 0 }}
items={sideMenuItems}
/>
</Sider>
<Layout style={{ padding: '0 24px 24px' }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>
<HomeOutlined />
</Breadcrumb.Item>
<Breadcrumb.Item overlay={<Menu items={subMenuItems} />}>Design</Breadcrumb.Item>
<Breadcrumb.Item>Themes</Breadcrumb.Item>
</Breadcrumb>
<Content>
<Typography.Title level={2}>{locale.customizeTheme}</Typography.Title>
<Card title={locale.myTheme}>
<Form
form={form}
initialValues={themeData}
onValuesChange={onThemeChange}
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
css={style.form}
>
<Form.Item label={locale.titleTheme} name="themeType">
<ThemePicker />
</Form.Item>
<Form.Item label={locale.titlePrimaryColor} name="colorPrimary">
<ColorPicker />
</Form.Item>
<Form.Item label={locale.titleBorderRadius} name="borderRadius">
<RadiusPicker />
</Form.Item>
<Form.Item label={locale.titleCompact} name="compact">
<Radio.Group>
<Radio value="default">{locale.default}</Radio>
<Radio value="compact">{locale.compact}</Radio>
</Radio.Group>
</Form.Item>
</Form>
</Card>
</Content>
</Layout>
</Layout>
</Layout>
</div>
</ConfigProvider>
);
const posStyle: React.CSSProperties = {
position: 'absolute',
};
return (
<Group
title={locale.themeTitle}
titleColor={getTitleColor(themeData.colorPrimary, isLight)}
description={locale.themeDesc}
id="flexible"
background={backgroundColor}
decoration={
// =========================== Theme Background ===========================
<>
{/* >>>>>> Default <<<<<< */}
<div
style={{
transition: `all ${token.motionDurationSlow}`,
opacity: isLight && closestColor === DEFAULT_COLOR ? 1 : 0,
}}
>
{/* Image Left Top */}
<img
style={{
...posStyle,
left: '50%',
transform: 'translateX(-900px)',
top: -100,
height: 500,
}}
src="https://gw.alipayobjects.com/zos/bmw-prod/bd71b0c6-f93a-4e52-9c8a-f01a9b8fe22b.svg"
/>
{/* Image Right Bottom */}
<img
style={{
...posStyle,
right: '50%',
transform: 'translateX(750px)',
bottom: -100,
height: 287,
}}
src="https://gw.alipayobjects.com/zos/bmw-prod/84ad805a-74cb-4916-b7ba-9cdc2bdec23a.svg"
/>
</div>
{/* >>>>>> Dark <<<<<< */}
<div
style={{
transition: `all ${token.motionDurationSlow}`,
opacity: !isLight || !closestColor ? 1 : 0,
}}
>
{/* Image Left Top */}
<img
style={{ ...posStyle, left: 0, top: -100, height: 500 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/a213184a-f212-4afb-beec-1e8b36bb4b8a.svg"
/>
{/* Image Right Bottom */}
<img
style={{ ...posStyle, right: 0, bottom: -100, height: 287 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/bb74a2fb-bff1-4d0d-8c2d-2ade0cd9bb0d.svg"
/>
</div>
{/* >>>>>> Background Image <<<<<< */}
<BackgroundImage isLight={isLight} colorPrimary={themeData.colorPrimary} />
</>
}
>
{themeNode}
</Group>
);
}