Skip to content

Commit 7e07425

Browse files
committed
feat: KeyValueTable - revamp hover, focus, error states; validationSchema prop - refactor
1 parent 19573f8 commit 7e07425

File tree

3 files changed

+203
-149
lines changed

3 files changed

+203
-149
lines changed

src/Shared/Components/KeyValueTable/KeyValueTable.component.tsx

Lines changed: 143 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { createRef, useEffect, useRef, useState, ReactElement } from 'react'
18+
import { createRef, useEffect, useRef, useState, ReactElement, Fragment } from 'react'
1919
import Tippy from '@tippyjs/react'
2020
// eslint-disable-next-line import/no-extraneous-dependencies
2121
import { followCursor } from 'tippy.js'
@@ -67,6 +67,9 @@ export const KeyValueTable = <K extends string>({
6767
const [updatedRows, setUpdatedRows] = useState<KeyValueRow<K>[]>(rows)
6868
const [newRowAdded, setNewRowAdded] = useState(false)
6969

70+
/** Boolean determining if table has rows. */
71+
const hasRows = (!readOnly && !isAdditionNotAllowed) || !!updatedRows.length
72+
7073
// HOOKS
7174
const { sortBy, sortOrder, handleSorting } = useStateFilters({
7275
initialSortKey: firstHeaderKey,
@@ -117,7 +120,8 @@ export const KeyValueTable = <K extends string>({
117120
const checkAllRowsAreValid = (_rows: KeyValueRow<K>[]) => {
118121
const isValid = _rows.every(
119122
({ data: _data }) =>
120-
validationSchema?.(_data[firstHeaderKey].value) && validationSchema?.(_data[secondHeaderKey].value),
123+
validationSchema?.(_data[firstHeaderKey].value, firstHeaderKey) &&
124+
validationSchema?.(_data[secondHeaderKey].value, secondHeaderKey),
121125
)
122126

123127
return isValid
@@ -201,132 +205,151 @@ export const KeyValueTable = <K extends string>({
201205
}
202206

203207
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>
234241
</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 && (
240245
<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'}`}
243247
>
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}
277259
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)}
292261
/>
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+
))}
313264
</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'}`}
321270
>
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>
327350
)}
328351
</div>
329-
))}
330-
</div>
352+
)}
353+
</>
331354
)
332355
}

0 commit comments

Comments
 (0)