UNPKG

@yamada-ui/segmented-control

Version:

Yamada UI segmented control components

311 lines (309 loc) • 9.27 kB
"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