diff --git a/src/components/Segments.tsx b/src/components/Segments.tsx new file mode 100644 index 0000000..9d6bf14 --- /dev/null +++ b/src/components/Segments.tsx @@ -0,0 +1,109 @@ +import cx from 'clsx'; +import { isNotNil } from 'es-toolkit'; +import { + Component, + createEffect, + createMemo, + createSignal, + Index, + JSX, + mergeProps, + Show, +} from 'solid-js'; + +interface Option { + label: string | JSX.Element; + value: string | number | symbol | boolean; +} + +interface SegmentsProps { + name?: string; + options: Option[]; + value?: Option['value']; + direction?: 'horizontal' | 'vertical'; + disabled?: boolean; + onChange?: (value: Option['value'] | undefined) => void; +} + +export const HSegmengts: Component = (props) => { + const mProps = mergeProps( + { + options: [], + disabled: false, + direction: 'horizontal', + }, + props, + ); + const optionRefs: HTMLDivElement[] = []; + + const originalValue = createMemo(() => mProps.value); + const [selected, setSelected] = createSignal(undefined); + const [indicatorTop, setIndicatorTop] = createSignal(0); + const [indicatorLeft, setIndicatorLeft] = createSignal(0); + const [indicatorHeight, setIndicatorHeight] = createSignal(0); + const [indicatorWidth, setIndicatorWidth] = createSignal(0); + const indicatorStyle = createMemo(() => ({ + top: `${indicatorTop()}px`, + left: `${indicatorLeft()}px`, + height: `${indicatorHeight()}px`, + width: `${indicatorWidth()}px`, + })); + + 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']) => { + setSelected(value); + mProps.onChange?.(value); + }; + + return ( +
+
+ + {(option, index) => ( +
(optionRefs[index] = el)} + class={cx( + 'z-[5] cursor-pointer rounded-sm px-2 py-1', + selected() === option().value + ? 'text-on-primary-surface hover:bg-primary-surface-hover/45' + : 'text-on-surface hover:bg-surface/35', + )} + onClick={() => handleSelect(option().value)}> + {option().label} +
+ )} +
+ +
+ +
+ + + +
+ ); +};