UNPKG

@nex-ui/react

Version:

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

243 lines (240 loc) • 7.71 kB
"use client"; import { jsxs, jsx } from 'react/jsx-runtime'; import { useRef, useId } from 'react'; import { nex } from '@nex-ui/styled'; import { mergeRefs, 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 { useSlotProps } from '../utils/useSlotProps.mjs'; import { Button } from '../button/Button.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 inputRoot = `${prefix}-input`; const { variant, clearable, radius, size, color, disabled, fullWidth, invaild, classes, labelPlacement } = ownerState; const slots = { root: [ 'root', `variant-${variant}`, `radius-${radius}`, `size-${size}`, `color-${color}`, disabled && 'disabled', fullWidth && 'full-width', invaild && 'invaild', clearable && 'clearable', labelPlacement && `label-placement-${labelPlacement}` ], input: [ 'input' ], clearBtn: [ 'clear-btn' ], prefix: [ 'prefix' ], suffix: [ 'suffix' ], label: [ 'label' ] }; const composedClasses = composeClasses(slots, getUtilityClass(inputRoot), classes); return composedClasses; }; const useSlotAriaProps = (ownerState)=>{ const { tabIndex, disabled, invaild, label } = ownerState; const labelString = isString(label); const id = useId(); const inputId = `input-${id}`; const labelId = `label-${id}`; const inputProps = { tabIndex: disabled ? -1 : tabIndex, 'aria-invalid': invaild, id: labelString ? inputId : undefined, 'aria-labelledby': labelString ? labelId : undefined, 'aria-label': labelString ? label : undefined }; const labelProps = { id: labelString ? labelId : undefined, htmlFor: labelString ? inputId : undefined }; const clearBtnProps = { 'aria-label': 'Clear input', tabIndex: -1 }; return { input: inputProps, label: labelProps, clearBtn: clearBtnProps }; }; const Input = (inProps)=>{ const { primaryThemeColor } = useNexUI(); const props = useDefaultProps({ name: 'Input', props: inProps }); const { sx, ref, label, className, prefix, suffix, onClear, slotProps, onValueChange, placeholder, tabIndex = 0, as = 'input', defaultValue = '', value: valueProp, color = primaryThemeColor, type = 'text', disabled = false, variant = 'outlined', fullWidth = false, invaild = 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 mergedRefs = mergeRefs(ref, inputRef); 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, as, tabIndex, color, disabled, variant, fullWidth, size, radius, invaild, type, clearable, value, labelPlacement }; const styles = useStyles({ ownerState, name: 'Input', recipe: inputRecipe }); const classes = useSlotClasses(ownerState); const slotAriaProps = useSlotAriaProps(ownerState); const onClearValue = useEvent(()=>{ setValue(''); onClear?.(); inputRef.current?.focus(); }); const handleChange = useEvent((e)=>{ setValue(e.target.value); }); const rootProps = useSlotProps({ ownerState, externalSlotProps: slotProps?.root, externalForwardedProps: { className, sx }, sx: styles.root, classNames: classes.root, additionalProps: { onClick: (e)=>{ if (inputRef.current && e.target === e.currentTarget) { inputRef.current.focus(); } } } }); const labelProps = useSlotProps({ ownerState, externalSlotProps: slotProps?.label, sx: styles.label, classNames: classes.label, additionalProps: slotAriaProps.label }); const inputProps = useSlotProps({ ownerState, externalForwardedProps: remainingProps, sx: styles.input, classNames: classes.input, additionalProps: { as, type, value, disabled, placeholder, ref: mergedRefs, onChange: handleChange, ...slotAriaProps.input } }); const clearBtnProps = useSlotProps({ ownerState, externalSlotProps: slotProps?.clearBtn, sx: styles.clearBtn, classNames: classes.clearBtn, additionalProps: { onClick: onClearValue, iconOnly: true, disableRipple: true, size: 'sm', color: variant === 'filled' ? color : 'gray', variant: 'text', disabled: disabled, sx: { visibility: value ? 'visible' : 'hidden' }, ...slotAriaProps.clearBtn } }); const prefixProps = useSlotProps({ ownerState, externalSlotProps: slotProps?.prefix, sx: styles.prefix, classNames: classes.prefix }); const suffixProps = useSlotProps({ ownerState, externalSlotProps: slotProps?.suffix, sx: styles.suffix, classNames: classes.suffix }); return /*#__PURE__*/ jsxs(nex.div, { ...rootProps, children: [ prefix && /*#__PURE__*/ jsx(nex.span, { ...prefixProps, children: prefix }), label && /*#__PURE__*/ jsx(nex.label, { ...labelProps, children: label }), /*#__PURE__*/ jsx(nex.input, { ...inputProps }), clearable && /*#__PURE__*/ jsx(Button, { ...clearBtnProps, children: /*#__PURE__*/ jsx(CloseCircleFilled, {}) }), suffix && /*#__PURE__*/ jsx(nex.span, { ...suffixProps, children: suffix }) ] }); }; Input.displayName = 'Input'; export { Input };