Skip to content

Commit 5b486b0

Browse files
committed
feat: add common component select picker with control styling
1 parent 472282b commit 5b486b0

File tree

5 files changed

+209
-0
lines changed

5 files changed

+209
-0
lines changed

src/Assets/Icon/ic-caret-down.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import ReactSelect, { ControlProps, Props as ReactSelectProps } from 'react-select'
2+
import { ReactNode, useCallback, useMemo } from 'react'
3+
import { ReactComponent as ErrorIcon } from '@Icons/ic-warning.svg'
4+
import { getCommonSelectStyle } from './utils'
5+
import { ControlWithIcon, DropdownIndicator, LoadingIndicator } from './common'
6+
7+
export interface SelectPickerProps
8+
extends Pick<
9+
ReactSelectProps,
10+
| 'isMulti'
11+
| 'options'
12+
| 'value'
13+
| 'onChange'
14+
| 'isSearchable'
15+
| 'isClearable'
16+
| 'isLoading'
17+
| 'placeholder'
18+
| 'hideSelectedOptions'
19+
| 'controlShouldRenderValue'
20+
| 'backspaceRemovesValue'
21+
| 'closeMenuOnSelect'
22+
| 'isDisabled'
23+
| 'isLoading'
24+
| 'inputId'
25+
> {
26+
icon?: ReactNode
27+
error?: ReactNode
28+
}
29+
30+
const SelectPicker = ({ error, icon, placeholder = 'Select a option', ...props }: SelectPickerProps) => {
31+
const selectStyles = useMemo(
32+
() =>
33+
getCommonSelectStyle({
34+
hasError: !!error,
35+
}),
36+
[error],
37+
)
38+
39+
const renderControl = useCallback(
40+
(controlProps: ControlProps) => <ControlWithIcon {...controlProps} icon={icon} />,
41+
[icon],
42+
)
43+
44+
return (
45+
<div>
46+
<ReactSelect
47+
{...props}
48+
placeholder={placeholder}
49+
components={{
50+
IndicatorSeparator: null,
51+
LoadingIndicator,
52+
DropdownIndicator,
53+
Control: renderControl,
54+
// Option,
55+
// ValueContainer,
56+
}}
57+
styles={selectStyles}
58+
/>
59+
{error && (
60+
<div className="flex left dc__gap-4 cr-5 fs-11 lh-16 fw-4">
61+
<div className="dc__no-shrink dc__align-self-start p-2">
62+
<ErrorIcon className="icon-dim-12 form__icon--error" />
63+
</div>
64+
<span>{error}</span>
65+
</div>
66+
)}
67+
</div>
68+
)
69+
}
70+
71+
export default SelectPicker
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { components, DropdownIndicatorProps, ControlProps } from 'react-select'
2+
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
3+
import { Progressing } from '@Common/Progressing'
4+
import { SelectPickerProps } from './SelectPicker.component'
5+
6+
export const DropdownIndicator = (props: DropdownIndicatorProps) => {
7+
const { isDisabled } = props
8+
9+
return (
10+
<components.DropdownIndicator {...props}>
11+
<ICCaretDown className={`icon-dim-16 ${isDisabled ? 'fcn-3' : 'fcn-6'} dc__no-shrink`} />
12+
</components.DropdownIndicator>
13+
)
14+
}
15+
16+
export const ControlWithIcon = ({ icon, ...props }: ControlProps & Pick<SelectPickerProps, 'icon'>) => {
17+
const { children } = props
18+
19+
return (
20+
<components.Control {...props}>
21+
{icon && <div className="p-2 dc__no-shrink flex left">{icon}</div>}
22+
{children}
23+
</components.Control>
24+
)
25+
}
26+
27+
export const LoadingIndicator = () => <Progressing />
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as SelectPicker } from './SelectPicker.component'
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
export const getCommonSelectStyle = ({ hasError }: { hasError: boolean }) => ({
2+
container: (base, state) => ({
3+
...base,
4+
...(state.isDisabled && {
5+
cursor: 'not-allowed',
6+
pointerEvents: 'auto',
7+
}),
8+
}),
9+
menuList: (base) => ({
10+
...base,
11+
padding: 0,
12+
paddingBlock: '4px',
13+
cursor: 'pointer',
14+
}),
15+
control: (base, state) => ({
16+
...base,
17+
// Add support for configurable: 36px, 28px
18+
minHeight: 'auto',
19+
boxShadow: 'none',
20+
backgroundColor: state.isDisabled ? 'var(--N100)' : 'var(--N50)',
21+
border: `1px solid ${hasError ? 'var(--R500)' : 'var(--N200)'}`,
22+
cursor: state.isDisabled ? 'not-allowed' : 'pointer',
23+
padding: '5px 8px',
24+
gap: '8px',
25+
26+
'&:hover': {
27+
borderColor: state.isDisabled ? 'var(--N200)' : 'var(--N300)',
28+
},
29+
'&:focus, &:focus-within': {
30+
borderColor: state.isDisabled ? 'var(--N200)' : 'var(--B500)',
31+
outline: 'none',
32+
},
33+
}),
34+
option: (base, state) => ({
35+
...base,
36+
color: 'var(--N900)',
37+
// eslint-disable-next-line no-nested-ternary
38+
backgroundColor: state.isSelected ? 'var(--B100)' : state.isFocused ? 'var(--N100)' : 'white',
39+
padding: '10px 12px',
40+
cursor: 'pointer',
41+
fontSize: '13px',
42+
lineHeight: '20px',
43+
fontWeight: 400,
44+
45+
':active': {
46+
backgroundColor: 'var(--N100)',
47+
},
48+
}),
49+
dropdownIndicator: (base, state) => ({
50+
...base,
51+
color: 'var(--N600)',
52+
padding: '0',
53+
transition: 'all .2s ease',
54+
transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : 'rotate(0deg)',
55+
}),
56+
valueContainer: (base) => ({
57+
...base,
58+
padding: '0',
59+
fontWeight: '400',
60+
}),
61+
loadingMessage: (base) => ({
62+
...base,
63+
color: 'var(--N600)',
64+
}),
65+
noOptionsMessage: (base) => ({
66+
...base,
67+
color: 'var(--N600)',
68+
}),
69+
group: (base) => ({
70+
...base,
71+
paddingTop: '4px',
72+
paddingBottom: 0,
73+
}),
74+
groupHeading: (base) => ({
75+
...base,
76+
fontWeight: 600,
77+
fontSize: '12px',
78+
color: 'var(--N900)',
79+
backgroundColor: 'var(--N100)',
80+
marginBottom: 0,
81+
padding: '4px 8px',
82+
textTransform: 'none',
83+
overflow: 'hidden',
84+
textOverflow: 'ellipsis',
85+
whiteSpace: 'nowrap',
86+
}),
87+
input: (base) => ({
88+
...base,
89+
margin: 0,
90+
padding: 0,
91+
color: 'var(--N900)',
92+
size: '13px',
93+
fontWeight: 400,
94+
lineHeight: '20px',
95+
}),
96+
placeholder: (base, state) => ({
97+
...base,
98+
color: state.isDisabled ? 'var(--N600)' : 'var(--N500)',
99+
fontSize: '13px',
100+
lineHeight: '20px',
101+
fontWeight: 400,
102+
margin: 0,
103+
overflow: 'hidden',
104+
textOverflow: 'ellipsis',
105+
whiteSpace: 'nowrap',
106+
}),
107+
})

0 commit comments

Comments
 (0)