@nex-ui/react
Version:
🎉 A beautiful, modern, and reliable React component library.
241 lines (238 loc) • 7.9 kB
JavaScript
"use client";
import { jsxs, jsx } from 'react/jsx-runtime';
import { useMemo, useId } from 'react';
import { isFunction, isString } from '@nex-ui/utils';
import { useFocusRing, useControlledState, useEvent } from '@nex-ui/hooks';
import { useNexUI } from '../provider/Context.mjs';
import { useDefaultProps } from '../utils/useDefaultProps.mjs';
import { useStyles } from '../utils/useStyles.mjs';
import { useSlot } from '../utils/useSlot.mjs';
import { composeClasses } from '../utils/composeClasses.mjs';
import { switchRecipe } from '../../theme/recipes/switch.mjs';
import { getUtilityClass } from '../utils/getUtilityClass.mjs';
const useSlotClasses = (ownerState)=>{
const { prefix } = useNexUI();
const { size, color, disabled, checked, classes } = ownerState;
return useMemo(()=>{
const switchRoot = `${prefix}-switch`;
const slots = {
root: [
'root',
`color-${color}`,
`size-${size}`,
disabled && 'disabled',
checked && 'checked'
],
input: [
'input'
],
track: [
'track'
],
thumb: [
'thumb'
],
startIcon: [
'start-icon'
],
endIcon: [
'end-icon'
],
label: [
'label'
]
};
return composeClasses(slots, getUtilityClass(switchRoot), classes);
}, [
checked,
classes,
color,
disabled,
size,
prefix
]);
};
const useSlotAriaProps = (ownerState)=>{
const { checked, disabled, children, slotProps, role = 'switch', tabIndex = 0, type = 'checkbox', as = 'input', 'aria-labelledby': labelledBy, 'aria-label': ariaLabel, 'aria-checked': ariaChecked, 'aria-disabled': ariaDisabled } = ownerState;
const id = useId();
return useMemo(()=>{
const stringChildren = isString(children);
const labelProps = slotProps?.label ?? {};
const labelId = labelProps.id ?? (stringChildren ? id : undefined);
let input = {
role,
tabIndex: disabled ? -1 : tabIndex,
'aria-labelledby': labelledBy ?? labelId,
'aria-label': ariaLabel ?? (stringChildren ? children : undefined)
};
if (as === 'input' || isFunction(as)) {
input = {
...input,
type,
disabled,
checked
};
} else {
input = {
...input,
'aria-checked': ariaChecked ?? checked,
'aria-disabled': ariaDisabled ?? (disabled || undefined)
};
}
const label = {
id: labelId
};
return {
input,
label
};
}, [
ariaChecked,
ariaDisabled,
ariaLabel,
as,
checked,
children,
disabled,
id,
labelledBy,
role,
slotProps?.label,
tabIndex,
type
]);
};
const Switch = (inProps)=>{
const { primaryThemeColor } = useNexUI();
const props = useDefaultProps({
name: 'Switch',
props: inProps
});
const { sx, children, slotProps, className, startIcon, endIcon, onCheckedChange, thumbIcon: thumbIconProp, checked: checkdeProp, disabled = false, size = 'md', defaultChecked = false, color = primaryThemeColor, ...remainingProps } = props;
const { focusVisible, focusProps } = useFocusRing();
const [checked, setChecked] = useControlledState(checkdeProp, defaultChecked, onCheckedChange);
const ownerState = {
...props,
color,
checked,
disabled,
size,
defaultChecked
};
const handleChange = useEvent((event)=>{
setChecked(event.target.checked);
});
const handleClick = useEvent((event)=>{
// Compatible with non interactive elements
if (event.currentTarget.tagName !== 'INPUT' && event.target === event.currentTarget) {
setChecked(!checked);
}
});
const handleKeyUp = useEvent((event)=>{
// Keyboard accessibility for non interactive elements
if (focusVisible && event.key === 'Space' && event.target === event.currentTarget && event.currentTarget.tagName !== 'INPUT') {
event.currentTarget.click();
}
});
const classes = useSlotClasses(ownerState);
const styles = useStyles({
ownerState,
name: 'Switch',
recipe: switchRecipe
});
const slotAriaProps = useSlotAriaProps(ownerState);
const [SwitchRoot, getSwitchRootProps] = useSlot({
ownerState,
elementType: 'label',
externalSlotProps: slotProps?.root,
style: styles.root,
classNames: classes.root,
additionalProps: {
className,
sx
}
});
const [SwitchInput, getSwitchInputProps] = useSlot({
ownerState,
elementType: 'input',
externalForwardedProps: remainingProps,
style: styles.input,
classNames: classes.input,
a11y: slotAriaProps.input,
additionalProps: {
onChange: handleChange,
onClick: handleClick,
onKeyUp: handleKeyUp,
'data-focus-visible': focusVisible || undefined,
...focusProps
}
});
const [SwitchTrack, getSwitchTrackProps] = useSlot({
ownerState,
elementType: 'span',
externalSlotProps: slotProps?.track,
style: styles.track,
classNames: classes.track
});
const [SwitchThumb, getSwitchThumbProps] = useSlot({
ownerState,
elementType: 'span',
externalSlotProps: slotProps?.thumb,
style: styles.thumb,
classNames: classes.thumb
});
const [SwitchStartIcon, getSwitchStartIconProps] = useSlot({
ownerState,
elementType: 'span',
externalSlotProps: slotProps?.startIcon,
style: styles.startIcon,
classNames: classes.startIcon
});
const [SwitchEndIcon, getSwitchEndIconProps] = useSlot({
ownerState,
elementType: 'span',
externalSlotProps: slotProps?.endIcon,
style: styles.endIcon,
classNames: classes.endIcon
});
const [SwitchLabel, getSwitchLabelProps] = useSlot({
ownerState,
elementType: 'span',
externalSlotProps: slotProps?.label,
style: styles.label,
classNames: classes.label,
a11y: slotAriaProps.label
});
const thumbIcon = isFunction(thumbIconProp) ? thumbIconProp(ownerState) : thumbIconProp;
return /*#__PURE__*/ jsxs(SwitchRoot, {
...getSwitchRootProps(),
children: [
/*#__PURE__*/ jsx(SwitchInput, {
...getSwitchInputProps()
}),
/*#__PURE__*/ jsxs(SwitchTrack, {
...getSwitchTrackProps(),
children: [
startIcon && /*#__PURE__*/ jsx(SwitchStartIcon, {
...getSwitchStartIconProps(),
children: startIcon
}),
/*#__PURE__*/ jsx(SwitchThumb, {
...getSwitchThumbProps(),
children: thumbIcon
}),
endIcon && /*#__PURE__*/ jsx(SwitchEndIcon, {
...getSwitchEndIconProps(),
children: endIcon
})
]
}),
children && /*#__PURE__*/ jsx(SwitchLabel, {
...getSwitchLabelProps(),
children: children
})
]
});
};
Switch.displayName = 'Switch';
export { Switch };