ant-design/components/_util/hooks/useMergeSemantic/index.ts
二货爱吃白萝卜 0a50c71d59
refactor: Align the structure of semantic interface (#53453)
* chore: looper

* refactor: merge logic

* chore: comment

* chore: image semantic

* test: fix test case

* chore: fix lint

* chore: fix lint

* chore: show the strcture

* chore: desc update
2025-04-09 17:58:16 +08:00

84 lines
2.8 KiB
TypeScript

import * as React from 'react';
import classnames from 'classnames';
import { ValidChar } from './interface';
type TemplateSemanticClassNames<T extends string> = Partial<Record<T, string>>;
export type SemanticSchema = {
_default?: string;
} & {
[key: `${ValidChar}${string}`]: SemanticSchema;
};
// ========================= ClassNames =========================
export function mergeClassNames<
T extends string,
SemanticClassNames extends Partial<Record<T, any>> = TemplateSemanticClassNames<T>,
>(schema: SemanticSchema | undefined, ...classNames: (SemanticClassNames | undefined)[]) {
const mergedSchema = schema || {};
return classNames.reduce((acc: any, cur) => {
// Loop keys of the current classNames
Object.keys(cur || {}).forEach((key) => {
const keySchema = mergedSchema[key as keyof SemanticSchema] as SemanticSchema;
const curVal = (cur as SemanticClassNames)[key as keyof SemanticClassNames];
if (keySchema && typeof keySchema === 'object') {
if (curVal && typeof curVal === 'object') {
// Loop fill
acc[key] = mergeClassNames(keySchema, acc[key], curVal);
} else {
// Covert string to object structure
const { _default: defaultField } = keySchema;
acc[key] = acc[key] || {};
acc[key][defaultField!] = classnames(acc[key][defaultField!], curVal);
}
} else {
// Flatten fill
acc[key] = classnames(acc[key], curVal);
}
});
return acc;
}, {} as SemanticClassNames) as SemanticClassNames;
}
function useSemanticClassNames<ClassNamesType extends object>(
schema: SemanticSchema | undefined,
...classNames: (Partial<ClassNamesType> | undefined)[]
): Partial<ClassNamesType> {
return React.useMemo(
() => mergeClassNames(schema, ...classNames),
[classNames],
) as ClassNamesType;
}
// =========================== Styles ===========================
function useSemanticStyles<StylesType extends object>(
...styles: (Partial<StylesType> | undefined)[]
) {
return React.useMemo(() => {
return styles.reduce(
(acc, cur = {}) => {
Object.keys(cur).forEach((key) => {
acc[key] = { ...acc[key], ...(cur as Record<string, React.CSSProperties>)[key] };
});
return acc;
},
{} as Record<string, React.CSSProperties>,
);
}, [styles]) as StylesType;
}
// =========================== Export ===========================
export default function useMergeSemantic<ClassNamesType extends object, StylesType extends object>(
classNamesList: (ClassNamesType | undefined)[],
stylesList: (StylesType | undefined)[],
schema?: SemanticSchema,
) {
const mergedClassNames = useSemanticClassNames(schema, ...classNamesList) as ClassNamesType;
const mergedStyles = useSemanticStyles(...stylesList) as StylesType;
return [mergedClassNames, mergedStyles] as const;
}