-
-
Notifications
You must be signed in to change notification settings - Fork 343
/
Copy pathSegment.tsx
118 lines (109 loc) · 3.28 KB
/
Segment.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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
'use client';
import { FC, useContext, useId } from 'react';
import * as radio from '@zag-js/radio-group';
import { useMachine, normalizeProps } from '@zag-js/react';
import type { SegmentProps, SegmentItemsProps } from './types.js';
import { SegmentContext } from './contexts.js';
import { noop } from '../../internal/noop.js';
const SegmentRoot: FC<SegmentProps> = ({
orientation = 'horizontal',
// Root
base = 'inline-flex items-stretch overflow-hidden',
background = 'preset-outlined-surface-200-800',
border = 'p-2',
gap = 'gap-2',
padding = '',
rounded = 'rounded-container',
width = '',
classes = '',
// States
orientVertical = 'flex-col',
orientHorizontal = 'flex-row',
stateDisabled = 'disabled',
stateReadOnly = 'pointer-events-none',
// Indicator
indicatorBase = 'top-[var(--top)] left-[var(--left)] w-[var(--width)] h-[var(--height)]',
indicatorBg = 'preset-filled',
indicatorText = 'text-surface-contrast-950 dark:text-surface-contrast-50',
indicatorRounded = 'rounded',
indicatorClasses = '',
// Events
onValueChange = noop,
// Children
children,
// Zag
...zagProps
}) => {
// Zag
const [state, send] = useMachine(
radio.machine({
id: useId(),
onValueChange: (details) => onValueChange(details.value),
orientation
}),
{ context: zagProps }
);
const api = radio.connect(state, send, normalizeProps);
// Set Context
const ctx = { api, indicatorText };
// Reactive
const rxOrientation = state.context.orientation === 'vertical' ? orientVertical : orientHorizontal;
const rxDisabled = state.context.disabled ? stateDisabled : '';
const rxReadOnly = state.context.readOnly ? stateReadOnly : '';
return (
<div
{...api.getRootProps()}
className={`${base} ${rxOrientation} ${background} ${border} ${padding} ${gap} ${rounded} ${width} ${rxDisabled} ${rxReadOnly} ${classes}`}
data-testid="segment"
>
{/* Indicator */}
<div
{...api.getIndicatorProps()}
className={`${indicatorBase} ${indicatorBg} ${indicatorRounded} ${indicatorClasses}`}
data-testid="segment-indicator"
></div>
{/* Items */}
<SegmentContext.Provider value={ctx}>{children}</SegmentContext.Provider>
</div>
);
};
const SegmentItem: FC<SegmentItemsProps> = ({
// Root
base = 'btn cursor-pointer z-[1]',
classes = '',
// Label
labelBase = 'pointer-events-none transition-colors duration-100',
labelClasses = '',
// State
stateDisabled = 'disabled',
stateFocused = 'focused',
// Children
children,
// Zag
...zagProps
}) => {
// Get Context
const ctx = useContext(SegmentContext);
const state = ctx.api.getItemState(zagProps);
// Reactive
const rxDisabled = state.disabled ? stateDisabled : '';
const rxActiveText = state.checked ? ctx.indicatorText : '';
const rxFocused = state.focused ? stateFocused : '';
return (
<label {...ctx.api.getItemProps(zagProps)} className={`${base} ${rxDisabled} ${rxFocused} ${classes}`} data-testid="segment-item">
{/* Label */}
<span
{...ctx.api.getItemTextProps(zagProps)}
className={`${labelBase} ${rxActiveText} ${labelClasses}`}
data-testid="segment-item-label"
>
{children}
</span>
{/* Hidden Input */}
<input {...ctx.api.getItemHiddenInputProps(zagProps)} data-testid="segment-item-input" />
</label>
);
};
export const Segment = Object.assign(SegmentRoot, {
Item: SegmentItem
});