/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useState, useRef, useEffect } from 'react'; import { useMinimumLoadingTime } from '../../../hooks/common/useMinimumLoadingTime'; import { useContainerWidth } from '../../../hooks/common/useContainerWidth'; import { Divider, Button, Tag, Row, Col, Collapsible, Checkbox, Skeleton, Tooltip, } from '@douyinfe/semi-ui'; import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons'; /** * 通用可选择按钮组组件 * * @param {string} title 标题 * @param {Array<{value:any,label:string,icon?:React.ReactNode,tagCount?:number}>} items 按钮项 * @param {*|Array} activeValue 当前激活的值,可以是单个值或数组(多选) * @param {(value:any)=>void} onChange 选择改变回调 * @param {function} t i18n * @param {object} style 额外样式 * @param {boolean} collapsible 是否支持折叠,默认true * @param {number} collapseHeight 折叠时的高度,默认200 * @param {boolean} withCheckbox 是否启用前缀 Checkbox 来控制激活状态 * @param {boolean} loading 是否处于加载状态 */ const SelectableButtonGroup = ({ title, items = [], activeValue, onChange, t = (v) => v, style = {}, collapsible = true, collapseHeight = 200, withCheckbox = false, loading = false, }) => { const [isOpen, setIsOpen] = useState(false); const [skeletonCount] = useState(12); const [containerRef, containerWidth] = useContainerWidth(); const ConditionalTooltipText = ({ text }) => { const textRef = useRef(null); const [isOverflowing, setIsOverflowing] = useState(false); useEffect(() => { const el = textRef.current; if (!el) return; setIsOverflowing(el.scrollWidth > el.clientWidth); }, [text, containerWidth]); const textElement = ( {text} ); return isOverflowing ? ( {textElement} ) : ( textElement ); }; // 基于容器宽度计算响应式列数和标签显示策略 const getResponsiveConfig = () => { if (containerWidth <= 280) return { columns: 1, showTags: true }; // 极窄:1列+标签 if (containerWidth <= 380) return { columns: 2, showTags: true }; // 窄屏:2列+标签 if (containerWidth <= 460) return { columns: 3, showTags: false }; // 中等:3列不加标签 return { columns: 3, showTags: true }; // 最宽:3列+标签 }; const { columns: perRow, showTags: shouldShowTags } = getResponsiveConfig(); const maxVisibleRows = Math.max(1, Math.floor(collapseHeight / 32)); // Approx row height 32 const needCollapse = collapsible && items.length > perRow * maxVisibleRows; const showSkeleton = useMinimumLoadingTime(loading); // 统一使用紧凑的网格间距 const gutterSize = [4, 4]; // 计算 Semi UI Col 的 span 值 const getColSpan = () => { return Math.floor(24 / perRow); }; const maskStyle = isOpen ? {} : { WebkitMaskImage: 'linear-gradient(to bottom, black 0%, rgba(0, 0, 0, 1) 60%, rgba(0, 0, 0, 0.2) 80%, transparent 100%)', }; const toggle = () => { setIsOpen(!isOpen); }; const linkStyle = { position: 'absolute', left: 0, right: 0, textAlign: 'center', bottom: -10, fontWeight: 400, cursor: 'pointer', fontSize: '12px', color: 'var(--semi-color-text-2)', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, }; const renderSkeletonButtons = () => { const placeholder = ( {Array.from({ length: skeletonCount }).map((_, index) => (
{withCheckbox && ( )}
))}
); return ( ); }; const contentElement = showSkeleton ? ( renderSkeletonButtons() ) : ( {items.map((item) => { const isDisabled = item.disabled || (typeof item.tagCount === 'number' && item.tagCount === 0); const isActive = Array.isArray(activeValue) ? activeValue.includes(item.value) : activeValue === item.value; if (withCheckbox) { return ( ); } return ( ); })} ); return (
{title && ( {showSkeleton ? ( ) : ( title )} )} {needCollapse && !showSkeleton ? (
{contentElement} {isOpen ? null : (
{t('展开更多')}
)} {isOpen && (
{t('收起')}
)}
) : ( contentElement )}
); }; export default SelectableButtonGroup;