feat: enhance PricingTags and SelectableButtonGroup with new badge styles and color variants
Some checks failed
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (amd64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (arm64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Create multi-arch manifests (Docker Hub) (push) Has been cancelled

This commit is contained in:
CaIon 2026-03-04 00:35:52 +08:00
parent 6f818574ab
commit bd6b728622
9 changed files with 131 additions and 52 deletions

View File

@ -23,7 +23,6 @@ import { useContainerWidth } from '../../../hooks/common/useContainerWidth';
import { import {
Divider, Divider,
Button, Button,
Tag,
Row, Row,
Col, Col,
Collapsible, Collapsible,
@ -46,6 +45,7 @@ import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons';
* @param {number} collapseHeight 折叠时的高度默认200 * @param {number} collapseHeight 折叠时的高度默认200
* @param {boolean} withCheckbox 是否启用前缀 Checkbox 来控制激活状态 * @param {boolean} withCheckbox 是否启用前缀 Checkbox 来控制激活状态
* @param {boolean} loading 是否处于加载状态 * @param {boolean} loading 是否处于加载状态
* @param {string} variant 颜色变体: 'violet' | 'teal' | 'amber' | 'rose' | 'green'不传则使用默认蓝色
*/ */
const SelectableButtonGroup = ({ const SelectableButtonGroup = ({
title, title,
@ -58,6 +58,7 @@ const SelectableButtonGroup = ({
collapseHeight = 200, collapseHeight = 200,
withCheckbox = false, withCheckbox = false,
loading = false, loading = false,
variant,
}) => { }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [skeletonCount] = useState(12); const [skeletonCount] = useState(12);
@ -178,9 +179,6 @@ const SelectableButtonGroup = ({
) : ( ) : (
<Row gutter={gutterSize} style={{ lineHeight: '32px', ...style }}> <Row gutter={gutterSize} style={{ lineHeight: '32px', ...style }}>
{items.map((item) => { {items.map((item) => {
const isDisabled =
item.disabled ||
(typeof item.tagCount === 'number' && item.tagCount === 0);
const isActive = Array.isArray(activeValue) const isActive = Array.isArray(activeValue)
? activeValue.includes(item.value) ? activeValue.includes(item.value)
: activeValue === item.value; : activeValue === item.value;
@ -194,13 +192,11 @@ const SelectableButtonGroup = ({
}} }}
theme={isActive ? 'light' : 'outline'} theme={isActive ? 'light' : 'outline'}
type={isActive ? 'primary' : 'tertiary'} type={isActive ? 'primary' : 'tertiary'}
disabled={isDisabled}
className='sbg-button' className='sbg-button'
icon={ icon={
<Checkbox <Checkbox
checked={isActive} checked={isActive}
onChange={() => onChange(item.value)} onChange={() => onChange(item.value)}
disabled={isDisabled}
style={{ pointerEvents: 'auto' }} style={{ pointerEvents: 'auto' }}
/> />
} }
@ -210,14 +206,9 @@ const SelectableButtonGroup = ({
{item.icon && <span className='sbg-icon'>{item.icon}</span>} {item.icon && <span className='sbg-icon'>{item.icon}</span>}
<ConditionalTooltipText text={item.label} /> <ConditionalTooltipText text={item.label} />
{item.tagCount !== undefined && shouldShowTags && ( {item.tagCount !== undefined && shouldShowTags && (
<Tag <span className={`sbg-badge ${isActive ? 'sbg-badge-active' : ''}`}>
className='sbg-tag'
color='white'
shape='circle'
size='small'
>
{item.tagCount} {item.tagCount}
</Tag> </span>
)} )}
</div> </div>
</Button> </Button>
@ -231,22 +222,16 @@ const SelectableButtonGroup = ({
onClick={() => onChange(item.value)} onClick={() => onChange(item.value)}
theme={isActive ? 'light' : 'outline'} theme={isActive ? 'light' : 'outline'}
type={isActive ? 'primary' : 'tertiary'} type={isActive ? 'primary' : 'tertiary'}
disabled={isDisabled}
className='sbg-button' className='sbg-button'
style={{ width: '100%' }} style={{ width: '100%' }}
> >
<div className='sbg-content'> <div className='sbg-content'>
{item.icon && <span className='sbg-icon'>{item.icon}</span>} {item.icon && <span className='sbg-icon'>{item.icon}</span>}
<ConditionalTooltipText text={item.label} /> <ConditionalTooltipText text={item.label} />
{item.tagCount !== undefined && shouldShowTags && ( {item.tagCount !== undefined && shouldShowTags && item.tagCount !== '' && (
<Tag <span className={`sbg-badge ${isActive ? 'sbg-badge-active' : ''}`}>
className='sbg-tag'
color='white'
shape='circle'
size='small'
>
{item.tagCount} {item.tagCount}
</Tag> </span>
)} )}
</div> </div>
</Button> </Button>
@ -258,7 +243,7 @@ const SelectableButtonGroup = ({
return ( return (
<div <div
className={`mb-8 ${containerWidth <= 400 ? 'sbg-compact' : ''}`} className={`mb-8 ${containerWidth <= 400 ? 'sbg-compact' : ''}${variant ? ` sbg-variant-${variant}` : ''}`}
ref={containerRef} ref={containerRef}
> >
{title && ( {title && (

View File

@ -76,7 +76,6 @@ const PricingEndpointTypes = ({
value: 'all', value: 'all',
label: t('全部端点'), label: t('全部端点'),
tagCount: getEndpointTypeCount('all'), tagCount: getEndpointTypeCount('all'),
disabled: models.length === 0,
}, },
...availableEndpointTypes.map((endpointType) => { ...availableEndpointTypes.map((endpointType) => {
const count = getEndpointTypeCount(endpointType); const count = getEndpointTypeCount(endpointType);
@ -84,7 +83,6 @@ const PricingEndpointTypes = ({
value: endpointType, value: endpointType,
label: getEndpointTypeLabel(endpointType), label: getEndpointTypeLabel(endpointType),
tagCount: count, tagCount: count,
disabled: count === 0,
}; };
}), }),
]; ];
@ -96,6 +94,7 @@ const PricingEndpointTypes = ({
activeValue={filterEndpointType} activeValue={filterEndpointType}
onChange={setFilterEndpointType} onChange={setFilterEndpointType}
loading={loading} loading={loading}
variant='green'
t={t} t={t}
/> />
); );

View File

@ -52,20 +52,19 @@ const PricingGroups = ({
.length; .length;
let ratioDisplay = ''; let ratioDisplay = '';
if (g === 'all') { if (g === 'all') {
ratioDisplay = t('全部'); // ratioDisplay = t('');
} else { } else {
const ratio = groupRatio[g]; const ratio = groupRatio[g];
if (ratio !== undefined && ratio !== null) { if (ratio !== undefined && ratio !== null) {
ratioDisplay = `x${ratio}`; ratioDisplay = `${ratio}x`;
} else { } else {
ratioDisplay = 'x1'; ratioDisplay = '1x';
} }
} }
return { return {
value: g, value: g,
label: g === 'all' ? t('全部分组') : g, label: g === 'all' ? t('全部分组') : g,
tagCount: ratioDisplay, tagCount: ratioDisplay,
disabled: modelCount === 0,
}; };
}); });
@ -76,6 +75,7 @@ const PricingGroups = ({
activeValue={filterGroup} activeValue={filterGroup}
onChange={setFilterGroup} onChange={setFilterGroup}
loading={loading} loading={loading}
variant='teal'
t={t} t={t}
/> />
); );

View File

@ -52,6 +52,7 @@ const PricingQuotaTypes = ({
activeValue={filterQuotaType} activeValue={filterQuotaType}
onChange={setFilterQuotaType} onChange={setFilterQuotaType}
loading={loading} loading={loading}
variant='amber'
t={t} t={t}
/> />
); );

View File

@ -78,7 +78,6 @@ const PricingTags = ({
value: 'all', value: 'all',
label: t('全部标签'), label: t('全部标签'),
tagCount: getTagCount('all'), tagCount: getTagCount('all'),
disabled: models.length === 0,
}, },
]; ];
@ -88,7 +87,6 @@ const PricingTags = ({
value: tag, value: tag,
label: tag, label: tag,
tagCount: count, tagCount: count,
disabled: count === 0,
}); });
}); });
@ -102,6 +100,7 @@ const PricingTags = ({
activeValue={filterTag} activeValue={filterTag}
onChange={setFilterTag} onChange={setFilterTag}
loading={loading} loading={loading}
variant='rose'
t={t} t={t}
/> />
); );

View File

@ -83,7 +83,6 @@ const PricingVendors = ({
value: 'all', value: 'all',
label: t('全部供应商'), label: t('全部供应商'),
tagCount: getVendorCount('all'), tagCount: getVendorCount('all'),
disabled: models.length === 0,
}, },
]; ];
@ -96,7 +95,6 @@ const PricingVendors = ({
label: vendor, label: vendor,
icon: icon ? getLobeHubIcon(icon, 16) : null, icon: icon ? getLobeHubIcon(icon, 16) : null,
tagCount: count, tagCount: count,
disabled: count === 0,
}); });
}); });
@ -107,7 +105,6 @@ const PricingVendors = ({
value: 'unknown', value: 'unknown',
label: t('未知供应商'), label: t('未知供应商'),
tagCount: count, tagCount: count,
disabled: count === 0,
}); });
} }
@ -121,6 +118,7 @@ const PricingVendors = ({
activeValue={filterVendor} activeValue={filterVendor}
onChange={setFilterVendor} onChange={setFilterVendor}
loading={loading} loading={loading}
variant='violet'
t={t} t={t}
/> />
); );

View File

@ -113,15 +113,6 @@ const PricingSidebar = ({
t={t} t={t}
/> />
<PricingTags
filterTag={filterTag}
setFilterTag={setFilterTag}
models={tagModels}
allModels={categoryProps.models}
loading={loading}
t={t}
/>
<PricingGroups <PricingGroups
filterGroup={filterGroup} filterGroup={filterGroup}
setFilterGroup={handleGroupClick} setFilterGroup={handleGroupClick}
@ -140,6 +131,15 @@ const PricingSidebar = ({
t={t} t={t}
/> />
<PricingTags
filterTag={filterTag}
setFilterTag={setFilterTag}
models={tagModels}
allModels={categoryProps.models}
loading={loading}
t={t}
/>
<PricingEndpointTypes <PricingEndpointTypes
filterEndpointType={filterEndpointType} filterEndpointType={filterEndpointType}
setFilterEndpointType={setFilterEndpointType} setFilterEndpointType={setFilterEndpointType}

View File

@ -96,15 +96,6 @@ const FilterModalContent = ({ sidebarProps, t }) => {
t={t} t={t}
/> />
<PricingTags
filterTag={filterTag}
setFilterTag={setFilterTag}
models={tagModels}
allModels={categoryProps.models}
loading={loading}
t={t}
/>
<PricingGroups <PricingGroups
filterGroup={filterGroup} filterGroup={filterGroup}
setFilterGroup={setFilterGroup} setFilterGroup={setFilterGroup}
@ -123,6 +114,16 @@ const FilterModalContent = ({ sidebarProps, t }) => {
t={t} t={t}
/> />
<PricingTags
filterTag={filterTag}
setFilterTag={setFilterTag}
models={tagModels}
allModels={categoryProps.models}
loading={loading}
t={t}
/>
<PricingEndpointTypes <PricingEndpointTypes
filterEndpointType={filterEndpointType} filterEndpointType={filterEndpointType}
setFilterEndpointType={setFilterEndpointType} setFilterEndpointType={setFilterEndpointType}

96
web/src/index.css vendored
View File

@ -344,6 +344,102 @@ code {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* Badge for count/multiplier in filter buttons */
.sbg-badge {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
min-width: 18px;
height: 18px;
padding: 0 6px;
border-radius: 9px;
font-size: 11px;
font-weight: 600;
font-variant-numeric: tabular-nums;
line-height: 1;
background-color: var(--semi-color-fill-0);
color: var(--semi-color-text-2);
transition: background-color 0.15s ease, color 0.15s ease;
}
.sbg-badge-active {
background-color: var(--semi-color-primary-light-active);
color: var(--semi-color-primary);
}
/* ---- SelectableButtonGroup color variants ---- */
.sbg-variant-violet {
--semi-color-primary: #6d28d9;
--semi-color-primary-light-default: rgba(124, 58, 237, 0.08);
--semi-color-primary-light-hover: rgba(124, 58, 237, 0.15);
--semi-color-primary-light-active: rgba(124, 58, 237, 0.22);
}
.sbg-variant-teal {
--semi-color-primary: #0f766e;
--semi-color-primary-light-default: rgba(20, 184, 166, 0.08);
--semi-color-primary-light-hover: rgba(20, 184, 166, 0.15);
--semi-color-primary-light-active: rgba(20, 184, 166, 0.22);
}
.sbg-variant-amber {
--semi-color-primary: #b45309;
--semi-color-primary-light-default: rgba(245, 158, 11, 0.08);
--semi-color-primary-light-hover: rgba(245, 158, 11, 0.15);
--semi-color-primary-light-active: rgba(245, 158, 11, 0.22);
}
.sbg-variant-rose {
--semi-color-primary: #be123c;
--semi-color-primary-light-default: rgba(244, 63, 94, 0.08);
--semi-color-primary-light-hover: rgba(244, 63, 94, 0.15);
--semi-color-primary-light-active: rgba(244, 63, 94, 0.22);
}
.sbg-variant-green {
--semi-color-primary: #047857;
--semi-color-primary-light-default: rgba(16, 185, 129, 0.08);
--semi-color-primary-light-hover: rgba(16, 185, 129, 0.15);
--semi-color-primary-light-active: rgba(16, 185, 129, 0.22);
}
/* Dark mode: lighter text, slightly stronger backgrounds */
html.dark .sbg-variant-violet {
--semi-color-primary: #a78bfa;
--semi-color-primary-light-default: rgba(139, 92, 246, 0.14);
--semi-color-primary-light-hover: rgba(139, 92, 246, 0.22);
--semi-color-primary-light-active: rgba(139, 92, 246, 0.3);
}
html.dark .sbg-variant-teal {
--semi-color-primary: #2dd4bf;
--semi-color-primary-light-default: rgba(45, 212, 191, 0.14);
--semi-color-primary-light-hover: rgba(45, 212, 191, 0.22);
--semi-color-primary-light-active: rgba(45, 212, 191, 0.3);
}
html.dark .sbg-variant-amber {
--semi-color-primary: #fbbf24;
--semi-color-primary-light-default: rgba(251, 191, 36, 0.14);
--semi-color-primary-light-hover: rgba(251, 191, 36, 0.22);
--semi-color-primary-light-active: rgba(251, 191, 36, 0.3);
}
html.dark .sbg-variant-rose {
--semi-color-primary: #fb7185;
--semi-color-primary-light-default: rgba(251, 113, 133, 0.14);
--semi-color-primary-light-hover: rgba(251, 113, 133, 0.22);
--semi-color-primary-light-active: rgba(251, 113, 133, 0.3);
}
html.dark .sbg-variant-green {
--semi-color-primary: #34d399;
--semi-color-primary-light-default: rgba(52, 211, 153, 0.14);
--semi-color-primary-light-hover: rgba(52, 211, 153, 0.22);
--semi-color-primary-light-active: rgba(52, 211, 153, 0.3);
}
/* Tabs组件样式 */ /* Tabs组件样式 */
.semi-tabs-content { .semi-tabs-content {
padding: 0 !important; padding: 0 !important;