|
15 | 15 | * limitations under the License.
|
16 | 16 | */
|
17 | 17 |
|
18 |
| -import { createRef, useEffect, useRef, useState, ReactElement } from 'react' |
| 18 | +import { createRef, useEffect, useRef, useState, ReactElement, Fragment } from 'react' |
19 | 19 | import Tippy from '@tippyjs/react'
|
20 | 20 | // eslint-disable-next-line import/no-extraneous-dependencies
|
21 | 21 | import { followCursor } from 'tippy.js'
|
@@ -67,6 +67,9 @@ export const KeyValueTable = <K extends string>({
|
67 | 67 | const [updatedRows, setUpdatedRows] = useState<KeyValueRow<K>[]>(rows)
|
68 | 68 | const [newRowAdded, setNewRowAdded] = useState(false)
|
69 | 69 |
|
| 70 | + /** Boolean determining if table has rows. */ |
| 71 | + const hasRows = (!readOnly && !isAdditionNotAllowed) || !!updatedRows.length |
| 72 | + |
70 | 73 | // HOOKS
|
71 | 74 | const { sortBy, sortOrder, handleSorting } = useStateFilters({
|
72 | 75 | initialSortKey: firstHeaderKey,
|
@@ -117,7 +120,8 @@ export const KeyValueTable = <K extends string>({
|
117 | 120 | const checkAllRowsAreValid = (_rows: KeyValueRow<K>[]) => {
|
118 | 121 | const isValid = _rows.every(
|
119 | 122 | ({ data: _data }) =>
|
120 |
| - validationSchema?.(_data[firstHeaderKey].value) && validationSchema?.(_data[secondHeaderKey].value), |
| 123 | + validationSchema?.(_data[firstHeaderKey].value, firstHeaderKey) && |
| 124 | + validationSchema?.(_data[secondHeaderKey].value, secondHeaderKey), |
121 | 125 | )
|
122 | 126 |
|
123 | 127 | return isValid
|
@@ -201,132 +205,151 @@ export const KeyValueTable = <K extends string>({
|
201 | 205 | }
|
202 | 206 |
|
203 | 207 | return (
|
204 |
| - <div className="dc__border br-4 w-100 bcn-0 key-value"> |
205 |
| - <div |
206 |
| - className={`key-value__row flexbox dc__align-items-center bcn-50 ${(!readOnly && !isAdditionNotAllowed) || updatedRows.length ? 'dc__border-bottom-n1' : ''}`} |
207 |
| - > |
208 |
| - {headers.map(({ key, label, className }) => |
209 |
| - isSortable && key === firstHeaderKey ? ( |
210 |
| - <button |
211 |
| - key={key} |
212 |
| - type="button" |
213 |
| - 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 || ''}`} |
214 |
| - onClick={onSortBtnClick} |
215 |
| - > |
216 |
| - {label} |
217 |
| - <ICArrowDown |
218 |
| - className="icon-dim-16 scn-7 rotate cursor" |
219 |
| - style={{ |
220 |
| - ['--rotateBy' as string]: sortOrder === SortingOrder.ASC ? '0deg' : '180deg', |
221 |
| - }} |
222 |
| - /> |
223 |
| - </button> |
224 |
| - ) : ( |
225 |
| - <div |
226 |
| - key={key} |
227 |
| - 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 || ''}`} |
228 |
| - > |
229 |
| - {label} |
230 |
| - </div> |
231 |
| - ), |
232 |
| - )} |
233 |
| - {!!headerComponent && <div className="px-12">{headerComponent}</div>} |
| 208 | + <> |
| 209 | + <div className={`bcn-2 p-1 ${hasRows ? 'dc__top-radius-4' : 'br-4'}`}> |
| 210 | + <div className="key-value-table two-columns w-100 bcn-1 br-4"> |
| 211 | + <div className="key-value-table__row"> |
| 212 | + {headers.map(({ key, label, className }) => |
| 213 | + isSortable && key === firstHeaderKey ? ( |
| 214 | + <button |
| 215 | + key={key} |
| 216 | + type="button" |
| 217 | + className={`bcn-50 dc__unset-button-styles cn-9 fs-13 lh-20-imp py-8 px-12 fw-6 flexbox dc__align-items-center dc__gap-2 ${updatedRows.length || (!readOnly && !isAdditionNotAllowed) ? 'dc__top-left-radius' : 'dc__left-radius-4'} ${className || ''}`} |
| 218 | + onClick={onSortBtnClick} |
| 219 | + > |
| 220 | + {label} |
| 221 | + <ICArrowDown |
| 222 | + className="icon-dim-16 scn-7 rotate cursor" |
| 223 | + style={{ |
| 224 | + ['--rotateBy' as string]: |
| 225 | + sortOrder === SortingOrder.ASC ? '0deg' : '180deg', |
| 226 | + }} |
| 227 | + /> |
| 228 | + </button> |
| 229 | + ) : ( |
| 230 | + <div |
| 231 | + key={key} |
| 232 | + className={`bcn-50 cn-9 fs-13 lh-20 py-8 px-12 fw-6 flexbox dc__align-items-center dc__content-space dc__gap-2 ${key === firstHeaderKey ? `${hasRows ? 'dc__top-left-radius' : 'dc__left-radius-4'}` : `${hasRows ? 'dc__top-right-radius' : 'dc__right-radius-4'}`} ${className || ''}`} |
| 233 | + > |
| 234 | + {label} |
| 235 | + {!!headerComponent && headerComponent} |
| 236 | + </div> |
| 237 | + ), |
| 238 | + )} |
| 239 | + </div> |
| 240 | + </div> |
234 | 241 | </div>
|
235 |
| - {!readOnly && !isAdditionNotAllowed && ( |
236 |
| - <div |
237 |
| - className={`key-value__row flexbox dc__align-items-center ${updatedRows.length ? 'dc__border-bottom-n1' : ''}`} |
238 |
| - > |
239 |
| - {headers.map(({ key }) => ( |
| 242 | + {hasRows && ( |
| 243 | + <div className="bcn-2 px-1 pb-1 dc__bottom-radius-4"> |
| 244 | + {!readOnly && !isAdditionNotAllowed && ( |
240 | 245 | <div
|
241 |
| - key={key} |
242 |
| - className={`cn-9 fs-13 lh-20 flex dc__overflow-auto ${key === firstHeaderKey ? 'dc__align-self-stretch key-value__header__col-1' : 'flex-grow-1 dc__border-left-n1'}`} |
| 246 | + className={`key-value-table two-columns-top-row bcn-1 ${updatedRows.length ? 'pb-1' : 'dc__bottom-radius-4'}`} |
243 | 247 | >
|
244 |
| - <textarea |
245 |
| - ref={key === firstHeaderKey ? inputRowRef : undefined} |
246 |
| - className="key-value__row-input key-value__row-input--add placeholder-cn5 py-8 px-12 lh-20 fs-13 fw-4 dc__no-border-radius" |
247 |
| - value="" |
248 |
| - rows={1} |
249 |
| - placeholder={placeholder[key]} |
250 |
| - onChange={onNewRowAdd(key)} |
251 |
| - /> |
252 |
| - </div> |
253 |
| - ))} |
254 |
| - </div> |
255 |
| - )} |
256 |
| - {updatedRows.map((row, index) => ( |
257 |
| - <div |
258 |
| - key={row.id} |
259 |
| - className={`key-value__row flexbox dc__align-items-center ${index !== updatedRows.length - 1 ? 'dc__border-bottom-n1' : ''}`} |
260 |
| - > |
261 |
| - {headers.map(({ key }) => ( |
262 |
| - <ConditionalWrap wrap={renderWithReadOnlyTippy} condition={readOnly}> |
263 |
| - <div |
264 |
| - key={key} |
265 |
| - className={`cn-9 fs-13 lh-20 flexbox dc__align-items-center dc__gap-4 dc__position-rel ${key === firstHeaderKey ? 'dc__align-self-stretch key-value__header__col-1' : 'dc__border-left-n1 flex-grow-1'}`} |
266 |
| - > |
267 |
| - {maskValue?.[key] && row.data[key].value ? ( |
268 |
| - <div className="py-8 px-12 h-36 flex">{DEFAULT_SECRET_PLACEHOLDER}</div> |
269 |
| - ) : ( |
270 |
| - <> |
271 |
| - <ResizableTagTextArea |
272 |
| - {...row.data[key]} |
273 |
| - className={`key-value__row-input placeholder-cn5 py-8 px-12 dc__no-border-radius ${readOnly || row.data[key].disabled ? 'cursor-not-allowed no-hover' : ''} ${showError && !validationSchema?.(row.data[key].value) ? 'key-value__row-input--error no-hover' : ''}`} |
274 |
| - minHeight={20} |
275 |
| - maxHeight={160} |
276 |
| - value={row.data[key].value} |
| 248 | + <div className="key-value-table__row"> |
| 249 | + {headers.map(({ key }) => ( |
| 250 | + <div |
| 251 | + key={key} |
| 252 | + className={`key-value-table__cell bcn-0 flex dc__overflow-auto ${(!updatedRows.length && (key === firstHeaderKey ? 'dc__bottom-left-radius' : 'dc__bottom-right-radius')) || ''}`} |
| 253 | + > |
| 254 | + <textarea |
| 255 | + ref={key === firstHeaderKey ? inputRowRef : undefined} |
| 256 | + className="key-value-table__cell-input key-value-table__cell-input--add placeholder-cn5 py-8 px-12 cn-9 lh-20 fs-13 fw-4 dc__no-border-radius" |
| 257 | + value="" |
| 258 | + rows={1} |
277 | 259 | placeholder={placeholder[key]}
|
278 |
| - onChange={onRowDataEdit(row, key)} |
279 |
| - onBlur={onRowDataBlur(row, key)} |
280 |
| - refVar={ |
281 |
| - key === firstHeaderKey |
282 |
| - ? keyTextAreaRef.current?.[row.id] |
283 |
| - : valueTextAreaRef.current?.[row.id] |
284 |
| - } |
285 |
| - dependentRef={ |
286 |
| - key === firstHeaderKey |
287 |
| - ? valueTextAreaRef.current?.[row.id] |
288 |
| - : keyTextAreaRef.current?.[row.id] |
289 |
| - } |
290 |
| - disabled={readOnly || row.data[key].disabled} |
291 |
| - disableOnBlurResizeToMinHeight |
| 260 | + onChange={onNewRowAdd(key)} |
292 | 261 | />
|
293 |
| - {row.data[key].required && ( |
294 |
| - <span className="cr-5 fs-16 dc__align-self-start px-6 py-8">*</span> |
295 |
| - )} |
296 |
| - {showError && |
297 |
| - !validationSchema?.(row.data[key].value) && |
298 |
| - errorMessages.length && ( |
299 |
| - <div className="key-value__error bcn-0 dc__border br-4 py-7 px-8 flexbox-col dc__gap-4"> |
300 |
| - {errorMessages.map((error) => ( |
301 |
| - <div |
302 |
| - key={error} |
303 |
| - className="flexbox align-items-center dc__gap-4" |
304 |
| - > |
305 |
| - <ICClose className="icon-dim-16 fcr-5 dc__align-self-start dc__no-shrink" /> |
306 |
| - <p className="fs-12 lh-16 cn-7 m-0">{error}</p> |
307 |
| - </div> |
308 |
| - ))} |
309 |
| - </div> |
310 |
| - )} |
311 |
| - </> |
312 |
| - )} |
| 262 | + </div> |
| 263 | + ))} |
313 | 264 | </div>
|
314 |
| - </ConditionalWrap> |
315 |
| - ))} |
316 |
| - {!readOnly && ( |
317 |
| - <button |
318 |
| - type="button" |
319 |
| - className="dc__unset-button-styles dc__align-self-stretch dc__no-shrink flex py-10 px-8 dc__border-left-n1--important dc__hover-n50 dc__tab-focus" |
320 |
| - onClick={onRowDelete(row)} |
| 265 | + </div> |
| 266 | + )} |
| 267 | + {!!updatedRows.length && ( |
| 268 | + <div |
| 269 | + className={`key-value-table w-100 bcn-1 dc__bottom-radius-4 ${!readOnly ? 'three-columns' : 'two-columns'}`} |
321 | 270 | >
|
322 |
| - <ICCross |
323 |
| - aria-label="delete-row" |
324 |
| - className="icon-dim-16 fcn-4 dc__align-self-start cursor" |
325 |
| - /> |
326 |
| - </button> |
| 271 | + {updatedRows.map((row) => ( |
| 272 | + <div key={row.id} className="key-value-table__row"> |
| 273 | + {headers.map(({ key }) => ( |
| 274 | + <Fragment key={key}> |
| 275 | + <ConditionalWrap wrap={renderWithReadOnlyTippy} condition={readOnly}> |
| 276 | + <div |
| 277 | + className={`key-value-table__cell bcn-0 flexbox dc__align-items-center dc__gap-4 dc__position-rel ${readOnly || row.data[key].disabled ? 'cursor-not-allowed no-hover' : ''} ${showError && !validationSchema?.(row.data[key].value, key) ? 'key-value-table__cell--error no-hover' : ''}`} |
| 278 | + > |
| 279 | + {maskValue?.[key] && row.data[key].value ? ( |
| 280 | + <div className="py-8 px-12 h-36 flex"> |
| 281 | + {DEFAULT_SECRET_PLACEHOLDER} |
| 282 | + </div> |
| 283 | + ) : ( |
| 284 | + <> |
| 285 | + <ResizableTagTextArea |
| 286 | + {...row.data[key]} |
| 287 | + className={`key-value-table__cell-input placeholder-cn5 py-8 px-12 cn-9 fs-13 lh-20 dc__no-border-radius ${readOnly || row.data[key].disabled ? 'cursor-not-allowed' : ''}`} |
| 288 | + minHeight={20} |
| 289 | + maxHeight={160} |
| 290 | + value={row.data[key].value} |
| 291 | + placeholder={placeholder[key]} |
| 292 | + onChange={onRowDataEdit(row, key)} |
| 293 | + onBlur={onRowDataBlur(row, key)} |
| 294 | + refVar={ |
| 295 | + key === firstHeaderKey |
| 296 | + ? keyTextAreaRef.current?.[row.id] |
| 297 | + : valueTextAreaRef.current?.[row.id] |
| 298 | + } |
| 299 | + dependentRef={ |
| 300 | + key === firstHeaderKey |
| 301 | + ? valueTextAreaRef.current?.[row.id] |
| 302 | + : keyTextAreaRef.current?.[row.id] |
| 303 | + } |
| 304 | + disabled={readOnly || row.data[key].disabled} |
| 305 | + disableOnBlurResizeToMinHeight |
| 306 | + /> |
| 307 | + {row.data[key].required && ( |
| 308 | + <span className="cr-5 fs-16 dc__align-self-start px-6 py-8"> |
| 309 | + * |
| 310 | + </span> |
| 311 | + )} |
| 312 | + {showError && |
| 313 | + !validationSchema?.(row.data[key].value, key) && |
| 314 | + errorMessages.length && ( |
| 315 | + <div className="key-value-table__error bcn-0 dc__border br-4 py-7 px-8 flexbox-col dc__gap-4"> |
| 316 | + {errorMessages.map((error) => ( |
| 317 | + <div |
| 318 | + key={error} |
| 319 | + className="flexbox align-items-center dc__gap-4" |
| 320 | + > |
| 321 | + <ICClose className="icon-dim-16 fcr-5 dc__align-self-start dc__no-shrink" /> |
| 322 | + <p className="fs-12 lh-16 cn-7 m-0"> |
| 323 | + {error} |
| 324 | + </p> |
| 325 | + </div> |
| 326 | + ))} |
| 327 | + </div> |
| 328 | + )} |
| 329 | + </> |
| 330 | + )} |
| 331 | + </div> |
| 332 | + </ConditionalWrap> |
| 333 | + </Fragment> |
| 334 | + ))} |
| 335 | + {!readOnly && ( |
| 336 | + <button |
| 337 | + type="button" |
| 338 | + className="key-value-table__row-delete-btn dc__unset-button-styles dc__align-self-stretch dc__no-shrink flex py-10 px-8 bcn-0 dc__hover-n50 dc__tab-focus" |
| 339 | + onClick={onRowDelete(row)} |
| 340 | + > |
| 341 | + <ICCross |
| 342 | + aria-label="delete-row" |
| 343 | + className="icon-dim-16 fcn-4 dc__align-self-start cursor" |
| 344 | + /> |
| 345 | + </button> |
| 346 | + )} |
| 347 | + </div> |
| 348 | + ))} |
| 349 | + </div> |
327 | 350 | )}
|
328 | 351 | </div>
|
329 |
| - ))} |
330 |
| - </div> |
| 352 | + )} |
| 353 | + </> |
331 | 354 | )
|
332 | 355 | }
|
0 commit comments