14
14
* limitations under the License.
15
15
*/
16
16
17
- import {
18
- createElement ,
19
- createRef ,
20
- Fragment ,
21
- ReactElement ,
22
- RefObject ,
23
- useEffect ,
24
- useMemo ,
25
- useRef ,
26
- useState ,
27
- } from 'react'
17
+ import { createElement , createRef , Fragment , ReactElement , RefObject , useEffect , useMemo , useRef } from 'react'
28
18
// eslint-disable-next-line import/no-extraneous-dependencies
29
19
import { followCursor } from 'tippy.js'
30
20
31
- import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
32
21
import { ResizableTagTextArea } from '@Common/CustomTagSelector'
33
22
import { ConditionalWrap } from '@Common/Helper'
34
23
import { Tooltip } from '@Common/Tooltip'
35
24
import { ComponentSizeType } from '@Shared/constants'
36
25
37
26
import { Button , ButtonStyleType , ButtonVariantType } from '../Button'
38
27
import { FileUpload } from '../FileUpload'
28
+ import { Icon } from '../Icon'
39
29
import {
40
30
getSelectPickerOptionByValue ,
41
31
SelectPicker ,
@@ -73,8 +63,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
73
63
trailingCellIcon,
74
64
buttonCellWrapComponent,
75
65
focusableFieldKey,
76
- isAddRowButtonClicked,
77
- setIsAddRowButtonClicked,
66
+ shouldAutoFocusOnMount = false ,
78
67
} : DynamicDataTableRowProps < K , CustomStateType > ) => {
79
68
// CONSTANTS
80
69
const isFirstRowEmpty = headers . every ( ( { key } ) => ! rows [ 0 ] ?. data [ key ] . value )
@@ -88,25 +77,18 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
88
77
isDeletionNotAllowed || readOnly ,
89
78
)
90
79
91
- // STATES
92
- const [ isRowAdded , setIsRowAdded ] = useState ( false )
93
-
94
80
// CELL REFS
95
- const cellRef = useRef < Record < string | number , Record < K , RefObject < HTMLTextAreaElement > > > > ( )
81
+ const shouldAutoFocusNewRowRef = useRef ( shouldAutoFocusOnMount )
82
+ const cellRef = useRef < Record < string | number , Record < K , RefObject < HTMLTextAreaElement > > > > ( null )
96
83
if ( ! cellRef . current ) {
97
- cellRef . current = rows . reduce (
98
- ( acc , curr ) => ( {
99
- ...acc ,
100
- [ curr . id ] : headers . reduce ( ( headerAcc , { key } ) => ( { ...headerAcc , [ key ] : createRef ( ) } ) , { } ) ,
101
- } ) ,
102
- { } ,
103
- )
84
+ cellRef . current = rows . reduce ( ( acc , curr ) => {
85
+ acc [ curr . id ] = headers . reduce ( ( headerAcc , { key } ) => ( { ...headerAcc , [ key ] : createRef ( ) } ) , { } )
86
+ return acc
87
+ } , { } )
104
88
}
105
89
const rowIds = useMemo ( ( ) => rows . map ( ( { id } ) => id ) , [ rows ] )
106
90
107
91
useEffect ( ( ) => {
108
- setIsRowAdded ( rows . length > 0 && Object . keys ( cellRef . current ) . length < rows . length )
109
-
110
92
// When a new row is added, we create references for its cells.
111
93
// This logic ensures that references are created only for the newly added row, while retaining the existing references.
112
94
const updatedCellRef = rowIds . reduce ( ( acc , curr ) => {
@@ -121,18 +103,6 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
121
103
cellRef . current = updatedCellRef
122
104
} , [ JSON . stringify ( rowIds ) ] )
123
105
124
- useEffect ( ( ) => {
125
- if ( isAddRowButtonClicked && isRowAdded ) {
126
- // Using the below logic to ensure the cell is focused after row addition.
127
- const cell = cellRef . current [ rows [ 0 ] . id ] [ focusableFieldKey || headers [ 0 ] . key ] . current
128
- if ( cell ) {
129
- cell . focus ( )
130
- setIsRowAdded ( false )
131
- setIsAddRowButtonClicked ( false )
132
- }
133
- }
134
- } , [ isRowAdded , isAddRowButtonClicked ] )
135
-
136
106
// METHODS
137
107
const onChange =
138
108
( row : DynamicDataTableRowType < K , CustomStateType > , key : K ) =>
@@ -163,14 +133,30 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
163
133
}
164
134
165
135
// RENDERERS
166
- const renderCellContent = ( row : DynamicDataTableRowType < K , CustomStateType > , key : K ) => {
136
+ const renderCellContent = ( row : DynamicDataTableRowType < K , CustomStateType > , key : K , index : number ) => {
167
137
const isDisabled = readOnly || row . data [ key ] . disabled
138
+ const autoFocus =
139
+ shouldAutoFocusNewRowRef . current && key === ( focusableFieldKey ?? headers [ 0 ] . key ) && index === 0
140
+
141
+ // This logic ensures only newly added rows get autofocus.
142
+ // On the initial mount, all rows are treated as new, so autofocus is enabled.
143
+ // After the first render, when cellRef is set (DOM rendered), we set shouldAutoFocusNewRowRef to true,
144
+ // so autofocus is applied only to the correct cell in any subsequently added row.
145
+ if (
146
+ ! shouldAutoFocusOnMount &&
147
+ ! shouldAutoFocusNewRowRef . current &&
148
+ index === 0 &&
149
+ cellRef ?. current ?. [ row . id ] ?. [ key ] . current
150
+ ) {
151
+ shouldAutoFocusNewRowRef . current = true
152
+ }
168
153
169
154
switch ( row . data [ key ] . type ) {
170
155
case DynamicDataTableRowDataType . DROPDOWN :
171
156
return (
172
157
< div className = "w-100 h-100 flex top dc__align-self-start" >
173
158
< SelectPicker < string , false >
159
+ autoFocus = { autoFocus }
174
160
{ ...row . data [ key ] . props }
175
161
inputId = { `data-table-${ row . id } -${ key } -cell` }
176
162
classNamePrefix = "dynamic-data-table__cell__select-picker"
@@ -193,6 +179,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
193
179
return (
194
180
< div className = "w-100 h-100 flex top dc__align-self-start" >
195
181
< SelectPickerTextArea
182
+ autoFocus = { autoFocus }
196
183
isCreatable = { isCreatable }
197
184
isClearable
198
185
{ ...props }
@@ -248,6 +235,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
248
235
default :
249
236
return (
250
237
< ResizableTagTextArea
238
+ autoFocus = { autoFocus }
251
239
{ ...row . data [ key ] . props }
252
240
id = { `data-table-${ row . id } -${ key } -cell` }
253
241
className = { `dynamic-data-table__cell-input placeholder-cn5 p-8 cn-9 fs-13 lh-20 dc__align-self-start dc__no-border-radius ${ isDisabled ? 'cursor-not-allowed' : '' } ` }
@@ -283,8 +271,8 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
283
271
}
284
272
285
273
const renderErrorMessage = ( errorMessage : string ) => (
286
- < div key = { errorMessage } className = "flexbox align-items-center dc__gap-4" >
287
- < ICClose className = "icon-dim-16 fcr-5 dc__align-self-start dc__no-shrink " />
274
+ < div key = { errorMessage } className = "flexbox dc__gap-4" >
275
+ < Icon name = "ic-close-small" color = "R500 " />
288
276
< p className = "fs-12 lh-16 cn-7 m-0" > { errorMessage } </ p >
289
277
</ div >
290
278
)
@@ -329,7 +317,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
329
317
className = { `dynamic-data-table__cell bg__primary 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' : '' } ` }
330
318
>
331
319
{ renderCellIcon ( row , key , true ) }
332
- { renderCellContent ( row , key ) }
320
+ { renderCellContent ( row , key , index ) }
333
321
{ renderAsterisk ( row , key ) }
334
322
{ renderCellIcon ( row , key ) }
335
323
{ renderErrorMessages ( row , key ) }
@@ -383,7 +371,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
383
371
dataTestId = "dynamic-data-table-row-delete-btn"
384
372
ariaLabel = "Delete Row"
385
373
showAriaLabelInTippy = { false }
386
- icon = { < ICClose /> }
374
+ icon = { < Icon name = "ic-close-large" color = { null } /> }
387
375
disabled = { disableDeleteRow || row . disableDelete }
388
376
onClick = { onDelete ( row ) }
389
377
variant = { ButtonVariantType . borderLess }
0 commit comments