Skip to content

Commit be70194

Browse files
committed
feat: SelectPickerTextArea - add component, DynamicDataTable - replace SelectTextArea with SelectPickerTextArea, remove SelectTextArea component, add modified react-select using patch-package
1 parent 97d0945 commit be70194

File tree

14 files changed

+1692
-214
lines changed

14 files changed

+1692
-214
lines changed

package-lock.json

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

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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",
@@ -61,13 +62,14 @@
6162
"husky": "^7.0.4",
6263
"lint-staged": "^12.5.0",
6364
"moment": "^2.29.4",
65+
"monaco-editor": "0.44.0",
66+
"monaco-yaml": "5.1.1",
67+
"patch-package": "^8.0.0",
6468
"prettier": "^3.1.1",
6569
"react-ga4": "^1.4.1",
6670
"react-toastify": "9.1.3",
6771
"sharp": "^0.33.5",
6872
"svgo": "^3.3.2",
69-
"monaco-editor": "0.44.0",
70-
"monaco-yaml": "5.1.1",
7173
"typescript": "5.5.4",
7274
"vite": "5.4.11",
7375
"vite-plugin-dts": "4.0.3",
@@ -101,8 +103,8 @@
101103
"jsonpath-plus": "^10.0.0",
102104
"marked": "^13.0.3",
103105
"react-dates": "^21.8.0",
104-
"react-monaco-editor": "^0.54.0",
105106
"react-diff-viewer-continued": "^3.4.0",
107+
"react-monaco-editor": "^0.54.0",
106108
"sass": "^1.69.7",
107109
"tslib": "2.7.0"
108110
},

patches/react-select+5.8.0.patch

Lines changed: 1125 additions & 0 deletions
Large diffs are not rendered by default.

src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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

@@ -9,10 +9,10 @@ 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 && 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">
@@ -240,12 +256,17 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
240256
? cellError[row.id][key]
241257
: { isValid: true, errorMessages: [] }
242258

259+
const isSelectText = row.data[key].type === DynamicDataTableRowDataType.SELECT_TEXT
260+
243261
if (isValid) {
244262
return null
245263
}
246264

265+
// Adding 'no-error' class to hide error when SelectPickerTextArea is focused.
247266
return (
248-
<div className="dynamic-data-table__error bcn-0 dc__border br-4 py-7 px-8 flexbox-col dc__gap-4">
267+
<div
268+
className={`dynamic-data-table__error bcn-0 dc__border br-4 py-7 px-8 flexbox-col dc__gap-4 ${isSelectText ? 'no-error' : ''}`}
269+
>
249270
{errorMessages.map((error) => renderErrorMessage(error))}
250271
</div>
251272
)
@@ -264,7 +285,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
264285
plugins={[followCursor]}
265286
>
266287
<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' : ''}`}
288+
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' : ''}`}
268289
>
269290
{renderCellIcon(row, key, true)}
270291
{renderCellContent(row, key)}

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);

src/Shared/Components/DynamicDataTable/types.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import { ResizableTagTextAreaProps } from '@Common/CustomTagSelector'
2020
import { UseStateFiltersReturnType } from '@Common/Hooks'
2121

2222
import { TooltipProps } from '@Common/Tooltip/types'
23-
import { SelectPickerOptionType, SelectPickerProps } from '../SelectPicker'
24-
import { SelectTextAreaProps } from '../SelectTextArea'
23+
import { SelectPickerOptionType, SelectPickerProps, SelectPickerTextAreaProps } from '../SelectPicker'
2524
import { FileUploadProps } from '../FileUpload'
2625

