Skip to content

Commit f9fc10c

Browse files
authored
Merge pull request #488 from devtron-labs/refactor/dynamic-data-table
refactor: dynamic data table
2 parents c25953d + dfd3b4d commit f9fc10c

File tree

10 files changed

+152
-50
lines changed

10 files changed

+152
-50
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.3.11-beta-1",
3+
"version": "1.3.11-beta-2",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Common/Constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export const DOCUMENTATION = {
3535
export const PATTERNS = {
3636
STRING: /^[a-zA-Z0-9_]+$/,
3737
DECIMAL_NUMBERS: /^-?\d*\.?\d*$/,
38-
NATURAL_NUMBERS: /^\d*\.?\d*$/,
38+
POSITIVE_DECIMAL_NUMBERS: /^\d*\.?\d*$/,
39+
NATURAL_NUMBERS: /^[1-9]\d*$/,
3940
KUBERNETES_KEY_PREFIX: /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/,
4041
KUBERNETES_KEY_NAME: /^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$/,
4142
START_END_ALPHANUMERIC: /^(([A-Za-z0-9].*[A-Za-z0-9])|[A-Za-z0-9])$/,

src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,10 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
176176
isClearable
177177
{...props}
178178
variant={SelectPickerVariantType.BORDER_LESS}
179-
classNamePrefix="dynamic-data-table__cell__select-picker"
179+
classNamePrefix="dynamic-data-table__cell__select-picker-text-area"
180180
inputId={`data-table-${row.id}-${key}-cell`}
181+
minHeight={20}
182+
maxHeight={160}
181183
value={getSelectPickerOptionByValue(
182184
props?.options,
183185
value,
@@ -186,6 +188,8 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
186188
onChange={onChange(row, key)}
187189
isDisabled={isDisabled}
188190
formatCreateLabel={(input) => `Use ${input}`}
191+
refVar={cellRef?.current?.[row.id]?.[key]}
192+
dependentRefs={cellRef?.current?.[row.id]}
189193
fullWidth
190194
/>
191195
</div>
@@ -299,7 +303,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
299303
plugins={[followCursor]}
300304
>
301305
<div
302-
className={`dynamic-data-table__cell bcn-0 flexbox dc__align-items-center dc__gap-4 dc__position-rel ${isDisabled ? 'dc__disabled no-hover' : ''} ${!isDisabled && hasError ? 'dynamic-data-table__cell--error no-hover' : ''} ${!rowTypeHasInputField(row.data[key].type) ? 'no-hover no-focus' : ''}`}
306+
className={`dynamic-data-table__cell bcn-0 flexbox dc__align-items-center dc__gap-4 dc__position-rel ${isDisabled ? 'cursor-not-allowed no-hover' : ''} ${!isDisabled && hasError ? 'dynamic-data-table__cell--error no-hover' : ''} ${!rowTypeHasInputField(row.data[key].type) ? 'no-hover no-focus' : ''}`}
303307
>
304308
{renderCellIcon(row, key, true)}
305309
{renderCellContent(row, key)}

src/Shared/Components/DynamicDataTable/styles.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,38 @@
8686
max-height: 160px !important;
8787
}
8888

89+
&__select-picker-text-area {
90+
&__control {
91+
padding: 0 !important;
92+
max-height: 160px !important;
93+
}
94+
95+
&__placeholder {
96+
align-self: start;
97+
padding: 8px 50px 8px 8px;
98+
}
99+
100+
&__indicators {
101+
position: absolute;
102+
top: 10px;
103+
right: 8px;
104+
}
105+
106+
&__input-container {
107+
display: flex !important;
108+
width: 100%;
109+
110+
&::after {
111+
content: none !important;
112+
}
113+
}
114+
115+
&__input {
116+
flex-grow: 1;
117+
padding: 8px 50px 8px 8px !important;
118+
}
119+
}
120+
89121
&:hover:not(:focus-within):not(.no-hover) {
90122
outline: 1px solid var(--N200);
91123
}

src/Shared/Components/DynamicDataTable/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,25 @@ export type DynamicDataTableCellPropsMap = {
6363
| 'disableOnBlurResizeToMinHeight'
6464
| 'refVar'
6565
| 'dependentRef'
66+
| 'dependentRefs'
6667
>
6768
[DynamicDataTableRowDataType.DROPDOWN]: Omit<
6869
SelectPickerProps<string, false>,
6970
'inputId' | 'value' | 'onChange' | 'fullWidth' | 'isDisabled'
7071
>
7172
[DynamicDataTableRowDataType.SELECT_TEXT]: Omit<
7273
SelectPickerTextAreaProps,
73-
'inputId' | 'value' | 'onChange' | 'fullWidth' | 'isDisabled' | 'variant' | 'formatCreateLabel'
74+
| 'inputId'
75+
| 'value'
76+
| 'onChange'
77+
| 'fullWidth'
78+
| 'isDisabled'
79+
| 'variant'
80+
| 'formatCreateLabel'
81+
| 'minHeight'
82+
| 'maxHeight'
83+
| 'refVar'
84+
| 'dependentRefs'
7485
>
7586
[DynamicDataTableRowDataType.BUTTON]: Pick<
7687
DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import {
4444
SelectPickerOption,
4545
SelectPickerValueContainer,
4646
SelectPickerInput,
47-
SelectPickerIndicatorsContainer,
4847
} from './common'
4948
import { SelectPickerOptionType, SelectPickerProps, SelectPickerVariantType } from './type'
5049
import { GenericSectionErrorState } from '../GenericSectionErrorState'
@@ -441,7 +440,6 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
441440
Control: SelectPickerControl,
442441
Option: renderOption,
443442
MenuList: SelectPickerMenuList,
444-
IndicatorsContainer: SelectPickerIndicatorsContainer,
445443
ClearIndicator: SelectPickerClearIndicator,
446444
ValueContainer: renderValueContainer,
447445
MultiValueLabel: renderMultiValueLabel,
@@ -467,7 +465,7 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
467465
onKeyDown={handleKeyDown}
468466
shouldRenderTextArea={shouldRenderTextArea}
469467
customDisplayText={customDisplayText}
470-
onFocus={handleFocus}
468+
{...(!shouldRenderTextArea ? { onFocus: handleFocus } : {})}
471469
onBlur={handleBlur}
472470
onChange={handleChange}
473471
controlShouldRenderValue={controlShouldRenderValue}

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

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
22
import { InputActionMeta, SelectInstance, SingleValue } from 'react-select'
33

44
import { ReactSelectInputAction } from '@Common/Constants'
5+
import { useThrottledEffect } from '@Common/Helper'
56

67
import SelectPicker from './SelectPicker.component'
78
import { SelectPickerOptionType, SelectPickerTextAreaProps } from './type'
@@ -11,6 +12,10 @@ export const SelectPickerTextArea = ({
1112
options,
1213
isCreatable,
1314
onChange,
15+
minHeight,
16+
maxHeight,
17+
refVar,
18+
dependentRefs,
1419
...props
1520
}: SelectPickerTextAreaProps) => {
1621
// STATES
@@ -19,12 +24,63 @@ export const SelectPickerTextArea = ({
1924
// REFS
2025
const selectRef = useRef<SelectInstance<SelectPickerOptionType<string>>>(null)
2126

27+
useEffect(() => {
28+
const inputRef = refVar
29+
if (inputRef) {
30+
inputRef.current = selectRef.current.inputRef as unknown as HTMLTextAreaElement
31+
}
32+
}, [refVar])
33+
2234
useEffect(() => {
2335
const selectValue = value as SingleValue<SelectPickerOptionType<string>>
2436
setInputValue(selectValue?.value || '')
2537
}, [value])
2638

2739
// METHODS
40+
const updateDependentRefsHeight = (height: number) => {
41+
Object.values(dependentRefs || {}).forEach((ref) => {
42+
const dependentRefElement = ref?.current
43+
if (dependentRefElement) {
44+
dependentRefElement.style.height = `${height}px`
45+
}
46+
})
47+
}
48+
49+
const updateRefsHeight = (height: number) => {
50+
const refElement = refVar?.current
51+
if (refElement) {
52+
refElement.style.height = `${height}px`
53+
}
54+
updateDependentRefsHeight(height)
55+
}
56+
57+
const reInitHeight = () => {
58+
updateRefsHeight(minHeight || 0)
59+
60+
let nextHeight = refVar?.current?.scrollHeight || 0
61+
62+
if (dependentRefs) {
63+
Object.values(dependentRefs).forEach((ref) => {
64+
const refElement = ref.current
65+
if (refElement && refElement.scrollHeight > nextHeight) {
66+
nextHeight = refElement.scrollHeight
67+
}
68+
})
69+
}
70+
71+
if (minHeight && nextHeight < minHeight) {
72+
nextHeight = minHeight
73+
}
74+
75+
if (maxHeight && nextHeight > maxHeight) {
76+
nextHeight = maxHeight
77+
}
78+
79+
updateRefsHeight(nextHeight)
80+
}
81+
82+
useThrottledEffect(reInitHeight, 500, [inputValue])
83+
2884
const onInputChange = (newValue: string, { action }: InputActionMeta) => {
2985
if (action === ReactSelectInputAction.inputChange) {
3086
setInputValue(newValue)
@@ -62,24 +118,27 @@ export const SelectPickerTextArea = ({
62118
setInputValue(updatedText)
63119

64120
const textarea = selectRef.current.inputRef
65-
const wrapper = selectRef.current.controlRef
66121

67-
// Get the caret position relative to the scrollable area
68-
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10)
122+
// Get the caret position
69123
const caretPosition = textarea.selectionStart
70-
const linesAboveCaret = textarea.value.substring(0, caretPosition).split('\n').length
71124

72-
// Estimate caret position by line height
73-
const caretY = linesAboveCaret * lineHeight
74-
const scrollOffset = caretY - wrapper.scrollTop
125+
// Split the text up to the caret position into lines
126+
const textBeforeCaret = textarea.value.substring(0, caretPosition)
127+
const lines = textBeforeCaret.split('\n')
128+
129+
// Calculate the caret position in pixels
130+
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10)
131+
const caretY = lines.length * lineHeight
132+
133+
// Check if caret is outside of the visible area
134+
const scrollOffset = caretY - textarea.scrollTop
75135

76-
// Adjust scroll to ensure caret is visible
77136
if (scrollOffset < 0) {
78-
// Scroll up
79-
wrapper.scrollTop += scrollOffset
80-
} else if (scrollOffset > wrapper.offsetHeight - lineHeight) {
81-
// Scroll down
82-
wrapper.scrollTop += scrollOffset - wrapper.offsetHeight + lineHeight
137+
// Scroll up if the caret is above the visible area
138+
textarea.scrollTop += scrollOffset
139+
} else if (scrollOffset > textarea.offsetHeight - lineHeight) {
140+
// Scroll down if the caret is below the visible area
141+
textarea.scrollTop += scrollOffset - textarea.offsetHeight + lineHeight
83142
}
84143

85144
// Move the cursor to the next line

src/Shared/Components/SelectPicker/common.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import {
2626
MultiValueProps,
2727
MultiValue,
2828
InputProps,
29-
IndicatorsContainerProps,
3029
} from 'react-select'
3130
import { Progressing } from '@Common/Progressing'
3231
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
@@ -62,30 +61,22 @@ const getTooltipProps = (tooltipProps: SelectPickerOptionType['tooltipProps'] =
6261
}
6362
}
6463

65-
export const SelectPickerIndicatorsContainer = <OptionValue,>({
66-
className = '',
67-
...props
68-
}: IndicatorsContainerProps<SelectPickerOptionType<OptionValue>>) => (
69-
<components.IndicatorsContainer {...props} className={`${className} dc__position-sticky dc__top-0 pt-2`} />
70-
)
71-
7264
export const SelectPickerDropdownIndicator = <OptionValue,>(
7365
props: DropdownIndicatorProps<SelectPickerOptionType<OptionValue>>,
7466
) => {
75-
const { isDisabled, className = '' } = props
67+
const { isDisabled } = props
7668

7769
return (
78-
<components.DropdownIndicator {...props} className={`${className} dc__align-self-start`}>
70+
<components.DropdownIndicator {...props}>
7971
<ICCaretDown className={isDisabled ? 'scn-3' : 'scn-6'} />
8072
</components.DropdownIndicator>
8173
)
8274
}
8375

84-
export const SelectPickerClearIndicator = <OptionValue,>({
85-
className = '',
86-
...props
87-
}: ClearIndicatorProps<SelectPickerOptionType<OptionValue>>) => (
88-
<components.ClearIndicator {...props} className={`${className} dc__align-self-start`}>
76+
export const SelectPickerClearIndicator = <OptionValue,>(
77+
props: ClearIndicatorProps<SelectPickerOptionType<OptionValue>>,
78+
) => (
79+
<components.ClearIndicator {...props}>
8980
<ICClose className="icon-use-fill-n6" />
9081
</components.ClearIndicator>
9182
)
@@ -136,9 +127,14 @@ export const SelectPickerValueContainer = <OptionValue, IsMulti extends boolean>
136127
}) => {
137128
const {
138129
getValue,
139-
selectProps: { customDisplayText },
130+
selectProps: { customDisplayText, shouldRenderTextArea },
140131
children,
141132
} = props
133+
134+
if (shouldRenderTextArea) {
135+
return <components.ValueContainer {...props} />
136+
}
137+
142138
const selectedOptionsLength = isNullOrUndefined(customSelectedOptionsCount)
143139
? (getValue() ?? []).length
144140
: customSelectedOptionsCount

src/Shared/Components/SelectPicker/type.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { CreatableProps } from 'react-select/creatable'
2323
// This import allows to extend the base interface in react-select module via module augmentation
2424
import type {} from 'react-select/base'
2525
import { TooltipProps } from '@Common/Tooltip/types'
26+
import { ResizableTagTextAreaProps } from '@Common/CustomTagSelector'
2627

2728
export interface SelectPickerOptionType<OptionValue = string | number> extends OptionType<OptionValue, ReactNode> {
2829
/**
@@ -307,14 +308,14 @@ export interface FilterSelectPickerProps
307308
handleApplyFilter: (filtersToApply: SelectPickerOptionType<number | string>[]) => void
308309
}
309310

310-
export interface SelectPickerTextAreaProps
311-
extends Omit<
312-
SelectPickerProps<string, false>,
313-
| 'selectRef'
314-
| 'inputValue'
315-
| 'onInputChange'
316-
| 'controlShouldRenderValue'
317-
| 'onKeyDown'
318-
| 'onCreateOption'
319-
| 'shouldRenderTextArea'
320-
> {}
311+
export type SelectPickerTextAreaProps = Omit<
312+
SelectPickerProps<string, false>,
313+
| 'selectRef'
314+
| 'inputValue'
315+
| 'onInputChange'
316+
| 'controlShouldRenderValue'
317+
| 'onKeyDown'
318+
| 'onCreateOption'
319+
| 'shouldRenderTextArea'
320+
> &
321+
Pick<ResizableTagTextAreaProps, 'maxHeight' | 'minHeight' | 'refVar' | 'dependentRefs'>

0 commit comments

Comments
 (0)