@nex-ui/react
Version:
🎉 A beautiful, modern, and reliable React component library.
262 lines (259 loc) • 8.53 kB
JavaScript
"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 };