@yamada-ui/segmented-control
Version:
Yamada UI segmented control components
311 lines (309 loc) • 9.27 kB
JavaScript
"use client"
// src/segmented-control.tsx
import {
forwardRef,
omitThemeProps,
ui,
useComponentMultiStyle
} from "@yamada-ui/core";
import { LayoutGroup, motion } from "@yamada-ui/motion";
import { useControllableState } from "@yamada-ui/use-controllable-state";
import { createDescendant } from "@yamada-ui/use-descendant";
import { trackFocusVisible } from "@yamada-ui/use-focus-visible";
import {
ariaAttr,
createContext,
cx,
dataAttr,
getValidChildren,
handlerAll,
mergeRefs,
useCallbackRef,
useMounted
} from "@yamada-ui/utils";
import { useCallback, useEffect, useId, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
var { DescendantsContextProvider, useDescendant, useDescendants } = createDescendant();
var [SegmentedControlProvider, useSegmentedControl] = createContext({
name: "SegmentedControlContext",
errorMessage: `useSegmentedControl returned is 'undefined'. Seems you forgot to wrap the components in "<SegmentedControl />"`
});
var SegmentedControl = forwardRef(
(props, ref) => {
const [styles, mergedProps] = useComponentMultiStyle(
"SegmentedControl",
props
);
const uuid = useId();
const {
id = uuid,
name = `segmented-control-${uuid}`,
className,
children,
defaultValue,
isDisabled,
disabled = isDisabled,
isReadOnly,
items = [],
readOnly = isReadOnly,
value: valueProp,
onChange: onChangeProp,
...rest
} = omitThemeProps(mergedProps);
const containerRef = useRef(null);
const descendants = useDescendants();
const [focusedIndex, setFocusedIndex] = useState(-1);
const [focusVisible, setFocusVisible] = useState(false);
const onChangeRef = useCallbackRef(onChangeProp);
const [value, setValue] = useControllableState({
defaultValue,
value: valueProp,
onChange: onChangeRef
});
const onChange = useCallback(
(ev) => {
if (disabled || readOnly) {
ev.preventDefault();
return;
}
setValue(ev.target.value);
},
[disabled, readOnly, setValue]
);
const onFocus = useCallback(
(index, skip) => {
if (disabled) return;
if (skip) {
const next = descendants.enabledNextValue(index);
if (next) setFocusedIndex(next.index);
} else {
setFocusedIndex(index);
}
},
[descendants, disabled]
);
const onBlur = useCallback(() => setFocusedIndex(-1), []);
const getContainerProps = useCallback(
(props2 = {}, ref2 = null) => ({
"aria-disabled": ariaAttr(disabled),
"data-readonly": dataAttr(readOnly),
role: "radiogroup",
...rest,
...props2,
id,
ref: mergeRefs(containerRef, ref2),
onBlur: handlerAll(props2.onBlur, onBlur)
}),
[id, disabled, readOnly, onBlur, rest]
);
const getInputProps = useCallback(
({ index, ...props2 }, ref2 = null) => {
var _a, _b;
const trulyDisabled = (_a = props2.disabled) != null ? _a : disabled;
const trulyReadOnly = (_b = props2.readOnly) != null ? _b : readOnly;
const checked = props2.value === value;
return {
...props2,
id: `${id}-${index}`,
ref: ref2,
type: "radio",
name,
style: {
border: "0px",
clip: "rect(0px, 0px, 0px, 0px)",
height: "1px",
margin: "-1px",
overflow: "hidden",
padding: "0px",
position: "absolute",
whiteSpace: "nowrap",
width: "1px"
},
"aria-disabled": ariaAttr(trulyDisabled),
"data-checked": dataAttr(checked),
"data-focus": dataAttr(index === focusedIndex),
"data-readonly": dataAttr(trulyReadOnly),
checked,
disabled: trulyDisabled || trulyReadOnly,
readOnly: trulyReadOnly,
onChange: handlerAll(
props2.onChange,
(ev) => !trulyDisabled && !trulyReadOnly ? onChange(ev) : {}
)
};
},
[disabled, readOnly, value, id, name, focusedIndex, onChange]
);
const getLabelProps = useCallback(
({ index, ...props2 }, ref2 = null) => {
var _a, _b;
const trulyDisabled = (_a = props2.disabled) != null ? _a : disabled;
const trulyReadOnly = (_b = props2.readOnly) != null ? _b : readOnly;
const checked = props2.value === value;
const focused = index === focusedIndex;
return {
...props2,
ref: ref2,
"aria-disabled": ariaAttr(trulyDisabled),
"data-checked": dataAttr(checked),
"data-focus": dataAttr(focused),
"data-focus-visible": dataAttr(focused && focusVisible),
"data-readonly": dataAttr(trulyReadOnly),
onFocus: handlerAll(
props2.onFocus,
() => onFocus(index, trulyDisabled || trulyReadOnly || false)
),
...trulyDisabled || trulyReadOnly ? {
_active: {},
_focus: {},
_focusVisible: {},
_hover: {},
_invalid: {}
} : {}
};
},
[focusedIndex, disabled, readOnly, focusVisible, onFocus, value]
);
useEffect(() => {
return trackFocusVisible(setFocusVisible);
}, []);
const css = {
alignItems: "center",
display: "inline-flex",
...styles.container
};
const validChildren = getValidChildren(children);
let computedChildren = [];
if (!validChildren.length && items.length) {
computedChildren = items.map(({ label, value: value2, ...props2 }, i) => /* @__PURE__ */ jsx(SegmentedControlButton, { value: value2, ...props2, children: label }, i));
} else {
computedChildren = validChildren;
}
if (value == null && defaultValue == null) {
for (const child of computedChildren) {
if (child.type !== SegmentedControlButton) {
if (child.type.displayName !== SegmentedControlButton.displayName)
continue;
}
const value2 = child.props.value;
setValue(value2);
break;
}
}
return /* @__PURE__ */ jsx(DescendantsContextProvider, { value: descendants, children: /* @__PURE__ */ jsx(
SegmentedControlProvider,
{
value: { styles, value, getInputProps, getLabelProps },
children: /* @__PURE__ */ jsx(LayoutGroup, { id, children: /* @__PURE__ */ jsx(
ui.div,
{
...getContainerProps({}, ref),
className: cx("ui-segmented-control", className),
__css: css,
children: computedChildren
}
) })
}
) });
}
);
SegmentedControl.displayName = "SegmentedControl";
SegmentedControl.__ui__ = "SegmentedControl";
var SegmentedControlButton = forwardRef(
({
className,
children,
isDisabled,
disabled = isDisabled,
isReadOnly,
readOnly = isReadOnly,
value,
motionProps,
onChange,
...rest
}, ref) => {
const [, mounted] = useMounted({ rerender: true });
const {
styles,
value: selectedValue,
getInputProps,
getLabelProps
} = useSegmentedControl();
const { index, register } = useDescendant({
disabled: disabled || readOnly
});
const props = {
disabled,
index,
readOnly,
value
};
const css = {
alignItems: "center",
cursor: "pointer",
display: "inline-flex",
flex: "1 1 0%",
justifyContent: "center",
position: "relative",
...styles.button
};
const selected = value === selectedValue;
return /* @__PURE__ */ jsxs(
ui.label,
{
...getLabelProps(props),
className: cx("ui-segmented-control__button", className),
__css: css,
...rest,
children: [
/* @__PURE__ */ jsx(
ui.input,
{
...getInputProps({ onChange, ...props }, mergeRefs(register, ref))
}
),
selected && mounted ? /* @__PURE__ */ jsx(SegmentedControlCursor, { ...motionProps }) : null,
/* @__PURE__ */ jsx(ui.span, { zIndex: "1", children })
]
}
);
}
);
SegmentedControlButton.displayName = "SegmentedControlButton";
SegmentedControlButton.__ui__ = "SegmentedControlButton";
var SegmentedControlCursor = ({
className,
transition,
...rest
}) => {
const { styles } = useSegmentedControl();
const css = {
h: "100%",
position: "absolute",
w: "100%",
...styles.cursor
};
return /* @__PURE__ */ jsx(
motion.div,
{
className: cx("ui-segmented-control__cursor", className),
layoutDependency: false,
layoutId: "cursor",
transition: {
type: "spring",
bounce: 0.15,
duration: 0.4,
...transition
},
__css: css,
...rest
}
);
};
SegmentedControlCursor.displayName = "SegmentedControlCursor";
SegmentedControlCursor.__ui__ = "SegmentedControlCursor";
export {
SegmentedControl,
SegmentedControlButton
};
//# sourceMappingURL=chunk-HDPS6FC6.mjs.map