Skip to content

Commit 6dfab57

Browse files
committed
feat: add support for generic typing in select picker
1 parent 3750364 commit 6dfab57

File tree

5 files changed

+105
-76
lines changed

5 files changed

+105
-76
lines changed

src/Shared/Components/SelectPicker/FilterSelectPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const FilterSelectPicker = ({
2525
setIsMenuOpen(false)
2626
}
2727

28-
const handleSelectOnChange: SelectPickerProps['onChange'] = (selectedOptionsToUpdate) => {
28+
const handleSelectOnChange: SelectPickerProps<number | string, true>['onChange'] = (selectedOptionsToUpdate) => {
2929
setSelectedOptions(structuredClone(selectedOptionsToUpdate))
3030
}
3131

src/Shared/Components/SelectPicker/SelectPicker.component.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ import { SelectPickerOptionType, SelectPickerProps, SelectPickerVariantType } fr
173173
* />
174174
* ```
175175
*/
176-
const SelectPicker = ({
176+
const SelectPicker = <OptionValue, IsMulti extends boolean>({
177177
error,
178178
icon,
179179
renderMenuListFooter,
@@ -197,7 +197,7 @@ const SelectPicker = ({
197197
isSearchable,
198198
selectRef,
199199
...props
200-
}: SelectPickerProps) => {
200+
}: SelectPickerProps<OptionValue, IsMulti>) => {
201201
const { inputId, required, isDisabled, controlShouldRenderValue = true, value } = props
202202
const {
203203
isCreatable = false,
@@ -231,17 +231,17 @@ const SelectPicker = ({
231231
const isValidNewOption = (inputValue: string) =>
232232
isCreatable &&
233233
!!inputValue?.trim() &&
234-
!getSelectPickerOptionByValue(value as SelectPickerOptionType[], inputValue.trim(), null)
234+
!getSelectPickerOptionByValue(value as SelectPickerOptionType<OptionValue>[], inputValue.trim(), null)
235235

236236
const renderControl = useCallback(
237-
(controlProps: ControlProps<SelectPickerOptionType>) => (
237+
(controlProps: ControlProps<SelectPickerOptionType<OptionValue>>) => (
238238
<SelectPickerControl {...controlProps} icon={icon} showSelectedOptionIcon={shouldShowSelectedOptionIcon} />
239239
),
240240
[icon, shouldShowSelectedOptionIcon],
241241
)
242242

243243
const renderMenuList = useCallback(
244-
(menuProps: MenuListProps<SelectPickerOptionType>) => (
244+
(menuProps: MenuListProps<SelectPickerOptionType<OptionValue>>) => (
245245
<SelectPickerMenuList
246246
{...menuProps}
247247
renderMenuListFooter={renderMenuListFooter}
@@ -253,25 +253,25 @@ const SelectPicker = ({
253253
)
254254

255255
const renderValueContainer = useCallback(
256-
(valueContainerProps: ValueContainerProps<SelectPickerOptionType>) => (
256+
(valueContainerProps: ValueContainerProps<SelectPickerOptionType<OptionValue>>) => (
257257
<SelectPickerValueContainer {...valueContainerProps} showSelectedOptionsCount={showSelectedOptionsCount} />
258258
),
259259
[showSelectedOptionsCount],
260260
)
261261

262262
const renderOption = useCallback(
263-
(optionProps: OptionProps<SelectPickerOptionType>) => (
263+
(optionProps: OptionProps<SelectPickerOptionType<OptionValue>>) => (
264264
<SelectPickerOption {...optionProps} disableDescriptionEllipsis={disableDescriptionEllipsis} />
265265
),
266266
[disableDescriptionEllipsis],
267267
)
268268

269-
const renderMultiValueLabel = (multiValueLabelProps: MultiValueProps<SelectPickerOptionType, true>) => (
270-
<SelectPickerMultiValueLabel {...multiValueLabelProps} getIsOptionValid={getIsOptionValid} />
271-
)
269+
const renderMultiValueLabel = (
270+
multiValueLabelProps: MultiValueProps<SelectPickerOptionType<OptionValue>, true>,
271+
) => <SelectPickerMultiValueLabel {...multiValueLabelProps} getIsOptionValid={getIsOptionValid} />
272272

273273
const renderGroupHeading = useCallback(
274-
(groupHeadingProps: GroupHeadingProps<SelectPickerOptionType>) => (
274+
(groupHeadingProps: GroupHeadingProps<SelectPickerOptionType<OptionValue>>) => (
275275
<SelectPickerGroupHeading {...groupHeadingProps} isGroupHeadingSelectable={isGroupHeadingSelectable} />
276276
),
277277
[isGroupHeadingSelectable],
@@ -308,9 +308,9 @@ const SelectPicker = ({
308308
<ConditionalWrap condition={isDisabled && !!disabledTippyContent} wrap={renderDisabledTippy}>
309309
<div className="w-100">
310310
{isMulti ? (
311-
<CreatableSelect<SelectPickerOptionType, true>
311+
<CreatableSelect
312312
{...props}
313-
ref={selectRef as MutableRefObject<SelectInstance<SelectPickerOptionType, true>>}
313+
ref={selectRef as SelectPickerProps<OptionValue, true>['selectRef']}
314314
isMulti
315315
name={name || inputId}
316316
classNamePrefix={classNamePrefix || inputId}
@@ -345,9 +345,9 @@ const SelectPicker = ({
345345
onCreateOption={onCreateOption}
346346
/>
347347
) : (
348-
<ReactSelect<SelectPickerOptionType, false>
348+
<ReactSelect
349349
{...props}
350-
ref={selectRef as MutableRefObject<SelectInstance<SelectPickerOptionType>>}
350+
ref={selectRef as MutableRefObject<SelectInstance<SelectPickerOptionType<OptionValue>>>}
351351
name={name || inputId}
352352
classNamePrefix={classNamePrefix || inputId}
353353
isSearchable={isSelectSearchable}

src/Shared/Components/SelectPicker/common.tsx

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
MenuListProps,
2525
MultiValueRemoveProps,
2626
MultiValueProps,
27-
GroupHeadingProps,
2827
MultiValue,
2928
} from 'react-select'
3029
import { Progressing } from '@Common/Progressing'
@@ -36,10 +35,12 @@ import { noop } from '@Common/Helper'
3635
import { CHECKBOX_VALUE } from '@Common/Types'
3736
import { Checkbox } from '@Common/Checkbox'
3837
import { ReactSelectInputAction } from '@Common/Constants'
39-
import { SelectPickerOptionType, SelectPickerProps } from './type'
38+
import { SelectPickerGroupHeadingProps, SelectPickerOptionType, SelectPickerProps } from './type'
4039
import { getGroupCheckboxValue } from './utils'
4140

42-
export const SelectPickerDropdownIndicator = (props: DropdownIndicatorProps<SelectPickerOptionType>) => {
41+
export const SelectPickerDropdownIndicator = <OptionValue,>(
42+
props: DropdownIndicatorProps<SelectPickerOptionType<OptionValue>>,
43+
) => {
4344
const { isDisabled } = props
4445

4546
return (
@@ -49,21 +50,24 @@ export const SelectPickerDropdownIndicator = (props: DropdownIndicatorProps<Sele
4950
)
5051
}
5152

52-
export const SelectPickerClearIndicator = (props: ClearIndicatorProps<SelectPickerOptionType>) => (
53+
export const SelectPickerClearIndicator = <OptionValue,>(
54+
props: ClearIndicatorProps<SelectPickerOptionType<OptionValue>>,
55+
) => (
5356
<components.ClearIndicator {...props}>
5457
<ICClose className="icon-dim-16 icon-use-fill-n6 dc__no-shrink" />
5558
</components.ClearIndicator>
5659
)
5760

58-
export const SelectPickerControl = ({
61+
export const SelectPickerControl = <OptionValue, IsMulti extends boolean>({
5962
icon,
6063
showSelectedOptionIcon,
6164
...props
62-
}: ControlProps<SelectPickerOptionType> & Pick<SelectPickerProps, 'icon' | 'showSelectedOptionIcon'>) => {
65+
}: ControlProps<SelectPickerOptionType<OptionValue>> &
66+
Pick<SelectPickerProps<OptionValue, IsMulti>, 'icon' | 'showSelectedOptionIcon'>) => {
6367
const { children, getValue } = props
6468
const { startIcon, endIcon } = getValue()?.[0] ?? {}
6569

66-
let iconToDisplay = icon
70+
let iconToDisplay: SelectPickerOptionType<OptionValue>['startIcon'] = icon
6771

6872
if (showSelectedOptionIcon && (startIcon || endIcon)) {
6973
iconToDisplay = startIcon || endIcon
@@ -79,10 +83,11 @@ export const SelectPickerControl = ({
7983
)
8084
}
8185

82-
export const SelectPickerValueContainer = ({
86+
export const SelectPickerValueContainer = <OptionValue, IsMulti extends boolean>({
8387
showSelectedOptionsCount,
8488
...props
85-
}: ValueContainerProps<SelectPickerOptionType> & Pick<SelectPickerProps, 'showSelectedOptionsCount'>) => {
89+
}: ValueContainerProps<SelectPickerOptionType<OptionValue>> &
90+
Pick<SelectPickerProps<OptionValue, IsMulti>, 'showSelectedOptionsCount'>) => {
8691
const { getValue } = props
8792
const selectedOptionsLength = (getValue() ?? []).length
8893

@@ -102,10 +107,11 @@ export const SelectPickerValueContainer = ({
102107

103108
export const SelectPickerLoadingIndicator = () => <Progressing />
104109

105-
export const SelectPickerOption = ({
110+
export const SelectPickerOption = <OptionValue, IsMulti extends boolean>({
106111
disableDescriptionEllipsis,
107112
...props
108-
}: OptionProps<SelectPickerOptionType> & Pick<SelectPickerProps, 'disableDescriptionEllipsis'>) => {
113+
}: OptionProps<SelectPickerOptionType<OptionValue>> &
114+
Pick<SelectPickerProps<OptionValue, IsMulti>, 'disableDescriptionEllipsis'>) => {
109115
const {
110116
label,
111117
data,
@@ -161,13 +167,16 @@ export const SelectPickerOption = ({
161167
)
162168
}
163169

164-
export const SelectPickerMenuList = ({
170+
export const SelectPickerMenuList = <OptionValue, IsMulti extends boolean>({
165171
renderMenuListFooter,
166172
shouldRenderCustomOptions,
167173
renderCustomOptions,
168174
...props
169-
}: MenuListProps<SelectPickerOptionType> &
170-
Pick<SelectPickerProps, 'renderMenuListFooter' | 'shouldRenderCustomOptions' | 'renderCustomOptions'>) => {
175+
}: MenuListProps<SelectPickerOptionType<OptionValue>> &
176+
Pick<
177+
SelectPickerProps<OptionValue, IsMulti>,
178+
'renderMenuListFooter' | 'shouldRenderCustomOptions' | 'renderCustomOptions'
179+
>) => {
171180
const { children } = props
172181

173182
return (
@@ -183,10 +192,11 @@ export const SelectPickerMenuList = ({
183192
)
184193
}
185194

186-
export const SelectPickerMultiValueLabel = ({
195+
export const SelectPickerMultiValueLabel = <OptionValue, IsMulti extends boolean>({
187196
getIsOptionValid,
188197
...props
189-
}: MultiValueProps<SelectPickerOptionType, true> & Pick<SelectPickerProps['multiSelectProps'], 'getIsOptionValid'>) => {
198+
}: MultiValueProps<SelectPickerOptionType<OptionValue>, true> &
199+
Pick<SelectPickerProps<OptionValue, IsMulti>['multiSelectProps'], 'getIsOptionValid'>) => {
190200
const { data, selectProps } = props
191201
const isOptionValid = getIsOptionValid(data)
192202
const iconToDisplay = isOptionValid ? data.startIcon || data.endIcon : <ICErrorExclamation />
@@ -213,11 +223,10 @@ export const SelectPickerMultiValueRemove = (props: MultiValueRemoveProps) => (
213223
</components.MultiValueLabel>
214224
)
215225

216-
export const SelectPickerGroupHeading = ({
226+
export const SelectPickerGroupHeading = <OptionValue,>({
217227
isGroupHeadingSelectable,
218228
...props
219-
}: GroupHeadingProps<SelectPickerOptionType> &
220-
Pick<SelectPickerProps['multiSelectProps'], 'isGroupHeadingSelectable'>) => {
229+
}: SelectPickerGroupHeadingProps<OptionValue>) => {
221230
const { data, selectProps } = props
222231

223232
if (!data.label) {
@@ -228,7 +237,7 @@ export const SelectPickerGroupHeading = ({
228237
return <components.GroupHeading {...props} />
229238
}
230239

231-
const selectedOptions = (selectProps.value as MultiValue<SelectPickerOptionType>) ?? []
240+
const selectedOptions = (selectProps.value as MultiValue<SelectPickerOptionType<OptionValue>>) ?? []
232241
const groupHeadingOptions = data.options ?? []
233242

234243
const checkboxValue = getGroupCheckboxValue(groupHeadingOptions, selectedOptions, selectProps.getOptionValue)

src/Shared/Components/SelectPicker/type.ts

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
import { OptionType } from '@Common/Types'
1818
import { ComponentSizeType } from '@Shared/constants'
1919
import { MutableRefObject, ReactElement, ReactNode } from 'react'
20-
import { GroupBase, Props as ReactSelectProps, SelectInstance } from 'react-select'
20+
import { GroupBase, GroupHeadingProps, Props as ReactSelectProps, SelectInstance } from 'react-select'
2121
import { CreatableProps } from 'react-select/creatable'
2222

23-
export interface SelectPickerOptionType extends OptionType<number | string | object, ReactNode> {
23+
export interface SelectPickerOptionType<OptionValue = string | number> extends OptionType<OptionValue, ReactNode> {
2424
/**
2525
* Description to be displayed for the option
2626
*/
@@ -40,10 +40,14 @@ export enum SelectPickerVariantType {
4040
BORDER_LESS = 'border-less',
4141
}
4242

43-
type SelectProps = ReactSelectProps<SelectPickerOptionType, boolean, GroupBase<SelectPickerOptionType>>
43+
type SelectProps<OptionValue, IsMulti extends boolean> = ReactSelectProps<
44+
SelectPickerOptionType<OptionValue>,
45+
IsMulti,
46+
GroupBase<SelectPickerOptionType<OptionValue>>
47+
>
4448

45-
export type SelectPickerProps = Pick<
46-
SelectProps,
49+
export type SelectPickerProps<OptionValue = number | string, IsMulti extends boolean = false> = Pick<
50+
SelectProps<OptionValue, IsMulti>,
4751
| 'name'
4852
| 'classNamePrefix'
4953
| 'options'
@@ -69,7 +73,7 @@ export type SelectPickerProps = Pick<
6973
| 'onMenuClose'
7074
| 'autoFocus'
7175
> &
72-
Required<Pick<SelectProps, 'inputId'>> & {
76+
Required<Pick<SelectProps<OptionValue, IsMulti>, 'inputId'>> & {
7377
/**
7478
* Icon to be rendered in the control
7579
*/
@@ -136,42 +140,45 @@ export type SelectPickerProps = Pick<
136140
/**
137141
* Ref for the select picker
138142
*/
139-
selectRef?: MutableRefObject<
140-
SelectInstance<SelectPickerOptionType> | SelectInstance<SelectPickerOptionType, true>
141-
>
142-
} & (
143-
| {
144-
isMulti?: never
145-
multiSelectProps?: never
146-
}
147-
| {
148-
isMulti: boolean
143+
selectRef?: IsMulti extends true
144+
? MutableRefObject<SelectInstance<SelectPickerOptionType<OptionValue>, true>>
145+
: MutableRefObject<SelectInstance<SelectPickerOptionType<OptionValue>>>
146+
} & (IsMulti extends true
147+
? {
148+
isMulti: IsMulti | boolean
149149
multiSelectProps?: Pick<
150-
CreatableProps<SelectPickerOptionType, true, GroupBase<SelectPickerOptionType>>,
150+
CreatableProps<
151+
SelectPickerOptionType<OptionValue>,
152+
true,
153+
GroupBase<SelectPickerOptionType<OptionValue>>
154+
>,
151155
'onCreateOption'
152156
> & {
153157
/**
154158
* If true, the select picker creates the new option
155-
* Only applicable for isMulti: true
159+
* Only applicable for IsMulti: true
156160
*
157161
* @default false
158162
*/
159163
isCreatable?: boolean
160164
/**
161165
* If true, the group heading can be selected
162166
*
163-
* Only applicable for isMulti: true
167+
* Only applicable for IsMulti: true
164168
*
165169
* @default false
166170
*/
167171
isGroupHeadingSelectable?: boolean
168172
/**
169173
* Callback handler to check if the given selection is valid or not
170174
*/
171-
getIsOptionValid?: (option: SelectPickerOptionType) => boolean
175+
getIsOptionValid?: (option: SelectPickerOptionType<OptionValue>) => boolean
172176
}
173177
}
174-
) &
178+
: {
179+
isMulti?: never
180+
multiSelectProps?: never
181+
}) &
175182
(
176183
| {
177184
/**
@@ -195,9 +202,16 @@ export type SelectPickerProps = Pick<
195202
}
196203
)
197204

205+
// Doing like this, because of global export error GroupHeadingPropsDefinedProps
206+
export type SelectPickerGroupHeadingProps<OptionValue> = GroupHeadingProps<SelectPickerOptionType<OptionValue>> & {
207+
isGroupHeadingSelectable: boolean
208+
}
209+
198210
export interface FilterSelectPickerProps
199-
extends Required<Pick<SelectPickerProps, 'options' | 'isDisabled' | 'placeholder' | 'isLoading'>>,
200-
Pick<SelectPickerProps, 'selectRef' | 'inputId' | 'menuPosition' | 'autoFocus'> {
201-
appliedFilterOptions: SelectPickerProps['options']
202-
handleApplyFilter: (filtersToApply: SelectPickerOptionType[]) => void
211+
extends Required<
212+
Pick<SelectPickerProps<number | string, true>, 'options' | 'isDisabled' | 'placeholder' | 'isLoading'>
213+
>,
214+
Pick<SelectPickerProps<number | string, true>, 'selectRef' | 'inputId' | 'menuPosition' | 'autoFocus'> {
215+
appliedFilterOptions: SelectPickerProps<number | string, true>['options']
216+
handleApplyFilter: (filtersToApply: SelectPickerOptionType<number | string>[]) => void
203217
}

0 commit comments

Comments
 (0)