import type { ReactNode } from 'react'; import React from 'react'; import CloseOutlined from '@ant-design/icons/CloseOutlined'; import type { DialogProps } from '@rc-component/dialog'; import pickAttrs from '@rc-component/util/lib/pickAttrs'; import { useLocale } from '../../locale'; import defaultLocale from '../../locale/en_US'; import type { HTMLAriaDataAttributes } from '../aria-data-attrs'; import extendsObject from '../extendsObject'; export type ClosableType = DialogProps['closable']; export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode }; export type ContextClosable = Partial< Pick >; export function pickClosable( context?: ContextClosable, ): ContextClosable | undefined { if (!context) { return undefined; } return { closable: context.closable, closeIcon: context.closeIcon, }; } /** Collection contains the all the props related with closable. e.g. `closable`, `closeIcon` */ interface ClosableCollection { closable?: ClosableType; closeIcon?: ReactNode; disabled?: boolean; } interface FallbackCloseCollection extends ClosableCollection { /** * Some components need to wrap CloseIcon twice, * this method will be executed once after the final CloseIcon is calculated */ closeIconRender?: (closeIcon: ReactNode) => ReactNode; } const EmptyFallbackCloseCollection: ClosableCollection = {}; type DataAttributes = { [key: `data-${string}`]: string; }; function computeClosableConfig( closable?: ClosableType, closeIcon?: ReactNode, ): ClosableType | boolean | null { if (!closable && (closable === false || closeIcon === false || closeIcon === null)) { return false; } if (closable === undefined && closeIcon === undefined) { return null; } let closableConfig: ClosableType = { closeIcon: typeof closeIcon !== 'boolean' && closeIcon !== null ? closeIcon : undefined, }; if (closable && typeof closable === 'object') { closableConfig = { ...closableConfig, ...closable, }; } return closableConfig; } function mergeClosableConfigs( propConfig: ReturnType, contextConfig: ReturnType, fallbackConfig: ClosableCollection & { closeIconRender?: (icon: ReactNode) => ReactNode }, ) { if (propConfig === false) return false; if (propConfig) return extendsObject(fallbackConfig, contextConfig, propConfig); if (contextConfig === false) return false; if (contextConfig) return extendsObject(fallbackConfig, contextConfig); return fallbackConfig.closable ? fallbackConfig : false; } function computeCloseIcon( mergedConfig: ClosableCollection, fallbackCloseCollection: FallbackCloseCollection, closeLabel: string, ): [ReactNode, React.AriaAttributes & DataAttributes] { const { closeIconRender } = fallbackCloseCollection; const { closeIcon, ...restConfig } = mergedConfig; let finalCloseIcon = closeIcon; const ariaOrDataProps = pickAttrs(restConfig, true); if (finalCloseIcon !== null && finalCloseIcon !== undefined) { if (closeIconRender) { finalCloseIcon = closeIconRender(finalCloseIcon); } finalCloseIcon = React.isValidElement(finalCloseIcon) ? ( React.cloneElement(finalCloseIcon, { 'aria-label': closeLabel, ...ariaOrDataProps, } as HTMLAriaDataAttributes) ) : ( {finalCloseIcon} ); } return [finalCloseIcon, ariaOrDataProps]; } export function computeClosable( propCloseCollection?: ClosableCollection, contextCloseCollection?: ClosableCollection | null, fallbackCloseCollection: FallbackCloseCollection = EmptyFallbackCloseCollection, closeLabel = 'Close', ): [ closable: boolean, closeIcon: React.ReactNode, closeBtnIsDisabled: boolean, ariaOrDataProps: React.AriaAttributes & DataAttributes, ] { const propConfig = computeClosableConfig( propCloseCollection?.closable, propCloseCollection?.closeIcon, ); const contextConfig = computeClosableConfig( contextCloseCollection?.closable, contextCloseCollection?.closeIcon, ); const mergedFallback = { closeIcon: , ...fallbackCloseCollection, }; const mergedConfig = mergeClosableConfigs(propConfig, contextConfig, mergedFallback); const closeBtnIsDisabled = typeof mergedConfig !== 'boolean' ? !!mergedConfig?.disabled : false; if (mergedConfig === false) { return [false, null, closeBtnIsDisabled, {}]; } const [closeIcon, ariaProps] = computeCloseIcon(mergedConfig, mergedFallback, closeLabel); return [true, closeIcon, closeBtnIsDisabled, ariaProps]; } export default function useClosable( propCloseCollection?: ClosableCollection, contextCloseCollection?: ClosableCollection | null, fallbackCloseCollection: FallbackCloseCollection = EmptyFallbackCloseCollection, ) { const [contextLocale] = useLocale('global', defaultLocale.global); return React.useMemo(() => { return computeClosable( propCloseCollection, contextCloseCollection, { closeIcon: , ...fallbackCloseCollection, }, contextLocale.close, ); }, [propCloseCollection, contextCloseCollection, fallbackCloseCollection, contextLocale.close]); }