1
1
import { ExclamationCircleIcon , XMarkIcon } from '@heroicons/react/20/solid'
2
2
import { cn } from '@/lib/utils'
3
- import React , { useEffect , useId } from 'react'
3
+ import React , { useId } from 'react'
4
4
import { Input } from '../ui/input'
5
5
import { Label } from '../ui/label'
6
+ import { Button } from '../ui/button'
6
7
7
8
export interface FieldRow {
8
9
key : string
@@ -14,10 +15,10 @@ interface Props {
14
15
rows : FieldRow [ ]
15
16
lockKeys ?: boolean
16
17
readOnly ?: boolean
17
- canClearRow ?: boolean
18
18
valueRequired ?: boolean
19
19
valueErrors ?: Record < number , FieldRow >
20
20
setRows : ( value : FieldRow [ ] ) => void
21
+ addRowLabel ?: string
21
22
}
22
23
23
24
const FieldRows : React . FC < Props > = ( {
@@ -28,127 +29,133 @@ const FieldRows: React.FC<Props> = ({
28
29
setRows,
29
30
valueErrors,
30
31
valueRequired,
31
- canClearRow = true ,
32
+ addRowLabel = 'Add Row' ,
32
33
} ) => {
33
34
const id = useId ( )
34
35
35
- useEffect ( ( ) => {
36
- if (
37
- ! lockKeys &&
38
- ( rows [ rows . length - 1 ] . key || rows [ rows . length - 1 ] . value )
39
- ) {
40
- setRows ( [
41
- ...rows ,
42
- {
43
- key : '' ,
44
- value : '' ,
45
- } ,
46
- ] )
47
- }
48
- } , [ rows ] )
49
-
50
36
return (
51
- < ul className = "divide-y divide-gray-200" >
52
- { rows . map ( ( r , i ) => {
53
- const keyId = `${ id } -${ i } -key`
54
- const valueId = `${ id } -${ i } -value`
55
- const valueHasError = Boolean ( valueErrors && valueErrors [ i ] )
37
+ < div >
38
+ < ul className = "divide-y divide-gray-200" >
39
+ { rows . map ( ( r , i ) => {
40
+ const keyId = `${ id } -${ i } -key`
41
+ const valueId = `${ id } -${ i } -value`
42
+ const valueHasError = Boolean ( valueErrors && valueErrors [ i ] )
56
43
57
- return (
58
- < li
59
- key = { i }
60
- className = "group relative grid grid-cols-2 items-center gap-4 py-4"
61
- >
62
- < div >
63
- < Label htmlFor = { keyId } className = "sr-only" >
64
- Key
65
- </ Label >
66
- < div className = "mt-2 sm:col-span-2 sm:mt-0" >
67
- < Input
68
- type = "text"
69
- data-testid = { `${ testId } -${ i } -key` }
70
- readOnly = { lockKeys || readOnly }
71
- placeholder = "Key"
72
- className = "read-only:opacity-100"
73
- onChange = { ( e ) => {
74
- const updatedRow : FieldRow = { ...r , key : e . target . value }
75
- const newArr = [ ...rows ]
44
+ return (
45
+ < li key = { i } className = "group flex items-center gap-4 py-4" >
46
+ < div className = "w-full" >
47
+ < Label htmlFor = { keyId } className = "sr-only" >
48
+ Key
49
+ </ Label >
50
+ < div className = "mt-2 sm:col-span-2 sm:mt-0" >
51
+ < Input
52
+ type = "text"
53
+ data-testid = { `${ testId } -${ i } -key` }
54
+ readOnly = { lockKeys || readOnly }
55
+ placeholder = "Key"
56
+ className = "read-only:opacity-100"
57
+ onChange = { ( e ) => {
58
+ const updatedRow : FieldRow = { ...r , key : e . target . value }
59
+ const newArr = [ ...rows ]
76
60
77
- newArr [ i ] = updatedRow
61
+ newArr [ i ] = updatedRow
78
62
79
- setRows ( newArr )
80
- } }
81
- value = { r . key }
82
- name = { keyId }
83
- id = { keyId }
84
- />
63
+ setRows ( newArr )
64
+ } }
65
+ value = { r . key }
66
+ name = { keyId }
67
+ id = { keyId }
68
+ />
69
+ </ div >
85
70
</ div >
86
- </ div >
87
- < div className = "pr-8" >
88
- < Label htmlFor = { valueId } className = "sr-only" >
89
- { r . value }
90
- </ Label >
91
- < div className = "relative mt-2 sm:col-span-2 sm:mt-0" >
92
- < Input
93
- type = "text"
94
- placeholder = "Value"
95
- readOnly = { readOnly }
96
- data-testid = { `${ testId } -${ i } -value` }
97
- onChange = { ( e ) => {
98
- const updatedRow : FieldRow = {
99
- ...r ,
100
- value : e . target . value ,
101
- }
102
- const newArr = [ ...rows ]
71
+ < div className = { cn ( 'w-full' , lockKeys && 'mr-11' ) } >
72
+ < Label htmlFor = { valueId } className = "sr-only" >
73
+ { r . value }
74
+ </ Label >
75
+ < div className = "relative mt-2 sm:col-span-2 sm:mt-0" >
76
+ < Input
77
+ type = "text"
78
+ placeholder = "Value"
79
+ readOnly = { readOnly }
80
+ data-testid = { `${ testId } -${ i } -value` }
81
+ onChange = { ( e ) => {
82
+ const updatedRow : FieldRow = {
83
+ ...r ,
84
+ value : e . target . value ,
85
+ }
86
+ const newArr = [ ...rows ]
103
87
104
- newArr [ i ] = updatedRow
88
+ newArr [ i ] = updatedRow
105
89
106
- setRows ( newArr )
90
+ setRows ( newArr )
91
+ } }
92
+ required = { valueRequired }
93
+ name = { valueId }
94
+ id = { valueId }
95
+ value = { r . value }
96
+ className = { cn (
97
+ valueHasError &&
98
+ 'text-red-900 !ring-red-500 placeholder:text-red-300' ,
99
+ ) }
100
+ />
101
+ { valueHasError && (
102
+ < div
103
+ data-testid = { `${ testId } -${ i } -value-error-icon` }
104
+ className = "pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"
105
+ >
106
+ < ExclamationCircleIcon
107
+ className = "h-5 w-5 text-red-500"
108
+ aria-hidden = "true"
109
+ />
110
+ </ div >
111
+ ) }
112
+ </ div >
113
+ </ div >
114
+ { ! lockKeys && (
115
+ < button
116
+ type = "button"
117
+ onClick = { ( ) => {
118
+ const newArray = [ ...rows ]
119
+ newArray . splice ( i , 1 )
120
+ setRows ( newArray )
107
121
} }
108
- required = { valueRequired }
109
- name = { valueId }
110
- id = { valueId }
111
- value = { r . value }
122
+ aria-label = "Remove row"
112
123
className = { cn (
113
- valueHasError &&
114
- 'text-red-900 !ring-red-500 placeholder:text-red-300 ',
124
+ 'rounded-full bg-gray-600 p-1 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600' ,
125
+ 'flex items-center opacity-30 transition-all group-hover:opacity-100 ',
115
126
) }
116
- />
117
- { valueHasError && (
118
- < div
119
- data-testid = { `${ testId } -${ i } -value-error-icon` }
120
- className = "pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"
121
- >
122
- < ExclamationCircleIcon
123
- className = "h-5 w-5 text-red-500"
124
- aria-hidden = "true"
125
- />
126
- </ div >
127
- ) }
128
- </ div >
129
- </ div >
130
- { canClearRow && (
131
- < button
132
- type = "button"
133
- onClick = { ( ) => {
134
- const newArray = [ ...rows ]
135
- newArray . splice ( i , 1 )
136
- setRows ( newArray )
137
- } }
138
- className = { cn (
139
- 'absolute right-0 hidden rounded-full bg-gray-600 p-1 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600' ,
140
- rows . length > 1 && ( r . key || r . value )
141
- ? 'group-hover:block'
142
- : '' ,
143
- ) }
144
- >
145
- < XMarkIcon className = "h-5 w-5" aria-hidden = "true" />
146
- </ button >
147
- ) }
127
+ >
128
+ < XMarkIcon className = "h-5 w-5" aria-hidden = "true" />
129
+ </ button >
130
+ ) }
131
+ </ li >
132
+ )
133
+ } ) }
134
+ { rows . length === 0 && (
135
+ < li className = "mt-1 text-sm text-foreground" >
136
+ No rows to display. Click '{ addRowLabel } ' to begin adding
137
+ data.
148
138
</ li >
149
- )
150
- } ) }
151
- </ ul >
139
+ ) }
140
+ </ ul >
141
+ { ! lockKeys && (
142
+ < Button
143
+ onClick = { ( ) => {
144
+ setRows ( [
145
+ ...rows ,
146
+ {
147
+ key : '' ,
148
+ value : '' ,
149
+ } ,
150
+ ] )
151
+ } }
152
+ data-testid = "add-row-btn"
153
+ className = "ml-auto mt-4 flex"
154
+ >
155
+ { addRowLabel }
156
+ </ Button >
157
+ ) }
158
+ </ div >
152
159
)
153
160
}
154
161
0 commit comments