Skip to content

Commit 76b7106

Browse files
committed
ignore
1 parent ec24292 commit 76b7106

File tree

1 file changed

+164
-86
lines changed

1 file changed

+164
-86
lines changed

src/components/Select/Select.tsx

Lines changed: 164 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,196 @@
1-
// @ts-ignore
2-
import React, { useState, useRef, useEffect } from 'react';
1+
// @ts-nocheck
2+
import React, { useState, forwardRef, useEffect } from 'react';
33
import ReactSelect, {
4-
components as ReactSelectComponents,
5-
Props as ReactSelectProps,
4+
OptionProps,
5+
MultiValueProps,
6+
IndicatorProps,
7+
StylesConfig,
8+
Props as SelectProps,
69
ActionMeta,
7-
SingleValue,
8-
MultiValue,
9-
ValueContainerProps,
1010
} from 'react-select';
1111
import AsyncSelect from 'react-select/async';
1212
import AsyncCreatableSelect from 'react-select/async-creatable';
1313
import CreatableSelect from 'react-select/creatable';
14+
import Badge from '../Badge/Badge';
15+
import Icon from '../Icon/Icon';
1416

15-
export interface Option {
16-
label: string;
17-
value: any;
18-
}
17+
// Type definitions
18+
type OptionType = { label: string; value: any; disabled?: boolean };
1919

