Skip to content

Commit fcdf3db

Browse files
committed
Merge branch 'develop' of github.com:devtron-labs/devtron-fe-common-lib into refactor/plugin-policy-v1
2 parents 6cd33d3 + 16f8380 commit fcdf3db

File tree

16 files changed

+201
-57
lines changed

16 files changed

+201
-57
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": "1.10.1",
3+
"version": "1.10.3",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Common/DeleteCINodeButton/DeleteCINodeButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export const DeleteCINodeButton = ({
117117
disabled={disabled}
118118
onClick={onClickDeleteShowModal}
119119
text="Delete Pipeline"
120+
variant={ButtonVariantType.secondary}
120121
style={ButtonStyleType.negative}
121122
/>
122123
)

src/Shared/Components/Button/Button.component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,9 @@ const Button = <ComponentType extends ButtonComponentType>({
180180

181181
autoClickTimeoutRef.current = setTimeout(() => {
182182
elementRef.current.click()
183-
// This is 100ms less than the duration of the transition in CSS
183+
// This is 5ms less than the duration of the transition in CSS
184184
// Make sure to update the same in CSS if this is changed
185-
}, 2400)
185+
}, 1495)
186186
}, 100)
187187
}
188188

src/Shared/Components/Button/button.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
background: linear-gradient(to left, $background 50%, $auto-click-bg-color 50%) left;
4040
background-size: 200% 100%;
4141
// Make sure to change the transition in Button.component.tsx as well if changed
42-
transition: background-position 2.5s linear;
42+
transition: background-position 1.5s ease-out;
4343
}
4444
}
4545

src/Shared/Components/Button/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@
1515
*/
1616

1717
export { default as Button } from './Button.component'
18+
export { default as useTriggerAutoClickTimestamp } from './useTriggerAutoClickTimestamp'
1819
export * from './types'

src/Shared/Components/Button/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ export type ButtonProps<ComponentType extends ButtonComponentType = ButtonCompon
115115
isOpacityHoverChild?: boolean
116116
/**
117117
* If provided, the button is clicked automatically after the pre-defined time
118+
*
119+
* Use from useTriggerAutoClickTimestamp hook
118120
*/
119121
triggerAutoClickTimestamp?: number | null
120122
} & (
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useState } from 'react'
2+
import { ButtonProps } from './types'
3+
4+
const useTriggerAutoClickTimestamp = () => {
5+
const [triggerAutoClickTimestamp, setTriggerAutoClickTimestamp] =
6+
useState<ButtonProps['triggerAutoClickTimestamp']>(null)
7+
8+
const setTriggerAutoClickTimestampToNow = () => {
9+
setTriggerAutoClickTimestamp(Date.now())
10+
}
11+
12+
const resetTriggerAutoClickTimestamp = () => {
13+
setTriggerAutoClickTimestamp(null)
14+
}
15+
16+
return {
17+
triggerAutoClickTimestamp,
18+
setTriggerAutoClickTimestampToNow,
19+
resetTriggerAutoClickTimestamp,
20+
}
21+
}
22+
23+
export default useTriggerAutoClickTimestamp

