mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 02:49:18 +08:00
233 lines
6.4 KiB
TypeScript
233 lines
6.4 KiB
TypeScript
import React, { useImperativeHandle, useMemo } from 'react';
|
|
import type { ReactNode } from 'react';
|
|
import { useControlledState } from '@rc-component/util';
|
|
import pickAttrs from '@rc-component/util/lib/pickAttrs';
|
|
import { clsx } from 'clsx';
|
|
|
|
import type { SemanticClassNamesType, SemanticStylesType } from '../_util/hooks';
|
|
import { useMergeSemantic } from '../_util/hooks';
|
|
import { useComponentConfig } from '../config-provider/context';
|
|
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
|
|
import CheckableTag from './CheckableTag';
|
|
import useStyle from './style';
|
|
|
|
export type CheckableTagOption<CheckableTagValue> = {
|
|
value: CheckableTagValue;
|
|
label: ReactNode;
|
|
};
|
|
|
|
interface CheckableTagGroupSingleProps<CheckableTagValue> {
|
|
multiple?: false;
|
|
value?: CheckableTagValue | null;
|
|
defaultValue?: CheckableTagValue | null;
|
|
onChange?: (value: CheckableTagValue | null) => void;
|
|
}
|
|
|
|
interface CheckableTagGroupMultipleProps<CheckableTagValue> {
|
|
multiple: true;
|
|
value?: CheckableTagValue[];
|
|
defaultValue?: CheckableTagValue[];
|
|
onChange?: (value: CheckableTagValue[]) => void;
|
|
}
|
|
|
|
export type SemanticName = keyof TagGroupSemanticClassNames & keyof TagGroupSemanticStyles;
|
|
|
|
export type TagGroupSemanticClassNames = {
|
|
root?: string;
|
|
item?: string;
|
|
};
|
|
|
|
export type TagGroupSemanticStyles = {
|
|
root?: React.CSSProperties;
|
|
item?: React.CSSProperties;
|
|
};
|
|
|
|
type CheckableTagGroupBaseProps<CheckableTagValue> = {
|
|
// style
|
|
prefixCls?: string;
|
|
rootClassName?: string;
|
|
options?: (CheckableTagOption<CheckableTagValue> | CheckableTagValue)[];
|
|
disabled?: boolean;
|
|
} & (
|
|
| CheckableTagGroupSingleProps<CheckableTagValue>
|
|
| CheckableTagGroupMultipleProps<CheckableTagValue>
|
|
) &
|
|
Pick<React.HTMLAttributes<HTMLDivElement>, 'className' | 'style' | 'id' | 'role'> & {
|
|
[key: `data-${string}`]: any;
|
|
[key: `aria-${string}`]: any;
|
|
};
|
|
|
|
export type CheckableTagGroupClassNamesType = SemanticClassNamesType<
|
|
CheckableTagGroupBaseProps<any>,
|
|
TagGroupSemanticClassNames
|
|
>;
|
|
|
|
export type CheckableTagGroupStylesType = SemanticStylesType<
|
|
CheckableTagGroupBaseProps<any>,
|
|
TagGroupSemanticStyles
|
|
>;
|
|
|
|
export type CheckableTagGroupProps<CheckableTagValue> =
|
|
CheckableTagGroupBaseProps<CheckableTagValue> & {
|
|
classNames?: CheckableTagGroupClassNamesType;
|
|
styles?: CheckableTagGroupStylesType;
|
|
};
|
|
|
|
export interface CheckableTagGroupRef {
|
|
nativeElement: HTMLDivElement;
|
|
}
|
|
|
|
function CheckableTagGroup<CheckableTagValue extends string | number>(
|
|
props: CheckableTagGroupProps<CheckableTagValue>,
|
|
ref: React.Ref<CheckableTagGroupRef>,
|
|
) {
|
|
const {
|
|
id,
|
|
|
|
prefixCls: customizePrefixCls,
|
|
rootClassName,
|
|
className,
|
|
style,
|
|
classNames,
|
|
styles,
|
|
|
|
disabled,
|
|
options,
|
|
value,
|
|
defaultValue,
|
|
onChange,
|
|
multiple,
|
|
|
|
...restProps
|
|
} = props;
|
|
|
|
const {
|
|
getPrefixCls,
|
|
direction,
|
|
className: contextClassName,
|
|
style: contextStyle,
|
|
classNames: contextClassNames,
|
|
styles: contextStyles,
|
|
} = useComponentConfig('tag');
|
|
|
|
const prefixCls = getPrefixCls('tag', customizePrefixCls);
|
|
const groupPrefixCls = `${prefixCls}-checkable-group`;
|
|
|
|
const rootCls = useCSSVarCls(prefixCls);
|
|
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
|
|
|
// ====================== Styles ======================
|
|
const [mergedClassNames, mergedStyles] = useMergeSemantic<
|
|
CheckableTagGroupClassNamesType,
|
|
CheckableTagGroupStylesType,
|
|
typeof props
|
|
>([contextClassNames, classNames], [contextStyles, styles], {
|
|
props,
|
|
});
|
|
|
|
// =============================== Option ===============================
|
|
const parsedOptions = useMemo(
|
|
() =>
|
|
(options || []).map((option) => {
|
|
if (option && typeof option === 'object') {
|
|
return option;
|
|
}
|
|
return {
|
|
value: option,
|
|
label: option,
|
|
};
|
|
}),
|
|
[options],
|
|
);
|
|
|
|
// =============================== Values ===============================
|
|
const [mergedValue, setMergedValue] = useControlledState(defaultValue, value);
|
|
|
|
const handleChange = (checked: boolean, option: CheckableTagOption<CheckableTagValue>) => {
|
|
let newValue: CheckableTagValue | CheckableTagValue[] | null = null;
|
|
|
|
if (multiple) {
|
|
const valueList = (mergedValue || []) as CheckableTagValue[];
|
|
newValue = checked
|
|
? [...valueList, option.value]
|
|
: valueList.filter((item) => item !== option.value);
|
|
} else {
|
|
newValue = checked ? option.value : null;
|
|
}
|
|
|
|
setMergedValue(newValue);
|
|
onChange?.(newValue as any); // TS not support generic type in function call
|
|
};
|
|
|
|
// ================================ Refs ================================
|
|
const divRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
nativeElement: divRef.current!,
|
|
}));
|
|
|
|
// ================================ ARIA ================================
|
|
const ariaProps = pickAttrs(restProps, {
|
|
aria: true,
|
|
data: true,
|
|
});
|
|
|
|
// =============================== Render ===============================
|
|
return (
|
|
<div
|
|
{...ariaProps}
|
|
className={clsx(
|
|
groupPrefixCls,
|
|
contextClassName,
|
|
rootClassName,
|
|
{
|
|
[`${groupPrefixCls}-disabled`]: disabled,
|
|
[`${groupPrefixCls}-rtl`]: direction === 'rtl',
|
|
},
|
|
hashId,
|
|
cssVarCls,
|
|
className,
|
|
mergedClassNames.root,
|
|
)}
|
|
style={{
|
|
...contextStyle,
|
|
...mergedStyles.root,
|
|
...style,
|
|
}}
|
|
id={id}
|
|
ref={divRef}
|
|
>
|
|
{parsedOptions.map((option) => (
|
|
<CheckableTag
|
|
key={option.value}
|
|
className={clsx(`${groupPrefixCls}-item`, mergedClassNames.item)}
|
|
style={mergedStyles.item}
|
|
checked={
|
|
multiple
|
|
? ((mergedValue as CheckableTagValue[]) || []).includes(option.value)
|
|
: mergedValue === option.value
|
|
}
|
|
onChange={(checked) => handleChange(checked, option)}
|
|
disabled={disabled}
|
|
>
|
|
{option.label}
|
|
</CheckableTag>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ForwardCheckableTagGroup = React.forwardRef(CheckableTagGroup) as (<
|
|
CheckableTagValue extends string | number,
|
|
>(
|
|
props: CheckableTagGroupProps<CheckableTagValue> & { ref?: React.Ref<CheckableTagGroupRef> },
|
|
) => React.ReactElement) & {
|
|
displayName?: string;
|
|
};
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
ForwardCheckableTagGroup.displayName = 'CheckableTagGroup';
|
|
}
|
|
|
|
export default ForwardCheckableTagGroup;
|