增加SegmentedControl选择组件。

This commit is contained in:
徐涛 2024-12-30 14:25:35 +08:00
parent 2a125d9432
commit 927950680c
2 changed files with 97 additions and 0 deletions

View File

@ -0,0 +1,41 @@
@layer components {
.segmented_control {
display: inline-block;
border-radius: var(--border-radius-xxs);
overflow: hidden;
user-select: none;
.options {
display: flex;
position: relative;
.option {
border-radius: var(--border-radius-xxs);
padding: var(--spacing-xxs) var(--spacing-xs);
line-height: 1.5em;
white-space: nowrap;
cursor: pointer;
z-index: 5;
transition: background-color 300ms;
&.selected {
background-color: var(--color-primary-active);
}
&:hover {
background-color: var(--color-primary-hover);
}
&:active {
background-color: var(--color-primary-active);
}
}
.slider {
position: absolute;
top: 0;
bottom: 0;
transition: left 300ms ease, width 300ms ease;
pointer-events: none;
border-radius: var(--border-radius-xxs);
background-color: var(--color-primary-active);
height: 100%;
z-index: 2;
}
}
}
}

View File

@ -0,0 +1,56 @@
import cx from 'clsx';
import { isEqual, isNil } from 'lodash-es';
import { useCallback, useRef, useState } from 'react';
import styles from './SegmentedControl.module.css';
type Option = {
label: string;
value: string | number | null;
};
type SegmentedConttrolProps = {
options?: Option[];
value?: Option['value'];
onChange?: (value: Option['value']) => void;
};
export function SegmentedControl({ options = [], value, onChange }: SegmentedConttrolProps) {
const [selected, setSelected] = useState(value ?? options[0].value ?? null);
const [sliderPosition, setSliderPosition] = useState(0);
const [sliderWidth, setSliderWidth] = useState(0);
const sliderRef = useRef<HTMLDivElement>(null);
const optionsRef = useRef<HTMLDivElement[]>([]);
const handleSelectAction = useCallback((option: Option['value'], index: number) => {
setSelected(option);
onChange?.(option);
if (optionsRef.current && optionsRef.current.length > index) {
const optionElement = optionsRef.current[index];
setSliderPosition(optionElement.offsetLeft);
setSliderWidth(optionElement.offsetWidth);
}
}, []);
return (
<div className={styles.segmented_control}>
<div className={styles.options}>
{options.map((option, index) => (
<div
key={`${index}_${option.value}`}
className={cx(styles.option, isEqual(selected, option.value) && styles.selected)}
ref={(el) => (optionsRef.current[index] = el!)}
onClick={() => handleSelectAction(option.value, index)}>
{option.label}
</div>
))}
{!isNil(selected) && (
<div
className={styles.slider}
ref={sliderRef}
style={{ left: `${sliderPosition}px`, width: `${sliderWidth}px` }}
/>
)}
</div>
</div>
);
}