-
-
Notifications
You must be signed in to change notification settings - Fork 97
/
Copy pathSegmentedField.tsx
104 lines (93 loc) · 2.7 KB
/
SegmentedField.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import * as styles from './SegmentedField.css';
import {
createMemo,
createSignal,
For,
JSX,
JSXElement,
on,
onCleanup,
onMount,
} from 'solid-js';
import clsx from 'clsx';
import {assignInlineVars} from '@vanilla-extract/dynamic';
import {useText, UseTextProps} from '../Text';
import {Box} from '../Box';
export interface SegmentedFieldItem<T> {
label: string | JSXElement;
value: T;
}
interface SegmentedFieldProps<T> {
items: readonly SegmentedFieldItem<T>[];
value: T;
onChange?: (value: T) => void;
size?: UseTextProps['size'];
id?: string;
}
export function SegmentedField<T>(props: SegmentedFieldProps<T>): JSX.Element {
const [container, setContainer] = createSignal<HTMLDivElement>();
const [width, setWidth] = createSignal(0);
onMount(() => {
const containerValue = container();
if (containerValue) {
setWidth(containerValue.clientWidth);
}
const observer = new ResizeObserver(([entry]) =>
requestAnimationFrame(() => setWidth(entry.target.clientWidth)),
);
observer.observe(container() as HTMLElement);
return onCleanup(() => observer.disconnect());
});
const activeIndex = createMemo(() =>
props.items.findIndex(item => item.value === props.value),
);
const activeItem = createMemo(() => {
const ref = container();
if (!ref) return undefined;
return ref.querySelectorAll('[data-index]').item(activeIndex()) as
| HTMLElement
| undefined;
});
const segmentWidth = createMemo(
on([width, activeItem], ([, activeItem]) => {
if (!activeItem) {
return `calc(100% / ${props.items.length})`;
}
return `${activeItem.clientWidth}px`;
}),
);
const activeSegmentOffset = () => {
const item = activeItem();
if (!item) {
return `calc(${segmentWidth()} * ${activeIndex()})`;
}
return `${item.offsetLeft}px`;
};
const segmentedTextStyle = useText(props);
return (
<Box class={clsx(styles.wrapper)} id={props.id}>
<div class={styles.box} ref={setContainer}>
<div
style={assignInlineVars({
[styles.segmentedFieldVars.activeSegmentedWidth]: segmentWidth(),
[styles.segmentedFieldVars.activeSegmentedOffset]:
activeSegmentOffset(),
})}
class={clsx(styles.segmentActive)}
/>
<For each={props.items}>
{(item, index) => (
<div
data-index={index()}
class={clsx(styles.segment, segmentedTextStyle())}
data-active={index() === activeIndex()}
onClick={() => props.onChange?.(item.value)}
>
{item.label}
</div>
)}
</For>
</div>
</Box>
);
}