20-
interface SelectProps extends Omit<ReactSelectProps<Option, boolean>, 'onChange'> {
21-
options?: Option[];
22-
defaultValue?: Option | Option[] | null;
23-
value?: Option | Option[] | null;
24-
onChange?: (value: Option | Option[] | null, action: ActionMeta<Option>) => void;
25-
loadOptions?: (inputValue: string, callback: (options: Option[]) => void) => void;
20+
interface CustomSelectProps extends Omit<SelectProps<OptionType, boolean>, 'onChange' | 'isMulti' | 'isDisabled'> {
21+
onChange?: (value: any, action?: ActionMeta<OptionType>) => void;
22+
arrowRenderer?: (props: { isOpen: boolean }) => React.ReactNode;
23+
valueComponent?: React.ComponentType<MultiValueProps<OptionType>>;
24+
optionComponent?: React.ComponentType<OptionProps<OptionType>>;
25+
loadOptions?: (input: string, callback: (options: OptionType[]) => void) => void;
2626
creatable?: boolean;
27-
multi?: boolean;
28-
name?: string;
2927
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
28+
multi?: boolean;
29+
disabled?: boolean;
30+
isValidNewOption?: (inputValue: any) => boolean;
3031
}
3132

32-
const Select: React.FC<SelectProps> = ({
33-
options,
34-
defaultValue,
35-
value,
36-
onChange,
37-
loadOptions,
38-
creatable,
39-
multi,
40-
name,
41-
inputProps,
42-
className,
43-
...props
44-
}) => {
45-
const [internalValue, setInternalValue] = useState<Option | Option[] | null>(
46-
value || defaultValue || null
33+
// Utility functions
34+
const getSelectArrow = (isOpen: boolean, arrowRenderer?: (props: { isOpen: boolean }) => React.ReactNode) =>
35+
arrowRenderer ? arrowRenderer({ isOpen }) : <Icon name={`caret-${isOpen ? 'up' : 'down'}`} />;
36+
37+
const getCloseButton = () => (
38+
<Icon name="xmark" className="ms-1" style={{ opacity: 0.5, fontSize: '.5rem' }} />
39+
);
40+
41+
// Custom components
42+
const CustomMultiValue: React.FC<MultiValueProps<OptionType>> = (props) => {
43+
const { children, removeProps, ...badgeProps } = props;
44+
45+
return (
46+
<Badge
47+
color="light"
48+
className="ms-1 fw-normal border d-inline-flex align-items-center text-start"
49+
style={{ textTransform: 'none', whiteSpace: 'normal' }}
50+
{...badgeProps}
51+
>
52+
{children}
53+
<span {...removeProps}>
54+
{getCloseButton()}
55+
</span>
56+
</Badge>
4757
);
48-
const selectRef = useRef<any>(null);
49-
const isControlled = value !== undefined;
58+
};
59+
60+
const CustomOption: React.FC<OptionProps<OptionType>> = (props) => {
61+
const { children, isDisabled, isFocused, isSelected, innerProps, data } = props;
62+
63+
return (
64+
<div
65+
className={`
66+
dropdown-item
67+
${isSelected && !isFocused ? 'bg-light' : ''}
68+
${isFocused ? 'bg-primary text-white' : ''}
69+
${isDisabled || data.disabled ? 'disabled' : ''}
70+
`.trim()}
71+
{...innerProps}
72+
aria-disabled={isDisabled || data.disabled}
73+
>
74+
{children}
75+
</div>
76+
);
77+
};
78+
79+
const CustomArrow: React.FC<IndicatorProps<OptionType>> = ({ selectProps }) => {
80+
const { menuIsOpen, arrowRenderer } = selectProps as CustomSelectProps;
81+
return <>{getSelectArrow(!!menuIsOpen, arrowRenderer)}</>;
82+
};
83+
84+
// Main Select component
85+
const Select = forwardRef<any, CustomSelectProps>((props, ref) => {
86+
const {
87+
arrowRenderer,
88+
className,
89+
defaultValue,
90+
inputProps,
91+
valueComponent,
92+
optionComponent,
93+
loadOptions,
94+
creatable,
95+
onChange,
96+
multi,
97+
isValidNewOption,
98+
value: propsValue,
99+
options: propsOptions,
100+
disabled,
101+
...restProps
102+
} = props;
103+
104+
const [value, setValue] = useState(propsValue || defaultValue);
105+
const [options, setOptions] = useState(propsOptions || []);
106+
107+
useEffect(() => {
108+
if (propsValue !== undefined) {
109+
setValue(propsValue);
110+
}
111+
}, [propsValue]);
50112

51113
useEffect(() => {
52-
if (isControlled) {
53-
setInternalValue(value);
114+
if (propsOptions) {
115+
setOptions(propsOptions);
54116
}
55-
}, [value, isControlled]);
56-
57-
const handleChange = (
58-
newValue: SingleValue<Option> | MultiValue<Option>,
59-
action: ActionMeta<Option>
60-
) => {
61-
if (!isControlled) {
62-
setInternalValue(newValue);
117+
}, [propsOptions]);
118+
119+
const handleChange = (newValue: any, action: ActionMeta<OptionType>) => {
120+
setValue(newValue);
121+
if (onChange) {
122+
// For multi-select, always pass an array
123+
if (multi) {
124+
onChange(newValue || [], action);
125+
} else {
126+
onChange(newValue, action);
127+
}
63128
}
64-
onChange?.(newValue, action);
65129
};
66130

67-
let SelectComponent: any = ReactSelect;
68-
if (loadOptions) {
69-
SelectComponent = creatable ? AsyncCreatableSelect : AsyncSelect;
131+
// Handle async options loading
132+
const loadOptionsWrapper = loadOptions
133+
? (inputValue: string) =>
134+
new Promise<OptionType[]>((resolve) => {
135+
loadOptions(inputValue, (result: any) => {
136+
resolve(result.options || []);
137+
});
138+
})
139+
: undefined;
140+
141+
// Determine which Select component to use
142+
let SelectElement: typeof ReactSelect | typeof AsyncSelect | typeof CreatableSelect | typeof AsyncCreatableSelect = ReactSelect;
143+
if (loadOptionsWrapper && creatable) {
144+
SelectElement = AsyncCreatableSelect;
145+
} else if (loadOptionsWrapper) {
146+
SelectElement = AsyncSelect;
70147
} else if (creatable) {
71-
SelectComponent = CreatableSelect;
148+
SelectElement = CreatableSelect;
72149
}
73150

74-
const selectClassName = `Select ${multi ? 'Select--multi' : 'Select--single'} ${
75-
loadOptions ? 'select-async' : ''
76-
} ${className || ''}`.trim();
77-
78-
const CustomValueContainer = ({ children, ...props }: ValueContainerProps<Option, boolean>) => {
79-
return (
80-
<ReactSelectComponents.ValueContainer {...props}>
81-
{children}
82-
{name && <input type="hidden" name={name} value={props.getValue()[0]?.value || ''} />}
83-
</ReactSelectComponents.ValueContainer>
84-
);
151+
// Custom styles
152+
const customStyles: StylesConfig<OptionType, boolean> = {
153+
control: (base) => {return {
154+
...base,
155+
minHeight: '2.35rem',
156+
}},
157+
option: (base, state) => {return {
158+
...base,
159+
backgroundColor: state.isDisabled ? '#f8f9fa' : base.backgroundColor,
160+
color: state.isDisabled ? '#6c757d' : base.color,
161+
cursor: state.isDisabled ? 'not-allowed' : 'default',
162+
}},
85163
};
86164

165+
const isValidNewOptionWrapper = isValidNewOption
166+
// eslint-disable-next-line no-shadow
167+
? ({ label, value, options }: CreateOptionProps<OptionType>) => isValidNewOption({ label, value })
168+
: undefined;
169+
87170
return (
88-
<SelectComponent
89-
ref={selectRef}
90-
options={options}
91-
value={internalValue}
92-
onChange={handleChange}
93-
loadOptions={loadOptions}
94-
isMulti={multi}
95-
className={selectClassName}
96-
classNamePrefix="Select"
97-
{...props}
171+
<SelectElement
172+
ref={ref}
173+
className={`${className || ''} ${loadOptionsWrapper ? 'select-async' : ''}`.trim()}
98174
components={{
99-
...ReactSelectComponents,
100-
Input: (inputComponentProps: any) => (
101-
<ReactSelectComponents.Input
102-
{...inputComponentProps}
103-
{...inputProps}
104-
name={inputProps?.name || name}
105-
/>
106-
),
107-
ValueContainer: CustomValueContainer,
175+
MultiValue: valueComponent || CustomMultiValue,
176+
Option: optionComponent || CustomOption,
177+
DropdownIndicator: CustomArrow,
108178
}}
179+
styles={customStyles}
180+
inputProps={{ name: props.name, ...inputProps }}
181+
isMulti={multi}
182+
isDisabled={disabled}
183+
loadOptions={loadOptionsWrapper}
184+
onChange={handleChange}
185+
value={value}
186+
options={options}
187+
isValidNewOption={isValidNewOptionWrapper}
188+
isOptionDisabled={(option: OptionType) => !!option.disabled}
189+
{...restProps}
109190
/>
110191
);
111-
};
192+
});
112193

113-
// For testing purposes
114-
Select.Async = AsyncSelect;
115-
Select.AsyncCreatable = AsyncCreatableSelect;
116-
Select.Creatable = CreatableSelect;
194+
Select.displayName = 'Select';
117195

118196
export default Select;

0 commit comments

Comments
 (0)