通用组件开发

This commit is contained in:
juguohong
2025-08-17 00:00:56 +08:00
parent 86e14cb445
commit 4f6ca73148
25 changed files with 2554 additions and 24 deletions

View File

@@ -0,0 +1,152 @@
import React, { useState, useEffect } from 'react';
import './index.scss';
import BubbleItem from './BubbleItem';
export interface BubbleOption {
id: string | number;
label: string;
value: string | number;
disabled?: boolean;
icon?: React.ReactNode;
description?: string;
}
export interface BubbleProps {
options: BubbleOption[];
value?: string | number | (string | number)[];
onChange?: (value: string | number | (string | number)[], option: BubbleOption | BubbleOption[]) => void;
multiple?: boolean;
layout?: 'horizontal' | 'vertical' | 'grid';
columns?: number;
size?: 'small' | 'medium' | 'large';
className?: string;
style?: React.CSSProperties;
disabled?: boolean;
}
const Bubble: React.FC<BubbleProps> = ({
options,
value,
onChange,
multiple = false,
layout = 'horizontal',
columns = 3,
size = 'small',
className = '',
style = {},
disabled = false
}) => {
const [selectedValues, setSelectedValues] = useState<(string | number)[]>([]);
// 同步外部传入的value
useEffect(() => {
if (value !== undefined) {
const newValues = Array.isArray(value) ? value : [value];
setSelectedValues(newValues);
}
}, [value]);
const handleOptionClick = (option: BubbleOption) => {
if (disabled || option.disabled) return;
let newSelectedValues: (string | number)[];
if (multiple) {
if (selectedValues.includes(option.value)) {
newSelectedValues = selectedValues.filter(v => v !== option.value);
} else {
newSelectedValues = [...selectedValues, option.value];
}
} else {
newSelectedValues = [option.value];
}
setSelectedValues(newSelectedValues);
// 调用onChange回调传递选中的值和对应的选项
if (onChange) {
if (multiple) {
const selectedOptions = options.filter(opt => newSelectedValues.includes(opt.value));
onChange(newSelectedValues, selectedOptions);
} else {
onChange(option.value, option);
}
}
};
const isSelected = (option: BubbleOption) => {
return selectedValues.includes(option.value);
};
const renderHorizontalLayout = () => (
<div className="bubble-horizontal">
{options.map((option) => (
<BubbleItem
key={option.id}
option={option}
isSelected={isSelected(option)}
size={size}
disabled={disabled}
onClick={handleOptionClick}
/>
))}
</div>
);
const renderVerticalLayout = () => (
<div className="bubble-vertical">
{options.map((option) => (
<BubbleItem
key={option.id}
option={option}
isSelected={isSelected(option)}
size={size}
disabled={disabled}
onClick={handleOptionClick}
/>
))}
</div>
);
const renderGridLayout = () => (
<div
className="bubble-grid"
style={{
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: size === 'small' ? '8px' : size === 'large' ? '16px' : '12px'
}}
>
{options.map((option) => (
<BubbleItem
key={option.id}
option={option}
isSelected={isSelected(option)}
size={size}
disabled={disabled}
onClick={handleOptionClick}
/>
))}
</div>
);
const renderLayout = () => {
switch (layout) {
case 'horizontal':
return renderHorizontalLayout();
case 'vertical':
return renderVerticalLayout();
case 'grid':
return renderGridLayout();
default:
return renderHorizontalLayout();
}
};
return (
<div className={`bubble-container ${className}`} style={style}>
{renderLayout()}
</div>
);
};
export default Bubble;