筛选组件开发
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { BubbleOption } from './index';
|
||||
import './bubbleItem.scss';
|
||||
import styles from './bubbleItem.module.scss';
|
||||
|
||||
export interface BubbleItemProps {
|
||||
option: BubbleOption;
|
||||
@@ -8,6 +8,7 @@ export interface BubbleItemProps {
|
||||
size: 'small' | 'medium' | 'large';
|
||||
disabled: boolean;
|
||||
onClick: (option: BubbleOption) => void;
|
||||
itemClassName?: string;
|
||||
}
|
||||
|
||||
const BubbleItem: React.FC<BubbleItemProps> = ({
|
||||
@@ -15,19 +16,20 @@ const BubbleItem: React.FC<BubbleItemProps> = ({
|
||||
isSelected,
|
||||
size,
|
||||
disabled,
|
||||
onClick
|
||||
onClick,
|
||||
itemClassName
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={`bubble-option ${size} ${isSelected ? 'selected' : ''} ${
|
||||
option.disabled || disabled ? 'disabled' : ''
|
||||
}`}
|
||||
className={`${styles.bubbleOption} ${size} ${isSelected ? styles.selected : ''} ${
|
||||
option.disabled || disabled ? styles.disabled : ''
|
||||
} ${itemClassName ? itemClassName : ''}`}
|
||||
onClick={() => onClick(option)}
|
||||
disabled={option.disabled || disabled}
|
||||
>
|
||||
{option.icon && <span className="bubble-icon">{option.icon}</span>}
|
||||
<span className="bubble-label">{option.label}</span>
|
||||
{option.description && <span className="bubble-description">{option.description}</span>}
|
||||
{option.icon && <span className={ styles.bubbleIcon}>{option.icon}</span>}
|
||||
<span className={styles.bubbleLabel}>{option.label}</span>
|
||||
{option.description && <span className={ styles.bubbleDescription}>{option.description}</span>}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.bubble-option {
|
||||
.bubbleOption {
|
||||
position: relative;
|
||||
border: 1px solid #e5e5e5;
|
||||
outline: none; // 移除浏览器默认的outline
|
||||
@@ -14,11 +14,11 @@
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
border-radius: 28px;
|
||||
margin: 0;
|
||||
height: 36px;
|
||||
width: 116px;
|
||||
height: 28px;
|
||||
|
||||
// 移除浏览器默认样式
|
||||
&:focus {
|
||||
@@ -38,8 +38,6 @@
|
||||
|
||||
// 尺寸变体
|
||||
&.small {
|
||||
padding: 8px 12px;
|
||||
min-height: 32px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -71,7 +69,7 @@
|
||||
}
|
||||
|
||||
// 图标样式
|
||||
.bubble-icon {
|
||||
.bubbleIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -83,12 +81,12 @@
|
||||
}
|
||||
|
||||
// 标签样式
|
||||
.bubble-label {
|
||||
.bubbleLabel {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 描述样式
|
||||
.bubble-description {
|
||||
.bubbleDescription {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
font-weight: 400;
|
||||
@@ -1,34 +1,23 @@
|
||||
.bubble-container {
|
||||
.bubbleContainer {
|
||||
width: 100%;
|
||||
|
||||
// 水平布局
|
||||
.bubble-horizontal {
|
||||
.bubbleHorizontal {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.bubble-option {
|
||||
$gap: 8px;
|
||||
$count: 3;
|
||||
flex-basis: calc(100% / $count - $gap * 2);
|
||||
gap: $gap;
|
||||
}
|
||||
}
|
||||
|
||||
// 垂直布局
|
||||
.bubble-vertical {
|
||||
.bubbleVertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.bubble-option {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 网格布局
|
||||
.bubble-grid {
|
||||
.bubbleGrid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './index.scss';
|
||||
import BubbleItem from './BubbleItem';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import styles from "./index.module.scss";
|
||||
import BubbleItem from "./BubbleItem";
|
||||
|
||||
export interface BubbleOption {
|
||||
id: string | number;
|
||||
@@ -14,12 +14,16 @@ export interface BubbleOption {
|
||||
export interface BubbleProps {
|
||||
options: BubbleOption[];
|
||||
value?: string | number | (string | number)[];
|
||||
onChange?: (value: string | number | (string | number)[], option: BubbleOption | BubbleOption[]) => void;
|
||||
onChange?: (
|
||||
value: string | number | (string | number)[],
|
||||
option: BubbleOption | BubbleOption[]
|
||||
) => void;
|
||||
multiple?: boolean;
|
||||
layout?: 'horizontal' | 'vertical' | 'grid';
|
||||
layout?: "horizontal" | "vertical" | "grid";
|
||||
columns?: number;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
size?: "small" | "medium" | "large";
|
||||
className?: string;
|
||||
itemClassName?: string;
|
||||
style?: React.CSSProperties;
|
||||
disabled?: boolean;
|
||||
}
|
||||
@@ -29,12 +33,13 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
multiple = false,
|
||||
layout = 'horizontal',
|
||||
layout = "horizontal",
|
||||
columns = 3,
|
||||
size = 'small',
|
||||
className = '',
|
||||
size = "small",
|
||||
className = "",
|
||||
itemClassName = "",
|
||||
style = {},
|
||||
disabled = false
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [selectedValues, setSelectedValues] = useState<(string | number)[]>([]);
|
||||
|
||||
@@ -53,7 +58,7 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
|
||||
if (multiple) {
|
||||
if (selectedValues.includes(option.value)) {
|
||||
newSelectedValues = selectedValues.filter(v => v !== option.value);
|
||||
newSelectedValues = selectedValues.filter((v) => v !== option.value);
|
||||
} else {
|
||||
newSelectedValues = [...selectedValues, option.value];
|
||||
}
|
||||
@@ -62,11 +67,13 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
}
|
||||
|
||||
setSelectedValues(newSelectedValues);
|
||||
|
||||
|
||||
// 调用onChange回调,传递选中的值和对应的选项
|
||||
if (onChange) {
|
||||
if (multiple) {
|
||||
const selectedOptions = options.filter(opt => newSelectedValues.includes(opt.value));
|
||||
const selectedOptions = options.filter((opt) =>
|
||||
newSelectedValues.includes(opt.value)
|
||||
);
|
||||
onChange(newSelectedValues, selectedOptions);
|
||||
} else {
|
||||
onChange(option.value, option);
|
||||
@@ -79,7 +86,7 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
};
|
||||
|
||||
const renderHorizontalLayout = () => (
|
||||
<div className="bubble-horizontal">
|
||||
<div className={styles.bubbleHorizontal}>
|
||||
{options.map((option) => (
|
||||
<BubbleItem
|
||||
key={option.id}
|
||||
@@ -88,13 +95,14 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
onClick={handleOptionClick}
|
||||
itemClassName={itemClassName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderVerticalLayout = () => (
|
||||
<div className="bubble-vertical">
|
||||
<div className={styles.bubbleVertical}>
|
||||
{options.map((option) => (
|
||||
<BubbleItem
|
||||
key={option.id}
|
||||
@@ -103,17 +111,18 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
onClick={handleOptionClick}
|
||||
itemClassName={itemClassName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderGridLayout = () => (
|
||||
<div
|
||||
className="bubble-grid"
|
||||
style={{
|
||||
<div
|
||||
className={styles.bubbleGrid}
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
||||
gap: size === 'small' ? '8px' : size === 'large' ? '16px' : '12px'
|
||||
gap: size === "small" ? "8px" : size === "large" ? "16px" : "12px",
|
||||
}}
|
||||
>
|
||||
{options.map((option) => (
|
||||
@@ -124,6 +133,7 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
onClick={handleOptionClick}
|
||||
itemClassName={itemClassName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -131,11 +141,11 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
|
||||
const renderLayout = () => {
|
||||
switch (layout) {
|
||||
case 'horizontal':
|
||||
case "horizontal":
|
||||
return renderHorizontalLayout();
|
||||
case 'vertical':
|
||||
case "vertical":
|
||||
return renderVerticalLayout();
|
||||
case 'grid':
|
||||
case "grid":
|
||||
return renderGridLayout();
|
||||
default:
|
||||
return renderHorizontalLayout();
|
||||
@@ -143,7 +153,7 @@ const Bubble: React.FC<BubbleProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`bubble-container ${className}`} style={style}>
|
||||
<div className={`${styles.bubbleContainer} ${className}`} style={style}>
|
||||
{renderLayout()}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user