162 lines
7.1 KiB
TypeScript
162 lines
7.1 KiB
TypeScript
/* 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<ThemeConfig>({});
|
|
const [isCompact, setIsCompact] = useState<boolean>(false);
|
|
const [schema, setSchema] = useState<E.COLOR_SCHEMA>(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<ThemeConfig>['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<Record<`screen${keyof typeof E.SCREEN_BREAK_POINTS}`, number>> = {};
|
|
|
|
ObjectEntries(E.SCREEN_BREAK_POINTS).forEach(([k, v]) => {
|
|
screenBreakPoints[`screen${k}`] = v;
|
|
});
|
|
|
|
return screenBreakPoints;
|
|
};
|
|
|
|
return { theme, schema, isCompact };
|
|
}
|