Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-select v5 #1247

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
nodejs 20.14.0
ruby 3.2.2
nodejs 18.19.1
yarn 1.22.18
golang 1.22.1
python 3.10.10
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"prop-types": "^15.7.2",
"react-imask": "^6.2.2",
"react-resize-detector": "^4.2.3",
"react-select": "^5.8.0",
"react-select-plus": "1.2.0",
"react-sortable-hoc": "^1.11.0",
"react-text-mask": "~5.0.2",
Expand Down
73 changes: 0 additions & 73 deletions src/components/Select/Select.js

This file was deleted.

196 changes: 196 additions & 0 deletions src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// @ts-nocheck
import React, { useState, forwardRef, useEffect } from 'react';
import ReactSelect, {
OptionProps,
MultiValueProps,
IndicatorProps,
StylesConfig,
Props as SelectProps,
ActionMeta,
} from 'react-select';
import AsyncSelect from 'react-select/async';
import AsyncCreatableSelect from 'react-select/async-creatable';
import CreatableSelect from 'react-select/creatable';
import Badge from '../Badge/Badge';
import Icon from '../Icon/Icon';

// Type definitions
type OptionType = { label: string; value: any; disabled?: boolean };

interface CustomSelectProps extends Omit<SelectProps<OptionType, boolean>, 'onChange' | 'isMulti' | 'isDisabled'> {
onChange?: (value: any, action?: ActionMeta<OptionType>) => void;
arrowRenderer?: (props: { isOpen: boolean }) => React.ReactNode;
valueComponent?: React.ComponentType<MultiValueProps<OptionType>>;
optionComponent?: React.ComponentType<OptionProps<OptionType>>;
loadOptions?: (input: string, callback: (options: OptionType[]) => void) => void;
creatable?: boolean;
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
multi?: boolean;
disabled?: boolean;
isValidNewOption?: (inputValue: any) => boolean;
}

// Utility functions
const getSelectArrow = (isOpen: boolean, arrowRenderer?: (props: { isOpen: boolean }) => React.ReactNode) =>
arrowRenderer ? arrowRenderer({ isOpen }) : <Icon name={`caret-${isOpen ? 'up' : 'down'}`} />;

const getCloseButton = () => (
<Icon name="xmark" className="ms-1" style={{ opacity: 0.5, fontSize: '.5rem' }} />
);

// Custom components
const CustomMultiValue: React.FC<MultiValueProps<OptionType>> = (props) => {
const { children, removeProps, ...badgeProps } = props;

return (
<Badge
color="light"
className="ms-1 fw-normal border d-inline-flex align-items-center text-start"
style={{ textTransform: 'none', whiteSpace: 'normal' }}
{...badgeProps}
>
{children}
<span {...removeProps}>
{getCloseButton()}
</span>
</Badge>
);
};

const CustomOption: React.FC<OptionProps<OptionType>> = (props) => {
const { children, isDisabled, isFocused, isSelected, innerProps, data } = props;

return (
<div
className={`
dropdown-item
${isSelected && !isFocused ? 'bg-light' : ''}
${isFocused ? 'bg-primary text-white' : ''}
${isDisabled || data.disabled ? 'disabled' : ''}
`.trim()}
{...innerProps}
aria-disabled={isDisabled || data.disabled}
>
{children}
</div>
);
};

const CustomArrow: React.FC<IndicatorProps<OptionType>> = ({ selectProps }) => {
const { menuIsOpen, arrowRenderer } = selectProps as CustomSelectProps;
return <>{getSelectArrow(!!menuIsOpen, arrowRenderer)}</>;
};

// Main Select component
const Select = forwardRef<any, CustomSelectProps>((props, ref) => {
const {
arrowRenderer,

Check warning on line 87 in src/components/Select/Select.tsx

View workflow job for this annotation

GitHub Actions / test

'arrowRenderer' is assigned a value but never used
className,
defaultValue,
inputProps,
valueComponent,
optionComponent,
loadOptions,
creatable,
onChange,
multi,
isValidNewOption,
value: propsValue,
options: propsOptions,
disabled,
...restProps
} = props;

const [value, setValue] = useState(propsValue || defaultValue);
const [options, setOptions] = useState(propsOptions || []);

useEffect(() => {
if (propsValue !== undefined) {
setValue(propsValue);
}
}, [propsValue]);

useEffect(() => {
if (propsOptions) {
setOptions(propsOptions);
}
}, [propsOptions]);

const handleChange = (newValue: any, action: ActionMeta<OptionType>) => {
setValue(newValue);
if (onChange) {
// For multi-select, always pass an array
if (multi) {
onChange(newValue || [], action);
} else {
onChange(newValue, action);
}
}
};

// Handle async options loading
const loadOptionsWrapper = loadOptions
? (inputValue: string) =>
new Promise<OptionType[]>((resolve) => {
loadOptions(inputValue, (result: any) => {
resolve(result.options || []);
});
})
: undefined;

// Determine which Select component to use
let SelectElement: typeof ReactSelect | typeof AsyncSelect | typeof CreatableSelect | typeof AsyncCreatableSelect = ReactSelect;
if (loadOptionsWrapper && creatable) {
SelectElement = AsyncCreatableSelect;
} else if (loadOptionsWrapper) {
SelectElement = AsyncSelect;
} else if (creatable) {
SelectElement = CreatableSelect;
}

// Custom styles
const customStyles: StylesConfig<OptionType, boolean> = {
control: (base) => {return {
...base,
minHeight: '2.35rem',
}},
option: (base, state) => {return {
...base,
backgroundColor: state.isDisabled ? '#f8f9fa' : base.backgroundColor,
color: state.isDisabled ? '#6c757d' : base.color,
cursor: state.isDisabled ? 'not-allowed' : 'default',
}},
};

const isValidNewOptionWrapper = isValidNewOption
// eslint-disable-next-line no-shadow
? ({ label, value, options }: CreateOptionProps<OptionType>) => isValidNewOption({ label, value })

Check warning on line 167 in src/components/Select/Select.tsx

View workflow job for this annotation

GitHub Actions / test

'options' is defined but never used

Check failure on line 167 in src/components/Select/Select.tsx

View workflow job for this annotation

GitHub Actions / test

'options' is defined but never used
: undefined;

return (
<SelectElement
ref={ref}
className={`${className || ''} ${loadOptionsWrapper ? 'select-async' : ''}`.trim()}
components={{
MultiValue: valueComponent || CustomMultiValue,
Option: optionComponent || CustomOption,
DropdownIndicator: CustomArrow,
}}
styles={customStyles}
inputProps={{ name: props.name, ...inputProps }}
isMulti={multi}
isDisabled={disabled}
loadOptions={loadOptionsWrapper}
onChange={handleChange}
value={value}
options={options}
isValidNewOption={isValidNewOptionWrapper}
isOptionDisabled={(option: OptionType) => !!option.disabled}
{...restProps}
/>
);
});

Select.displayName = 'Select';

export default Select;
Loading
Loading