feat: Integrate react-select for multi-select fields and add support for file upload field types.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Select, { MultiValue, StylesConfig } from 'react-select';
|
||||
import { ProfileField } from '@/services/profile-sections.service';
|
||||
import { FormInput } from './FormInput';
|
||||
import { FormCheckbox } from './FormCheckbox';
|
||||
@@ -8,6 +9,12 @@ import { FormTextarea } from './FormTextarea';
|
||||
import { FormSelect } from './FormSelect';
|
||||
import { TagInput } from './TagInput';
|
||||
|
||||
// Type for react-select options
|
||||
interface SelectOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface DynamicFieldProps {
|
||||
field: ProfileField;
|
||||
value: unknown;
|
||||
@@ -240,19 +247,94 @@ function RadioGroup({ field, value, onChange }: RadioGroupProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// Multi-Select Component
|
||||
// Multi-Select Component using react-select
|
||||
interface MultiSelectProps {
|
||||
field: ProfileField;
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
}
|
||||
|
||||
// Custom styles for react-select to match the design
|
||||
const multiSelectStyles: StylesConfig<SelectOption, true> = {
|
||||
control: (base, state) => ({
|
||||
...base,
|
||||
borderRadius: '15px',
|
||||
borderColor: state.isFocused ? '#E58625' : 'rgba(0, 41, 61, 0.2)',
|
||||
boxShadow: state.isFocused ? '0 0 0 1px #E58625' : 'none',
|
||||
padding: '4px',
|
||||
minHeight: '48px',
|
||||
fontFamily: 'inherit',
|
||||
'&:hover': {
|
||||
borderColor: '#E58625',
|
||||
},
|
||||
}),
|
||||
placeholder: (base) => ({
|
||||
...base,
|
||||
color: 'rgba(0, 41, 61, 0.4)',
|
||||
fontSize: '14px',
|
||||
}),
|
||||
input: (base) => ({
|
||||
...base,
|
||||
fontSize: '14px',
|
||||
color: '#00293D',
|
||||
}),
|
||||
multiValue: (base) => ({
|
||||
...base,
|
||||
backgroundColor: 'rgba(229, 134, 37, 0.1)',
|
||||
borderRadius: '20px',
|
||||
}),
|
||||
multiValueLabel: (base) => ({
|
||||
...base,
|
||||
color: '#E58625',
|
||||
fontSize: '12px',
|
||||
padding: '4px 8px',
|
||||
}),
|
||||
multiValueRemove: (base) => ({
|
||||
...base,
|
||||
color: '#E58625',
|
||||
borderRadius: '0 20px 20px 0',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(229, 134, 37, 0.2)',
|
||||
color: '#E58625',
|
||||
},
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
fontSize: '14px',
|
||||
color: '#00293D',
|
||||
backgroundColor: state.isSelected
|
||||
? 'rgba(229, 134, 37, 0.1)'
|
||||
: state.isFocused
|
||||
? 'rgba(229, 134, 37, 0.05)'
|
||||
: 'white',
|
||||
'&:active': {
|
||||
backgroundColor: 'rgba(229, 134, 37, 0.15)',
|
||||
},
|
||||
}),
|
||||
menu: (base) => ({
|
||||
...base,
|
||||
borderRadius: '15px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0px 10px 20px rgba(217, 217, 217, 0.5)',
|
||||
}),
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
padding: '8px',
|
||||
}),
|
||||
};
|
||||
|
||||
function MultiSelect({ field, value, onChange }: MultiSelectProps) {
|
||||
const handleToggle = (optionValue: string) => {
|
||||
const newValue = value.includes(optionValue)
|
||||
? value.filter(v => v !== optionValue)
|
||||
: [...value, optionValue];
|
||||
onChange(newValue);
|
||||
// Convert field options to react-select format
|
||||
const options: SelectOption[] = field.options?.map(opt => ({
|
||||
value: opt.value,
|
||||
label: opt.label,
|
||||
})) || [];
|
||||
|
||||
// Convert current value to react-select format
|
||||
const selectedOptions = options.filter(opt => value.includes(opt.value));
|
||||
|
||||
const handleChange = (newValue: MultiValue<SelectOption>) => {
|
||||
onChange(newValue.map(opt => opt.value));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -261,44 +343,19 @@ function MultiSelect({ field, value, onChange }: MultiSelectProps) {
|
||||
{field.name}
|
||||
{field.isRequired && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<div className="border border-[#00293D]/20 rounded-[15px] p-3 max-h-[200px] overflow-y-auto">
|
||||
{field.options?.map((option) => (
|
||||
<label
|
||||
key={option.value}
|
||||
className="flex items-center gap-2 cursor-pointer py-1 hover:bg-gray-50 px-2 rounded"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value.includes(option.value)}
|
||||
onChange={() => handleToggle(option.value)}
|
||||
className="w-4 h-4 rounded border-[#00293D]/20 text-[#E58625] focus:ring-[#E58625]"
|
||||
/>
|
||||
<span className="text-[14px] font-serif text-[#00293D]">{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{value.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{value.map((v) => {
|
||||
const option = field.options?.find(o => o.value === v);
|
||||
return (
|
||||
<span
|
||||
key={v}
|
||||
className="inline-flex items-center gap-1 px-2 py-1 bg-[#E58625]/10 text-[#E58625] rounded-full text-[12px]"
|
||||
>
|
||||
{option?.label || v}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleToggle(v)}
|
||||
className="hover:text-[#E58625]/70"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Select<SelectOption, true>
|
||||
isMulti
|
||||
options={options}
|
||||
value={selectedOptions}
|
||||
onChange={handleChange}
|
||||
placeholder={field.placeholder || `Select ${field.name.toLowerCase()}...`}
|
||||
styles={multiSelectStyles}
|
||||
classNamePrefix="react-select"
|
||||
isClearable
|
||||
isSearchable
|
||||
closeMenuOnSelect={false}
|
||||
noOptionsMessage={() => 'No options available'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user