UNPKG

@nex-ui/react

Version:

🎉 A beautiful, modern, and reliable React component library.

205 lines (202 loc) • 6.22 kB
import { jsx } from 'react/jsx-runtime'; import { useState, useMemo } from 'react'; import { useFocusRing, useEvent } from '@nex-ui/hooks'; import { isFunction } from '@nex-ui/utils'; import { defineRecipe } from '@nex-ui/system'; import { useSlot } from '../utils/useSlot.mjs'; const getRole = (type)=>{ switch(type){ case 'checkbox': return 'checkbox'; case 'radio': return 'radio'; case 'text': return 'textbox'; default: return undefined; } }; const useAriaProps = (props)=>{ const { disabled, invalid, checked, required, readOnly, role, type, as, tabIndex, 'aria-disabled': ariaDisabled, 'aria-checked': ariaChecked, 'aria-required': ariaRequired, 'aria-readonly': ariaReadOnly } = props; return useMemo(()=>{ if (isFunction(as)) { return {}; } let ariaProps = { tabIndex: disabled ? -1 : tabIndex, 'aria-invalid': invalid || undefined }; if (as === 'input') { ariaProps = { ...ariaProps, role }; } else { ariaProps = { ...ariaProps, role: role ?? getRole(type), 'aria-disabled': ariaDisabled ?? disabled, 'aria-checked': ariaChecked ?? checked, 'aria-required': ariaRequired ?? required, 'aria-readonly': ariaReadOnly ?? readOnly }; } return ariaProps; }, [ as, disabled, tabIndex, invalid, type, checked, required, readOnly, role, ariaDisabled, ariaChecked, ariaRequired, ariaReadOnly ]); }; const isCheckableControl = (element)=>{ const role = element.getAttribute('role'); const type = element.getAttribute('type'); const { tagName } = element; if (role) { return [ 'switch', 'radio', 'checkbox' ].includes(role); } if (tagName === 'INPUT' && type) { return [ 'radio', 'checkbox' ].includes(type); } }; /** * InputBase is a lower-level construct that is leveraged by the following components: * * - Switch * - Input * - Checkbox * - Radio */ const recipe = defineRecipe({ base: { m: '0', p: '0', border: 'none', background: 'none', outline: 'none', appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none', borderRadius: '0', boxShadow: 'none', boxSizing: 'border-box', '&[type="search"]': { '::-webkit-search-decoration': { WebkitAppearance: 'none' }, '::-webkit-search-cancel-button': { WebkitAppearance: 'none' } }, '&:is(:-webkit-autofill, :autofill)': { bg: 'transparent !important', transition: 'background-color 50000s ease-in-out 0s' } } }); const style = recipe(); const InputBase = (props)=>{ const { defaultChecked, disabled, autoFocus, onCheckedChange, onClick, onKeyUp, onChange, checked: checkProp, type = 'text', as = 'input', tabIndex = 0, ...remainingProps } = props; const controlled = 'checked' in props; const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false); const currentChecked = controlled ? checkProp : internalChecked; const { focusVisible, focusProps } = useFocusRing({ autoFocus, input: as === 'input' }); const toggleCheckableState = useEvent((element)=>{ const role = element.getAttribute('role'); const ariaChecked = element.getAttribute('aria-checked'); const newChecked = role === 'radio' ? true : !(ariaChecked === 'true'); onCheckedChange?.(newChecked); if (!controlled) { setInternalChecked(newChecked); } }); const handleClick = useEvent((event)=>{ if (disabled) { event.preventDefault(); event.stopPropagation(); return; } const { currentTarget } = event; if (currentTarget.tagName !== 'INPUT' && currentTarget === event.target && isCheckableControl(currentTarget)) { toggleCheckableState(currentTarget); } onClick?.(event); }); const handleKeyUp = useEvent((event)=>{ if (disabled) { event.preventDefault(); event.stopPropagation(); return; } const { currentTarget } = event; // Keyboard accessibility for non interactive elements if (focusVisible && event.key === ' ' && currentTarget.tagName !== 'INPUT' && currentTarget === event.target && isCheckableControl(currentTarget)) { toggleCheckableState(currentTarget); } onKeyUp?.(event); }); const handleChange = useEvent((event)=>{ if (disabled) { event.preventDefault(); event.stopPropagation(); return; } if (event.currentTarget.tagName === 'INPUT' && isCheckableControl(event.currentTarget)) { onCheckedChange?.(event.currentTarget.checked); } onChange?.(event); }); const ariaProps = useAriaProps({ ...props, as, type, tabIndex, checked: currentChecked }); const [InputRoot, getInputRootProps] = useSlot({ style, elementType: 'input', a11y: { ...ariaProps, onKeyUp: handleKeyUp, onClick: handleClick, onChange: handleChange }, externalForwardedProps: remainingProps, additionalProps: { as, type, disabled, autoFocus, checked: currentChecked, ...focusProps }, dataAttrs: { focusVisible: focusVisible || undefined } }); return /*#__PURE__*/ jsx(InputRoot, { ...getInputRootProps() }); }; InputBase.displayName = 'InputBase'; export { InputBase };