feat(FE-114): add inputPrefix and inputSuffix props for enhanced input customization

This commit is contained in:
rstubryan
2025-10-25 14:24:23 +07:00
parent a0556ea1f4
commit 189c152745
2 changed files with 114 additions and 30 deletions
+7 -1
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { ChangeEvent } from 'react'; import { ChangeEvent, ReactNode } from 'react';
import { NumericFormat, OnValueChange } from 'react-number-format'; import { NumericFormat, OnValueChange } from 'react-number-format';
import TextInput, { TextInputProps } from '@/components/input/TextInput'; import TextInput, { TextInputProps } from '@/components/input/TextInput';
@@ -12,6 +12,8 @@ interface NumberInputProps extends Omit<TextInputProps, 'type'> {
prefix?: string; prefix?: string;
suffix?: string; suffix?: string;
fixedDecimalScale?: boolean; fixedDecimalScale?: boolean;
inputPrefix?: ReactNode;
inputSuffix?: ReactNode;
} }
const NumberInput = ({ const NumberInput = ({
@@ -20,6 +22,8 @@ const NumberInput = ({
decimalScale = 5, decimalScale = 5,
allowNegative = true, allowNegative = true,
onChange, onChange,
inputPrefix,
inputSuffix,
...restProps ...restProps
}: NumberInputProps) => { }: NumberInputProps) => {
const valueChangeHandler: OnValueChange = ( const valueChangeHandler: OnValueChange = (
@@ -45,6 +49,8 @@ const NumberInput = ({
onValueChange={valueChangeHandler} onValueChange={valueChangeHandler}
decimalScale={decimalScale} decimalScale={decimalScale}
allowNegative={allowNegative} allowNegative={allowNegative}
inputPrefix={inputPrefix}
inputSuffix={inputSuffix}
{...restProps} {...restProps}
/> />
); );
+107 -29
View File
@@ -31,6 +31,8 @@ export interface TextInputProps {
errorMessage?: string; errorMessage?: string;
startAdornment?: ReactNode; startAdornment?: ReactNode;
endAdornment?: ReactNode; endAdornment?: ReactNode;
inputPrefix?: ReactNode;
inputSuffix?: ReactNode;
onChange?: ChangeEventHandler<HTMLInputElement>; onChange?: ChangeEventHandler<HTMLInputElement>;
onBlur?: FocusEventHandler<HTMLInputElement>; onBlur?: FocusEventHandler<HTMLInputElement>;
} }
@@ -48,6 +50,8 @@ const TextInput = ({
errorMessage, errorMessage,
startAdornment, startAdornment,
endAdornment, endAdornment,
inputPrefix,
inputSuffix,
disabled = false, disabled = false,
required = false, required = false,
onChange, onChange,
@@ -85,39 +89,113 @@ const TextInput = ({
</label> </label>
)} )}
<div {inputPrefix || inputSuffix ? (
className={cn( <div className='relative flex'>
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200 bg-white', {inputPrefix && (
{ <div
'border-error': isError, className={cn(
'border-success!': isValid, 'inline-flex items-center px-4 py-2 border border-r-0 rounded-l-md transition-all duration-200',
}, {
className?.inputWrapper 'bg-gray-100 border-gray-300': !disabled,
)} 'bg-gray-50 border-gray-200': disabled,
> }
{startAdornment && startAdornment} )}
>
{inputPrefix}
</div>
)}
<input <div
type={type} className={cn(
id={name} 'input h-12 text-base font-normal leading-6 flex-1 rounded-lg! outline-none! transition-all duration-200 flex items-center bg-white',
name={name} {
placeholder={placeholder} 'border-error': isError,
value={value} 'border-success!': isValid,
onChange={onChange} 'rounded-l-none!': inputPrefix,
onBlur={onBlur} 'rounded-r-none!': inputSuffix,
disabled={disabled} 'input-disabled': disabled,
className={cn('grow', className?.input)} 'cursor-not-allowed': disabled,
readOnly={readOnly} 'bg-gray-50': disabled,
/> },
className?.inputWrapper
)}
>
{startAdornment && startAdornment}
{(isLoading || endAdornment) && ( <input
<div className='flex flex-row gap-2'> type={type}
{isLoading && <span className='loading loading-spinner' />} id={name}
name={name}
placeholder={placeholder}
value={value}
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
className={cn('grow bg-transparent outline-none', {
'cursor-not-allowed': disabled,
'text-gray-500': disabled,
}, className?.input)}
readOnly={readOnly}
/>
{endAdornment && endAdornment} {(isLoading || endAdornment) && (
<div className='flex flex-row gap-2'>
{isLoading && <span className='loading loading-spinner' />}
{endAdornment && endAdornment}
</div>
)}
</div> </div>
)}
</div> {inputSuffix && (
<div
className={cn(
'inline-flex items-center px-4 py-2 border border-l-0 rounded-r-md transition-all duration-200',
{
'bg-gray-100 border-gray-300': !disabled,
'bg-gray-50 border-gray-200': disabled,
}
)}
>
{inputSuffix}
</div>
)}
</div>
) : (
<div
className={cn(
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200 bg-white',
{
'border-error': isError,
'border-success!': isValid,
},
className?.inputWrapper
)}
>
{startAdornment && startAdornment}
<input
type={type}
id={name}
name={name}
placeholder={placeholder}
value={value}
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
className={cn('grow', className?.input)}
readOnly={readOnly}
/>
{(isLoading || endAdornment) && (
<div className='flex flex-row gap-2'>
{isLoading && <span className='loading loading-spinner' />}
{endAdornment && endAdornment}
</div>
)}
</div>
)}
{!isError && bottomLabel && ( {!isError && bottomLabel && (
<p className='w-full text-sm opacity-60'>{bottomLabel}</p> <p className='w-full text-sm opacity-60'>{bottomLabel}</p>