Files
comfy-downloader/src/components/Segments.tsx
Vixalie 5523047b69 feat(components): 添加默认选中功能
- 在 Check、Radio、RadioGroup 和 Segments 组件中添加默认选中值属性
- 使用 onMount 在组件挂载时设置默认选中值
- 优化了组件的初始化逻辑,确保默认值能够正确显示
2025-08-13 22:48:01 +08:00

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;