/* eslint-disable react-hooks/exhaustive-deps */ import { useCallback, useEffect, useRef, useState } from 'react'; import { useResponsive } from 'ahooks'; import type { ThemeConfig } from 'antd'; import { theme as antdTheme } from 'antd'; import type { MapToken } from 'antd/es/theme/interface'; import * as E from '@handpear/enums'; import { ObjectEntries } from '@handpear/utils'; export function useTheme(props: [E.COLOR_SCHEMA, RGB_HEX, boolean]) { const [colorSchema, colorPrimary, isFollowSystem] = props; const responsive = useResponsive(); const media = useRef(globalThis.matchMedia('(prefers-color-scheme: dark)')); const [theme, setTheme] = useState({}); const [isCompact, setIsCompact] = useState(false); const [schema, setSchema] = useState(E.COLOR_SCHEMA.亮色模式); useEffect(() => { media.current.addEventListener('change', onMediaMatchesChange); }, []); useEffect(() => { onMediaMatchesChange(); }, [colorSchema, colorPrimary, isFollowSystem, responsive]); const onMediaMatchesChange = () => { const schema = resolveColorSchema(); const { algorithm, isCompact } = resolveAlgorithm(schema); const { token } = resolveToken(schema); setTheme({ token, algorithm }); setIsCompact(isCompact); setSchema(schema); }; const resolveColorSchema = useCallback(() => { const classList = document.documentElement.classList; const isDark = classList.contains('dark'); const isLight = classList.contains('light'); const prefersColor = media.current.matches ? E.COLOR_SCHEMA.暗色模式 : E.COLOR_SCHEMA.亮色模式; const result = isFollowSystem ? prefersColor : colorSchema; if (result === E.COLOR_SCHEMA.暗色模式) { isLight && classList.remove('light'); !isDark && classList.add('dark'); } else { isDark && classList.remove('dark'); !isLight && classList.add('light'); } return result; }, [colorSchema, isFollowSystem]); const resolveAlgorithm = useCallback( (schema: E.COLOR_SCHEMA) => { const algorithmList: NonNullable['algorithm'] = []; const isCompact = !responsive['xl']; const isDark = schema === E.COLOR_SCHEMA.暗色模式; algorithmList.push(isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm); isCompact && algorithmList.push(antdTheme.compactAlgorithm); return { isCompact, algorithm: algorithmList }; }, [responsive], ); const resolveToken = useCallback( (schema: E.COLOR_SCHEMA) => { const isLight = schema === E.COLOR_SCHEMA.亮色模式; const screenBreakPoints = resolveScreenBreakPoints(); const lightMapToken = antdTheme.defaultAlgorithm({ ...antdTheme.defaultSeed, colorPrimary }); const darkMapToken = antdTheme.darkAlgorithm({ ...antdTheme.defaultSeed, colorPrimary }); const getStyleMaps = (lightMapToken: MapToken, darkMapToken: MapToken) => { const mapToken = isLight ? lightMapToken : darkMapToken; const invertMapToken = isLight ? darkMapToken : lightMapToken; const styleMaps = [ [`--color-text`, mapToken.colorText], [`--color-text-invert`, invertMapToken.colorText], [`--color-text-base`, mapToken.colorTextBase], [`--color-text-base-invert`, invertMapToken.colorTextBase], [`--color-text-disabled`, isLight ? 'rgba(0, 0, 0, 0.25)' : 'rgba(255, 255, 255, 0.25)'], [`--color-text-disabled-invert`, isLight ? 'rgba(255, 255, 255, 0.25)' : 'rgba(0, 0, 0, 0.25)'], [`--color-bg-base`, mapToken.colorBgBase], [`--color-bg-base-invert`, invertMapToken.colorBgBase], [`--color-bg-container`, mapToken.colorBgContainer], [`--color-bg-container-invert`, invertMapToken.colorBgContainer], [`--color-bg-elevated`, mapToken.colorBgElevated], [`--color-bg-elevated-invert`, invertMapToken.colorBgElevated], [`--color-bg-layout`, mapToken.colorBgLayout], [`--color-bg-layout-invert`, invertMapToken.colorBgLayout], [`--color-bg-mask`, mapToken.colorBgMask], [`--color-bg-mask-invert`, invertMapToken.colorBgMask], [`--color-bg-spotlight`, mapToken.colorBgSpotlight], [`--color-bg-spotlight-invert`, invertMapToken.colorBgSpotlight], [`--color-border`, mapToken.colorBorder], [`--color-border-invert`, invertMapToken.colorBorder], [`--color-border-secondary`, mapToken.colorBorderSecondary], [`--color-border-secondary-invert`, invertMapToken.colorBorderSecondary], [`--color-border-disabled`, isLight ? 'rgba(0, 0, 0, 0.25)' : 'rgba(255, 255, 255, 0.25)'], [`--color-border-disabled-invert`, isLight ? 'rgba(255, 255, 255, 0.25)' : 'rgba(0, 0, 0, 0.25)'], [`--color-primary`, mapToken.colorPrimary], [`--color-primary-hover`, mapToken.colorPrimaryHover], [`--color-primary-active`, mapToken.colorPrimaryActive], [`--color-primary-bg`, mapToken.colorPrimaryBg], [`--color-primary-bg-hover`, mapToken.colorPrimaryBgHover], [`--color-info`, mapToken.colorInfo], [`--color-info-hover`, mapToken.colorInfoHover], [`--color-info-active`, mapToken.colorInfoActive], [`--color-info-bg`, mapToken.colorInfoBg], [`--color-info-bg-hover`, mapToken.colorInfoBgHover], [`--color-success`, mapToken.colorSuccess], [`--color-success-hover`, mapToken.colorSuccessHover], [`--color-success-active`, mapToken.colorSuccessActive], [`--color-success-bg`, mapToken.colorSuccessBg], [`--color-success-bg-hover`, mapToken.colorSuccessBgHover], [`--color-warning`, mapToken.colorWarning], [`--color-warning-hover`, mapToken.colorWarningHover], [`--color-warning-active`, mapToken.colorWarningActive], [`--color-warning-bg`, mapToken.colorWarningBg], [`--color-warning-bg-hover`, mapToken.colorWarningBgHover], [`--color-error`, mapToken.colorError], [`--color-error-hover`, mapToken.colorErrorHover], [`--color-error-active`, mapToken.colorErrorActive], [`--color-error-bg`, mapToken.colorErrorBg], [`--color-error-bg-hover`, mapToken.colorErrorBgHover], ] as const; return styleMaps; }; [...getStyleMaps(lightMapToken, darkMapToken)].forEach(([key, value]) => { document.documentElement.style.setProperty(key, value); }); const token = { ...screenBreakPoints, colorPrimary, colorLink: colorPrimary, colorLinkActive: (isLight ? lightMapToken : darkMapToken).colorPrimaryActive, colorLinkHover: (isLight ? lightMapToken : darkMapToken).colorPrimaryHover, }; return { token }; }, [colorPrimary], ); const resolveScreenBreakPoints = () => { const screenBreakPoints: Partial> = {}; ObjectEntries(E.SCREEN_BREAK_POINTS).forEach(([k, v]) => { screenBreakPoints[`screen${k}`] = v; }); return screenBreakPoints; }; return { theme, schema, isCompact }; }