2726
/**
@@ -70,14 +69,9 @@ export type DynamicDataTableCellPropsMap = {
7069
'inputId' | 'value' | 'onChange' | 'fullWidth' | 'isDisabled'
7170
>
7271
[DynamicDataTableRowDataType.SELECT_TEXT]: Omit<
73-
SelectTextAreaProps,
74-
'value' | 'onChange' | 'inputId' | 'isDisabled' | 'dependentRef' | 'refVar' | 'textAreaProps'
75-
> & {
76-
textAreaProps?: Omit<
77-
SelectTextAreaProps['textAreaProps'],
78-
'className' | 'disableOnBlurResizeToMinHeight' | 'minHeight' | 'maxHeight'
79-
>
80-
}
72+
SelectPickerTextAreaProps,
73+
'inputId' | 'value' | 'onChange' | 'fullWidth' | 'isDisabled' | 'variant' | 'formatCreateLabel'
74+
>
8175
[DynamicDataTableRowDataType.BUTTON]: Pick<
8276
DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
8377
'onClick'
@@ -214,6 +208,11 @@ export type DynamicDataTableProps<K extends string, CustomStateType = Record<str
214208
* Validation state for a specific cell in a dynamic data table.
215209
*/
216210
cellError?: DynamicDataTableCellErrorType<K>
211+
/**
212+
* Header key to determine which cell should be focused after row addition.
213+
* @default 'first column key'
214+
*/
215+
focusableFieldKey?: K
217216
}
218217

219218
export interface DynamicDataTableHeaderProps<K extends string, CustomStateType = Record<string, unknown>>
@@ -245,4 +244,5 @@ export interface DynamicDataTableRowProps<K extends string, CustomStateType = Re
245244
| 'leadingCellIcon'
246245
| 'trailingCellIcon'
247246
| 'buttonCellWrapComponent'
247+
| 'focusableFieldKey'
248248
> {}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import {
4242
SelectPickerMenuList,
4343
SelectPickerOption,
4444
SelectPickerValueContainer,
45+
SelectPickerInput,
46+
SelectPickerIndicatorsContainer,
4547
} from './common'
4648
import { SelectPickerOptionType, SelectPickerProps, SelectPickerVariantType } from './type'
4749
import { GenericSectionErrorState } from '../GenericSectionErrorState'
@@ -215,6 +217,8 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
215217
closeMenuOnSelect = false,
216218
shouldShowNoOptionsMessage = true,
217219
formatCreateLabel,
220+
shouldRenderTextArea = false,
221+
onKeyDown,
218222
...props
219223
}: SelectPickerProps<OptionValue, IsMulti>) => {
220224
const { inputId, required, isDisabled, controlShouldRenderValue = true, value, options, getOptionValue } = props
@@ -312,11 +316,12 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
312316
): void => {
313317
const trimmedInputValue = _inputValue?.trim()
314318
if (trimmedInputValue) {
315-
onCreateOption(trimmedInputValue)
319+
onCreateOption?.(trimmedInputValue)
316320
}
317321
}
318322

319323
const handleKeyDown: ReactSelectProps['onKeyDown'] = (e) => {
324+
onKeyDown?.(e)
320325
// Prevent the option from being selected if meta or control key is pressed
321326
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
322327
e.preventDefault()
@@ -391,12 +396,14 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
391396
Control: SelectPickerControl,
392397
Option: renderOption,
393398
MenuList: SelectPickerMenuList,
399+
IndicatorsContainer: SelectPickerIndicatorsContainer,
394400
ClearIndicator: SelectPickerClearIndicator,
395401
ValueContainer: renderValueContainer,
396402
MultiValueLabel: renderMultiValueLabel,
397403
MultiValueRemove: SelectPickerMultiValueRemove,
398404
GroupHeading: renderGroupHeading,
399405
NoOptionsMessage: renderNoOptionsMessage,
406+
Input: SelectPickerInput,
400407
}}
401408
closeMenuOnSelect={!isMulti || closeMenuOnSelect}
402409
allowCreateWhileLoading={false}
@@ -410,6 +417,7 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
410417
showSelectedOptionIcon={shouldShowSelectedOptionIcon}
411418
formatCreateLabel={formatCreateLabel}
412419
onKeyDown={handleKeyDown}
420+
shouldRenderTextArea={shouldRenderTextArea}
413421
/>
414422
</div>
415423
</ConditionalWrap>

0 commit comments

Comments
 (0)