mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Support inputPrefix/inputSuffix on SelectInput
This commit is contained in:
@@ -54,6 +54,9 @@ interface SelectInputBaseProps<T = OptionType> {
|
||||
wrapper?: string;
|
||||
label?: string;
|
||||
select?: string;
|
||||
inputPrefix?: string;
|
||||
inputSuffix?: string;
|
||||
inputPrefixSuffixWrapper?: string;
|
||||
};
|
||||
isError?: boolean;
|
||||
errorMessage?: string;
|
||||
@@ -62,6 +65,8 @@ interface SelectInputBaseProps<T = OptionType> {
|
||||
delay?: number;
|
||||
onInputChange?: (search: string) => void;
|
||||
startAdornment?: ReactNode;
|
||||
inputPrefix?: ReactNode;
|
||||
inputSuffix?: ReactNode;
|
||||
menuPortalTarget?: HTMLElement | null;
|
||||
closeMenuOnSelect?: boolean;
|
||||
hideSelectedOptions?: boolean;
|
||||
@@ -153,6 +158,8 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
createables = false,
|
||||
onInputChange,
|
||||
startAdornment,
|
||||
inputPrefix,
|
||||
inputSuffix,
|
||||
menuPortalTarget,
|
||||
closeMenuOnSelect,
|
||||
hideSelectedOptions,
|
||||
@@ -227,111 +234,261 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
</span>
|
||||
)}
|
||||
|
||||
<SelectComponent<T, boolean, GroupBase<T>>
|
||||
instanceId='select'
|
||||
value={value ?? (isMulti ? [] : null)}
|
||||
onChange={onChange ? handleChange : undefined}
|
||||
options={options}
|
||||
menuIsOpen={openMenu}
|
||||
inputValue={internalInputValue}
|
||||
onInputChange={internalInputChangeHandler}
|
||||
onMenuClose={() => setInternalInputValue('')}
|
||||
isMulti={isMulti}
|
||||
isDisabled={isDisabled || readOnly}
|
||||
isLoading={isLoading}
|
||||
isClearable={isClearable}
|
||||
isRtl={isRtl}
|
||||
isSearchable={isSearchable}
|
||||
placeholder={placeholder}
|
||||
closeMenuOnSelect={closeMenuOnSelect}
|
||||
hideSelectedOptions={hideSelectedOptions}
|
||||
className={cn('w-full', className?.select)}
|
||||
classNames={{
|
||||
...(!startAdornment && {
|
||||
control: ({ isFocused, isDisabled }) =>
|
||||
cn('w-full rounded-lg! border bg-white transition-shadow', {
|
||||
'cursor-pointer!': !readOnly && !isDisabled,
|
||||
'border-red-500! ring-2 ring-red-200': isError,
|
||||
'border-indigo-500 ring-2 ring-indigo-200': isFocused,
|
||||
'border-base-content/10!': !isError && !isFocused,
|
||||
'bg-gray-100 text-gray-400 cursor-not-allowed':
|
||||
isDisabled && !readOnly,
|
||||
'bg-transparent! cursor-not-allowed!': readOnly,
|
||||
{inputPrefix || inputSuffix ? (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex text-sm',
|
||||
className?.inputPrefixSuffixWrapper
|
||||
)}
|
||||
>
|
||||
{inputPrefix && (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex items-center px-3 border border-r-0 border-base-content/10 rounded-l-lg transition-all duration-200',
|
||||
{
|
||||
'bg-gray-100 border-base-content/10': !isDisabled,
|
||||
'bg-gray-50 border-base-content/10': isDisabled,
|
||||
'border-error': isError,
|
||||
},
|
||||
className?.inputPrefix
|
||||
)}
|
||||
>
|
||||
{inputPrefix}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SelectComponent<T, boolean, GroupBase<T>>
|
||||
instanceId='select'
|
||||
value={value ?? (isMulti ? [] : null)}
|
||||
onChange={onChange ? handleChange : undefined}
|
||||
options={options}
|
||||
menuIsOpen={openMenu}
|
||||
inputValue={internalInputValue}
|
||||
onInputChange={internalInputChangeHandler}
|
||||
onMenuClose={() => setInternalInputValue('')}
|
||||
isMulti={isMulti}
|
||||
isDisabled={isDisabled || readOnly}
|
||||
isLoading={isLoading}
|
||||
isClearable={isClearable}
|
||||
isRtl={isRtl}
|
||||
isSearchable={isSearchable}
|
||||
placeholder={placeholder}
|
||||
closeMenuOnSelect={closeMenuOnSelect}
|
||||
hideSelectedOptions={hideSelectedOptions}
|
||||
className={cn('w-full flex-1', className?.select)}
|
||||
classNames={{
|
||||
...(!startAdornment && {
|
||||
control: ({ isFocused, isDisabled }) =>
|
||||
cn('w-full rounded-lg! border bg-white transition-shadow', {
|
||||
'cursor-pointer!': !readOnly && !isDisabled,
|
||||
'border-red-500! ring-2 ring-red-200': isError,
|
||||
'border-indigo-500 ring-2 ring-indigo-200': isFocused,
|
||||
'border-base-content/10!': !isError && !isFocused,
|
||||
'bg-gray-100 text-gray-400 cursor-not-allowed':
|
||||
isDisabled && !readOnly,
|
||||
'bg-transparent! cursor-not-allowed!': readOnly,
|
||||
'rounded-l-none!': inputPrefix,
|
||||
'rounded-r-none!': inputSuffix,
|
||||
}),
|
||||
valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'),
|
||||
}),
|
||||
valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'),
|
||||
}),
|
||||
placeholder: () =>
|
||||
cn({
|
||||
'text-gray-400 text-sm leading-tight': !isError,
|
||||
'text-red-300!': isError,
|
||||
placeholder: () =>
|
||||
cn({
|
||||
'text-gray-400 text-sm leading-tight': !isError,
|
||||
'text-red-300!': isError,
|
||||
}),
|
||||
singleValue: () =>
|
||||
cn({
|
||||
'm-0! text-gray-900 text-sm leading-tight': !isError,
|
||||
'text-error!': isError,
|
||||
'text-gray-900!': readOnly,
|
||||
}),
|
||||
input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'),
|
||||
indicatorsContainer: () =>
|
||||
cn('flex items-center gap-1 pr-3 py-2'),
|
||||
dropdownIndicator: ({ isFocused }) =>
|
||||
cn('p-0! rounded hover:bg-gray-100', {
|
||||
'text-gray-900': isFocused,
|
||||
'text-gray-500': !isFocused,
|
||||
'text-error!': isError,
|
||||
}),
|
||||
clearIndicator: () => cn('p-0! rounded hover:bg-gray-100'),
|
||||
menu: () =>
|
||||
cn(
|
||||
'border border-base-content/5 rounded-xl! bg-base-100 shadow-lg! my-1.5!'
|
||||
),
|
||||
menuList: () => cn('p-0! max-h-60 overflow-auto'),
|
||||
option: ({ isFocused, isSelected }) =>
|
||||
cn('px-3 py-2 rounded-md cursor-pointer!', {
|
||||
'bg-indigo-600 text-white': isFocused,
|
||||
'bg-blue-500!': isSelected,
|
||||
'text-gray-700': !isFocused && !isSelected,
|
||||
}),
|
||||
multiValue: ({ getValue, index }) => {
|
||||
const selectedValues = getValue() as T[];
|
||||
return cn(
|
||||
'bg-base-200! rounded-lg! py-[3px] px-2.5 m-0! flex items-center gap-1! w-fit gap-2!',
|
||||
selectedValues[index]?.className
|
||||
);
|
||||
},
|
||||
multiValueRemove: () => cn('p-0! w-3 h-3'),
|
||||
multiValueLabel: ({ getValue, index }) => {
|
||||
const selectedValues = getValue() as T[];
|
||||
return cn(
|
||||
'p-0! text-base-content! text-xs!',
|
||||
selectedValues[index]?.labelClassName
|
||||
);
|
||||
},
|
||||
}}
|
||||
components={{
|
||||
...components,
|
||||
...(optionComponent ? { Option: optionComponent } : {}),
|
||||
MenuList: CustomMenuList,
|
||||
}}
|
||||
{...(startAdornment && {
|
||||
shouldShowAdornment,
|
||||
startAdornment,
|
||||
})}
|
||||
menuPortalTarget={
|
||||
typeof document !== 'undefined'
|
||||
? (menuPortalTarget ?? document.body)
|
||||
: undefined
|
||||
}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
multiValue(base) {
|
||||
return {
|
||||
...base,
|
||||
borderRadius: '8px',
|
||||
};
|
||||
},
|
||||
}}
|
||||
onMenuScrollToBottom={onMenuScrollToBottom}
|
||||
/>
|
||||
|
||||
{inputSuffix && (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex items-center px-3 border border-l-0 border-base-content/10 rounded-r-lg transition-all duration-200',
|
||||
{
|
||||
'bg-gray-100 border-base-content/10': !isDisabled,
|
||||
'bg-gray-50 border-base-content/10': isDisabled,
|
||||
'border-error': isError,
|
||||
},
|
||||
className?.inputSuffix
|
||||
)}
|
||||
>
|
||||
{inputSuffix}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<SelectComponent<T, boolean, GroupBase<T>>
|
||||
instanceId='select'
|
||||
value={value ?? (isMulti ? [] : null)}
|
||||
onChange={onChange ? handleChange : undefined}
|
||||
options={options}
|
||||
menuIsOpen={openMenu}
|
||||
inputValue={internalInputValue}
|
||||
onInputChange={internalInputChangeHandler}
|
||||
onMenuClose={() => setInternalInputValue('')}
|
||||
isMulti={isMulti}
|
||||
isDisabled={isDisabled || readOnly}
|
||||
isLoading={isLoading}
|
||||
isClearable={isClearable}
|
||||
isRtl={isRtl}
|
||||
isSearchable={isSearchable}
|
||||
placeholder={placeholder}
|
||||
closeMenuOnSelect={closeMenuOnSelect}
|
||||
hideSelectedOptions={hideSelectedOptions}
|
||||
className={cn('w-full', className?.select)}
|
||||
classNames={{
|
||||
...(!startAdornment && {
|
||||
control: ({ isFocused, isDisabled }) =>
|
||||
cn('w-full rounded-lg! border bg-white transition-shadow', {
|
||||
'cursor-pointer!': !readOnly && !isDisabled,
|
||||
'border-red-500! ring-2 ring-red-200': isError,
|
||||
'border-indigo-500 ring-2 ring-indigo-200': isFocused,
|
||||
'border-base-content/10!': !isError && !isFocused,
|
||||
'bg-gray-100 text-gray-400 cursor-not-allowed':
|
||||
isDisabled && !readOnly,
|
||||
'bg-transparent! cursor-not-allowed!': readOnly,
|
||||
}),
|
||||
valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'),
|
||||
}),
|
||||
singleValue: () =>
|
||||
cn({
|
||||
'm-0! text-gray-900 text-sm leading-tight': !isError,
|
||||
'text-error!': isError,
|
||||
'text-gray-900!': readOnly,
|
||||
}),
|
||||
input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'),
|
||||
indicatorsContainer: () => cn('flex items-center gap-1 pr-3 py-2'),
|
||||
dropdownIndicator: ({ isFocused }) =>
|
||||
cn('p-0! rounded hover:bg-gray-100', {
|
||||
'text-gray-900': isFocused,
|
||||
'text-gray-500': !isFocused,
|
||||
'text-error!': isError,
|
||||
}),
|
||||
clearIndicator: () => cn('p-0! rounded hover:bg-gray-100'),
|
||||
menu: () =>
|
||||
cn(
|
||||
'border border-base-content/5 rounded-xl! bg-base-100 shadow-lg! my-1.5!'
|
||||
),
|
||||
menuList: () => cn('p-0! max-h-60 overflow-auto'),
|
||||
option: ({ isFocused, isSelected }) =>
|
||||
cn('px-3 py-2 rounded-md cursor-pointer!', {
|
||||
'bg-indigo-600 text-white': isFocused,
|
||||
'bg-blue-500!': isSelected,
|
||||
'text-gray-700': !isFocused && !isSelected,
|
||||
}),
|
||||
multiValue: ({ getValue, index }) => {
|
||||
const selectedValues = getValue() as T[];
|
||||
return cn(
|
||||
'bg-base-200! rounded-lg! py-[3px] px-2.5 m-0! flex items-center gap-1! w-fit gap-2!',
|
||||
selectedValues[index]?.className
|
||||
);
|
||||
},
|
||||
multiValueRemove: () => cn('p-0! w-3 h-3'),
|
||||
multiValueLabel: ({ getValue, index }) => {
|
||||
const selectedValues = getValue() as T[];
|
||||
return cn(
|
||||
'p-0! text-base-content! text-xs!',
|
||||
selectedValues[index]?.labelClassName
|
||||
);
|
||||
},
|
||||
}}
|
||||
components={{
|
||||
...components,
|
||||
...(optionComponent ? { Option: optionComponent } : {}),
|
||||
MenuList: CustomMenuList,
|
||||
}}
|
||||
{...(startAdornment && {
|
||||
shouldShowAdornment,
|
||||
startAdornment,
|
||||
})}
|
||||
menuPortalTarget={
|
||||
typeof document !== 'undefined'
|
||||
? (menuPortalTarget ?? document.body)
|
||||
: undefined
|
||||
}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
multiValue(base) {
|
||||
return {
|
||||
...base,
|
||||
borderRadius: '8px',
|
||||
};
|
||||
},
|
||||
}}
|
||||
onMenuScrollToBottom={onMenuScrollToBottom}
|
||||
/>
|
||||
placeholder: () =>
|
||||
cn({
|
||||
'text-gray-400 text-sm leading-tight': !isError,
|
||||
'text-red-300!': isError,
|
||||
}),
|
||||
singleValue: () =>
|
||||
cn({
|
||||
'm-0! text-gray-900 text-sm leading-tight': !isError,
|
||||
'text-error!': isError,
|
||||
'text-gray-900!': readOnly,
|
||||
}),
|
||||
input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'),
|
||||
indicatorsContainer: () => cn('flex items-center gap-1 pr-3 py-2'),
|
||||
dropdownIndicator: ({ isFocused }) =>
|
||||
cn('p-0! rounded hover:bg-gray-100', {
|
||||
'text-gray-900': isFocused,
|
||||
'text-gray-500': !isFocused,
|
||||
'text-error!': isError,
|
||||
}),
|
||||
clearIndicator: () => cn('p-0! rounded hover:bg-gray-100'),
|
||||
menu: () =>
|
||||
cn(
|
||||
'border border-base-content/5 rounded-xl! bg-base-100 shadow-lg! my-1.5!'
|
||||
),
|
||||
menuList: () => cn('p-0! max-h-60 overflow-auto'),
|
||||
option: ({ isFocused, isSelected }) =>
|
||||
cn('px-3 py-2 rounded-md cursor-pointer!', {
|
||||
'bg-indigo-600 text-white': isFocused,
|
||||
'bg-blue-500!': isSelected,
|
||||
'text-gray-700': !isFocused && !isSelected,
|
||||
}),
|
||||
multiValue: ({ getValue, index }) => {
|
||||
const selectedValues = getValue() as T[];
|
||||
return cn(
|
||||
'bg-base-200! rounded-lg! py-[3px] px-2.5 m-0! flex items-center gap-1! w-fit gap-2!',
|
||||
selectedValues[index]?.className
|
||||
);
|
||||
},
|
||||
multiValueRemove: () => cn('p-0! w-3 h-3'),
|
||||
multiValueLabel: ({ getValue, index }) => {
|
||||
const selectedValues = getValue() as T[];
|
||||
return cn(
|
||||
'p-0! text-base-content! text-xs!',
|
||||
selectedValues[index]?.labelClassName
|
||||
);
|
||||
},
|
||||
}}
|
||||
components={{
|
||||
...components,
|
||||
...(optionComponent ? { Option: optionComponent } : {}),
|
||||
MenuList: CustomMenuList,
|
||||
}}
|
||||
{...(startAdornment && {
|
||||
shouldShowAdornment,
|
||||
startAdornment,
|
||||
})}
|
||||
menuPortalTarget={
|
||||
typeof document !== 'undefined'
|
||||
? (menuPortalTarget ?? document.body)
|
||||
: undefined
|
||||
}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
multiValue(base) {
|
||||
return {
|
||||
...base,
|
||||
borderRadius: '8px',
|
||||
};
|
||||
},
|
||||
}}
|
||||
onMenuScrollToBottom={onMenuScrollToBottom}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||
{!isError && bottomLabel && (
|
||||
|
||||
Reference in New Issue
Block a user