140 lines
3.5 KiB
TypeScript
140 lines
3.5 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
||
import styles from "./index.module.scss";
|
||
import BubbleItem from "./BubbleItem";
|
||
import {BubbleProps} from '../../../types/list/types'
|
||
|
||
const Bubble: React.FC<BubbleProps> = ({
|
||
options,
|
||
value,
|
||
onChange,
|
||
multiple = false,
|
||
layout = "horizontal",
|
||
columns = 3,
|
||
size = "small",
|
||
className = "",
|
||
itemClassName = "",
|
||
style = {},
|
||
disabled = false,
|
||
name,
|
||
}) => {
|
||
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(name, newSelectedValues, selectedOptions);
|
||
} else {
|
||
console.log('===111', name, option.value)
|
||
onChange(name, option.value, option);
|
||
}
|
||
}
|
||
};
|
||
|
||
const isSelected = (option: BubbleOption) => {
|
||
return selectedValues.includes(option.value);
|
||
};
|
||
|
||
const renderHorizontalLayout = () => (
|
||
<div className={styles.bubbleHorizontal}>
|
||
{options.map((option) => (
|
||
<BubbleItem
|
||
key={option.id}
|
||
option={option}
|
||
isSelected={isSelected(option)}
|
||
size={size}
|
||
disabled={disabled}
|
||
onClick={handleOptionClick}
|
||
itemClassName={itemClassName}
|
||
/>
|
||
))}
|
||
</div>
|
||
);
|
||
|
||
const renderVerticalLayout = () => (
|
||
<div className={styles.bubbleVertical}>
|
||
{options.map((option) => (
|
||
<BubbleItem
|
||
key={option.id}
|
||
option={option}
|
||
isSelected={isSelected(option)}
|
||
size={size}
|
||
disabled={disabled}
|
||
onClick={handleOptionClick}
|
||
itemClassName={itemClassName}
|
||
/>
|
||
))}
|
||
</div>
|
||
);
|
||
|
||
const renderGridLayout = () => (
|
||
<div
|
||
className={styles.bubbleGrid}
|
||
style={{
|
||
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
||
gap: size === "small" ? "6px" : size === "large" ? "16px" : "12px",
|
||
}}
|
||
>
|
||
{options.map((option) => (
|
||
<BubbleItem
|
||
key={option.id}
|
||
option={option}
|
||
isSelected={isSelected(option)}
|
||
size={size}
|
||
disabled={disabled}
|
||
onClick={handleOptionClick}
|
||
itemClassName={itemClassName || styles.bubbleOption}
|
||
/>
|
||
))}
|
||
</div>
|
||
);
|
||
|
||
const renderLayout = () => {
|
||
switch (layout) {
|
||
case "horizontal":
|
||
return renderHorizontalLayout();
|
||
case "vertical":
|
||
return renderVerticalLayout();
|
||
case "grid":
|
||
return renderGridLayout();
|
||
default:
|
||
return renderHorizontalLayout();
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className={`${styles.bubbleContainer} ${className}`} style={style}>
|
||
{renderLayout()}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Bubble;
|