|
1 | | -import React, { useState } from 'react'; |
| 1 | +import React, { useMemo, useCallback } from 'react'; |
2 | 2 | import { CaretRightOutlined, QuestionCircleOutlined } from '@ant-design/icons'; |
3 | | -import { Collapse, theme, Flex, Typography, Select, Space, Tooltip } from 'antd'; |
| 3 | +import { Collapse, theme, Flex, Typography, Space, Tooltip } from 'antd'; |
| 4 | +import type { CollapseProps } from 'antd'; |
4 | 5 | import { useDynamicStyle } from '../hooks/useDynamicStyle'; |
5 | | -interface IAdvancedSettingProps { |
| 6 | + |
| 7 | +/** |
| 8 | + * CollapseCard 组件的属性接口 |
| 9 | + */ |
| 10 | +export interface ICollapseCardProps { |
| 11 | + /** 是否显示边框 */ |
6 | 12 | bordered?: boolean; |
| 13 | + /** 是否透明背景 */ |
7 | 14 | ghost?: boolean; |
| 15 | + /** 卡片标题 */ |
8 | 16 | title: React.ReactNode; |
| 17 | + /** 卡片内容 */ |
9 | 18 | children: React.ReactNode; |
| 19 | + /** 默认是否折叠 */ |
10 | 20 | defaultCollapse?: boolean; |
| 21 | + /** 提示信息 */ |
11 | 22 | tooltip?: React.ReactNode; |
| 23 | + /** 自定义样式 */ |
12 | 24 | style?: React.CSSProperties; |
| 25 | + /** 自定义类名 */ |
| 26 | + className?: string; |
| 27 | + /** 展开/收起时的回调 */ |
| 28 | + onChange?: (isActive: boolean) => void; |
13 | 29 | } |
14 | 30 |
|
15 | | -const CollapseCard: React.FunctionComponent<IAdvancedSettingProps> = props => { |
16 | | - const { bordered, children, title, defaultCollapse, tooltip, style = {} } = props; |
| 31 | +/** |
| 32 | + * 可折叠卡片组件 |
| 33 | + * @description 一个可折叠的卡片组件,支持自定义标题、内容和样式 |
| 34 | + */ |
| 35 | +const CollapseCard: React.FC<ICollapseCardProps> = ({ |
| 36 | + bordered = false, |
| 37 | + ghost = true, |
| 38 | + children, |
| 39 | + title, |
| 40 | + defaultCollapse = false, |
| 41 | + tooltip, |
| 42 | + style = {}, |
| 43 | + className = '', |
| 44 | + onChange, |
| 45 | +}) => { |
| 46 | + const { token } = theme.useToken(); |
17 | 47 | const id = 'Studio-Collapse-Card'; |
18 | 48 | const defaultActiveKey = defaultCollapse ? [] : [id]; |
| 49 | + |
| 50 | + // 使用 useMemo 优化样式计算 |
| 51 | + const cardStyle = useMemo( |
| 52 | + () => ({ |
| 53 | + ...style, |
| 54 | + backgroundColor: token.colorBgContainer, |
| 55 | + borderRadius: token.borderRadiusLG, |
| 56 | + boxShadow: bordered ? token.boxShadowTertiary : 'none', |
| 57 | + }), |
| 58 | + [style, token, bordered], |
| 59 | + ); |
| 60 | + |
| 61 | + // 使用 useMemo 优化标题样式 |
| 62 | + const titleStyle = useMemo( |
| 63 | + () => ({ |
| 64 | + margin: 0, |
| 65 | + color: token.colorTextHeading, |
| 66 | + fontSize: token.fontSizeLG, |
| 67 | + fontWeight: token.fontWeightStrong, |
| 68 | + }), |
| 69 | + [token], |
| 70 | + ); |
| 71 | + |
| 72 | + // 使用 useMemo 优化图标样式 |
| 73 | + const iconStyle = useMemo( |
| 74 | + () => ({ |
| 75 | + transition: `transform ${token.motionDurationMid} ease`, |
| 76 | + fontSize: token.fontSizeLG, |
| 77 | + }), |
| 78 | + [token], |
| 79 | + ); |
| 80 | + |
| 81 | + // 动态样式注入 |
19 | 82 | useDynamicStyle( |
20 | | - `.${id} .ant-collapse-header {padding:0px !important;} |
21 | | - .${id} .ant-collapse-content-box {padding:12px 0px !important;} |
| 83 | + `.${id} .ant-collapse-header { |
| 84 | + padding: ${token.padding}px !important; |
| 85 | + transition: all ${token.motionDurationMid} ease; |
| 86 | + } |
| 87 | + .${id} .ant-collapse-content-box { |
| 88 | + padding: ${token.padding}px ${token.paddingLG}px !important; |
| 89 | + } |
22 | 90 | `, |
23 | 91 | id, |
24 | 92 | ); |
| 93 | + |
| 94 | + // 使用 useCallback 优化事件处理函数 |
| 95 | + const handleChange = useCallback( |
| 96 | + (activeKeys: string | string[]) => { |
| 97 | + const isActive = Array.isArray(activeKeys) ? activeKeys.includes(id) : activeKeys === id; |
| 98 | + onChange?.(isActive); |
| 99 | + }, |
| 100 | + [id, onChange], |
| 101 | + ); |
| 102 | + |
| 103 | + // 使用 useCallback 优化展开图标渲染函数 |
| 104 | + const renderExpandIcon = useCallback( |
| 105 | + (props: { isActive?: boolean }) => <CaretRightOutlined rotate={props.isActive ? 90 : 0} style={iconStyle} />, |
| 106 | + [iconStyle], |
| 107 | + ); |
| 108 | + |
25 | 109 | return ( |
26 | 110 | <Collapse |
27 | | - style={style} |
28 | | - className={id} |
| 111 | + style={cardStyle} |
| 112 | + className={`${id} ${className}`} |
29 | 113 | bordered={bordered} |
30 | | - ghost |
| 114 | + ghost={ghost} |
31 | 115 | expandIconPosition="end" |
32 | 116 | defaultActiveKey={defaultActiveKey} |
33 | | - expandIcon={({ isActive }) => <CaretRightOutlined rotate={isActive ? 90 : 0} />} |
| 117 | + onChange={handleChange} |
| 118 | + expandIcon={renderExpandIcon} |
34 | 119 | items={[ |
35 | 120 | { |
36 | 121 | key: id, |
37 | 122 | label: ( |
38 | 123 | <Space> |
39 | | - <Typography.Title level={5} style={{ margin: '0px' }}> |
| 124 | + <Typography.Title level={5} style={titleStyle}> |
40 | 125 | {title} |
41 | 126 | </Typography.Title> |
42 | 127 | {tooltip && ( |
43 | 128 | <Tooltip title={tooltip}> |
44 | | - <QuestionCircleOutlined /> |
| 129 | + <QuestionCircleOutlined style={{ color: token.colorTextSecondary }} /> |
45 | 130 | </Tooltip> |
46 | 131 | )} |
47 | 132 | </Space> |
|
0 commit comments