diff --git a/src/app.tsx b/src/app.tsx index d310a6c..5f04a3a 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -115,6 +115,8 @@ export default function App() { input={{ autoComplete: "off" }} locale={getAntdLocale()} theme={{ + cssVar: true, + hashed: false, algorithm: isDark ? antdTheme.darkAlgorithm diff --git a/src/components/antd-app/constants.ts b/src/components/antd-app/constants.ts new file mode 100644 index 0000000..3b7e820 --- /dev/null +++ b/src/components/antd-app/constants.ts @@ -0,0 +1,124 @@ +// CSS 变量前缀 +export const prefix = "oo"; +// 基础色 +export const colors = [ + "blue", + "purple", + "cyan", + "green", + "magenta", + "pink", + "red", + "orange", + "yellow", + "volcano", + "geekblue", + "gold", + "lime", +]; +// 品牌色 +export const brandColors = [ + "colorPrimary", + "colorPrimaryBg", + "colorPrimaryBgHover", + "colorPrimaryBorder", + "colorPrimaryBorderHover", + "colorPrimaryHover", + "colorPrimaryActive", + "colorPrimaryTextHover", + "colorPrimaryText", + "colorPrimaryTextActive", +]; +// 成功色 +export const successColors = [ + "colorSuccess", + "colorSuccessBg", + "colorSuccessBgHover", + "colorSuccessBorder", + "colorSuccessBorderHover", + "colorSuccessHover", + "colorSuccessActive", + "colorSuccessTextHover", + "colorSuccessText", + "colorSuccessTextActive", +]; +// 警告色 +export const warningColors = [ + "colorWarning", + "colorWarningBg", + "colorWarningBgHover", + "colorWarningBorder", + "colorWarningBorderHover", + "colorWarningHover", + "colorWarningActive", + "colorWarningTextHover", + "colorWarningText", + "colorWarningTextActive", +]; +// 错误色 +export const errorColors = [ + "colorError", + "colorErrorBg", + "colorErrorBgHover", + "colorErrorBorder", + "colorErrorBorderHover", + "colorErrorHover", + "colorErrorActive", + "colorErrorTextHover", + "colorErrorText", + "colorErrorTextActive", +]; +// 信息色 +export const infoColors = [ + "colorInfo", + "colorInfoBg", + "colorInfoBgHover", + "colorInfoBorder", + "colorInfoBorderHover", + "colorInfoHover", + "colorInfoActive", + "colorInfoTextHover", + "colorInfoText", + "colorInfoTextActive", +]; +// 功能性色 +export const functionalColors = [ + ...successColors, + ...warningColors, + ...errorColors, + ...infoColors, +]; +// 中性色 +export const neutralColors = [ + "colorText", + "colorTextSecondary", + "colorTextTertiary", + "colorTextQuaternary", + // 组件容器背景色 + "colorBgContainer", + "colorBgElevated", + // 布局背景色 + "colorBgLayout", + + "colorBgSpotlight", + "colorBgMask", + // 边框色 + "colorBorder", + "colorBorderSecondary", + // 填充色 + "colorFill", + "colorFillSecondary", + "colorFillTertiary", + "colorFillQuaternary", +]; +export const productLevelColorSystem = [ + ...brandColors, + ...functionalColors, +]; +export const colorPaletteNumbers = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]; +export const colorVariantsCount = 10; + +// ['blue', 'blue-1', 'blue-2', ……, 'blue-10', 'purple',……] +export const colorPalettes = colors.flatMap(color => + [color, ...Array.from({ length: colorVariantsCount }, (_, i) => `${color}-${i + 1}`)], +); diff --git a/src/components/antd-app/index.tsx b/src/components/antd-app/index.tsx index 3ab9a0b..50aa463 100644 --- a/src/components/antd-app/index.tsx +++ b/src/components/antd-app/index.tsx @@ -1,11 +1,25 @@ import type { ReactNode } from "react"; + import { StaticAntd } from "#src/utils"; -import { App } from "antd"; + +import { theme as antdTheme, App } from "antd"; +import { useEffect } from "react"; + +import { setupAntdThemeTokensToHtml } from "./setup-antd-theme"; export interface AntdAppProps { children: ReactNode } + export function AntdApp({ children }: AntdAppProps) { + const { token: antdTokens } = antdTheme.useToken(); + + useEffect(() => { + /* 打印查看支持的 token */ + // console.log("antdTokens", antdTokens); + setupAntdThemeTokensToHtml(antdTokens); + }, [antdTokens]); + return ( diff --git a/src/components/antd-app/setup-antd-theme.ts b/src/components/antd-app/setup-antd-theme.ts new file mode 100644 index 0000000..4fd33c8 --- /dev/null +++ b/src/components/antd-app/setup-antd-theme.ts @@ -0,0 +1,54 @@ +import type { GlobalToken } from "antd"; +import { colorPalettes, neutralColors, prefix, productLevelColorSystem } from "./constants"; + +/** + * 16 进制颜色值转 RGB 颜色值,因为 16 进制的颜色值在 tailwind 中不支持透明度,比如无法使用 bg-blue-500/20 + * @see https://tailwindcss.com/docs/customizing-colors#using-css-variables + */ +function hexToRGB(hex: string) { + // 移除可能存在的 # 号 + hex = hex.replace("#", ""); + + // 获取 R、G、B 的值 + const r = Number.parseInt(hex.substring(0, 2), 16); + const g = Number.parseInt(hex.substring(2, 4), 16); + const b = Number.parseInt(hex.substring(4, 6), 16); + + return `${r} ${g} ${b}`; +} + +// 判断是否是 RGB 颜色值 +function isRGBColor(color: string) { + return color.trim().startsWith("rgb"); +} + +export function getCSSVariablesByTokens(tokens: GlobalToken) { + return Object.entries(tokens) + .reduce((acc, [key, value]): string => { + // 功能色系,不包含中性色系 + if (productLevelColorSystem.includes(key)) { + const rgb = hexToRGB(value); + return `${acc}--${prefix}-${key}:${rgb};`; + } + + // 中性色系 + if (neutralColors.includes(key)) { + // 如果颜色值是 rgb 格式,则直接使用 + const rgb = isRGBColor(value) ? value : `rgb(${hexToRGB(value)})`; + return `${acc}--${prefix}-${key}:${rgb};`; + } + // 色板 + return colorPalettes.includes(key) ? `${acc}--${prefix}-${key}:${hexToRGB(value)};` : acc; + }, ""); +} + +/** Setup antd theme tokens to html */ +export function setupAntdThemeTokensToHtml(antdTokens: GlobalToken) { + const cssVariablesString = getCSSVariablesByTokens(antdTokens); + + const styleId = "antd-theme-tokens"; + const styleSheet = document.querySelector(`#${styleId}`) || document.createElement("style"); + styleSheet.id = styleId; + styleSheet.textContent = `:root { ${cssVariablesString} }`; + document.head.appendChild(styleSheet); +} diff --git a/src/components/fullscreen-button/index.tsx b/src/components/fullscreen-button/index.tsx index 5a28b4e..02c3ff4 100644 --- a/src/components/fullscreen-button/index.tsx +++ b/src/components/fullscreen-button/index.tsx @@ -5,7 +5,7 @@ import { FullscreenExitOutlined, FullscreenOutlined } from "@ant-design/icons"; import { useFullscreen } from "ahooks"; -interface FullscreenButtonProps extends Omit { +export interface FullscreenButtonProps extends Omit { target: HTMLElement | (() => Element) | RefObject fullscreenIcon?: React.ReactNode fullscreenExitIcon?: React.ReactNode diff --git a/src/layout/container-layout/index.tsx b/src/layout/container-layout/index.tsx index e8bfe4e..7e1baaf 100644 --- a/src/layout/container-layout/index.tsx +++ b/src/layout/container-layout/index.tsx @@ -165,7 +165,7 @@ export default function ContainerLayout() { - + ); diff --git a/src/layout/layout-footer/index.tsx b/src/layout/layout-footer/index.tsx index 940f983..6def24c 100644 --- a/src/layout/layout-footer/index.tsx +++ b/src/layout/layout-footer/index.tsx @@ -1,7 +1,15 @@ -export default function LayoutFooter() { +import { cn } from "#src/utils"; + +interface LayoutFooterProps { + className?: string +} +export default function LayoutFooter({ className }: LayoutFooterProps) { return (
Copyright © 2023 Condor Hero All right reserved
diff --git a/src/layout/layout-header/components/fullscreen-button.tsx b/src/layout/layout-header/components/fullscreen-button.tsx index c44349b..0732bf0 100644 --- a/src/layout/layout-header/components/fullscreen-button.tsx +++ b/src/layout/layout-header/components/fullscreen-button.tsx @@ -1,16 +1,11 @@ -import type { MutableRefObject } from "react"; +import type { FullscreenButtonProps } from "#src/components"; import { FullscreenButton as FullscreenButtonComponent, FullscreenExitIcon, FullscreenIcon } from "#src/components"; -interface FullscreenProps { - target: HTMLElement | (() => Element) | MutableRefObject -} - -export function FullscreenButton({ target }: FullscreenProps) { +export function FullscreenButton({ target, ...restProps }: FullscreenButtonProps) { return ( } fullscreenIcon={} diff --git a/src/layout/layout-header/components/language-button.tsx b/src/layout/layout-header/components/language-button.tsx index dd419e7..27b0cf5 100644 --- a/src/layout/layout-header/components/language-button.tsx +++ b/src/layout/layout-header/components/language-button.tsx @@ -1,16 +1,13 @@ import type { LanguageType } from "#src/locales"; -import type { MenuProps } from "antd"; +import type { ButtonProps, MenuProps } from "antd"; + +import { BasicButton } from "#src/components"; import { useLanguage } from "#src/hooks"; -import { cn } from "#src/utils"; import { TranslationOutlined } from "@ant-design/icons"; import { Dropdown } from "antd"; -interface LanguageButtonProps { - className?: string -} - -export function LanguageButton({ className }: LanguageButtonProps) { +export function LanguageButton({ ...restProps }: ButtonProps) { const { language, setLanguage } = useLanguage(); const items: MenuProps["items"] = [ @@ -40,9 +37,12 @@ export function LanguageButton({ className }: LanguageButtonProps) { arrow={false} placement="bottom" > -
+ -
+ ); } diff --git a/src/layout/layout-header/components/theme-button.tsx b/src/layout/layout-header/components/theme-button.tsx index f90acc1..88c0f59 100644 --- a/src/layout/layout-header/components/theme-button.tsx +++ b/src/layout/layout-header/components/theme-button.tsx @@ -1,3 +1,5 @@ +import type { ButtonProps } from "antd"; + import { BasicButton } from "#src/components"; import { usePreferences } from "#src/hooks"; import { MoonIcon, SunIcon } from "#src/icons"; @@ -9,16 +11,16 @@ import { MoonIcon, SunIcon } from "#src/icons"; * Theme Button Component * Allows users to toggle between light and dark themes of the website via a button */ -export function ThemeButton() { +export function ThemeButton({ ...restProps }: ButtonProps) { const { isLight, changeSiteTheme } = usePreferences(); return ( : } - onPointerDown={() => { + onPointerDown={(e) => { + restProps?.onPointerDown?.(e); changeSiteTheme(isLight ? "dark" : "light"); }} /> diff --git a/src/layout/layout-header/components/user-menu.tsx b/src/layout/layout-header/components/user-menu.tsx index c72d8c1..b24a4ab 100644 --- a/src/layout/layout-header/components/user-menu.tsx +++ b/src/layout/layout-header/components/user-menu.tsx @@ -1,8 +1,10 @@ -import type { MenuProps } from "antd"; +import type { ButtonProps, MenuProps } from "antd"; + +import { BasicButton } from "#src/components"; import { UserCircleIcon } from "#src/icons"; import { $t } from "#src/locales"; import { useAuthStore, useUserStore } from "#src/store"; -import { isWindowsOs } from "#src/utils"; +import { cn, isWindowsOs } from "#src/utils"; import { LogoutOutlined } from "@ant-design/icons"; import { useKeyPress } from "ahooks"; @@ -10,7 +12,7 @@ import { Avatar, Dropdown } from "antd"; import { useMemo } from "react"; import { useNavigate } from "react-router-dom"; -export function UserMenu() { +export function UserMenu({ ...restProps }: ButtonProps) { const navigate = useNavigate(); const avatar = useUserStore(state => state.avatar); const logout = useAuthStore(state => state.logout); @@ -56,9 +58,13 @@ export function UserMenu() { placement="bottomRight" trigger={["click"]} > -
+ -
+
); } diff --git a/src/layout/layout-header/index.tsx b/src/layout/layout-header/index.tsx index 752b6ae..2f786ff 100644 --- a/src/layout/layout-header/index.tsx +++ b/src/layout/layout-header/index.tsx @@ -1,3 +1,4 @@ +import type { ButtonProps } from "antd"; import { useDeviceType } from "#src/hooks"; import { LayoutContext } from "#src/layout/container-layout/layout-context"; import { NotificationContainer } from "#src/layout/widgets/notification/notification-container"; @@ -8,47 +9,26 @@ import { cn } from "#src/utils"; import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons"; import { Button, theme } from "antd"; import { useContext } from "react"; -import { createUseStyles } from "react-jss"; import { FullscreenButton } from "./components/fullscreen-button"; import { LanguageButton } from "./components/language-button"; import { ThemeButton } from "./components/theme-button"; import { UserMenu } from "./components/user-menu"; -const useStyles = createUseStyles(({ token }) => { - return { - layoutHeaderRight: { - "display": "flex", - "justifyContent": "center", - "alignItems": "center", - "height": "100%", - "&>*": { - height: "100%", - cursor: "pointer", - padding: ["0", ".7em"], - display: "flex", - justifyContent: "center", - alignItems: "center", - }, - "&>*:hover": { - background: { - color: token.colorBgTextHover, - }, - }, - }, - }; -}); - export interface LayoutHeaderProps { className?: string children?: React.ReactNode } +const buttonProps: ButtonProps = { + size: "large", + className: "px-[11px]", +}; + export default function LayoutHeader({ className, children }: LayoutHeaderProps) { const { token: { colorBgContainer }, } = theme.useToken(); - const classes = useStyles(); const { sidebarCollapsed, setSidebarCollapsed } = useContext(LayoutContext); const { isMobile } = useDeviceType(); const isMaximize = useTabsStore(state => state.isMaximize); @@ -76,17 +56,13 @@ export default function LayoutHeader({ className, children }: LayoutHeaderProps) {children} -
- - - - - - +
+ + + + + +
); diff --git a/src/layout/layout-sidebar/index.tsx b/src/layout/layout-sidebar/index.tsx index 73e3135..5e3bf27 100644 --- a/src/layout/layout-sidebar/index.tsx +++ b/src/layout/layout-sidebar/index.tsx @@ -24,7 +24,7 @@ export default function LayoutSidebar({ children, computedSidebarWidth }: Layout boxShadow: "3px 0 5px 0 rgb(29, 35, 41, 0.05)", } } - className="fixed left-0 top-0 bottom-0 transition-all overflow-y-auto" + className="fixed left-0 top-0 bottom-0 transition-all overflow-y-auto border-r border-r-colorBorderSecondary" > {children} diff --git a/src/layout/layout-tabbar/components/tab-maximize.tsx b/src/layout/layout-tabbar/components/tab-maximize.tsx index 2beac47..eb3f95a 100644 --- a/src/layout/layout-tabbar/components/tab-maximize.tsx +++ b/src/layout/layout-tabbar/components/tab-maximize.tsx @@ -1,15 +1,19 @@ import { BasicButton } from "#src/components"; import { useTabsStore } from "#src/store"; +import { cn } from "#src/utils"; import { FullscreenExitOutlined, FullscreenOutlined } from "@ant-design/icons"; import { useShallow } from "zustand/shallow"; +interface TabMaximizeProps { + className?: string +} /** * 切换标签页最大化 / 最小化 * * @returns 返回标签页最大化 / 最小化的按钮组件 */ -export function TabMaximize() { +export function TabMaximize({ className }: TabMaximizeProps) { /** * useShallow - it may cause infinite loops in zustand v5 * https://github.com/pmndrs/zustand/blob/v5.0.0/docs/migrations/migrating-to-v5.md#requiring-stable-selector-outputs @@ -24,7 +28,7 @@ export function TabMaximize() { return ( : } diff --git a/src/layout/layout-tabbar/components/tab-options.tsx b/src/layout/layout-tabbar/components/tab-options.tsx index a8c2c82..6ed4302 100644 --- a/src/layout/layout-tabbar/components/tab-options.tsx +++ b/src/layout/layout-tabbar/components/tab-options.tsx @@ -1,9 +1,12 @@ import type { MenuProps } from "antd"; + import { BasicButton } from "#src/components"; +import { cn } from "#src/utils"; + import { DownOutlined } from "@ant-design/icons"; import { Dropdown } from "antd"; - import { useState } from "react"; + import { useDropdownMenu } from "../hooks/use-dropdown-menu"; /** @@ -13,6 +16,7 @@ import { useDropdownMenu } from "../hooks/use-dropdown-menu"; */ interface TabOptionsProps { activeKey: string + className?: string } /** @@ -21,7 +25,7 @@ interface TabOptionsProps { * @param {TabOptionsProps} props - 组件属性 * @returns {JSX.Element} TabOptions组件 */ -export function TabOptions({ activeKey }: TabOptionsProps) { +export function TabOptions({ activeKey, className }: TabOptionsProps) { const [isOpen, setIsOpen] = useState(false); const [items, onClickMenu] = useDropdownMenu(); @@ -51,7 +55,7 @@ export function TabOptions({ activeKey }: TabOptionsProps) { onOpenChange={onOpenChange} > } diff --git a/src/layout/layout-tabbar/index.tsx b/src/layout/layout-tabbar/index.tsx index 6a6ac1c..c052fcb 100644 --- a/src/layout/layout-tabbar/index.tsx +++ b/src/layout/layout-tabbar/index.tsx @@ -107,35 +107,21 @@ export default function LayoutTabbar() { */ const tabBarExtraContent = useMemo(() => ({ right: ( -
-

-

), }), [isRefresh, activeKey, onClickMenu, tabbarShowMore, tabbarShowMaximize]); diff --git a/src/layout/layout-tabbar/style.ts b/src/layout/layout-tabbar/style.ts index 313afe2..9faa005 100644 --- a/src/layout/layout-tabbar/style.ts +++ b/src/layout/layout-tabbar/style.ts @@ -3,9 +3,9 @@ import { createUseStyles } from "react-jss"; export const useStyles = createUseStyles(({ token }) => { return { tabsContainer: { - backgroundColor: token.colorBgBase, - borderTop: "1px solid #e8e8e8", - borderBottom: "1px solid #e8e8e8", + backgroundColor: token.colorBgContainer, + borderTop: `1px solid ${token.colorBorderSecondary}`, + borderBottom: `1px solid ${token.colorBorderSecondary}`, }, resetTabs: { "& .ant-tabs-nav::before": { @@ -25,7 +25,7 @@ export const useStyles = createUseStyles(({ token }) => { brisk: { "& .ant-tabs-nav": { "& .ant-tabs-tab": { - borderRight: "1px solid #e8e8e8 !important", + borderRight: `1px solid ${token.colorBorder} !important`, // antd 自带的动画和 DND 动画冲突 transition: "inherit", }, @@ -38,7 +38,7 @@ export const useStyles = createUseStyles(({ token }) => { plain: { "& .ant-tabs-nav": { "& .ant-tabs-tab": { - borderRight: "1px solid #e8e8e8 !important", + borderRight: `1px solid ${token.colorBorder} !important`, }, }, }, @@ -48,7 +48,7 @@ export const useStyles = createUseStyles(({ token }) => { gap: "5px", }, "& .ant-tabs-tab:not(.ant-tabs-tab-active)": { - "backgroundColor": token.colorBgBase, + "backgroundColor": token.colorBgContainer, "position": "relative", "borderRadius": "7px !important", "padding": "0px 12px !important", @@ -62,7 +62,7 @@ export const useStyles = createUseStyles(({ token }) => { content: "' '", height: "100%", width: "1px", - backgroundColor: token.colorBgBase, + backgroundColor: token.colorBgContainer, left: "-3px", position: "absolute", }, @@ -102,7 +102,7 @@ export const useStyles = createUseStyles(({ token }) => { content: "' '", height: "100%", width: "1px", - backgroundColor: token.colorBgBase, + backgroundColor: token.colorBgContainer, left: "-3px", position: "absolute", }, @@ -126,8 +126,8 @@ export const useStyles = createUseStyles(({ token }) => { }, "& .ant-tabs-nav": { "& .ant-tabs-tab": { - "backgroundColor": token.colorBgBase, - "border": "1px solid #e8e8e8 !important", + "backgroundColor": token.colorBgContainer, + "border": `1px solid ${token.colorBorder} !important`, "position": "relative", "borderRadius": "7px !important", "padding": "0px 12px !important", diff --git a/src/layout/widgets/notification/index.tsx b/src/layout/widgets/notification/index.tsx index fee5c24..95aa75a 100644 --- a/src/layout/widgets/notification/index.tsx +++ b/src/layout/widgets/notification/index.tsx @@ -1,7 +1,9 @@ +import type { ButtonProps } from "antd"; import type { NotificationItem } from "./types"; import { BasicButton } from "#src/components"; import { MailCheckIcon } from "#src/icons"; +import { cn } from "#src/utils"; import { BellOutlined } from "@ant-design/icons"; import { useToggle } from "ahooks"; @@ -30,7 +32,7 @@ const useStyles = createUseStyles(({ token }) => ( type NotificationEventType = "viewAll" | "makeAll" | "clear" | "read"; -interface Props { +interface Props extends ButtonProps { /** * 显示圆点 */ @@ -45,7 +47,7 @@ interface Props { notifications?: NotificationItem[] } -export const NotificationPopup: React.FC = ({ dot, notifications, onEventChange }) => { +export const NotificationPopup: React.FC = ({ dot, notifications, onEventChange, ...restProps }) => { const [open, action] = useToggle(); const classes = useStyles(); const { t } = useTranslation(); @@ -136,11 +138,12 @@ export const NotificationPopup: React.FC = ({ dot, notifications, onEvent } > - {dot && } + {dot && } diff --git a/src/layout/widgets/notification/notification-container.tsx b/src/layout/widgets/notification/notification-container.tsx index f9c07b2..af9d96f 100644 --- a/src/layout/widgets/notification/notification-container.tsx +++ b/src/layout/widgets/notification/notification-container.tsx @@ -1,3 +1,4 @@ +import type { ButtonProps } from "antd"; import type { NotificationItem } from "./types"; import { fetchNotifications } from "#src/api/notifications"; @@ -5,7 +6,7 @@ import { fetchNotifications } from "#src/api/notifications"; import { useEffect, useState } from "react"; import { NotificationPopup } from "./index"; -export function NotificationContainer() { +export function NotificationContainer({ ...restProps }: ButtonProps) { const [notifications, setNotifications] = useState([]); useEffect(() => { @@ -17,6 +18,9 @@ export function NotificationContainer() { }, []); return ( - + ); } diff --git a/src/layout/widgets/preferences/index.tsx b/src/layout/widgets/preferences/index.tsx index 4a5ce67..da229af 100644 --- a/src/layout/widgets/preferences/index.tsx +++ b/src/layout/widgets/preferences/index.tsx @@ -1,3 +1,5 @@ +import type { ButtonProps } from "antd"; + import { BasicButton } from "#src/components"; import { useDeviceType, usePreferences } from "#src/hooks"; import { useAuthStore, usePreferencesStore } from "#src/store"; @@ -10,7 +12,7 @@ import { useNavigate } from "react-router-dom"; import { Animation, PreferencesLayout, SiteTheme, Tabbar } from "./blocks"; -export function Preferences() { +export function Preferences({ ...restProps }: ButtonProps) { const { t } = useTranslation(); const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); @@ -36,15 +38,16 @@ export function Preferences() { return ( <> -
setIsOpen(true)} - onKeyDown={() => { }} - className="text-lg" + { + restProps?.onClick?.(e); + setIsOpen(true); + }} > -
+
: } onClick={() => setSidebarCollapsed(!sidebarCollapsed)} - className={cn(className, "absolute bottom-0 h-10 !w-full rounded-none border border-t-gray-200")} + className={cn(className, "absolute bottom-0 h-10 !w-full rounded-none border-t border-t-colorBorderSecondary")} /> ); } diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index ea80499..5ada5aa 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -55,8 +55,8 @@ export default function Login() { return (
- - + +
diff --git a/src/styles/css-variables.ts b/src/styles/css-variables.ts new file mode 100644 index 0000000..3e7bf7f --- /dev/null +++ b/src/styles/css-variables.ts @@ -0,0 +1,42 @@ +import { colorPaletteNumbers, colors, neutralColors, productLevelColorSystem } from "#src/components/antd-app/constants"; + +/** + * @zh 使用 antd 的颜色变量覆盖 tailwind.css 中的颜色变量。 + * @en Override the color variables in tailwind.css with antd's color variables. + */ +function createColorPaletteVars() { + const colorPaletteVar: any = {}; + + /** + * @zh 基础色板 + * @en Base color palette + */ + colors.forEach((color) => { + colorPaletteNumbers.forEach((number, index) => { + const colorCount = index === 0 ? "" : `-${index}`; + colorPaletteVar[`${color}-${number}`] = `rgb(var(--oo-${color}${colorCount}))`; + }); + }); + + /** + * @zh 产品级色板 + * @en Product level color palette + */ + productLevelColorSystem.forEach((key) => { + const keyName = key.replace("color", ""); + const camelCaseName = keyName.charAt(0).toLowerCase() + keyName.slice(1); + colorPaletteVar[camelCaseName] = `rgb(var(--oo-${key}))`; + }); + + /** + * @zh 中性色 + * @en Neutrals + */ + neutralColors.forEach((key) => { + // 让这个 key 直接是带 rgb 函数的颜色值 + colorPaletteVar[key] = `var(--oo-${key})`; + }); + return colorPaletteVar; +} + +export const colorPaletteVars = createColorPaletteVars(); diff --git a/tailwind.config.ts b/tailwind.config.ts index a9d082e..8707127 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,5 +1,7 @@ import type { Config } from "tailwindcss"; +import { colorPaletteVars } from "./src/styles/css-variables"; + export default { darkMode: "class", corePlugins: { @@ -7,6 +9,32 @@ export default { }, content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { + /** + * @see https://tailwindcss.com/docs/customizing-colors#using-css-variables + */ + colors: { + transparent: "transparent", + inherit: "inherit", + current: "currentColor", + /** + * 使用 antd 的颜色覆盖 tailwind 的颜色 + * 注意:在亮色模式和黑模式下新的颜色是自适应的,不需要手动配置(EX:dark:bg-cyan-100) + */ + ...colorPaletteVars, + }, + /** + * Use ant design breakpoints + * @see https://tailwindcss.com/docs/breakpoints + * @see https://ant.design/components/layout-cn#breakpoint-width + */ + screens: { + "xs": "480px", + "sm": "576px", + "md": "768px", + "lg": "992px", + "xl": "1200px", + "2xl": "1600px", + }, extend: { keyframes: { wiggle: {