Skip to content

Commit ea983fc

Browse files
authored
Merge pull request #480 from devtron-labs/feat/select-picker-text-area
refactor: Dynamic Data Table Misc Fixes & Improvements
2 parents 7758cfb + b1a47fa commit ea983fc

File tree

17 files changed

+746
-254
lines changed

17 files changed

+746
-254
lines changed

package-lock.json

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

package.json

Lines changed: 4 additions & 2 deletions
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.10",
3+
"version": "1.3.11",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
@@ -31,7 +31,8 @@
3131
"build-watch": "NODE_OPTIONS=--max_old_space_size=3072 vite build --sourcemap --watch",
3232
"build-lib": "vite build",
3333
"preview": "vite preview",
34-
"lint-staged": "lint-staged"
34+
"lint-staged": "lint-staged",
35+
"postinstall": "patch-package"
3536
},
3637
"devDependencies": {
3738
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
@@ -82,6 +83,7 @@
8283
"@rjsf/validator-ajv8": "^5.13.3",
8384
"@typeform/embed-react": "2.20.0",
8485
"dompurify": "^3.0.2",
86+
"patch-package": "^8.0.0",
8587
"react": "^17.0.2",
8688
"react-dom": "^17.0.2",
8789
"react-draggable": "^4.4.5",

patches/react-select+5.8.0.patch

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
diff --git a/node_modules/react-select/.DS_Store b/node_modules/react-select/.DS_Store
2+
new file mode 100644
3+
index 0000000..5008ddf
4+
Binary files /dev/null and b/node_modules/react-select/.DS_Store differ
5+
diff --git a/node_modules/react-select/dist/declarations/src/components/Input.d.ts b/node_modules/react-select/dist/declarations/src/components/Input.d.ts
6+
index e5f6741..c78fd13 100644
7+
--- a/node_modules/react-select/dist/declarations/src/components/Input.d.ts
8+
+++ b/node_modules/react-select/dist/declarations/src/components/Input.d.ts
9+
@@ -13,6 +13,8 @@ export interface InputSpecificProps<Option = unknown, IsMulti extends boolean =
10+
form?: string;
11+
/** Set className for the input element */
12+
inputClassName?: string;
13+
+ /** Whether the input is textarea */
14+
+ isTextArea?: boolean;
15+
}
16+
export declare type InputProps<Option = unknown, IsMulti extends boolean = boolean, Group extends GroupBase<Option> = GroupBase<Option>> = InputSpecificProps<Option, IsMulti, Group>;
17+
export declare const inputCSS: <Option, IsMulti extends boolean, Group extends GroupBase<Option>>({ isDisabled, value, theme: { spacing, colors }, }: InputProps<Option, IsMulti, Group>, unstyled: boolean) => CSSObjectWithLabel;
18+
diff --git a/node_modules/react-select/dist/index-665c4ed8.cjs.prod.js b/node_modules/react-select/dist/index-665c4ed8.cjs.prod.js
19+
index 96cd04f..0636ebe 100644
20+
--- a/node_modules/react-select/dist/index-665c4ed8.cjs.prod.js
21+
+++ b/node_modules/react-select/dist/index-665c4ed8.cjs.prod.js
22+
@@ -1090,7 +1090,7 @@ var GroupHeading = function GroupHeading(props) {
23+
};
24+
var Group$1 = Group;
25+
26+
-var _excluded = ["innerRef", "isDisabled", "isHidden", "inputClassName"];
27+
+var _excluded = ["innerRef", "isDisabled", "isHidden", "isTextArea", "inputClassName"];
28+
var inputCSS = function inputCSS(_ref, unstyled) {
29+
var isDisabled = _ref.isDisabled,
30+
value = _ref.value,
31+
@@ -1145,13 +1145,25 @@ var Input = function Input(props) {
32+
innerRef = _cleanCommonProps.innerRef,
33+
isDisabled = _cleanCommonProps.isDisabled,
34+
isHidden = _cleanCommonProps.isHidden,
35+
+ isTextArea = _cleanCommonProps.isTextArea,
36+
inputClassName = _cleanCommonProps.inputClassName,
37+
innerProps = _objectWithoutProperties(_cleanCommonProps, _excluded);
38+
return react.jsx("div", _extends({}, getStyleProps(props, 'input', {
39+
'input-container': true
40+
}), {
41+
"data-value": value || ''
42+
- }), react.jsx("input", _extends({
43+
+ }), isTextArea ? react.jsx("textarea", _extends({
44+
+ className: cx({
45+
+ input: true
46+
+ }, inputClassName),
47+
+ ref: innerRef,
48+
+ style: _objectSpread(_objectSpread({}, inputStyle(isHidden)), {}, {
49+
+ resize: 'none'
50+
+ }),
51+
+ disabled: isDisabled
52+
+ }, innerProps, {
53+
+ rows: 1
54+
+ })) : react.jsx("input", _extends({
55+
className: cx({
56+
input: true
57+
}, inputClassName),
58+
diff --git a/node_modules/react-select/dist/index-a301f526.esm.js b/node_modules/react-select/dist/index-a301f526.esm.js
59+
index 514ba81..40d005d 100644
60+
--- a/node_modules/react-select/dist/index-a301f526.esm.js
61+
+++ b/node_modules/react-select/dist/index-a301f526.esm.js
62+
@@ -1090,7 +1090,7 @@ var GroupHeading = function GroupHeading(props) {
63+
};
64+
var Group$1 = Group;
65+
66+
-var _excluded = ["innerRef", "isDisabled", "isHidden", "inputClassName"];
67+
+var _excluded = ["innerRef", "isDisabled", "isHidden", "isTextArea", "inputClassName"];
68+
var inputCSS = function inputCSS(_ref, unstyled) {
69+
var isDisabled = _ref.isDisabled,
70+
value = _ref.value,
71+
@@ -1145,13 +1145,25 @@ var Input = function Input(props) {
72+
innerRef = _cleanCommonProps.innerRef,
73+
isDisabled = _cleanCommonProps.isDisabled,
74+
isHidden = _cleanCommonProps.isHidden,
75+
+ isTextArea = _cleanCommonProps.isTextArea,
76+
inputClassName = _cleanCommonProps.inputClassName,
77+
innerProps = _objectWithoutProperties(_cleanCommonProps, _excluded);
78+
return jsx("div", _extends({}, getStyleProps(props, 'input', {
79+
'input-container': true
80+
}), {
81+
"data-value": value || ''
82+
- }), jsx("input", _extends({
83+
+ }), isTextArea ? jsx("textarea", _extends({
84+
+ className: cx({
85+
+ input: true
86+
+ }, inputClassName),
87+
+ ref: innerRef,
88+
+ style: _objectSpread(_objectSpread({}, inputStyle(isHidden)), {}, {
89+
+ resize: 'none'
90+
+ }),
91+
+ disabled: isDisabled
92+
+ }, innerProps, {
93+
+ rows: 1
94+
+ })) : jsx("input", _extends({
95+
className: cx({
96+
input: true
97+
}, inputClassName),
98+
diff --git a/node_modules/react-select/dist/index-d1cb43f3.cjs.dev.js b/node_modules/react-select/dist/index-d1cb43f3.cjs.dev.js
99+
index 7defcdc..c8d796b 100644
100+
--- a/node_modules/react-select/dist/index-d1cb43f3.cjs.dev.js
101+
+++ b/node_modules/react-select/dist/index-d1cb43f3.cjs.dev.js
102+
@@ -1096,7 +1096,7 @@ var GroupHeading = function GroupHeading(props) {
103+
};
104+
var Group$1 = Group;
105+
106+
-var _excluded = ["innerRef", "isDisabled", "isHidden", "inputClassName"];
107+
+var _excluded = ["innerRef", "isDisabled", "isHidden", "isTextArea", "inputClassName"];
108+
var inputCSS = function inputCSS(_ref, unstyled) {
109+
var isDisabled = _ref.isDisabled,
110+
value = _ref.value,
111+
@@ -1151,13 +1151,25 @@ var Input = function Input(props) {
112+
innerRef = _cleanCommonProps.innerRef,
113+
isDisabled = _cleanCommonProps.isDisabled,
114+
isHidden = _cleanCommonProps.isHidden,
115+
+ isTextArea = _cleanCommonProps.isTextArea,
116+
inputClassName = _cleanCommonProps.inputClassName,
117+
innerProps = _objectWithoutProperties(_cleanCommonProps, _excluded);
118+
return react.jsx("div", _extends({}, getStyleProps(props, 'input', {
119+
'input-container': true
120+
}), {
121+
"data-value": value || ''
122+
- }), react.jsx("input", _extends({
123+
+ }), isTextArea ? react.jsx("textarea", _extends({
124+
+ className: cx({
125+
+ input: true
126+
+ }, inputClassName),
127+
+ ref: innerRef,
128+
+ style: _objectSpread(_objectSpread({}, inputStyle(isHidden)), {}, {
129+
+ resize: 'none'
130+
+ }),
131+
+ disabled: isDisabled
132+
+ }, innerProps, {
133+
+ rows: 1
134+
+ })) : react.jsx("input", _extends({
135+
className: cx({
136+
input: true
137+
}, inputClassName),
Lines changed: 1 addition & 0 deletions
Loading

src/Common/Constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ export enum ReactSelectInputAction {
509509
selectOption = 'select-option',
510510
deselectOption = 'deselect-option',
511511
removeValue = 'remove-value',
512+
inputBlur = 'input-blur',
512513
}
513514

514515
export const ZERO_TIME_STRING = '0001-01-01T00:00:00Z'

src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { createElement, createRef, Fragment, ReactElement, RefObject, useEffect, useRef } from 'react'
1+
import { createElement, createRef, Fragment, ReactElement, RefObject, useEffect, useRef, useState } from 'react'
22
// eslint-disable-next-line import/no-extraneous-dependencies
33
import { followCursor } from 'tippy.js'
44

5-
import { ReactComponent as ICCross } from '@Icons/ic-cross.svg'
5+
import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
66
import { Tooltip } from '@Common/Tooltip'
77
import { ConditionalWrap } from '@Common/Helper'
88
import { ResizableTagTextArea } from '@Common/CustomTagSelector'
99
import { ComponentSizeType } from '@Shared/constants'
1010

1111
import { Button, ButtonStyleType, ButtonVariantType } from '../Button'
12-
import { SelectTextArea } from '../SelectTextArea'
1312
import {
1413
getSelectPickerOptionByValue,
1514
SelectPicker,
15+
SelectPickerTextArea,
1616
SelectPickerOptionType,
1717
SelectPickerVariantType,
1818
} from '../SelectPicker'
@@ -46,6 +46,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
4646
leadingCellIcon,
4747
trailingCellIcon,
4848
buttonCellWrapComponent,
49+
focusableFieldKey,
4950
}: DynamicDataTableRowProps<K, CustomStateType>) => {
5051
// CONSTANTS
5152
const isFirstRowEmpty = headers.every(({ key }) => !rows[0]?.data[key].value)
@@ -59,6 +60,10 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
5960
isDeletionNotAllowed || readOnly,
6061
)
6162

63+
// STATES
64+
const [isRowAdded, setIsRowAdded] = useState(false)
65+
66+
// CELL REFS
6267
const cellRef = useRef<Record<string | number, Record<K, RefObject<HTMLTextAreaElement>>>>()
6368
if (!cellRef.current) {
6469
cellRef.current = rows.reduce(
@@ -71,6 +76,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
7176
}
7277

7378
useEffect(() => {
79+
setIsRowAdded(rows.length > 0 && Object.keys(cellRef.current).length < rows.length)
7480
const rowIds = rows.map(({ id }) => id)
7581

7682
const updatedCellRef = rowIds.reduce((acc, curr) => {
@@ -85,6 +91,15 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
8591
cellRef.current = updatedCellRef
8692
}, [rows.length])
8793

94+
useEffect(() => {
95+
// Using the below logic to ensure the cell is focused after row addition.
96+
const cell = cellRef.current[rows[0].id][focusableFieldKey || headers[0].key].current
97+
if (isRowAdded && cell) {
98+
cell.focus()
99+
setIsRowAdded(false)
100+
}
101+
}, [isRowAdded])
102+
88103
// METHODS
89104
const onChange =
90105
(row: DynamicDataTableRowType<K, CustomStateType>, key: K) =>
@@ -94,7 +109,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
94109
switch (row.data[key].type) {
95110
case DynamicDataTableRowDataType.DROPDOWN:
96111
case DynamicDataTableRowDataType.SELECT_TEXT:
97-
value = (e as SelectPickerOptionType<string>).value
112+
value = (e as SelectPickerOptionType<string>)?.value || ''
98113
extraData.selectedValue = e
99114
break
100115
case DynamicDataTableRowDataType.FILE_UPLOAD:
@@ -138,31 +153,32 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
138153
/>
139154
</div>
140155
)
141-
case DynamicDataTableRowDataType.SELECT_TEXT:
156+
case DynamicDataTableRowDataType.SELECT_TEXT: {
157+
const { value, props } = row.data[key]
158+
const { isCreatable = true } = props
159+
142160
return (
143-
<div className="dynamic-data-table__select-text-area w-100 h-100 flex top dc__align-self-start">
144-
<SelectTextArea
145-
{...row.data[key].props}
146-
value={row.data[key].value}
147-
onChange={onChange(row, key)}
161+
<div className="w-100 h-100 flex top dc__align-self-start">
162+
<SelectPickerTextArea
163+
isCreatable={isCreatable}
164+
isClearable
165+
{...props}
166+
variant={SelectPickerVariantType.BORDER_LESS}
167+
classNamePrefix="dynamic-data-table__cell__select-picker"
148168
inputId={`data-table-${row.id}-${key}-cell`}
149-
disabled={isDisabled}
150-
refVar={cellRef?.current?.[row.id]?.[key]}
151-
dependentRefs={cellRef?.current?.[row.id]}
152-
selectPickerProps={{
153-
...row.data[key].props?.selectPickerProps,
154-
classNamePrefix: 'dynamic-data-table__cell__select-picker',
155-
}}
156-
textAreaProps={{
157-
...row.data[key].props?.textAreaProps,
158-
className: 'dynamic-data-table__cell-input placeholder-cn5 py-8 pr-32 cn-9 fs-13 lh-20',
159-
disableOnBlurResizeToMinHeight: true,
160-
minHeight: 20,
161-
maxHeight: 160,
162-
}}
169+
value={getSelectPickerOptionByValue(
170+
props?.options,
171+
value,
172+
isCreatable && value ? { label: value, value } : null,
173+
)}
174+
onChange={onChange(row, key)}
175+
isDisabled={isDisabled}
176+
formatCreateLabel={(input) => `Use ${input}`}
177+
fullWidth
163178
/>
164179
</div>
165180
)
181+
}
166182
case DynamicDataTableRowDataType.BUTTON:
167183
return (
168184
<div className="w-100 h-100 flex top">
@@ -182,7 +198,9 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
182198
)
183199
case DynamicDataTableRowDataType.FILE_UPLOAD:
184200
return (
185-
<div className={`mw-none w-100 h-100 flex top left px-8 ${row.data[key].value ? 'py-3' : 'py-8'}`}>
201+
<div
202+
className={`mw-none w-100 h-100 flex top left px-8 ${row.data[key].props?.isLoading || row.data[key].value ? 'py-3' : 'py-8'}`}
203+
>
186204
<FileUpload
187205
{...row.data[key].props}
188206
fileName={row.data[key].value}
@@ -229,7 +247,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
229247

230248
const renderErrorMessage = (errorMessage: string) => (
231249
<div key={errorMessage} className="flexbox align-items-center dc__gap-4">
232-
<ICCross className="icon-dim-16 fcr-5 dc__align-self-start dc__no-shrink" />
250+
<ICClose className="icon-dim-16 fcr-5 dc__align-self-start dc__no-shrink" />
233251
<p className="fs-12 lh-16 cn-7 m-0">{errorMessage}</p>
234252
</div>
235253
)
@@ -240,12 +258,17 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
240258
? cellError[row.id][key]
241259
: { isValid: true, errorMessages: [] }
242260

261+
const isSelectText = row.data[key].type === DynamicDataTableRowDataType.SELECT_TEXT
262+
243263
if (isValid) {
244264
return null
245265
}
246266

267+
// Adding 'no-error' class to hide error when SelectPickerTextArea is focused.
247268
return (
248-
<div className="dynamic-data-table__error bcn-0 dc__border br-4 py-7 px-8 flexbox-col dc__gap-4">
269+
<div
270+
className={`dynamic-data-table__error bcn-0 dc__border br-4 py-7 px-8 flexbox-col dc__gap-4 ${isSelectText ? 'no-error' : ''}`}
271+
>
249272
{errorMessages.map((error) => renderErrorMessage(error))}
250273
</div>
251274
)
@@ -264,7 +287,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
264287
plugins={[followCursor]}
265288
>
266289
<div
267-
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' : ''}`}
290+
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' : ''}`}
268291
>
269292
{renderCellIcon(row, key, true)}
270293
{renderCellContent(row, key)}
@@ -321,7 +344,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
321344
dataTestId="dynamic-data-table-row-delete-btn"
322345
ariaLabel="Delete Row"
323346
showAriaLabelInTippy={false}
324-
icon={<ICCross />}
347+
icon={<ICClose />}
325348
disabled={disableDeleteRow || row.disableDelete}
326349
onClick={onDelete(row)}
327350
variant={ButtonVariantType.borderLess}

src/Shared/Components/DynamicDataTable/styles.scss

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,7 @@
8383
&__select-picker__control {
8484
gap: 6px !important;
8585
padding: 8px !important;
86-
}
87-
88-
&__select-picker__menu-portal {
89-
z-index: 3 !important;
90-
}
91-
92-
&:has(.select-picker-hidden) .dynamic-data-table__select-text-area {
93-
padding-left: 8px;
86+
max-height: 160px !important;
9487
}
9588

9689
&:hover:not(:focus-within):not(.no-hover) {
@@ -114,6 +107,10 @@
114107
display: none;
115108
}
116109

110+
&__cell:focus-within > &__error.no-error {
111+
display: none;
112+
}
113+
117114
&__error {
118115
position: absolute;
119116
top: calc(100% + 4px);

0 commit comments

Comments
 (0)