committed by
GitHub
26 changed files with 27 additions and 794 deletions
@ -1,29 +0,0 @@ |
|||
import { Avatar } from 'antd'; |
|||
import dayjs from 'dayjs'; |
|||
import React from 'react'; |
|||
import useStyles from './index.style'; |
|||
export type ApplicationsProps = { |
|||
data: { |
|||
content?: string; |
|||
updatedAt?: any; |
|||
avatar?: string; |
|||
owner?: string; |
|||
href?: string; |
|||
}; |
|||
}; |
|||
const ArticleListContent: React.FC<ApplicationsProps> = ({ |
|||
data: { content, updatedAt, avatar, owner, href }, |
|||
}) => { |
|||
const { styles } = useStyles(); |
|||
return ( |
|||
<div> |
|||
<div className={styles.description}>{content}</div> |
|||
<div className={styles.extra}> |
|||
<Avatar src={avatar} size="small" /> |
|||
<a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a> |
|||
<em>{dayjs(updatedAt).format('YYYY-MM-DD HH:mm')}</em> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default ArticleListContent; |
|||
@ -1,60 +0,0 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
standardFormRow: { |
|||
display: 'flex', |
|||
marginBottom: '16px', |
|||
paddingBottom: '16px', |
|||
borderBottom: `1px dashed ${token.colorSplit}`, |
|||
'.ant-form-item, .ant-legacy-form-item': { marginRight: '24px' }, |
|||
'.ant-form-item-label, .ant-legacy-form-item-label': { |
|||
label: { |
|||
marginRight: '0', |
|||
color: token.colorText, |
|||
}, |
|||
}, |
|||
'.ant-form-item-label, .ant-legacy-form-item-label, .ant-form-item-control, .ant-legacy-form-item-control': |
|||
{ padding: '0', lineHeight: '32px' }, |
|||
}, |
|||
label: { |
|||
flex: '0 0 auto', |
|||
marginRight: '24px', |
|||
color: token.colorTextHeading, |
|||
fontSize: token.fontSize, |
|||
textAlign: 'right', |
|||
'& > span': { |
|||
display: 'inline-block', |
|||
height: '32px', |
|||
lineHeight: '32px', |
|||
'&::after': { |
|||
content: "':'", |
|||
}, |
|||
}, |
|||
}, |
|||
content: { |
|||
flex: '1 1 0', |
|||
'.ant-form-item, .ant-legacy-form-item': { |
|||
'&:last-child': { |
|||
marginRight: '0', |
|||
}, |
|||
}, |
|||
}, |
|||
standardFormRowLast: { |
|||
marginBottom: '0', |
|||
paddingBottom: '0', |
|||
border: 'none', |
|||
}, |
|||
standardFormRowBlock: { |
|||
'.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': |
|||
{ display: 'block' }, |
|||
}, |
|||
standardFormRowGrid: { |
|||
'.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': |
|||
{ display: 'block' }, |
|||
'.ant-form-item-label, .ant-legacy-form-item-label': { float: 'left' }, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -1,38 +0,0 @@ |
|||
import { clsx } from 'clsx'; |
|||
import React from 'react'; |
|||
import useStyles from './index.style'; |
|||
|
|||
type StandardFormRowProps = { |
|||
title?: string; |
|||
last?: boolean; |
|||
block?: boolean; |
|||
grid?: boolean; |
|||
style?: React.CSSProperties; |
|||
children?: React.ReactNode; |
|||
}; |
|||
const StandardFormRow: React.FC<StandardFormRowProps> = ({ |
|||
title, |
|||
children, |
|||
last, |
|||
block, |
|||
grid, |
|||
...rest |
|||
}) => { |
|||
const { styles } = useStyles(); |
|||
const cls = clsx(styles.standardFormRow, { |
|||
[styles.standardFormRowBlock]: block, |
|||
[styles.standardFormRowLast]: last, |
|||
[styles.standardFormRowGrid]: grid, |
|||
}); |
|||
return ( |
|||
<div className={cls} {...rest}> |
|||
{title && ( |
|||
<div className={styles.label}> |
|||
<span>{title}</span> |
|||
</div> |
|||
)} |
|||
<div className={styles.content}>{children}</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default StandardFormRow; |
|||
@ -1,29 +0,0 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
description: { |
|||
maxWidth: '720px', |
|||
lineHeight: '22px', |
|||
}, |
|||
extra: { |
|||
marginTop: '16px', |
|||
color: token.colorTextSecondary, |
|||
lineHeight: '22px', |
|||
'& > em': { |
|||
marginLeft: '16px', |
|||
color: token.colorTextDisabled, |
|||
fontStyle: 'normal', |
|||
}, |
|||
[`@media screen and (max-width: ${token.screenXS}px)`]: { |
|||
'& > em': { |
|||
display: 'block', |
|||
marginTop: '8px', |
|||
marginLeft: '0', |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -1,35 +0,0 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
tagSelect: { |
|||
position: 'relative', |
|||
maxHeight: '32px', |
|||
marginLeft: '-8px', |
|||
overflow: 'hidden', |
|||
lineHeight: '32px', |
|||
transition: 'all 0.3s', |
|||
userSelect: 'none', |
|||
'.ant-tag': { |
|||
marginRight: '24px', |
|||
padding: '0 8px', |
|||
fontSize: token.fontSize, |
|||
}, |
|||
}, |
|||
trigger: { |
|||
position: 'absolute', |
|||
top: '0', |
|||
right: '0', |
|||
'span.anticon': { fontSize: '12px' }, |
|||
}, |
|||
expanded: { |
|||
maxHeight: '200px', |
|||
transition: 'all 0.3s', |
|||
}, |
|||
hasExpandTag: { |
|||
paddingRight: '50px', |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -1,160 +0,0 @@ |
|||
import { DownOutlined, UpOutlined } from '@ant-design/icons'; |
|||
import { useMergedState } from '@rc-component/util'; |
|||
import { Tag } from 'antd'; |
|||
import { clsx } from 'clsx'; |
|||
import React, { type FC, useMemo, useState } from 'react'; |
|||
import useStyles from './index.style'; |
|||
|
|||
const { CheckableTag } = Tag; |
|||
export interface TagSelectOptionProps { |
|||
value: string | number; |
|||
style?: React.CSSProperties; |
|||
checked?: boolean; |
|||
onChange?: (value: string | number, state: boolean) => void; |
|||
children?: React.ReactNode; |
|||
} |
|||
const TagSelectOption: React.FC<TagSelectOptionProps> & { |
|||
isTagSelectOption: boolean; |
|||
} = ({ children, checked, onChange, value }) => ( |
|||
<CheckableTag |
|||
checked={!!checked} |
|||
key={value} |
|||
onChange={(state) => onChange?.(value, state)} |
|||
> |
|||
{children} |
|||
</CheckableTag> |
|||
); |
|||
|
|||
TagSelectOption.isTagSelectOption = true; |
|||
|
|||
type TagSelectOptionElement = React.ReactElement< |
|||
TagSelectOptionProps, |
|||
typeof TagSelectOption |
|||
>; |
|||
|
|||
export interface TagSelectProps { |
|||
onChange?: (value: (string | number)[]) => void; |
|||
expandable?: boolean; |
|||
value?: (string | number)[]; |
|||
defaultValue?: (string | number)[]; |
|||
style?: React.CSSProperties; |
|||
hideCheckAll?: boolean; |
|||
actionsText?: { |
|||
expandText?: React.ReactNode; |
|||
collapseText?: React.ReactNode; |
|||
selectAllText?: React.ReactNode; |
|||
}; |
|||
className?: string; |
|||
Option?: TagSelectOptionProps; |
|||
children?: TagSelectOptionElement | TagSelectOptionElement[]; |
|||
} |
|||
const TagSelect: FC<TagSelectProps> & { |
|||
Option: typeof TagSelectOption; |
|||
} = (props) => { |
|||
const { styles } = useStyles(); |
|||
const { |
|||
children, |
|||
hideCheckAll = false, |
|||
className, |
|||
style, |
|||
expandable, |
|||
actionsText = {}, |
|||
} = props; |
|||
const [expand, setExpand] = useState<boolean>(false); |
|||
|
|||
const [value, setValue] = useMergedState<(string | number)[]>( |
|||
props.defaultValue || [], |
|||
{ |
|||
value: props.value, |
|||
defaultValue: props.defaultValue, |
|||
onChange: props.onChange, |
|||
}, |
|||
); |
|||
|
|||
const isTagSelectOption = (node: TagSelectOptionElement) => |
|||
node?.type && |
|||
(node.type.isTagSelectOption || |
|||
node.type.displayName === 'TagSelectOption'); |
|||
|
|||
// Memoize all tags to avoid recalculating on every render
|
|||
const allTags = useMemo(() => { |
|||
const childrenArray = React.Children.toArray( |
|||
children, |
|||
) as TagSelectOptionElement[]; |
|||
return childrenArray |
|||
.filter((child) => isTagSelectOption(child)) |
|||
.map((child) => child.props.value); |
|||
}, [children]); |
|||
|
|||
// Use Set for O(1) lookups
|
|||
const valueSet = useMemo(() => new Set(value || []), [value]); |
|||
|
|||
const onSelectAll = (checked: boolean) => { |
|||
setValue(checked ? [...allTags] : []); |
|||
}; |
|||
const handleTagChange = (tag: string | number, checked: boolean) => { |
|||
const checkedTags = new Set(value || []); |
|||
if (checked) { |
|||
checkedTags.add(tag); |
|||
} else { |
|||
checkedTags.delete(tag); |
|||
} |
|||
setValue([...checkedTags]); |
|||
}; |
|||
const checkedAll = allTags.length === value?.length && allTags.length > 0; |
|||
const { |
|||
expandText = '展开', |
|||
collapseText = '收起', |
|||
selectAllText = '全部', |
|||
} = actionsText; |
|||
const cls = clsx(styles.tagSelect, className, { |
|||
[styles.hasExpandTag]: expandable, |
|||
[styles.expanded]: expand, |
|||
}); |
|||
return ( |
|||
<div className={cls} style={style}> |
|||
{hideCheckAll ? null : ( |
|||
<CheckableTag |
|||
checked={checkedAll} |
|||
key="tag-select-__all__" |
|||
onChange={onSelectAll} |
|||
> |
|||
{selectAllText} |
|||
</CheckableTag> |
|||
)} |
|||
{children && |
|||
React.Children.map(children, (child: TagSelectOptionElement) => { |
|||
if (isTagSelectOption(child)) { |
|||
return React.cloneElement(child, { |
|||
key: `tag-select-${child.props.value}`, |
|||
value: child.props.value, |
|||
checked: valueSet.has(child.props.value), |
|||
onChange: handleTagChange, |
|||
}); |
|||
} |
|||
return child; |
|||
})} |
|||
{expandable && ( |
|||
<a |
|||
className={styles.trigger} |
|||
onClick={() => { |
|||
setExpand(!expand); |
|||
}} |
|||
> |
|||
{expand ? ( |
|||
<> |
|||
{collapseText} <UpOutlined /> |
|||
</> |
|||
) : ( |
|||
<> |
|||
{expandText} |
|||
<DownOutlined /> |
|||
</> |
|||
)} |
|||
</a> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
TagSelect.Option = TagSelectOption; |
|||
export default TagSelect; |
|||
@ -1,41 +0,0 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
avatarList: { |
|||
display: 'inline-block', |
|||
ul: { display: 'inline-block', marginLeft: '8px', fontSize: '0' }, |
|||
}, |
|||
avatarItem: { |
|||
display: 'inline-block', |
|||
width: token.controlHeight, |
|||
height: token.controlHeight, |
|||
marginLeft: '-8px', |
|||
fontSize: token.fontSize, |
|||
'.ant-avatar': { border: `1px solid ${token.colorBorder}` }, |
|||
}, |
|||
avatarItemLarge: { |
|||
width: token.controlHeightLG, |
|||
height: token.controlHeightLG, |
|||
}, |
|||
avatarItemSmall: { |
|||
width: token.controlHeightSM, |
|||
height: token.controlHeightSM, |
|||
}, |
|||
avatarItemMini: { |
|||
width: '20px', |
|||
height: '20px', |
|||
'.ant-avatar': { |
|||
width: '20px', |
|||
height: '20px', |
|||
lineHeight: '20px', |
|||
'.ant-avatar-string': { |
|||
fontSize: '12px', |
|||
lineHeight: '18px', |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -1,91 +0,0 @@ |
|||
import { Avatar, Tooltip } from 'antd'; |
|||
import { clsx } from 'clsx'; |
|||
import React from 'react'; |
|||
import useStyles from './index.style'; |
|||
export declare type SizeType = number | 'small' | 'default' | 'large'; |
|||
export type AvatarItemProps = { |
|||
tips: React.ReactNode; |
|||
src: string; |
|||
size?: SizeType; |
|||
style?: React.CSSProperties; |
|||
onClick?: () => void; |
|||
}; |
|||
export type AvatarListProps = { |
|||
Item?: React.ReactElement<AvatarItemProps>; |
|||
size?: SizeType; |
|||
maxLength?: number; |
|||
excessItemsStyle?: React.CSSProperties; |
|||
style?: React.CSSProperties; |
|||
children: |
|||
| React.ReactElement<AvatarItemProps> |
|||
| React.ReactElement<AvatarItemProps>[]; |
|||
}; |
|||
const avatarSizeToClassName = (size: SizeType | 'mini', styles: any) => |
|||
clsx(styles.avatarItem, { |
|||
[styles.avatarItemLarge]: size === 'large', |
|||
[styles.avatarItemSmall]: size === 'small', |
|||
[styles.avatarItemMini]: size === 'mini', |
|||
}); |
|||
|
|||
const Item: React.FC<AvatarItemProps> = ({ |
|||
src, |
|||
size, |
|||
tips, |
|||
onClick = () => {}, |
|||
}) => { |
|||
const { styles } = useStyles(); |
|||
|
|||
const cls = avatarSizeToClassName(size || 'default', styles); |
|||
|
|||
return ( |
|||
<li className={cls} onClick={onClick}> |
|||
{tips ? ( |
|||
<Tooltip title={tips}> |
|||
<Avatar |
|||
src={src} |
|||
size={size} |
|||
style={{ |
|||
cursor: 'pointer', |
|||
}} |
|||
/> |
|||
</Tooltip> |
|||
) : ( |
|||
<Avatar src={src} size={size} /> |
|||
)} |
|||
</li> |
|||
); |
|||
}; |
|||
const AvatarList: React.FC<AvatarListProps> & { |
|||
Item: typeof Item; |
|||
} = ({ children, size, maxLength = 5, excessItemsStyle, ...other }) => { |
|||
const { styles } = useStyles(); |
|||
const numOfChildren = React.Children.count(children); |
|||
const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength; |
|||
const childrenArray = React.Children.toArray( |
|||
children, |
|||
) as React.ReactElement<AvatarItemProps>[]; |
|||
|
|||
const childrenWithProps = childrenArray.slice(0, numToShow).map((child) => |
|||
React.cloneElement(child, { |
|||
size, |
|||
}), |
|||
); |
|||
if (numToShow < numOfChildren) { |
|||
const cls = avatarSizeToClassName(size || 'default', styles); |
|||
childrenWithProps.push( |
|||
<li key="exceed" className={cls}> |
|||
<Avatar |
|||
size={size} |
|||
style={excessItemsStyle} |
|||
>{`+${numOfChildren - maxLength}`}</Avatar> |
|||
</li>, |
|||
); |
|||
} |
|||
return ( |
|||
<div {...other} className={styles.avatarList}> |
|||
<ul> {childrenWithProps} </ul> |
|||
</div> |
|||
); |
|||
}; |
|||
AvatarList.Item = Item; |
|||
export default AvatarList; |
|||
@ -1,62 +0,0 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
standardFormRow: { |
|||
display: 'flex', |
|||
width: '100%', |
|||
marginBottom: '16px', |
|||
paddingBottom: '16px', |
|||
borderBottom: `1px dashed ${token.colorSplit}`, |
|||
'.ant-form-item, .ant-legacy-form-item': { marginRight: '24px' }, |
|||
'.ant-form-item-label, .ant-legacy-form-item-label': { |
|||
label: { |
|||
marginRight: '0', |
|||
color: token.colorText, |
|||
}, |
|||
}, |
|||
'.ant-form-item-label, .ant-legacy-form-item-label, .ant-form-item-control, .ant-legacy-form-item-control': |
|||
{ padding: '0', lineHeight: '32px' }, |
|||
}, |
|||
label: { |
|||
flex: '0 0 auto', |
|||
marginRight: '24px', |
|||
color: token.colorTextHeading, |
|||
fontSize: token.fontSize, |
|||
textAlign: 'right', |
|||
'& > span': { |
|||
display: 'inline-block', |
|||
height: '32px', |
|||
lineHeight: '32px', |
|||
'&::after': { |
|||
content: "':'", |
|||
}, |
|||
}, |
|||
}, |
|||
content: { |
|||
flex: '1 1 0', |
|||
'.ant-form-item, .ant-legacy-form-item': { |
|||
'&:last-child': { |
|||
display: 'block', |
|||
marginRight: '0', |
|||
}, |
|||
}, |
|||
}, |
|||
standardFormRowLast: { |
|||
marginBottom: '0', |
|||
paddingBottom: '0', |
|||
border: 'none', |
|||
}, |
|||
standardFormRowBlock: { |
|||
'.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': |
|||
{ display: 'block' }, |
|||
}, |
|||
standardFormRowGrid: { |
|||
'.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': |
|||
{ display: 'block' }, |
|||
'.ant-form-item-label, .ant-legacy-form-item-label': { float: 'left' }, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -1,38 +0,0 @@ |
|||
import { clsx } from 'clsx'; |
|||
import React from 'react'; |
|||
import useStyles from './index.style'; |
|||
|
|||
type StandardFormRowProps = { |
|||
title?: string; |
|||
last?: boolean; |
|||
block?: boolean; |
|||
grid?: boolean; |
|||
children?: React.ReactNode; |
|||
style?: React.CSSProperties; |
|||
}; |
|||
const StandardFormRow: React.FC<StandardFormRowProps> = ({ |
|||
title, |
|||
children, |
|||
last, |
|||
block, |
|||
grid, |
|||
...rest |
|||
}) => { |
|||
const { styles } = useStyles(); |
|||
const cls = clsx(styles.standardFormRow, { |
|||
[styles.standardFormRowBlock]: block, |
|||
[styles.standardFormRowLast]: last, |
|||
[styles.standardFormRowGrid]: grid, |
|||
}); |
|||
return ( |
|||
<div className={cls} {...rest}> |
|||
{title && ( |
|||
<div className={styles.label}> |
|||
<span>{title}</span> |
|||
</div> |
|||
)} |
|||
<div className={styles.content}>{children}</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default StandardFormRow; |
|||
@ -1,35 +0,0 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
tagSelect: { |
|||
position: 'relative', |
|||
maxHeight: '32px', |
|||
marginLeft: '-8px', |
|||
overflow: 'hidden', |
|||
lineHeight: '32px', |
|||
transition: 'all 0.3s', |
|||
userSelect: 'none', |
|||
'.ant-tag': { |
|||
marginRight: '24px', |
|||
padding: '0 8px', |
|||
fontSize: token.fontSize, |
|||
}, |
|||
}, |
|||
trigger: { |
|||
position: 'absolute', |
|||
top: '0', |
|||
right: '0', |
|||
'span.anticon': { fontSize: '12px' }, |
|||
}, |
|||
expanded: { |
|||
maxHeight: '200px', |
|||
transition: 'all 0.3s', |
|||
}, |
|||
hasExpandTag: { |
|||
paddingRight: '50px', |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -1,160 +0,0 @@ |
|||
import { DownOutlined, UpOutlined } from '@ant-design/icons'; |
|||
import { useMergedState } from '@rc-component/util'; |
|||
import { Tag } from 'antd'; |
|||
import { clsx } from 'clsx'; |
|||
import React, { type FC, useMemo, useState } from 'react'; |
|||
import useStyles from './index.style'; |
|||
|
|||
const { CheckableTag } = Tag; |
|||
export interface TagSelectOptionProps { |
|||
value: string | number; |
|||
style?: React.CSSProperties; |
|||
checked?: boolean; |
|||
onChange?: (value: string | number, state: boolean) => void; |
|||
children?: React.ReactNode; |
|||
} |
|||
const TagSelectOption: React.FC<TagSelectOptionProps> & { |
|||
isTagSelectOption: boolean; |
|||
} = ({ children, checked, onChange, value }) => ( |
|||
<CheckableTag |
|||
checked={!!checked} |
|||
key={value} |
|||
onChange={(state) => onChange?.(value, state)} |
|||
> |
|||
{children} |
|||
</CheckableTag> |
|||
); |
|||
|
|||
TagSelectOption.isTagSelectOption = true; |
|||
|
|||
type TagSelectOptionElement = React.ReactElement< |
|||
TagSelectOptionProps, |
|||
typeof TagSelectOption |
|||
>; |
|||
|
|||
export interface TagSelectProps { |
|||
onChange?: (value: (string | number)[]) => void; |
|||
expandable?: boolean; |
|||
value?: (string | number)[]; |
|||
defaultValue?: (string | number)[]; |
|||
style?: React.CSSProperties; |
|||
hideCheckAll?: boolean; |
|||
actionsText?: { |
|||
expandText?: React.ReactNode; |
|||
collapseText?: React.ReactNode; |
|||
selectAllText?: React.ReactNode; |
|||
}; |
|||
className?: string; |
|||
Option?: TagSelectOptionProps; |
|||
children?: TagSelectOptionElement | TagSelectOptionElement[]; |
|||
} |
|||
const TagSelect: FC<TagSelectProps> & { |
|||
Option: typeof TagSelectOption; |
|||
} = (props) => { |
|||
const { styles } = useStyles(); |
|||
const { |
|||
children, |
|||
hideCheckAll = false, |
|||
className, |
|||
style, |
|||
expandable, |
|||
actionsText = {}, |
|||
} = props; |
|||
const [expand, setExpand] = useState<boolean>(false); |
|||
|
|||
const [value, setValue] = useMergedState<(string | number)[]>( |
|||
props.defaultValue || [], |
|||
{ |
|||
value: props.value, |
|||
defaultValue: props.defaultValue, |
|||
onChange: props.onChange, |
|||
}, |
|||
); |
|||
|
|||
const isTagSelectOption = (node: TagSelectOptionElement) => |
|||
node?.type && |
|||
(node.type.isTagSelectOption || |
|||
node.type.displayName === 'TagSelectOption'); |
|||
|
|||
// Memoize all tags to avoid recalculating on every render
|
|||
const allTags = useMemo(() => { |
|||
const childrenArray = React.Children.toArray( |
|||
children, |
|||
) as TagSelectOptionElement[]; |
|||
return childrenArray |
|||
.filter((child) => isTagSelectOption(child)) |
|||
.map((child) => child.props.value); |
|||
}, [children]); |
|||
|
|||
// Use Set for O(1) lookups
|
|||
const valueSet = useMemo(() => new Set(value || []), [value]); |
|||
|
|||
const onSelectAll = (checked: boolean) => { |
|||
setValue(checked ? [...allTags] : []); |
|||
}; |
|||
const handleTagChange = (tag: string | number, checked: boolean) => { |
|||
const checkedTags = new Set(value || []); |
|||
if (checked) { |
|||
checkedTags.add(tag); |
|||
} else { |
|||
checkedTags.delete(tag); |
|||
} |
|||
setValue([...checkedTags]); |
|||
}; |
|||
const checkedAll = allTags.length === value?.length && allTags.length > 0; |
|||
const { |
|||
expandText = '展开', |
|||
collapseText = '收起', |
|||
selectAllText = '全部', |
|||
} = actionsText; |
|||
const cls = clsx(styles.tagSelect, className, { |
|||
[styles.hasExpandTag]: expandable, |
|||
[styles.expanded]: expand, |
|||
}); |
|||
return ( |
|||
<div className={cls} style={style}> |
|||
{hideCheckAll ? null : ( |
|||
<CheckableTag |
|||
checked={checkedAll} |
|||
key="tag-select-__all__" |
|||
onChange={onSelectAll} |
|||
> |
|||
{selectAllText} |
|||
</CheckableTag> |
|||
)} |
|||
{children && |
|||
React.Children.map(children, (child: TagSelectOptionElement) => { |
|||
if (isTagSelectOption(child)) { |
|||
return React.cloneElement(child, { |
|||
key: `tag-select-${child.props.value}`, |
|||
value: child.props.value, |
|||
checked: valueSet.has(child.props.value), |
|||
onChange: handleTagChange, |
|||
}); |
|||
} |
|||
return child; |
|||
})} |
|||
{expandable && ( |
|||
<a |
|||
className={styles.trigger} |
|||
onClick={() => { |
|||
setExpand(!expand); |
|||
}} |
|||
> |
|||
{expand ? ( |
|||
<> |
|||
{collapseText} <UpOutlined /> |
|||
</> |
|||
) : ( |
|||
<> |
|||
{expandText} |
|||
<DownOutlined /> |
|||
</> |
|||
)} |
|||
</a> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
TagSelect.Option = TagSelectOption; |
|||
export default TagSelect; |
|||
Loading…
Reference in new issue