diff --git a/src/pages/account/center/components/ArticleListContent/index.style.ts b/src/components/ArticleListContent/index.style.ts similarity index 100% rename from src/pages/account/center/components/ArticleListContent/index.style.ts rename to src/components/ArticleListContent/index.style.ts diff --git a/src/pages/list/search/articles/components/ArticleListContent/index.tsx b/src/components/ArticleListContent/index.tsx similarity index 80% rename from src/pages/list/search/articles/components/ArticleListContent/index.tsx rename to src/components/ArticleListContent/index.tsx index 0e5f60c0..1283b145 100644 --- a/src/pages/list/search/articles/components/ArticleListContent/index.tsx +++ b/src/components/ArticleListContent/index.tsx @@ -3,15 +3,16 @@ import dayjs from 'dayjs'; import React from 'react'; import useStyles from './index.style'; -type ArticleListContentProps = { +export type ArticleListContentProps = { data: { - content: React.ReactNode; - updatedAt: number; - avatar: string; - owner: string; - href: string; + content?: React.ReactNode; + updatedAt?: number; + avatar?: string; + owner?: string; + href?: string; }; }; + const ArticleListContent: React.FC = ({ data: { content, updatedAt, avatar, owner, href }, }) => { @@ -27,4 +28,5 @@ const ArticleListContent: React.FC = ({ ); }; + export default ArticleListContent; diff --git a/src/pages/account/center/components/AvatarList/index.style.ts b/src/components/AvatarList/index.style.ts similarity index 100% rename from src/pages/account/center/components/AvatarList/index.style.ts rename to src/components/AvatarList/index.style.ts diff --git a/src/pages/account/center/components/AvatarList/index.tsx b/src/components/AvatarList/index.tsx similarity index 99% rename from src/pages/account/center/components/AvatarList/index.tsx rename to src/components/AvatarList/index.tsx index a815bb6d..f6f8eba3 100644 --- a/src/pages/account/center/components/AvatarList/index.tsx +++ b/src/components/AvatarList/index.tsx @@ -2,7 +2,9 @@ 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; @@ -10,6 +12,7 @@ export type AvatarItemProps = { style?: React.CSSProperties; onClick?: () => void; }; + export type AvatarListProps = { Item?: React.ReactElement; size?: SizeType; @@ -54,6 +57,7 @@ const Item: React.FC = ({ ); }; + const AvatarList: React.FC & { Item: typeof Item; } = ({ children, size, maxLength = 5, excessItemsStyle, ...other }) => { @@ -85,5 +89,7 @@ const AvatarList: React.FC & { ); }; + AvatarList.Item = Item; + export default AvatarList; diff --git a/src/pages/list/search/articles/components/StandardFormRow/index.style.ts b/src/components/StandardFormRow/index.style.ts similarity index 100% rename from src/pages/list/search/articles/components/StandardFormRow/index.style.ts rename to src/components/StandardFormRow/index.style.ts diff --git a/src/pages/list/search/articles/components/StandardFormRow/index.tsx b/src/components/StandardFormRow/index.tsx similarity index 100% rename from src/pages/list/search/articles/components/StandardFormRow/index.tsx rename to src/components/StandardFormRow/index.tsx diff --git a/src/pages/list/search/applications/components/TagSelect/index.style.ts b/src/components/TagSelect/index.style.ts similarity index 100% rename from src/pages/list/search/applications/components/TagSelect/index.style.ts rename to src/components/TagSelect/index.style.ts diff --git a/src/pages/list/search/applications/components/TagSelect/index.tsx b/src/components/TagSelect/index.tsx similarity index 100% rename from src/pages/list/search/applications/components/TagSelect/index.tsx rename to src/components/TagSelect/index.tsx diff --git a/src/components/index.ts b/src/components/index.ts index 9c828925..30dc1d0f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,4 +9,12 @@ import Footer from './Footer'; import { Question, SelectLang } from './RightContent'; import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown'; +/** + * 业务组件 + */ +export { default as ArticleListContent } from './ArticleListContent'; +export { default as AvatarList } from './AvatarList'; +export { default as StandardFormRow } from './StandardFormRow'; +export { default as TagSelect } from './TagSelect'; + export { AvatarDropdown, AvatarName, Footer, Question, SelectLang }; diff --git a/src/pages/account/center/components/ArticleListContent/index.tsx b/src/pages/account/center/components/ArticleListContent/index.tsx deleted file mode 100644 index 24f81ad0..00000000 --- a/src/pages/account/center/components/ArticleListContent/index.tsx +++ /dev/null @@ -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 = ({ - data: { content, updatedAt, avatar, owner, href }, -}) => { - const { styles } = useStyles(); - return ( -
-
{content}
-
- - {owner} 发布在 {href} - {dayjs(updatedAt).format('YYYY-MM-DD HH:mm')} -
-
- ); -}; -export default ArticleListContent; diff --git a/src/pages/account/center/components/Articles/index.tsx b/src/pages/account/center/components/Articles/index.tsx index 939c9c4a..fdc86e2d 100644 --- a/src/pages/account/center/components/Articles/index.tsx +++ b/src/pages/account/center/components/Articles/index.tsx @@ -2,9 +2,9 @@ import { LikeOutlined, MessageFilled, StarTwoTone } from '@ant-design/icons'; import { useQuery } from '@tanstack/react-query'; import { Flex, List, Tag } from 'antd'; import React from 'react'; +import { ArticleListContent } from '@/components'; import type { ListItemDataType } from '../../data.d'; import { queryFakeList } from '../../service'; -import ArticleListContent from '../ArticleListContent'; import useStyles from './index.style'; const IconText: React.FC<{ diff --git a/src/pages/account/center/components/Projects/index.tsx b/src/pages/account/center/components/Projects/index.tsx index dffb03f4..99b4a2a1 100644 --- a/src/pages/account/center/components/Projects/index.tsx +++ b/src/pages/account/center/components/Projects/index.tsx @@ -3,9 +3,9 @@ import { Card, List } from 'antd'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import React from 'react'; +import { AvatarList } from '@/components'; import type { ListItemDataType } from '../../data.d'; import { queryFakeList } from '../../service'; -import AvatarList from '../AvatarList'; import useStyles from './index.style'; dayjs.extend(relativeTime); diff --git a/src/pages/list/search/applications/components/StandardFormRow/index.style.ts b/src/pages/list/search/applications/components/StandardFormRow/index.style.ts deleted file mode 100644 index 89156097..00000000 --- a/src/pages/list/search/applications/components/StandardFormRow/index.style.ts +++ /dev/null @@ -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; diff --git a/src/pages/list/search/applications/components/StandardFormRow/index.tsx b/src/pages/list/search/applications/components/StandardFormRow/index.tsx deleted file mode 100644 index 746a5062..00000000 --- a/src/pages/list/search/applications/components/StandardFormRow/index.tsx +++ /dev/null @@ -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 = ({ - title, - children, - last, - block, - grid, - ...rest -}) => { - const { styles } = useStyles(); - const cls = clsx(styles.standardFormRow, { - [styles.standardFormRowBlock]: block, - [styles.standardFormRowLast]: last, - [styles.standardFormRowGrid]: grid, - }); - return ( -
- {title && ( -
- {title} -
- )} -
{children}
-
- ); -}; -export default StandardFormRow; diff --git a/src/pages/list/search/applications/index.tsx b/src/pages/list/search/applications/index.tsx index 9b84ccba..acbff65d 100644 --- a/src/pages/list/search/applications/index.tsx +++ b/src/pages/list/search/applications/index.tsx @@ -19,9 +19,8 @@ import { import numeral from 'numeral'; import type { FC } from 'react'; import React from 'react'; +import { StandardFormRow, TagSelect } from '@/components'; import { categoryOptions } from '../../mock'; -import StandardFormRow from './components/StandardFormRow'; -import TagSelect from './components/TagSelect'; import type { ListItemDataType } from './data.d'; import { queryFakeList } from './service'; import useStyles from './style.style'; diff --git a/src/pages/list/search/articles/components/ArticleListContent/index.style.ts b/src/pages/list/search/articles/components/ArticleListContent/index.style.ts deleted file mode 100644 index c993635f..00000000 --- a/src/pages/list/search/articles/components/ArticleListContent/index.style.ts +++ /dev/null @@ -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; diff --git a/src/pages/list/search/articles/components/TagSelect/index.style.ts b/src/pages/list/search/articles/components/TagSelect/index.style.ts deleted file mode 100644 index d198ab7a..00000000 --- a/src/pages/list/search/articles/components/TagSelect/index.style.ts +++ /dev/null @@ -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; diff --git a/src/pages/list/search/articles/components/TagSelect/index.tsx b/src/pages/list/search/articles/components/TagSelect/index.tsx deleted file mode 100644 index 4b4a7b4f..00000000 --- a/src/pages/list/search/articles/components/TagSelect/index.tsx +++ /dev/null @@ -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 & { - isTagSelectOption: boolean; -} = ({ children, checked, onChange, value }) => ( - onChange?.(value, state)} - > - {children} - -); - -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 & { - Option: typeof TagSelectOption; -} = (props) => { - const { styles } = useStyles(); - const { - children, - hideCheckAll = false, - className, - style, - expandable, - actionsText = {}, - } = props; - const [expand, setExpand] = useState(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 ( -
- {hideCheckAll ? null : ( - - {selectAllText} - - )} - {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 && ( - { - setExpand(!expand); - }} - > - {expand ? ( - <> - {collapseText} - - ) : ( - <> - {expandText} - - - )} - - )} -
- ); -}; -TagSelect.Option = TagSelectOption; -export default TagSelect; diff --git a/src/pages/list/search/articles/index.tsx b/src/pages/list/search/articles/index.tsx index 19e55932..0cc8a04f 100644 --- a/src/pages/list/search/articles/index.tsx +++ b/src/pages/list/search/articles/index.tsx @@ -9,10 +9,8 @@ import { Button, Card, Col, Flex, Form, List, Row, Select, Tag } from 'antd'; import type { DefaultOptionType } from 'antd/es/select'; import type { FC } from 'react'; import React, { useMemo, useRef } from 'react'; +import { ArticleListContent, StandardFormRow, TagSelect } from '@/components'; import { categoryOptions } from '../../mock'; -import ArticleListContent from './components/ArticleListContent'; -import StandardFormRow from './components/StandardFormRow'; -import TagSelect from './components/TagSelect'; import type { ListItemDataType } from './data.d'; import { queryFakeList } from './service'; import useStyles from './style.style'; diff --git a/src/pages/list/search/projects/components/AvatarList/index.style.ts b/src/pages/list/search/projects/components/AvatarList/index.style.ts deleted file mode 100644 index d601a1d8..00000000 --- a/src/pages/list/search/projects/components/AvatarList/index.style.ts +++ /dev/null @@ -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; diff --git a/src/pages/list/search/projects/components/AvatarList/index.tsx b/src/pages/list/search/projects/components/AvatarList/index.tsx deleted file mode 100644 index 801d86fc..00000000 --- a/src/pages/list/search/projects/components/AvatarList/index.tsx +++ /dev/null @@ -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; - size?: SizeType; - maxLength?: number; - excessItemsStyle?: React.CSSProperties; - style?: React.CSSProperties; - children: - | React.ReactElement - | React.ReactElement[]; -}; -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 = ({ - src, - size, - tips, - onClick = () => {}, -}) => { - const { styles } = useStyles(); - - const cls = avatarSizeToClassName(size || 'default', styles); - - return ( -
  • - {tips ? ( - - - - ) : ( - - )} -
  • - ); -}; -const AvatarList: React.FC & { - 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[]; - - const childrenWithProps = childrenArray.slice(0, numToShow).map((child) => - React.cloneElement(child, { - size, - }), - ); - if (numToShow < numOfChildren) { - const cls = avatarSizeToClassName(size || 'default', styles); - childrenWithProps.push( -
  • - {`+${numOfChildren - maxLength}`} -
  • , - ); - } - return ( -
    -
      {childrenWithProps}
    -
    - ); -}; -AvatarList.Item = Item; -export default AvatarList; diff --git a/src/pages/list/search/projects/components/StandardFormRow/index.style.ts b/src/pages/list/search/projects/components/StandardFormRow/index.style.ts deleted file mode 100644 index 7b5f569d..00000000 --- a/src/pages/list/search/projects/components/StandardFormRow/index.style.ts +++ /dev/null @@ -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; diff --git a/src/pages/list/search/projects/components/StandardFormRow/index.tsx b/src/pages/list/search/projects/components/StandardFormRow/index.tsx deleted file mode 100644 index 83d8a2a8..00000000 --- a/src/pages/list/search/projects/components/StandardFormRow/index.tsx +++ /dev/null @@ -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 = ({ - title, - children, - last, - block, - grid, - ...rest -}) => { - const { styles } = useStyles(); - const cls = clsx(styles.standardFormRow, { - [styles.standardFormRowBlock]: block, - [styles.standardFormRowLast]: last, - [styles.standardFormRowGrid]: grid, - }); - return ( -
    - {title && ( -
    - {title} -
    - )} -
    {children}
    -
    - ); -}; -export default StandardFormRow; diff --git a/src/pages/list/search/projects/components/TagSelect/index.style.ts b/src/pages/list/search/projects/components/TagSelect/index.style.ts deleted file mode 100644 index d198ab7a..00000000 --- a/src/pages/list/search/projects/components/TagSelect/index.style.ts +++ /dev/null @@ -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; diff --git a/src/pages/list/search/projects/components/TagSelect/index.tsx b/src/pages/list/search/projects/components/TagSelect/index.tsx deleted file mode 100644 index 4b4a7b4f..00000000 --- a/src/pages/list/search/projects/components/TagSelect/index.tsx +++ /dev/null @@ -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 & { - isTagSelectOption: boolean; -} = ({ children, checked, onChange, value }) => ( - onChange?.(value, state)} - > - {children} - -); - -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 & { - Option: typeof TagSelectOption; -} = (props) => { - const { styles } = useStyles(); - const { - children, - hideCheckAll = false, - className, - style, - expandable, - actionsText = {}, - } = props; - const [expand, setExpand] = useState(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 ( -
    - {hideCheckAll ? null : ( - - {selectAllText} - - )} - {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 && ( - { - setExpand(!expand); - }} - > - {expand ? ( - <> - {collapseText} - - ) : ( - <> - {expandText} - - - )} - - )} -
    - ); -}; -TagSelect.Option = TagSelectOption; -export default TagSelect; diff --git a/src/pages/list/search/projects/index.tsx b/src/pages/list/search/projects/index.tsx index 3f2e1a60..81028088 100644 --- a/src/pages/list/search/projects/index.tsx +++ b/src/pages/list/search/projects/index.tsx @@ -4,10 +4,8 @@ import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import type { FC } from 'react'; import { useState } from 'react'; +import { AvatarList, StandardFormRow, TagSelect } from '@/components'; import { categoryOptions } from '../../mock'; -import AvatarList from './components/AvatarList'; -import StandardFormRow from './components/StandardFormRow'; -import TagSelect from './components/TagSelect'; import type { ListItemDataType } from './data.d'; import { queryFakeList } from './service'; import useStyles from './style.style';