UNPKG

@nex-ui/react

Version:

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

262 lines (259 loc) • 8.53 kB
"use client"; import { jsxs, jsx } from 'react/jsx-runtime'; import { useRef, useMemo, useId } from 'react'; import { isString } from '@nex-ui/utils'; import { useControlledState, useEvent } from '@nex-ui/hooks'; import { CloseCircleFilled } from '@nex-ui/icons'; 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 { InputBase } from '../inputBase/InputBase.mjs'; import { ButtonBase } from '../buttonBase/ButtonBase.mjs'; import { composeClasses } from '../utils/composeClasses.mjs'; import { inputRecipe } from '../../theme/recipes/input.mjs'; import { getUtilityClass } from '../utils/getUtilityClass.mjs'; const useSlotClasses = (ownerState)=>{ const { prefix } = useNexUI(); const { variant, radius, size, color, disabled, fullWidth, invalid, classes, labelPlacement } = ownerState; return useMemo(()=>{ const inputRoot = `${prefix}-input`; const slots = { root: [ 'root', `variant-${variant}`, `radius-${radius}`, `size-${size}`, `color-${color}`, disabled && 'disabled', fullWidth && 'full-width', invalid && 'invalid', labelPlacement && `label-placement-${labelPlacement}` ], input: [ 'input' ], clearButton: [ 'clear-btn' ], prefix: [ 'prefix' ], suffix: [ 'suffix' ], label: [ 'label' ] }; return composeClasses(slots, getUtilityClass(inputRoot), classes); }, [ classes, color, disabled, fullWidth, invalid, labelPlacement, prefix, radius, size, variant ]); }; const useSlotAriaProps = (ownerState)=>{ const { label, slotProps, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, id: idProp } = ownerState; const id = useId(); return useMemo(()=>{ const hasLabel = !!label; const stringLabel = isString(label); const labelProps = slotProps?.label ?? {}; const clearButtonProps = slotProps?.clearButton ?? {}; const inputId = idProp ?? (hasLabel ? `input-${id}` : undefined); const labelId = labelProps['id'] ?? (hasLabel ? `label-${id}` : undefined); return { input: { id: inputId, 'aria-labelledby': ariaLabelledBy ?? labelId, 'aria-label': ariaLabel ?? (stringLabel ? label : undefined) }, label: { id: labelId, htmlFor: inputId }, clearButton: { 'aria-label': clearButtonProps['aria-label'] ?? 'Clear input' } }; }, [ ariaLabel, ariaLabelledBy, id, idProp, label, slotProps?.clearButton, slotProps?.label ]); }; const Input = (inProps)=>{ const { primaryThemeColor } = useNexUI(); const props = useDefaultProps({ name: 'Input', props: inProps }); const { sx, label, className, prefix, suffix, onClear, slotProps, onValueChange, placeholder, defaultValue = '', as = 'input', value: valueProp, color = primaryThemeColor, type = 'text', disabled = false, variant = 'outlined', fullWidth = false, invalid = false, size = 'md', radius = size, clearable = false, labelPlacement: labelPlacementProp = 'float-outside', ...remainingProps } = props; const [value, setValue] = useControlledState(valueProp, defaultValue, onValueChange); const inputRef = useRef(null); const hasLabel = !!label; const hasValue = !!value; const hasPlaceholder = !!placeholder; const hasPrefix = !!prefix; let labelPlacement = labelPlacementProp; const floatLabel = labelPlacement === 'float-outside' || labelPlacement === 'float-inside'; const hasDefaultPlaceholder = [ 'date', 'datetime-local', 'time', 'week', 'month', 'range' ].includes(type); if (!hasLabel) { labelPlacement = undefined; } else if (floatLabel && (hasPlaceholder || hasValue || hasDefaultPlaceholder || hasPrefix)) { if (labelPlacementProp === 'float-outside') { labelPlacement = 'outside'; } else if (labelPlacementProp === 'float-inside') { labelPlacement = 'inside'; } } const ownerState = { ...props, color, disabled, variant, fullWidth, size, radius, invalid, type, clearable, value, labelPlacement, as }; const styles = useStyles({ ownerState, name: 'Input', recipe: inputRecipe }); const classes = useSlotClasses(ownerState); const slotAriaProps = useSlotAriaProps(ownerState); const handleClearValue = useEvent(()=>{ setValue(''); onClear?.(); inputRef.current?.focus(); }); const handleChange = useEvent((e)=>{ setValue(e.target.value); }); const handleFocusInput = useEvent((e)=>{ if (inputRef.current && e.target === e.currentTarget) { inputRef.current.focus(); e.preventDefault(); } }); const [InputRoot, getInputRootProps] = useSlot({ ownerState, elementType: 'div', externalSlotProps: slotProps?.root, style: styles.root, classNames: classes.root, additionalProps: { sx, className, onMouseDown: handleFocusInput } }); const [InputLabel, getInputLabelProps] = useSlot({ ownerState, elementType: 'label', externalSlotProps: slotProps?.label, style: styles.label, classNames: classes.label, a11y: slotAriaProps.label }); const [InputControl, getInputControlProps] = useSlot({ ownerState, elementType: InputBase, externalForwardedProps: remainingProps, style: styles.input, classNames: classes.input, a11y: slotAriaProps.input, shouldForwardComponent: false, additionalProps: { as, disabled, invalid, type, value, ref: inputRef, onChange: handleChange } }); const [InputClearButton, getClearButtonProps] = useSlot({ ownerState, elementType: ButtonBase, style: styles.clearButton, externalSlotProps: slotProps?.clearButton, classNames: classes.clearButton, a11y: slotAriaProps.clearButton, shouldForwardComponent: false, additionalProps: { onClick: handleClearValue, disabled: disabled, sx: { visibility: value ? 'visible' : 'hidden' } } }); const [InputPrefix, getInputPrefixProps] = useSlot({ ownerState, elementType: 'span', externalSlotProps: slotProps?.prefix, style: styles.prefix, classNames: classes.prefix }); const [InputSuffix, getInputSuffixProps] = useSlot({ ownerState, elementType: 'span', externalSlotProps: slotProps?.suffix, style: styles.suffix, classNames: classes.suffix }); return /*#__PURE__*/ jsxs(InputRoot, { ...getInputRootProps(), children: [ prefix && /*#__PURE__*/ jsx(InputPrefix, { ...getInputPrefixProps(), children: prefix }), label && /*#__PURE__*/ jsx(InputLabel, { ...getInputLabelProps(), children: label }), /*#__PURE__*/ jsx(InputControl, { ...getInputControlProps() }), clearable && /*#__PURE__*/ jsx(InputClearButton, { ...getClearButtonProps(), children: /*#__PURE__*/ jsx(CloseCircleFilled, {}) }), suffix && /*#__PURE__*/ jsx(InputSuffix, { ...getInputSuffixProps(), children: suffix }) ] }); }; Input.displayName = 'Input'; export { Input };