- 在 Check、Radio、RadioGroup 和 Segments 组件中添加默认选中值属性 - 使用 onMount 在组件挂载时设置默认选中值 - 优化了组件的初始化逻辑,确保默认值能够正确显示
122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import cx from 'clsx';
|
|
import { isNotNil } from 'es-toolkit';
|
|
import {
|
|
Component,
|
|
createEffect,
|
|
createMemo,
|
|
createSignal,
|
|
Index,
|
|
JSX,
|
|
mergeProps,
|
|
onMount,
|
|
Show,
|
|
} from 'solid-js';
|
|
|
|
interface Option {
|
|
label: string | JSX.Element;
|
|
value: string | number | symbol | boolean;
|
|
}
|
|
|
|
interface SegmentsProps {
|
|
name?: string;
|
|
options: Option[];
|
|
value?: Option['value'];
|
|
defaultValue?: Option['value'];
|
|
direction?: 'horizontal' | 'vertical';
|
|
disabled?: boolean;
|
|
onChange?: (value: Option['value'] | undefined) => void;
|
|
}
|
|
|
|
const Segments: Component<SegmentsProps> = (props) => {
|
|
const mProps = mergeProps<SegmentsProps[]>(
|
|
{
|
|
options: [],
|
|
disabled: false,
|
|
direction: 'horizontal',
|
|
},
|
|
props,
|
|
);
|
|
const optionRefs: HTMLDivElement[] = [];
|
|
|
|
const originalValue = createMemo(() => mProps.value);
|
|
const [selected, setSelected] = createSignal<Option['value'] | undefined>(undefined);
|
|
const [indicatorTop, setIndicatorTop] = createSignal(0);
|
|
const [indicatorLeft, setIndicatorLeft] = createSignal(0);
|
|
const [indicatorHeight, setIndicatorHeight] = createSignal(0);
|
|
const [indicatorWidth, setIndicatorWidth] = createSignal(0);
|
|
|
|
onMount(() => {
|
|
if (isNotNil(mProps.defaultValue)) {
|
|
setSelected(mProps.defaultValue);
|
|
}
|
|
});
|
|
createEffect(() => {
|
|
if (isNotNil(originalValue()) && originalValue() !== selected()) {
|
|
setSelected(originalValue());
|
|
}
|
|
});
|
|
createEffect(() => {
|
|
const selectedIndex = mProps.options?.findIndex((option) => option.value === selected());
|
|
if (isNotNil(selectedIndex) && optionRefs.length > 0 && optionRefs.length > selectedIndex) {
|
|
const optionElement = optionRefs[selectedIndex];
|
|
if (isNotNil(optionElement)) {
|
|
setIndicatorTop(optionElement.offsetTop);
|
|
setIndicatorLeft(optionElement.offsetLeft);
|
|
setIndicatorHeight(optionElement.offsetHeight);
|
|
setIndicatorWidth(optionElement.offsetWidth);
|
|
}
|
|
}
|
|
});
|
|
|
|
const handleSelect = (value: Option['value']) => {
|
|
if (mProps.disabled) {
|
|
return;
|
|
}
|
|
setSelected(value);
|
|
mProps.onChange?.(value);
|
|
};
|
|
|
|
return (
|
|
<div class="inline-block overflow-hidden rounded-sm p-1 select-none bg-neutral">
|
|
<div
|
|
class={cx(
|
|
'relative flex',
|
|
mProps.direction === 'horizontal' ? 'flex-row items-center' : 'flex-col',
|
|
)}>
|
|
<Index each={mProps.options}>
|
|
{(option, index) => (
|
|
<div
|
|
ref={(el) => (optionRefs[index] = el)}
|
|
aria-disabled={mProps.disabled}
|
|
class={cx(
|
|
'z-[5] cursor-pointer rounded-sm px-2 py-1',
|
|
selected() === option().value
|
|
? 'not-aria-disabled:text-on-primary-surface aria-disabled:text-primary-disabled hover:not-aria-disabled:bg-primary-surface-hover/45'
|
|
: 'not-aria-disabled:text-on-surface aria-disabled:text-surface-disabled hover:not-aria-disabled:bg-surface/35',
|
|
)}
|
|
onClick={() => handleSelect(option().value)}>
|
|
{option().label}
|
|
</div>
|
|
)}
|
|
</Index>
|
|
<Show when={isNotNil(selected())}>
|
|
<div
|
|
class="pointer-events-none absolute z-[2] rounded-sm bg-primary-surface transition-[top,left,height,width] duration-200 ease-in-out"
|
|
style={{
|
|
top: `${indicatorTop()}px`,
|
|
left: `${indicatorLeft()}px`,
|
|
height: `${indicatorHeight()}px`,
|
|
width: `${indicatorWidth()}px`,
|
|
}}
|
|
/>
|
|
</Show>
|
|
</div>
|
|
<Show when={isNotNil(mProps.name)}>
|
|
<input type="hidden" name={mProps.name} value={selected()} />
|
|
</Show>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Segments;
|