15
15
* limitations under the License.
16
16
*/
17
17
18
- /*
19
- * Copyright (c) 2024 Devtron Inc.
20
- * All rights reserved.
21
-
22
- * Licensed under the Apache License, Version 2.0 (the "License");
23
- * you may not use this file except in compliance with the License.
24
- * You may obtain a copy of the License at
25
-
26
- * http://www.apache.org/licenses/LICENSE-2.0
27
-
28
- * Unless required by applicable law or agreed to in writing, software
29
- * distributed under the License is distributed on an "AS IS" BASIS,
30
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31
- * See the License for the specific language governing permissions and
32
- * limitations under the License.
33
- */
34
-
35
18
import React , { createRef , useEffect , useRef , useState } from 'react'
36
19
37
20
import { ReactComponent as ICArrowDown } from '../../../Assets/Icon/ic-arrow-down.svg'
38
21
import { ReactComponent as ICCross } from '../../../Assets/Icon/ic-cross.svg'
39
22
import { ResizableTagTextArea , SortingOrder , useStateFilters } from '../../../Common'
23
+ import { DEFAULT_SECRET_PLACEHOLDER } from '../../constants'
40
24
import { stringComparatorBySortOrder } from '../../Helpers'
41
25
import { KeyValueRow , KeyValueTableProps } from './KeyValueTable.types'
42
26
@@ -58,7 +42,7 @@ export const KeyValueTable = <K extends string>({
58
42
const secondHeaderKey = headers [ 1 ] . key
59
43
60
44
// STATES
61
- const [ _rows , setRows ] = useState < KeyValueRow < K > [ ] > ( rows )
45
+ const [ updatedRows , setUpdatedRows ] = useState < KeyValueRow < K > [ ] > ( rows )
62
46
const [ newRowAdded , setNewRowAdded ] = useState ( false )
63
47
64
48
// HOOKS
@@ -70,21 +54,21 @@ export const KeyValueTable = <K extends string>({
70
54
const valueTextAreaRef = useRef < Record < string , React . RefObject < HTMLTextAreaElement > > > ( )
71
55
72
56
if ( ! keyTextAreaRef . current ) {
73
- keyTextAreaRef . current = _rows . reduce ( ( acc , curr ) => ( { ...acc , [ curr . id ] : createRef ( ) } ) , { } )
57
+ keyTextAreaRef . current = updatedRows . reduce ( ( acc , curr ) => ( { ...acc , [ curr . id ] : createRef ( ) } ) , { } )
74
58
}
75
59
76
60
if ( ! valueTextAreaRef . current ) {
77
- valueTextAreaRef . current = _rows . reduce ( ( acc , curr ) => ( { ...acc , [ curr . id ] : createRef ( ) } ) , { } )
61
+ valueTextAreaRef . current = updatedRows . reduce ( ( acc , curr ) => ( { ...acc , [ curr . id ] : createRef ( ) } ) , { } )
78
62
}
79
63
80
64
useEffect ( ( ) => {
81
- const sortedRows = [ ..._rows ]
65
+ const sortedRows = [ ...updatedRows ]
82
66
sortedRows . sort ( ( a , b ) => stringComparatorBySortOrder ( a . data [ sortBy ] . value , b . data [ sortBy ] . value , sortOrder ) )
83
- setRows ( sortedRows )
67
+ setUpdatedRows ( sortedRows )
84
68
} , [ sortOrder ] )
85
69
86
70
useEffect ( ( ) => {
87
- const firstRow = _rows ?. [ 0 ]
71
+ const firstRow = updatedRows ?. [ 0 ]
88
72
if ( firstRow && newRowAdded ) {
89
73
setNewRowAdded ( false )
90
74
@@ -103,15 +87,15 @@ export const KeyValueTable = <K extends string>({
103
87
valueTextAreaRef . current [ firstRow . id ] . current . focus ( )
104
88
}
105
89
}
106
- } , [ _rows , newRowAdded ] )
90
+ } , [ updatedRows , newRowAdded ] )
107
91
108
92
// METHODS
109
93
const onSortBtnClick = ( ) => handleSorting ( sortBy )
110
94
111
95
const onNewRowAdd = ( key : K ) => ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
112
96
const { value } = e . target
113
97
114
- const id = Date . now ( ) . toString ( 16 )
98
+ const id = ( Date . now ( ) * Math . random ( ) ) . toString ( 16 )
115
99
const data = {
116
100
data : {
117
101
[ firstHeaderKey ] : {
@@ -125,7 +109,8 @@ export const KeyValueTable = <K extends string>({
125
109
} as KeyValueRow < K >
126
110
127
111
setNewRowAdded ( true )
128
- setRows ( [ data , ..._rows ] )
112
+ setUpdatedRows ( [ data , ...updatedRows ] )
113
+ onChange ?.( id , key , value )
129
114
130
115
keyTextAreaRef . current = {
131
116
...keyTextAreaRef . current ,
@@ -137,162 +122,150 @@ export const KeyValueTable = <K extends string>({
137
122
}
138
123
}
139
124
140
- const onRowDataEdit =
141
- ( row : KeyValueRow < K > , key : K , rowIndex : number ) => ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
142
- const { value } = e . target
125
+ const onRowDataEdit = ( row : KeyValueRow < K > , key : K ) => ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
126
+ const { value } = e . target
143
127
144
- let newRows = [ ]
145
- if ( ! value && ! row . data [ key === firstHeaderKey ? secondHeaderKey : firstHeaderKey ] . value ) {
146
- newRows = _rows . filter ( ( _ , idx ) => idx !== rowIndex )
128
+ let editedRows = [ ]
129
+ if ( ! value && ! row . data [ key === firstHeaderKey ? secondHeaderKey : firstHeaderKey ] . value ) {
130
+ editedRows = updatedRows . filter ( ( { id } ) => id !== row . id )
147
131
148
- delete keyTextAreaRef . current [ row . id ]
149
- delete valueTextAreaRef . current [ row . id ]
132
+ delete keyTextAreaRef . current [ row . id ]
133
+ delete valueTextAreaRef . current [ row . id ]
150
134
151
- if ( inputRowRef . current ) {
152
- inputRowRef . current . focus ( )
153
- }
154
- } else {
155
- newRows = [
156
- ..._rows . slice ( 0 , rowIndex ) ,
157
- {
158
- ...row ,
159
- data : {
160
- ...row . data ,
161
- [ key ] : {
162
- ...row . data [ key ] ,
163
- value,
164
- } ,
165
- } ,
135
+ if ( inputRowRef . current ) {
136
+ inputRowRef . current . focus ( )
137
+ }
138
+ } else {
139
+ const rowData = {
140
+ ...row ,
141
+ data : {
142
+ ...row . data ,
143
+ [ key ] : {
144
+ ...row . data [ key ] ,
145
+ value,
166
146
} ,
167
- ..._rows . slice ( rowIndex + 1 ) ,
168
- ]
147
+ } ,
169
148
}
170
-
171
- setRows ( newRows )
172
- onChange ?.( rowIndex , key , value )
149
+ editedRows = updatedRows . map ( ( _row ) => ( _row . id === row . id ? rowData : _row ) )
173
150
}
174
151
175
- const onRowDelete = ( rowIndex : number , row : KeyValueRow < K > ) => ( e : React . MouseEvent < HTMLButtonElement > ) => {
176
- const newRows = _rows . filter ( ( _ , idx ) => idx !== rowIndex )
177
- setRows ( newRows )
152
+ setUpdatedRows ( editedRows )
153
+ onChange ?.( row . id , key , value )
154
+ }
155
+
156
+ const onRowDelete = ( row : KeyValueRow < K > ) => ( ) => {
157
+ const remainingRows = updatedRows . filter ( ( { id } ) => id !== row . id )
158
+ setUpdatedRows ( remainingRows )
178
159
179
160
delete keyTextAreaRef . current [ row . id ]
180
161
delete valueTextAreaRef . current [ row . id ]
181
162
182
- onDelete ?.( e , rowIndex )
163
+ onDelete ?.( row . id )
183
164
}
184
165
185
166
return (
186
- < div style = { { minHeight : '500px' , background : 'white' , padding : '2px' } } >
187
- < div className = "dc__border br-4 w-100 table-container" >
188
- < div
189
- className = { `table-row flexbox dc__align-items-center bcn-50 ${ ! isAdditionNotAllowed || _rows . length ? 'dc__border-bottom' : '' } ` }
190
- style = { { borderColor : 'var(--N100)' } }
191
- >
192
- { headers . map ( ( { key, label, className } ) =>
193
- isSortable && key === firstHeaderKey ? (
194
- < button
195
- key = { key }
196
- type = "button"
197
- className = { `dc__unset-button-styles cn-9 fs-13 lh-20 py-8 px-12 fw-6 flexbox dc__align-items-center dc__gap-2 head__key ${ className || '' } ` }
198
- onClick = { onSortBtnClick }
199
- >
200
- { label }
201
- < ICArrowDown
202
- className = "icon-dim-16 fcn-7 rotate cursor"
203
- style = { {
204
- [ '--rotateBy' as string ] : sortOrder === SortingOrder . ASC ? '0deg' : '180deg' ,
205
- } }
206
- />
207
- </ button >
208
- ) : (
209
- < div
210
- key = { key }
211
- className = { `cn-9 fs-13 lh-20 py-8 px-12 fw-6 flexbox dc__align-items-center dc__gap-2 ${ key === firstHeaderKey ? 'head__key' : 'flex-grow-1' } ${ className || '' } ` }
212
- >
213
- { label }
214
- </ div >
215
- ) ,
216
- ) }
217
- { ! ! headerComponent && < div className = "px-12" > { headerComponent } </ div > }
218
- </ div >
219
- { ! isAdditionNotAllowed && (
220
- < div
221
- className = { `table-row flexbox dc__align-items-center ${ _rows . length ? 'dc__border-bottom' : '' } ` }
222
- style = { { borderColor : 'var(--N100)' } }
223
- >
224
- { headers . map ( ( { key } ) => (
225
- < div
226
- key = { key }
227
- className = { `cn-9 fs-13 lh-20 py-8 px-12 flex dc__overflow-auto ${ key === firstHeaderKey ? 'head__key' : 'flex-grow-1' } ` }
228
- >
229
- < textarea
230
- ref = { key === firstHeaderKey ? inputRowRef : undefined }
231
- className = "table-input table-input__text-area pt-8 pb-8 pl-10 pb-10 lh-20 fs-13 fw-4"
232
- value = ""
233
- rows = { 1 }
234
- placeholder = { placeholder [ key ] }
235
- onChange = { onNewRowAdd ( key ) }
236
- />
237
- </ div >
238
- ) ) }
239
- </ div >
240
- ) }
241
- { _rows . map ( ( row , index ) => (
242
- < div
243
- key = { row . id }
244
- className = { `table-row flexbox dc__align-items-center ${ index !== _rows . length - 1 ? 'dc__border-bottom' : '' } ` }
245
- style = { { borderColor : 'var(--N100)' } }
246
- >
247
- { headers . map ( ( { key } ) => (
248
- < div
249
- key = { key }
250
- className = { `cn-9 fs-13 lh-20 py-8 px-12 dc__overflow-auto flexbox dc__align-items-center dc__gap-4 ${ key === firstHeaderKey ? 'head__key' : 'flex-grow-1' } ` }
251
- >
252
- { maskValue ?. [ key ] && row . data [ key ] . value ? (
253
- '*****'
254
- ) : (
255
- < >
256
- < ResizableTagTextArea
257
- { ...row . data [ key ] }
258
- className = "table-input"
259
- minHeight = { 20 }
260
- maxHeight = { 144 }
261
- value = { row . data [ key ] . value }
262
- placeholder = { placeholder [ key ] }
263
- onChange = { onRowDataEdit ( row , key , index ) }
264
- refVar = {
265
- key === firstHeaderKey
266
- ? keyTextAreaRef . current ?. [ row . id ]
267
- : valueTextAreaRef . current ?. [ row . id ]
268
- }
269
- dependentRef = {
270
- key === firstHeaderKey
271
- ? valueTextAreaRef . current ?. [ row . id ]
272
- : keyTextAreaRef . current ?. [ row . id ]
273
- }
274
- disableOnBlurResizeToMinHeight
275
- />
276
- { row . data [ key ] . showAsterisk && (
277
- < span className = "cr-5 fs-16 dc__align-self-start px-6" > *</ span >
278
- ) }
279
- </ >
280
- ) }
281
- </ div >
282
- ) ) }
167
+ < div className = "dc__border br-4 w-100 bcn-0" >
168
+ < div
169
+ className = { `key-value__row flexbox dc__align-items-center bcn-50 ${ ! isAdditionNotAllowed || updatedRows . length ? 'dc__border-bottom-n1' : '' } ` }
170
+ >
171
+ { headers . map ( ( { key, label, className } ) =>
172
+ isSortable && key === firstHeaderKey ? (
283
173
< button
174
+ key = { key }
284
175
type = "button"
285
- className = " dc__unset-button-styles icon flex dc__no-shrink py-10 px-8"
286
- onClick = { onRowDelete ( index , row ) }
176
+ className = { ` dc__unset-button-styles cn-9 fs-13 lh-20 py-8 px-12 fw-6 flexbox dc__align-items-center dc__gap-2 dc__align-self-stretch key-value__header__col-1 ${ className || '' } ` }
177
+ onClick = { onSortBtnClick }
287
178
>
288
- < ICCross
289
- aria-label = "delete-data"
290
- className = "icon-dim-16 fcn-4 dc__align-self-start cursor"
179
+ { label }
180
+ < ICArrowDown
181
+ className = "icon-dim-16 scn-7 rotate cursor"
182
+ style = { {
183
+ [ '--rotateBy' as string ] : sortOrder === SortingOrder . ASC ? '0deg' : '180deg' ,
184
+ } }
291
185
/>
292
186
</ button >
293
- </ div >
294
- ) ) }
187
+ ) : (
188
+ < div
189
+ key = { key }
190
+ className = { `cn-9 fs-13 lh-20 py-8 px-12 fw-6 flexbox dc__align-items-center dc__gap-2 ${ key === firstHeaderKey ? 'dc__align-self-stretch dc__border-right--n1 key-value__header__col-1' : 'flex-grow-1 dc__border-left-n1' } ${ className || '' } ` }
191
+ >
192
+ { label }
193
+ </ div >
194
+ ) ,
195
+ ) }
196
+ { ! ! headerComponent && < div className = "px-12" > { headerComponent } </ div > }
295
197
</ div >
198
+ { ! isAdditionNotAllowed && (
199
+ < div
200
+ className = { `key-value__row flexbox dc__align-items-center ${ updatedRows . length ? 'dc__border-bottom-n1' : '' } ` }
201
+ >
202
+ { headers . map ( ( { key } ) => (
203
+ < div
204
+ key = { key }
205
+ className = { `cn-9 fs-13 lh-20 py-8 px-12 flex dc__overflow-auto ${ key === firstHeaderKey ? 'dc__align-self-stretch dc__border-right--n1 key-value__header__col-1' : 'flex-grow-1' } ` }
206
+ >
207
+ < textarea
208
+ ref = { key === firstHeaderKey ? inputRowRef : undefined }
209
+ className = "key-value__row-input key-value__row-input--add placeholder-cn5 p-0 lh-20 fs-13 fw-4"
210
+ value = ""
211
+ rows = { 1 }
212
+ placeholder = { placeholder [ key ] }
213
+ onChange = { onNewRowAdd ( key ) }
214
+ />
215
+ </ div >
216
+ ) ) }
217
+ </ div >
218
+ ) }
219
+ { updatedRows . map ( ( row , index ) => (
220
+ < div
221
+ key = { row . id }
222
+ className = { `key-value__row flexbox dc__align-items-center ${ index !== updatedRows . length - 1 ? 'dc__border-bottom-n1' : '' } ` }
223
+ >
224
+ { headers . map ( ( { key } ) => (
225
+ < div
226
+ key = { key }
227
+ className = { `cn-9 fs-13 lh-20 px-12 dc__overflow-auto flexbox dc__align-items-center dc__gap-4 ${ key === firstHeaderKey ? 'dc__align-self-stretch dc__border-right--n1 key-value__header__col-1' : 'flex-grow-1' } ` }
228
+ >
229
+ { maskValue ?. [ key ] && row . data [ key ] . value ? (
230
+ DEFAULT_SECRET_PLACEHOLDER
231
+ ) : (
232
+ < >
233
+ < ResizableTagTextArea
234
+ { ...row . data [ key ] }
235
+ className = "key-value__row-input placeholder-cn5 py-8 px-0"
236
+ minHeight = { 20 }
237
+ maxHeight = { 160 }
238
+ value = { row . data [ key ] . value }
239
+ placeholder = { placeholder [ key ] }
240
+ onChange = { onRowDataEdit ( row , key ) }
241
+ refVar = {
242
+ key === firstHeaderKey
243
+ ? keyTextAreaRef . current ?. [ row . id ]
244
+ : valueTextAreaRef . current ?. [ row . id ]
245
+ }
246
+ dependentRef = {
247
+ key === firstHeaderKey
248
+ ? valueTextAreaRef . current ?. [ row . id ]
249
+ : keyTextAreaRef . current ?. [ row . id ]
250
+ }
251
+ disableOnBlurResizeToMinHeight
252
+ />
253
+ { row . data [ key ] . showAsterisk && (
254
+ < span className = "cr-5 fs-16 dc__align-self-start px-6" > *</ span >
255
+ ) }
256
+ </ >
257
+ ) }
258
+ </ div >
259
+ ) ) }
260
+ < button
261
+ type = "button"
262
+ className = "dc__unset-button-styles dc__align-self-stretch dc__no-shrink flex py-10 px-8 dc__border-left-n1--important"
263
+ onClick = { onRowDelete ( row ) }
264
+ >
265
+ < ICCross aria-label = "delete-row" className = "icon-dim-16 fcn-4 dc__align-self-start cursor" />
266
+ </ button >
267
+ </ div >
268
+ ) ) }
296
269
</ div >
297
270
)
298
271
}
0 commit comments