Skip to content

Commit b969e0d

Browse files
authored
Merge branch 'main' into fix/jobs-artifacts
2 parents dff9bb6 + b52ad10 commit b969e0d

File tree

10 files changed

+604
-3
lines changed

10 files changed

+604
-3
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "0.1.7",
3+
"version": "0.1.8",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

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

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import ReactSelect, { ControlProps, MenuListProps, ValueContainerProps } from 'react-select'
2+
import { ReactElement, useCallback, useMemo } from 'react'
3+
import { ReactComponent as ErrorIcon } from '@Icons/ic-warning.svg'
4+
import { ReactComponent as ICInfoFilledOverride } from '@Icons/ic-info-filled-override.svg'
5+
import { ComponentSizeType } from '@Shared/constants'
6+
import { ConditionalWrap } from '@Common/Helper'
7+
import Tippy from '@tippyjs/react'
8+
import { getCommonSelectStyle } from './utils'
9+
import {
10+
SelectPickerClearIndicator,
11+
SelectPickerControl,
12+
SelectPickerDropdownIndicator,
13+
SelectPickerLoadingIndicator,
14+
SelectPickerMenuList,
15+
SelectPickerOption,
16+
SelectPickerValueContainer,
17+
} from './common'
18+
import { SelectPickerOptionType, SelectPickerProps } from './type'
19+
20+
/**
21+
* Generic component for select picker
22+
*
23+
* @example With icon in control
24+
* ```tsx
25+
* <SelectPicker ... icon={<CustomIcon />} />
26+
* ```
27+
*
28+
* @example Medium menu list width
29+
* ```tsx
30+
* <SelectPicker ... menuSize={ComponentSizeType.medium} />
31+
* ```
32+
*
33+
* @example Large menu list width
34+
* ```tsx
35+
* <SelectPicker ... menuSize={ComponentSizeType.large} />
36+
* ```
37+
*
38+
* @example Required label
39+
* ```tsx
40+
* <SelectPicker ... required label="Label" />
41+
* ```
42+
*
43+
* @example Custom label
44+
* ```tsx
45+
* <SelectPicker ... label={<div>Label</div>} />
46+
* ```
47+
*
48+
* @example Error state
49+
* ```tsx
50+
* <SelectPicker ... error="Something went wrong" />
51+
* ```
52+
*
53+
* @example Helper text
54+
* ```tsx
55+
* <SelectPicker ... helperText="Help information" />
56+
* ```
57+
*
58+
* @example Menu list footer
59+
* The footer is sticky by default
60+
* ```tsx
61+
* <SelectPicker
62+
* ...
63+
* renderMenuListFooter={() => (
64+
* <div className="px-8 py-6 dc__border-top bcn-50 cn-6">
65+
* <div>Foot note</div>
66+
* </div>
67+
* )}
68+
* />
69+
* ```
70+
*
71+
* @example Loading state
72+
* ```tsx
73+
* <SelectPicker ... isLoading />
74+
* ```
75+
*
76+
* @example Disabled state
77+
* ```tsx
78+
* <SelectPicker ... isDisabled />
79+
* ```
80+
*
81+
* @example Loading & disabled state
82+
* ```tsx
83+
* <SelectPicker ... isLoading isDisabled />
84+
* ```
85+
*
86+
* @example Hide selected option icon in control
87+
* ```tsx
88+
* <SelectPicker ... showSelectedOptionIcon={false} />
89+
* ```
90+
*
91+
* @example Selected option clearable
92+
* ```tsx
93+
* <SelectPicker ... isClearable />
94+
* ```
95+
*
96+
* @example Selected option clearable
97+
* ```tsx
98+
* <SelectPicker ... showSelectedOptionsCount />
99+
* ```
100+
*/
101+
const SelectPicker = ({
102+
error,
103+
icon,
104+
renderMenuListFooter,
105+
helperText,
106+
placeholder = 'Select a option',
107+
label,
108+
showSelectedOptionIcon = true,
109+
size = ComponentSizeType.medium,
110+
disabledTippyContent,
111+
showSelectedOptionsCount = false,
112+
menuSize,
113+
...props
114+
}: SelectPickerProps) => {
115+
const { inputId, required, isDisabled } = props
116+
117+
const labelId = `${inputId}-label`
118+
const errorElementId = `${inputId}-error-msg`
119+
120+
const selectStyles = useMemo(
121+
() =>
122+
getCommonSelectStyle({
123+
error,
124+
size,
125+
menuSize,
126+
}),
127+
[error, size, menuSize],
128+
)
129+
130+
const renderControl = useCallback(
131+
(controlProps: ControlProps<SelectPickerOptionType>) => (
132+
<SelectPickerControl {...controlProps} icon={icon} showSelectedOptionIcon={showSelectedOptionIcon} />
133+
),
134+
[icon, showSelectedOptionIcon],
135+
)
136+
137+
const renderMenuList = useCallback(
138+
(menuProps: MenuListProps<SelectPickerOptionType>) => (
139+
<SelectPickerMenuList {...menuProps} renderMenuListFooter={renderMenuListFooter} />
140+
),
141+
[],
142+
)
143+
144+
const renderValueContainer = useCallback(
145+
(valueContainerProps: ValueContainerProps<SelectPickerOptionType>) => (
146+
<SelectPickerValueContainer {...valueContainerProps} showSelectedOptionsCount={showSelectedOptionsCount} />
147+
),
148+
[showSelectedOptionsCount],
149+
)
150+
151+
const renderDisabledTippy = (children: ReactElement) => (
152+
<Tippy content={disabledTippyContent} placement="top" className="default-tt" arrow={false}>
153+
{children}
154+
</Tippy>
155+
)
156+
157+
return (
158+
<div className="flex column left top dc__gap-4">
159+
{/* Note: Common out for fields */}
160+
<div className="flex column left top dc__gap-6 w-100">
161+
{label && (
162+
<label
163+
className="fs-13 lh-20 cn-7 fw-4 dc__block mb-0"
164+
htmlFor={inputId}
165+
data-testid={`label-${inputId}`}
166+
id={labelId}
167+
>
168+
{typeof label === 'string' ? (
169+
<span className={`flex left ${required ? 'dc__required-field' : ''}`}>
170+
<span className="dc__truncate">{label}</span>
171+
{required && <span>&nbsp;</span>}
172+
</span>
173+
) : (
174+
label
175+
)}
176+
</label>
177+
)}
178+
<ConditionalWrap condition={isDisabled && !!disabledTippyContent} wrap={renderDisabledTippy}>
179+
<div className="w-100">
180+
<ReactSelect<SelectPickerOptionType, boolean>
181+
{...props}
182+
placeholder={placeholder}
183+
components={{
184+
IndicatorSeparator: null,
185+
LoadingIndicator: SelectPickerLoadingIndicator,
186+
DropdownIndicator: SelectPickerDropdownIndicator,
187+
Control: renderControl,
188+
Option: SelectPickerOption,
189+
MenuList: renderMenuList,
190+
ClearIndicator: SelectPickerClearIndicator,
191+
ValueContainer: renderValueContainer,
192+
}}
193+
styles={selectStyles}
194+
menuPlacement="auto"
195+
menuPosition="fixed"
196+
menuShouldScrollIntoView
197+
backspaceRemovesValue={false}
198+
aria-errormessage={errorElementId}
199+
aria-invalid={!!error}
200+
aria-labelledby={labelId}
201+
/>
202+
</div>
203+
</ConditionalWrap>
204+
</div>
205+
{error && (
206+
<div className="flex left dc__gap-4 cr-5 fs-11 lh-16 fw-4" id={errorElementId}>
207+
<ErrorIcon className="icon-dim-16 p-1 form__icon--error dc__no-shrink dc__align-self-start" />
208+
<span className="dc__ellipsis-right__2nd-line">{error}</span>
209+
</div>
210+
)}
211+
{/* Note: Common out for input fields */}
212+
{helperText && (
213+
<div className="flex left dc__gap-4 fs-11 lh-16 cn-7">
214+
<ICInfoFilledOverride className="icon-dim-16 dc__no-shrink dc__align-self-start" />
215+
<span className="dc__ellipsis-right__2nd-line">{helperText}</span>
216+
</div>
217+
)}
218+
</div>
219+
)
220+
}
221+
222+
export default SelectPicker
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import {
2+
components,
3+
DropdownIndicatorProps,
4+
ControlProps,
5+
OptionProps,
6+
ClearIndicatorProps,
7+
ValueContainerProps,
8+
MenuListProps,
9+
} from 'react-select'
10+
import { Progressing } from '@Common/Progressing'
11+
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
12+
import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
13+
import { SelectPickerOptionType, SelectPickerProps } from './type'
14+
15+
export const SelectPickerDropdownIndicator = (props: DropdownIndicatorProps<SelectPickerOptionType>) => {
16+
const { isDisabled } = props
17+
18+
return (
19+
<components.DropdownIndicator {...props}>
20+
<ICCaretDown className={`icon-dim-16 ${isDisabled ? 'scn-3' : 'scn-6'} dc__no-shrink`} />
21+
</components.DropdownIndicator>
22+
)
23+
}
24+
25+
export const SelectPickerClearIndicator = (props: ClearIndicatorProps<SelectPickerOptionType>) => (
26+
<components.ClearIndicator {...props}>
27+
<ICClose className="icon-dim-16 fcn-6 dc__no-shrink" />
28+
</components.ClearIndicator>
29+
)
30+
31+
export const SelectPickerControl = ({
32+
icon,
33+
showSelectedOptionIcon,
34+
...props
35+
}: ControlProps<SelectPickerOptionType> & Pick<SelectPickerProps, 'icon' | 'showSelectedOptionIcon'>) => {
36+
const { children, getValue } = props
37+
const { startIcon, endIcon } = getValue()?.[0] ?? {}
38+
39+
let iconToDisplay = icon
40+
41+
if (showSelectedOptionIcon && (startIcon || endIcon)) {
42+
iconToDisplay = startIcon || endIcon
43+
}
44+
45+
return (
46+
<components.Control {...props}>
47+
{iconToDisplay && (
48+
<div className="dc__no-shrink icon-dim-20 flex dc__fill-available-space">{iconToDisplay}</div>
49+
)}
50+
{children}
51+
</components.Control>
52+
)
53+
}
54+
55+
export const SelectPickerValueContainer = ({
56+
showSelectedOptionsCount,
57+
...props
58+
}: ValueContainerProps<SelectPickerOptionType> & Pick<SelectPickerProps, 'showSelectedOptionsCount'>) => {
59+
const { getValue } = props
60+
const selectedOptionsLength = (getValue() ?? []).length
61+
62+
return (
63+
<div className="flex left dc__gap-8 flex-grow-1">
64+
<div className="flex left">
65+
<components.ValueContainer {...props} />
66+
</div>
67+
{showSelectedOptionsCount && selectedOptionsLength > 0 && (
68+
<div className="bcb-5 dc__border-radius-4-imp pl-5 pr-5 cn-0 fs-12 fw-6 lh-18 dc__truncate dc__no-shrink">
69+
{selectedOptionsLength}
70+
</div>
71+
)}
72+
</div>
73+
)
74+
}
75+
76+
export const SelectPickerLoadingIndicator = () => <Progressing />
77+
78+
export const SelectPickerOption = (props: OptionProps<SelectPickerOptionType>) => {
79+
const { label, data } = props
80+
const { description, startIcon, endIcon } = data ?? {}
81+
const showDescription = !!description
82+
83+
return (
84+
<components.Option {...props}>
85+
<div className={`flex left ${showDescription ? 'top' : ''} dc__gap-8`}>
86+
{startIcon && (
87+
<div className="dc__no-shrink icon-dim-20 flex dc__fill-available-space">{startIcon}</div>
88+
)}
89+
<div className="flex-grow-1">
90+
<h4 className="m-0 cn-9 fs-13 fw-4 lh-20 dc__truncate">{label}</h4>
91+
{/* Add support for custom ellipsis if required */}
92+
{showDescription && <p className="m-0 fs-12 fw-4 lh-18 cn-7 dc__truncate">{description}</p>}
93+
</div>
94+
{endIcon && <div className="dc__no-shrink icon-dim-20 flex dc__fill-available-space">{endIcon}</div>}
95+
</div>
96+
</components.Option>
97+
)
98+
}
99+
100+
export const SelectPickerMenuList = ({
101+
renderMenuListFooter,
102+
...props
103+
}: MenuListProps<SelectPickerOptionType> & Pick<SelectPickerProps, 'renderMenuListFooter'>) => {
104+
const { children } = props
105+
106+
return (
107+
<components.MenuList {...props}>
108+
<div className="py-4 cursor">{children}</div>
109+
{/* Added to the bottom of menu list to prevent from hiding when the menu is opened close to the bottom of the screen */}
110+
{renderMenuListFooter && (
111+
<div className=" dc__position-sticky dc__bottom-0 dc__bottom-radius-4 bcn-0">
112+
{renderMenuListFooter()}
113+
</div>
114+
)}
115+
</components.MenuList>
116+
)
117+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as SelectPicker } from './SelectPicker.component'
2+
export * from './type'

0 commit comments

Comments
 (0)