feat(web): 整体替换 UI 框架为 Ant Design 6 并接入自定义主题
以 antd ConfigProvider + antd-style 承载 zinc 风格自定义主题(明暗双套 token), button/input/select/notification 用 createStyles 定制;三态主题经 darkAlgorithm 无刷新切换;use-toast 改基于 antd notification; 移除 shadcn/ui 组件目录与 radix 依赖;样式桥接层保证排版与多端布局不变。
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
import { useMemo } from 'react'
|
||||
import { theme } from 'antd'
|
||||
import type { ConfigProviderProps } from 'antd'
|
||||
import { createStyles } from 'antd-style'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
/**
|
||||
* ConfigProvider 主题:把应用的黑白灰 zinc 体系映射到 antd token。
|
||||
* 浅色 token 与设计稿 theme-vars 一致;深色走 darkAlgorithm 并覆盖
|
||||
* index.css `.dark` 中定义的语义色。组件样式跟随 antd,本文件只做
|
||||
* 品牌色/圆角/阴影的对齐,不再逐像素复刻旧手搓样式。
|
||||
*/
|
||||
|
||||
const useStyles = createStyles(({ css }) => {
|
||||
return {
|
||||
buttonPrimaryLight: css({
|
||||
backgroundColor: '#18181b',
|
||||
color: '#ffffff',
|
||||
border: '1px solid #18181b',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}),
|
||||
buttonPrimaryDark: css({
|
||||
backgroundColor: '#fafafa',
|
||||
color: '#18181b',
|
||||
border: '1px solid #fafafa',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}),
|
||||
buttonDefaultLight: css({
|
||||
backgroundColor: '#ffffff',
|
||||
color: '#18181b',
|
||||
border: '1px solid #e4e4e7',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}),
|
||||
buttonDefaultDark: css({
|
||||
backgroundColor: '#27272a',
|
||||
color: '#fafafa',
|
||||
border: '1px solid #3f3f46',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}),
|
||||
buttonDangerLight: css({
|
||||
backgroundColor: '#dc2626',
|
||||
color: '#ffffff',
|
||||
border: '1px solid #dc2626',
|
||||
fontWeight: 500,
|
||||
}),
|
||||
buttonDangerDark: css({
|
||||
backgroundColor: '#ef4444',
|
||||
color: '#fafafa',
|
||||
border: '1px solid #ef4444',
|
||||
fontWeight: 500,
|
||||
}),
|
||||
inputRootLight: css({
|
||||
borderColor: '#e4e4e7',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}),
|
||||
inputRootDark: css({
|
||||
borderColor: '#3f3f46',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}),
|
||||
inputElementLight: css({
|
||||
color: '#18181b',
|
||||
}),
|
||||
inputElementDark: css({
|
||||
color: '#fafafa',
|
||||
}),
|
||||
inputErrorLight: css({
|
||||
borderColor: '#dc2626',
|
||||
}),
|
||||
inputErrorDark: css({
|
||||
borderColor: '#ef4444',
|
||||
}),
|
||||
selectRoot: css({
|
||||
borderColor: '#e4e4e7',
|
||||
}),
|
||||
notificationRootLight: css({
|
||||
'&.ant-notification-notice, & .ant-notification-notice': {
|
||||
border: '1px solid #e4e4e7',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
}),
|
||||
notificationRootDark: css({
|
||||
'&.ant-notification-notice, & .ant-notification-notice': {
|
||||
border: '1px solid #3f3f46',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3)',
|
||||
},
|
||||
}),
|
||||
notificationTitle: css({
|
||||
fontWeight: 600,
|
||||
}),
|
||||
notificationDescriptionLight: css({
|
||||
color: '#525252',
|
||||
}),
|
||||
notificationDescriptionDark: css({
|
||||
color: '#d4d4d8',
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
const useShadcnTheme = (dark: boolean) => {
|
||||
const { styles } = useStyles()
|
||||
|
||||
return useMemo<ConfigProviderProps>(
|
||||
() => ({
|
||||
theme: {
|
||||
algorithm: dark ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||
token: dark
|
||||
? {
|
||||
// 深色:与 index.css `.dark` 语义变量对齐(zinc 深色 ramp)
|
||||
fontFamily: 'var(--font-sans)',
|
||||
// 控件高度对齐设计稿:普通按钮 34 / 紧凑 24 / 登录大按钮 44
|
||||
controlHeight: 34,
|
||||
controlHeightSM: 24,
|
||||
controlHeightLG: 44,
|
||||
colorPrimary: '#fafafa',
|
||||
colorSuccess: '#22c55e',
|
||||
colorWarning: '#f97316',
|
||||
colorError: '#ef4444',
|
||||
colorInfo: '#e4e4e7',
|
||||
colorTextBase: '#fafafa',
|
||||
colorBgBase: '#18181b',
|
||||
colorPrimaryBg: '#27272a',
|
||||
colorPrimaryBgHover: '#3f3f46',
|
||||
colorPrimaryBorder: '#3f3f46',
|
||||
colorPrimaryBorderHover: '#52525b',
|
||||
colorPrimaryHover: '#ffffff',
|
||||
colorPrimaryActive: '#d4d4d8',
|
||||
colorPrimaryText: '#fafafa',
|
||||
colorPrimaryTextHover: '#ffffff',
|
||||
colorPrimaryTextActive: '#e4e4e7',
|
||||
colorSuccessBg: '#0f2417',
|
||||
colorSuccessBgHover: '#163321',
|
||||
colorSuccessBorder: '#166534',
|
||||
colorSuccessBorderHover: '#15803d',
|
||||
colorSuccessHover: '#16a34a',
|
||||
colorSuccessActive: '#15803d',
|
||||
colorSuccessText: '#4ade80',
|
||||
colorSuccessTextHover: '#4ade80',
|
||||
colorSuccessTextActive: '#22c55e',
|
||||
colorWarningBg: '#271a0c',
|
||||
colorWarningBgHover: '#33220f',
|
||||
colorWarningBorder: '#9a3412',
|
||||
colorWarningBorderHover: '#c2410c',
|
||||
colorWarningHover: '#ea580c',
|
||||
colorWarningActive: '#c2410c',
|
||||
colorWarningText: '#fb923c',
|
||||
colorWarningTextHover: '#fb923c',
|
||||
colorWarningTextActive: '#f97316',
|
||||
colorErrorBg: '#2a1416',
|
||||
colorErrorBgHover: '#38191c',
|
||||
colorErrorBorder: '#7f1d1d',
|
||||
colorErrorBorderHover: '#991b1b',
|
||||
colorErrorHover: '#dc2626',
|
||||
colorErrorActive: '#b91c1c',
|
||||
colorErrorText: '#fca5a5',
|
||||
colorErrorTextHover: '#fca5a5',
|
||||
colorErrorTextActive: '#f87171',
|
||||
colorInfoBg: '#27272a',
|
||||
colorInfoBgHover: '#3f3f46',
|
||||
colorInfoBorder: '#3f3f46',
|
||||
colorInfoBorderHover: '#52525b',
|
||||
colorInfoHover: '#f4f4f5',
|
||||
colorInfoActive: '#e4e4e7',
|
||||
colorInfoText: '#fafafa',
|
||||
colorInfoTextHover: '#f4f4f5',
|
||||
colorInfoTextActive: '#e4e4e7',
|
||||
colorText: '#fafafa',
|
||||
colorTextSecondary: '#d4d4d8',
|
||||
colorTextTertiary: '#a1a1aa',
|
||||
colorTextQuaternary: '#71717a',
|
||||
colorTextDisabled: '#71717a',
|
||||
colorBgContainer: '#27272a',
|
||||
colorBgElevated: '#27272a',
|
||||
colorBgLayout: '#18181b',
|
||||
colorBgSpotlight: 'rgba(250, 250, 250, 0.85)',
|
||||
colorBgMask: 'rgba(0, 0, 0, 0.6)',
|
||||
colorBorder: '#3f3f46',
|
||||
colorBorderSecondary: '#27272a',
|
||||
borderRadius: 10,
|
||||
borderRadiusXS: 2,
|
||||
borderRadiusSM: 6,
|
||||
borderRadiusLG: 14,
|
||||
padding: 16,
|
||||
paddingSM: 12,
|
||||
paddingLG: 24,
|
||||
margin: 16,
|
||||
marginSM: 12,
|
||||
marginLG: 24,
|
||||
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px -1px rgba(0, 0, 0, 0.25)',
|
||||
boxShadowSecondary:
|
||||
'0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3)',
|
||||
}
|
||||
: {
|
||||
fontFamily: 'var(--font-sans)',
|
||||
// 控件高度对齐设计稿:普通按钮 34 / 紧凑 24 / 登录大按钮 44
|
||||
controlHeight: 34,
|
||||
controlHeightSM: 24,
|
||||
controlHeightLG: 44,
|
||||
colorPrimary: '#262626',
|
||||
colorSuccess: '#22c55e',
|
||||
colorWarning: '#f97316',
|
||||
colorError: '#ef4444',
|
||||
colorInfo: '#262626',
|
||||
colorTextBase: '#262626',
|
||||
colorBgBase: '#ffffff',
|
||||
colorPrimaryBg: '#f5f5f5',
|
||||
colorPrimaryBgHover: '#e5e5e5',
|
||||
colorPrimaryBorder: '#d4d4d4',
|
||||
colorPrimaryBorderHover: '#a3a3a3',
|
||||
colorPrimaryHover: '#404040',
|
||||
colorPrimaryActive: '#171717',
|
||||
colorPrimaryText: '#262626',
|
||||
colorPrimaryTextHover: '#404040',
|
||||
colorPrimaryTextActive: '#171717',
|
||||
colorSuccessBg: '#f0fdf4',
|
||||
colorSuccessBgHover: '#dcfce7',
|
||||
colorSuccessBorder: '#bbf7d0',
|
||||
colorSuccessBorderHover: '#86efac',
|
||||
colorSuccessHover: '#16a34a',
|
||||
colorSuccessActive: '#15803d',
|
||||
colorSuccessText: '#16a34a',
|
||||
colorSuccessTextHover: '#16a34a',
|
||||
colorSuccessTextActive: '#15803d',
|
||||
colorWarningBg: '#fff7ed',
|
||||
colorWarningBgHover: '#fed7aa',
|
||||
colorWarningBorder: '#fdba74',
|
||||
colorWarningBorderHover: '#fb923c',
|
||||
colorWarningHover: '#ea580c',
|
||||
colorWarningActive: '#c2410c',
|
||||
colorWarningText: '#ea580c',
|
||||
colorWarningTextHover: '#ea580c',
|
||||
colorWarningTextActive: '#c2410c',
|
||||
colorErrorBg: '#fef2f2',
|
||||
colorErrorBgHover: '#fecaca',
|
||||
colorErrorBorder: '#fca5a5',
|
||||
colorErrorBorderHover: '#f87171',
|
||||
colorErrorHover: '#dc2626',
|
||||
colorErrorActive: '#b91c1c',
|
||||
colorErrorText: '#dc2626',
|
||||
colorErrorTextHover: '#dc2626',
|
||||
colorErrorTextActive: '#b91c1c',
|
||||
colorInfoBg: '#f5f5f5',
|
||||
colorInfoBgHover: '#e5e5e5',
|
||||
colorInfoBorder: '#d4d4d4',
|
||||
colorInfoBorderHover: '#a3a3a3',
|
||||
colorInfoHover: '#404040',
|
||||
colorInfoActive: '#171717',
|
||||
colorInfoText: '#262626',
|
||||
colorInfoTextHover: '#404040',
|
||||
colorInfoTextActive: '#171717',
|
||||
colorText: '#262626',
|
||||
colorTextSecondary: '#525252',
|
||||
colorTextTertiary: '#737373',
|
||||
colorTextQuaternary: '#a3a3a3',
|
||||
colorTextDisabled: '#a3a3a3',
|
||||
colorBgContainer: '#ffffff',
|
||||
colorBgElevated: '#ffffff',
|
||||
colorBgLayout: '#fafafa',
|
||||
colorBgSpotlight: 'rgba(38, 38, 38, 0.85)',
|
||||
colorBgMask: 'rgba(38, 38, 38, 0.45)',
|
||||
colorBorder: '#e5e5e5',
|
||||
colorBorderSecondary: '#f5f5f5',
|
||||
borderRadius: 10,
|
||||
borderRadiusXS: 2,
|
||||
borderRadiusSM: 6,
|
||||
borderRadiusLG: 14,
|
||||
padding: 16,
|
||||
paddingSM: 12,
|
||||
paddingLG: 24,
|
||||
margin: 16,
|
||||
marginSM: 12,
|
||||
marginLG: 24,
|
||||
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)',
|
||||
boxShadowSecondary:
|
||||
'0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
primaryShadow: 'none',
|
||||
defaultShadow: 'none',
|
||||
dangerShadow: 'none',
|
||||
defaultBorderColor: dark ? '#3f3f46' : '#e4e4e7',
|
||||
defaultColor: dark ? '#fafafa' : '#18181b',
|
||||
defaultBg: dark ? '#27272a' : '#ffffff',
|
||||
defaultHoverBg: dark ? '#3f3f46' : '#f4f4f5',
|
||||
defaultHoverBorderColor: dark ? '#52525b' : '#d4d4d8',
|
||||
defaultHoverColor: dark ? '#fafafa' : '#18181b',
|
||||
defaultActiveBg: dark ? '#52525b' : '#e4e4e7',
|
||||
defaultActiveBorderColor: dark ? '#52525b' : '#d4d4d8',
|
||||
borderRadius: 6,
|
||||
},
|
||||
Input: {
|
||||
activeShadow: 'none',
|
||||
hoverBorderColor: dark ? '#a1a1aa' : '#a1a1aa',
|
||||
activeBorderColor: dark ? '#d4d4d8' : '#18181b',
|
||||
borderRadius: 6,
|
||||
},
|
||||
Select: {
|
||||
optionSelectedBg: dark ? '#3f3f46' : '#f4f4f5',
|
||||
optionActiveBg: dark ? '#27272a' : '#fafafa',
|
||||
optionSelectedFontWeight: 500,
|
||||
borderRadius: 6,
|
||||
},
|
||||
Alert: {
|
||||
borderRadiusLG: 8,
|
||||
},
|
||||
Modal: {
|
||||
borderRadiusLG: 12,
|
||||
},
|
||||
Switch: {
|
||||
trackHeight: 22,
|
||||
trackMinWidth: 44,
|
||||
innerMinMargin: 4,
|
||||
innerMaxMargin: 24,
|
||||
},
|
||||
Checkbox: {
|
||||
borderRadiusSM: 4,
|
||||
},
|
||||
Notification: {
|
||||
colorSuccessBg: dark ? '#0f2417' : '#f0fdf4',
|
||||
colorErrorBg: dark ? '#2a1416' : '#fef2f2',
|
||||
colorInfoBg: dark ? '#27272a' : '#f5f5f5',
|
||||
colorWarningBg: dark ? '#271a0c' : '#fff7ed',
|
||||
},
|
||||
Card: {},
|
||||
Tooltip: {},
|
||||
Radio: {},
|
||||
},
|
||||
},
|
||||
button: {
|
||||
classNames: ({ props }) => ({
|
||||
root: clsx(
|
||||
(props as { type?: string }).type === 'primary' &&
|
||||
(dark ? styles.buttonPrimaryDark : styles.buttonPrimaryLight),
|
||||
(props as { type?: string }).type === 'default' &&
|
||||
(dark ? styles.buttonDefaultDark : styles.buttonDefaultLight),
|
||||
(props as { danger?: boolean }).danger &&
|
||||
(dark ? styles.buttonDangerDark : styles.buttonDangerLight),
|
||||
),
|
||||
}),
|
||||
},
|
||||
input: {
|
||||
classNames: ({ props }) => ({
|
||||
root: clsx(
|
||||
dark ? styles.inputRootDark : styles.inputRootLight,
|
||||
(props as { status?: string }).status === 'error' &&
|
||||
(dark ? styles.inputErrorDark : styles.inputErrorLight),
|
||||
),
|
||||
input: dark ? styles.inputElementDark : styles.inputElementLight,
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
classNames: {
|
||||
root: styles.selectRoot,
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
classNames: {
|
||||
root: dark ? styles.notificationRootDark : styles.notificationRootLight,
|
||||
title: styles.notificationTitle,
|
||||
description: dark
|
||||
? styles.notificationDescriptionDark
|
||||
: styles.notificationDescriptionLight,
|
||||
},
|
||||
},
|
||||
wave: {},
|
||||
app: {},
|
||||
card: {},
|
||||
modal: {},
|
||||
alert: {},
|
||||
colorPicker: {},
|
||||
checkbox: {},
|
||||
dropdown: {},
|
||||
datePicker: {},
|
||||
inputNumber: {},
|
||||
popover: {},
|
||||
tooltip: {},
|
||||
switch: {},
|
||||
radio: {},
|
||||
segmented: {},
|
||||
progress: {},
|
||||
}),
|
||||
[
|
||||
dark,
|
||||
styles.buttonDangerDark,
|
||||
styles.buttonDangerLight,
|
||||
styles.buttonDefaultDark,
|
||||
styles.buttonDefaultLight,
|
||||
styles.buttonPrimaryDark,
|
||||
styles.buttonPrimaryLight,
|
||||
styles.inputElementDark,
|
||||
styles.inputElementLight,
|
||||
styles.inputErrorDark,
|
||||
styles.inputErrorLight,
|
||||
styles.inputRootDark,
|
||||
styles.inputRootLight,
|
||||
styles.notificationDescriptionDark,
|
||||
styles.notificationDescriptionLight,
|
||||
styles.notificationRootDark,
|
||||
styles.notificationRootLight,
|
||||
styles.notificationTitle,
|
||||
styles.selectRoot,
|
||||
],
|
||||
)
|
||||
}
|
||||
/* eslint-enable react-hooks/exhaustive-deps */
|
||||
|
||||
export default useShadcnTheme
|
||||
Reference in New Issue
Block a user