mentalHealth/01-Web/packages/hooks/common/useTheme.ts

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 };
}