src/Shared/Components/CustomInput/CustomInput.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ const CustomInput = ({
8181
endIconButtonConfig?.onClick(event)
8282
}
8383

84+
const handleKeyDown: CustomInputProps['onKeyDown'] = (e) => {
85+
if (e.key === 'Escape') {
86+
inputRef.current.blur()
87+
}
88+
89+
props.onKeyDown?.(e)
90+
}
91+
8492
return (
8593
<FormFieldWrapper
8694
inputId={name}
@@ -115,6 +123,7 @@ const CustomInput = ({
115123
data-testid={name}
116124
required={required}
117125
onBlur={handleBlur}
126+
onKeyDown={handleKeyDown}
118127
type={type}
119128
ref={inputRef}
120129
className={`${COMPONENT_SIZE_TYPE_TO_FONT_AND_BLOCK_PADDING_MAP[size]} ${COMPONENT_SIZE_TYPE_TO_INLINE_PADDING_MAP[size]} ${deriveBorderRadiusAndBorderClassFromConfig({ borderConfig, borderRadiusConfig })} ${endIconButtonConfig ? `custom-input__with-icon-button--${size}` : ''} w-100 dc__overflow-auto`}

src/Shared/Components/SelectPicker/FilterSelectPicker.tsx

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useEffect, useMemo, useState } from 'react'
17+
import { useEffect, useMemo, useRef, useState } from 'react'
1818
import { ReactComponent as ICFilter } from '@Icons/ic-filter.svg'
1919
import { ReactComponent as ICFilterApplied } from '@Icons/ic-filter-applied.svg'
20-
import { ComponentSizeType } from '@Shared/constants'
2120
import { useRegisterShortcut, UseRegisterShortcutProvider } from '@Common/Hooks'
2221
import { IS_PLATFORM_MAC_OS } from '@Common/Constants'
2322
import { SupportedKeyboardKeysType } from '@Common/Hooks/UseRegisterShortcut/types'
2423
import SelectPicker from './SelectPicker.component'
2524
import { FilterSelectPickerProps, SelectPickerOptionType, SelectPickerProps } from './type'
26-
import { Button } from '../Button'
25+
import { ButtonProps, ButtonVariantType, useTriggerAutoClickTimestamp } from '../Button'
2726

2827
const APPLY_FILTER_SHORTCUT_KEYS: SupportedKeyboardKeysType[] = [IS_PLATFORM_MAC_OS ? 'Meta' : 'Control', 'Enter']
2928

@@ -33,7 +32,11 @@ const FilterSelectPicker = ({
3332
options,
3433
...props
3534
}: FilterSelectPickerProps) => {
35+
const selectRef = useRef<SelectPickerProps<string | number, true>['selectRef']['current']>()
36+
3637
const [isMenuOpen, setIsMenuOpen] = useState(false)
38+
const { triggerAutoClickTimestamp, setTriggerAutoClickTimestampToNow, resetTriggerAutoClickTimestamp } =
39+
useTriggerAutoClickTimestamp()
3740

3841
const [selectedOptions, setSelectedOptions] = useState<SelectPickerOptionType[]>(
3942
structuredClone(appliedFilterOptions ?? []),
@@ -57,10 +60,12 @@ const FilterSelectPicker = ({
5760
}
5861

5962
const closeMenu = () => {
63+
resetTriggerAutoClickTimestamp()
6064
setIsMenuOpen(false)
6165
}
6266

6367
const handleSelectOnChange: SelectPickerProps<number | string, true>['onChange'] = (selectedOptionsToUpdate) => {
68+
setTriggerAutoClickTimestampToNow()
6469
setSelectedOptions(structuredClone(selectedOptionsToUpdate) as SelectPickerOptionType[])
6570
}
6671

@@ -69,33 +74,26 @@ const FilterSelectPicker = ({
6974
setSelectedOptions(structuredClone(appliedFilterOptions ?? []))
7075
}
7176

72-
const handleApplyClick = () => {
77+
const handleApplyClick: ButtonProps['onClick'] = (e) => {
7378
handleApplyFilter(selectedOptions)
74-
closeMenu()
79+
resetTriggerAutoClickTimestamp()
80+
81+
// If true, depicts the click event is triggered by the user
82+
// Added !e to ensure it works for both click and keyboard shortcut event
83+
if (!e || e.isTrusted) {
84+
closeMenu()
85+
} else {
86+
// If the event is not triggered by the user, focus on the select picker
87+
// As it loses focus when auto-click is triggered and menu is not closed
88+
setTimeout(() => {
89+
selectRef.current.focus()
90+
}, 100)
91+
}
7592
}
7693

77-
const renderApplyButton = () => (
78-
<div className="p-8 dc__border-top-n1">
79-
<Button
80-
text="Apply"
81-
dataTestId="filter-select-picker-apply"
82-
onClick={handleApplyClick}
83-
size={ComponentSizeType.small}
84-
fullWidth
85-
showTooltip
86-
tooltipProps={{
87-
shortcutKeyCombo: {
88-
text: 'Apply filter',
89-
combo: APPLY_FILTER_SHORTCUT_KEYS,
90-
},
91-
}}
92-
/>
93-
</div>
94-
)
95-
9694
useEffect(() => {
9795
if (isMenuOpen) {
98-
registerShortcut({ keys: APPLY_FILTER_SHORTCUT_KEYS, callback: handleApplyClick })
96+
registerShortcut({ keys: APPLY_FILTER_SHORTCUT_KEYS, callback: handleApplyClick as () => void })
9997
}
10098

10199
return () => {
@@ -105,16 +103,33 @@ const FilterSelectPicker = ({
105103

106104
return (
107105
<div className="dc__mxw-250">
108-
<SelectPicker
106+
<SelectPicker<string | number, true>
109107
{...props}
108+
selectRef={selectRef}
110109
options={options}
111110
value={selectedOptions}
112111
isMulti
113112
menuIsOpen={isMenuOpen}
114113
onMenuOpen={openMenu}
115114
onMenuClose={handleMenuClose}
116115
onChange={handleSelectOnChange}
117-
renderMenuListFooter={renderApplyButton}
116+
menuListFooterConfig={{
117+
type: 'button',
118+
buttonProps: {
119+
text: 'Apply',
120+
dataTestId: 'filter-select-picker-apply',
121+
onClick: handleApplyClick,
122+
showTooltip: true,
123+
tooltipProps: {
124+
shortcutKeyCombo: {
125+
text: 'Apply filter',
126+
combo: APPLY_FILTER_SHORTCUT_KEYS,
127+
},
128+
},
129+
triggerAutoClickTimestamp,
130+
variant: ButtonVariantType.primary,
131+
},
132+
}}
118133
controlShouldRenderValue={false}
119134
showSelectedOptionsCount
120135
isSearchable

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

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
Props as ReactSelectProps,
2323
} from 'react-select'
2424
import CreatableSelect from 'react-select/creatable'
25-
import { ReactElement, useCallback, useMemo, useState } from 'react'
25+
import { ReactElement, useCallback, useMemo, useRef, useState } from 'react'
2626
import { ComponentSizeType } from '@Shared/constants'
2727
import { ConditionalWrap } from '@Common/Helper'
2828
import Tippy from '@tippyjs/react'
@@ -35,7 +35,6 @@ import {
3535
SelectPickerControl,
3636
SelectPickerDropdownIndicator,
3737
SelectPickerGroupHeading,
38-
SelectPickerLoadingIndicator,
3938
SelectPickerMenuList,
4039
SelectPickerOption,
4140
SelectPickerValueContainer,
@@ -45,6 +44,7 @@ import { SelectPickerOptionType, SelectPickerProps, SelectPickerVariantType } fr
4544
import { GenericSectionErrorState } from '../GenericSectionErrorState'
4645
import FormFieldWrapper from '../FormFieldWrapper/FormFieldWrapper'
4746
import { getFormFieldAriaAttributes } from '../FormFieldWrapper'
47+
import './selectPicker.scss'
4848

4949
/**
5050
* Generic component for select picker
@@ -84,16 +84,15 @@ import { getFormFieldAriaAttributes } from '../FormFieldWrapper'
8484
* <SelectPicker ... helperText="Help information" />
8585
* ```
8686
*
87-
* @example Menu list footer
87+
* @example Menu list footer config
8888
* The footer is sticky by default
8989
* ```tsx
9090
* <SelectPicker
9191
* ...
92-
* renderMenuListFooter={() => (
93-
* <div className="px-8 py-6 dc__border-top bg__secondary cn-6">
94-
* <div>Foot note</div>
95-
* </div>
96-
* )}
92+
* menuListFooterConfig={{
93+
* type: 'text',
94+
* value: 'Info text',
95+
* }}
9796
* />
9897
* ```
9998
*
@@ -203,11 +202,11 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
203202
classNamePrefix,
204203
shouldRenderCustomOptions = false,
205204
isSearchable,
206-
selectRef,
205+
selectRef: refFromConsumer,
207206
shouldMenuAlignRight = false,
208207
fullWidth = false,
209208
customSelectedOptionsCount = null,
210-
renderMenuListFooter,
209+
menuListFooterConfig,
211210
isCreatable = false,
212211
onCreateOption,
213212
closeMenuOnSelect = false,
@@ -224,6 +223,9 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
224223
labelTooltipConfig,
225224
...props
226225
}: SelectPickerProps<OptionValue, IsMulti>) => {
226+
const innerRef = useRef<SelectPickerProps<OptionValue, IsMulti>['selectRef']['current']>(null)
227+
const selectRef = refFromConsumer ?? innerRef
228+
227229
const [isFocussed, setIsFocussed] = useState(false)
228230
const [inputValue, setInputValue] = useState('')
229231

@@ -357,6 +359,11 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
357359
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
358360
e.preventDefault()
359361
}
362+
363+
if (e.key === 'Escape' && !selectRef.current.props.menuIsOpen) {
364+
selectRef.current.blur()
365+
}
366+
360367
onKeyDown?.(e)
361368
}
362369

@@ -410,6 +417,11 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
410417
classNames={{
411418
control: () =>
412419
deriveBorderRadiusAndBorderClassFromConfig({ borderConfig, borderRadiusConfig }),
420+
...(isMulti
421+
? {
422+
option: () => 'checkbox__parent-container',
423+
}
424+
: {}),
413425
}}
414426
name={name || inputId}
415427
classNamePrefix={classNamePrefix || inputId}
@@ -426,7 +438,7 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
426438
ref={selectRef}
427439
components={{
428440
IndicatorSeparator: null,
429-
LoadingIndicator: SelectPickerLoadingIndicator,
441+
LoadingIndicator: null,
430442
DropdownIndicator: SelectPickerDropdownIndicator,
431443
Control: SelectPickerControl,
432444
Option: renderOption,
@@ -448,7 +460,7 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
448460
isValidNewOption={isValidNewOption}
449461
createOptionPosition="first"
450462
onCreateOption={handleCreateOption}
451-
renderMenuListFooter={!optionListError && renderMenuListFooter}
463+
menuListFooterConfig={!optionListError ? menuListFooterConfig : null}
452464
inputValue={props.inputValue ?? inputValue}
453465
onInputChange={handleInputChange}
454466
icon={icon}
@@ -461,6 +473,7 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
461473
onChange={handleChange}
462474
controlShouldRenderValue={controlShouldRenderValue}
463475
isFocussed={isFocussed}
476+
tabSelectsValue={false}
464477
/>
465478
</div>
466479
</ConditionalWrap>

0 commit comments

Comments
 (0)