@yamada-ui/segmented-control 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,301 @@
1
+ // src/segmented-control.tsx
2
+ import {
3
+ ui,
4
+ forwardRef,
5
+ useMultiComponentStyle,
6
+ omitThemeProps
7
+ } from "@yamada-ui/core";
8
+ import { useControllableState } from "@yamada-ui/use-controllable-state";
9
+ import { createDescendant } from "@yamada-ui/use-descendant";
10
+ import { trackFocusVisible } from "@yamada-ui/use-focus-visible";
11
+ import { useResizeObserver } from "@yamada-ui/use-resize-observer";
12
+ import {
13
+ ariaAttr,
14
+ createContext,
15
+ cx,
16
+ dataAttr,
17
+ getValidChildren,
18
+ handlerAll,
19
+ mergeRefs,
20
+ omitObject,
21
+ useCallbackRef
22
+ } from "@yamada-ui/utils";
23
+ import {
24
+ useCallback,
25
+ useEffect,
26
+ useId,
27
+ useRef,
28
+ useState
29
+ } from "react";
30
+ import { jsx, jsxs } from "react/jsx-runtime";
31
+ var { DescendantsContextProvider, useDescendants, useDescendant } = createDescendant();
32
+ var [SegmentedControlProvider, useSegmentedControl] = createContext({
33
+ strict: false,
34
+ name: "SegmentedControlContext"
35
+ });
36
+ var SegmentedControl = forwardRef(
37
+ (props, ref) => {
38
+ const [styles, mergedProps] = useMultiComponentStyle(
39
+ "SegmentedControl",
40
+ props
41
+ );
42
+ let { className, id, name, isReadOnly, isDisabled, children, ...rest } = omitThemeProps(mergedProps);
43
+ id = id != null ? id : useId();
44
+ name = name != null ? name : `segmented-control-${useId()}`;
45
+ rest.onChange = useCallbackRef(rest.onChange);
46
+ const descendants = useDescendants();
47
+ const [focusedIndex, setFocusedIndex] = useState(-1);
48
+ const [isFocusVisible, setIsFocusVisible] = useState(false);
49
+ const [observerRef, containerRect] = useResizeObserver();
50
+ const containerRef = useRef(null);
51
+ const labelRefs = useRef(/* @__PURE__ */ new Map());
52
+ const [activePosition, setActivePosition] = useState({
53
+ width: 0,
54
+ height: 0,
55
+ x: 0,
56
+ y: 0
57
+ });
58
+ const [value, setValue] = useControllableState({
59
+ value: rest.value,
60
+ defaultValue: rest.defaultValue,
61
+ onChange: rest.onChange
62
+ });
63
+ useEffect(() => {
64
+ return trackFocusVisible(setIsFocusVisible);
65
+ }, []);
66
+ useEffect(() => {
67
+ const el = labelRefs.current.get(value);
68
+ if (!el || !containerRef.current || !observerRef.current)
69
+ return;
70
+ const { paddingLeft, paddingTop } = getComputedStyle(containerRef.current);
71
+ const gutterX = parseFloat(paddingLeft) || 0;
72
+ const gutterY = parseFloat(paddingTop) || 0;
73
+ let { width, height } = el.getBoundingClientRect();
74
+ const x = el.offsetLeft - gutterX;
75
+ const y = el.offsetTop - gutterY;
76
+ width = width * (el.offsetWidth / width) || 0;
77
+ height = height * (el.offsetWidth / width) || 0;
78
+ setActivePosition({ width, height, x, y });
79
+ }, [focusedIndex, containerRect, labelRefs, observerRef, value]);
80
+ const onChange = useCallback(
81
+ (ev) => {
82
+ if (isDisabled || isReadOnly) {
83
+ ev.preventDefault();
84
+ return;
85
+ }
86
+ setValue(ev.target.value);
87
+ },
88
+ [isDisabled, isReadOnly, setValue]
89
+ );
90
+ const onFocus = useCallback(
91
+ (index, skip) => {
92
+ if (isDisabled)
93
+ return;
94
+ if (skip) {
95
+ const next = descendants.enabledNextValue(index);
96
+ if (next)
97
+ setFocusedIndex(next.index);
98
+ } else {
99
+ setFocusedIndex(index);
100
+ }
101
+ },
102
+ [descendants, isDisabled]
103
+ );
104
+ const onBlur = useCallback(() => setFocusedIndex(-1), []);
105
+ const getContainerProps = useCallback(
106
+ (props2 = {}, ref2 = null) => ({
107
+ ...omitObject(rest, ["value", "defaultValue", "onChange"]),
108
+ ...props2,
109
+ ref: mergeRefs(containerRef, observerRef, ref2),
110
+ id,
111
+ "aria-disabled": ariaAttr(isDisabled),
112
+ "aria-readonly": ariaAttr(isReadOnly),
113
+ onBlur: handlerAll(props2.onBlur, onBlur)
114
+ }),
115
+ [id, isDisabled, isReadOnly, observerRef, onBlur, rest]
116
+ );
117
+ const getActiveProps = useCallback(
118
+ (props2 = {}, ref2 = null) => {
119
+ const { width, height, x, y } = activePosition;
120
+ return {
121
+ ...props2,
122
+ ref: ref2,
123
+ style: {
124
+ position: "absolute",
125
+ zIndex: 1,
126
+ width,
127
+ height,
128
+ transform: `translate(${x}px, ${y}px)`
129
+ }
130
+ };
131
+ },
132
+ [activePosition]
133
+ );
134
+ const getInputProps = useCallback(
135
+ ({ index, ...props2 } = {}, ref2 = null) => {
136
+ var _a, _b, _c, _d;
137
+ const disabled = (_b = (_a = props2.disabled) != null ? _a : props2.isDisabled) != null ? _b : isDisabled;
138
+ const readOnly = (_d = (_c = props2.readOnly) != null ? _c : props2.isReadOnly) != null ? _d : isReadOnly;
139
+ const checked = props2.value === value;
140
+ return {
141
+ ...omitObject(props2, ["isDisabled", "isReadOnly"]),
142
+ ref: ref2,
143
+ id: `${id}-${index}`,
144
+ type: "radio",
145
+ name,
146
+ disabled: disabled || readOnly,
147
+ readOnly,
148
+ checked,
149
+ "aria-disabled": ariaAttr(disabled),
150
+ "aria-readonly": ariaAttr(readOnly),
151
+ "data-checked": dataAttr(checked),
152
+ "data-focus": dataAttr(index === focusedIndex),
153
+ style: {
154
+ border: "0px",
155
+ clip: "rect(0px, 0px, 0px, 0px)",
156
+ height: "1px",
157
+ width: "1px",
158
+ margin: "-1px",
159
+ padding: "0px",
160
+ overflow: "hidden",
161
+ whiteSpace: "nowrap",
162
+ position: "absolute"
163
+ },
164
+ onChange: handlerAll(
165
+ props2.onChange,
166
+ (ev) => !disabled && !readOnly ? onChange(ev) : {}
167
+ )
168
+ };
169
+ },
170
+ [isDisabled, isReadOnly, value, id, name, focusedIndex, onChange]
171
+ );
172
+ const getLabelProps = useCallback(
173
+ ({ index, ...props2 } = {}, ref2 = null) => {
174
+ var _a, _b, _c, _d;
175
+ const disabled = (_b = (_a = props2.disabled) != null ? _a : props2.isDisabled) != null ? _b : isDisabled;
176
+ const readOnly = (_d = (_c = props2.readOnly) != null ? _c : props2.isReadOnly) != null ? _d : isReadOnly;
177
+ const checked = props2.value === value;
178
+ const focused = index === focusedIndex;
179
+ return {
180
+ props: props2,
181
+ ref: mergeRefs(
182
+ (node) => labelRefs.current.set(props2.value, node),
183
+ ref2
184
+ ),
185
+ "aria-disabled": ariaAttr(disabled),
186
+ "aria-readonly": ariaAttr(readOnly),
187
+ "data-checked": dataAttr(checked),
188
+ "data-focus": dataAttr(focused),
189
+ "data-focus-visible": dataAttr(focused && isFocusVisible),
190
+ onFocus: handlerAll(
191
+ props2.onFocus,
192
+ () => onFocus(index, disabled || readOnly)
193
+ ),
194
+ ...disabled || readOnly ? {
195
+ _hover: {},
196
+ _active: {},
197
+ _focus: {},
198
+ _invalid: {},
199
+ _focusVisible: {}
200
+ } : {},
201
+ style: { position: "relative", zIndex: 2 }
202
+ };
203
+ },
204
+ [focusedIndex, isDisabled, isFocusVisible, isReadOnly, onFocus, value]
205
+ );
206
+ const css = {
207
+ position: "relative",
208
+ display: "inline-flex",
209
+ alignItems: "center",
210
+ ...styles.container
211
+ };
212
+ const validChildren = getValidChildren(children);
213
+ if (value == null && rest.defaultValue == null) {
214
+ for (const child of validChildren) {
215
+ if (child.type !== SegmentedControlButton)
216
+ continue;
217
+ const value2 = child.props.value;
218
+ setValue(value2);
219
+ break;
220
+ }
221
+ }
222
+ return /* @__PURE__ */ jsx(DescendantsContextProvider, { value: descendants, children: /* @__PURE__ */ jsx(
223
+ SegmentedControlProvider,
224
+ {
225
+ value: { getInputProps, getLabelProps, styles },
226
+ children: /* @__PURE__ */ jsxs(
227
+ ui.div,
228
+ {
229
+ ...getContainerProps({}, ref),
230
+ className: cx("ui-segmented-control", className),
231
+ __css: css,
232
+ children: [
233
+ /* @__PURE__ */ jsx(
234
+ ui.span,
235
+ {
236
+ className: "ui-segmented-control-active",
237
+ ...getActiveProps(),
238
+ __css: styles.active
239
+ }
240
+ ),
241
+ validChildren
242
+ ]
243
+ }
244
+ )
245
+ }
246
+ ) });
247
+ }
248
+ );
249
+ var SegmentedControlButton = forwardRef(
250
+ ({
251
+ className,
252
+ disabled,
253
+ readOnly,
254
+ isDisabled,
255
+ isReadOnly,
256
+ value,
257
+ onChange,
258
+ children,
259
+ ...rest
260
+ }, ref) => {
261
+ const { getInputProps, getLabelProps, styles } = useSegmentedControl();
262
+ const { index, register } = useDescendant({
263
+ disabled: isDisabled || isReadOnly
264
+ });
265
+ const props = {
266
+ index,
267
+ value,
268
+ onChange,
269
+ disabled,
270
+ readOnly,
271
+ isDisabled,
272
+ isReadOnly
273
+ };
274
+ const css = {
275
+ cursor: "pointer",
276
+ flex: "1 1 0%",
277
+ display: "inline-flex",
278
+ justifyContent: "center",
279
+ alignItems: "center",
280
+ ...styles.button
281
+ };
282
+ return /* @__PURE__ */ jsxs(
283
+ ui.label,
284
+ {
285
+ ...getLabelProps(omitObject(props, ["onChange"])),
286
+ className: cx("ui-segmented-control-button", className),
287
+ __css: css,
288
+ ...rest,
289
+ children: [
290
+ /* @__PURE__ */ jsx(ui.input, { ...getInputProps(props, mergeRefs(register, ref)) }),
291
+ /* @__PURE__ */ jsx(ui.span, { children })
292
+ ]
293
+ }
294
+ );
295
+ }
296
+ );
297
+
298
+ export {
299
+ SegmentedControl,
300
+ SegmentedControlButton
301
+ };