@@ -24,21 +24,72 @@ interface ShareInputProps {
2424const GRID_COLS = 'md:grid-cols-[16rem_1fr_6rem_6rem_auto]'
2525const GRID_GAP = 'md:gap-x-md'
2626
27+ export const ShareInputTable = ( {
28+ children
29+ } : {
30+ children : React . ReactNode
31+ } ) => {
32+ return (
33+ < div
34+ role = "table"
35+ aria-labelledby = "revshare-table-caption"
36+ className = "contents"
37+ >
38+ < div id = "revshare-table-caption" className = "sr-only" >
39+ Revenue sharing recipients configuration table
40+ </ div >
41+ { children }
42+ </ div >
43+ )
44+ }
45+
2746export const ShareInputHeader = ( ) => {
2847 return (
2948 < div
49+ role = "row"
50+ aria-rowindex = { 1 }
3051 className = { cx (
3152 'hidden p-md leading-sm text-silver-600 rounded-sm bg-silver-50' ,
3253 'md:grid' ,
3354 GRID_COLS ,
3455 GRID_GAP
3556 ) }
3657 >
37- < div > Name</ div >
38- < div > Wallet Address/Payment Pointer</ div >
39- < div > Weight</ div >
40- < div > Percentage</ div >
41- < div > Action</ div >
58+ < div
59+ role = "columnheader"
60+ id = "col-recipient-name"
61+ aria-label = "Recipient name, optional field"
62+ >
63+ Name
64+ </ div >
65+ < div
66+ role = "columnheader"
67+ id = "col-payment-pointer"
68+ aria-label = "Wallet address or payment pointer for recipient, required field"
69+ >
70+ Wallet Address/Payment Pointer
71+ </ div >
72+ < div
73+ role = "columnheader"
74+ id = "col-weight"
75+ aria-label = "Weight value for revenue distribution, required field"
76+ >
77+ Weight
78+ </ div >
79+ < div
80+ role = "columnheader"
81+ id = "col-percentage"
82+ aria-label = "Calculated percentage of total revenue based on weight"
83+ >
84+ Percentage
85+ </ div >
86+ < div
87+ role = "columnheader"
88+ id = "col-action"
89+ aria-label = "Action to remove recipient from table"
90+ >
91+ Action
92+ </ div >
4293 </ div >
4394 )
4495}
@@ -61,8 +112,16 @@ export const ShareInput = React.memo(
61112 weightDisabled = false
62113 } : ShareInputProps ) => {
63114 const hasError = ! validatePointer ( pointer )
115+ const nameInputId = `name-input-${ index } `
116+ const pointerInputId = `pointer-input-${ index } `
117+ const weightInputId = `weight-input-${ index } `
118+ const percentInputId = `percent-input-${ index } `
119+
64120 return (
65121 < div
122+ role = "row"
123+ aria-rowindex = { index + 2 }
124+ aria-invalid = { hasError }
66125 className = { cx (
67126 'bg-white flex flex-col gap-md p-md rounded-lg border border-silver-200' ,
68127 'md:rounded-none md:border-none md:grid md:px-md md:py-0 md:items-center' ,
@@ -76,34 +135,74 @@ export const ShareInput = React.memo(
76135 < ToolsSecondaryButton
77136 onClick = { onRemove }
78137 className = "border-none py-sm px-xs shrink-0"
79- aria-label = { `Recipient ${ index + 1 } ` }
138+ aria-label = { `Remove recipient ${ index + 1 } ` }
80139 >
81- < SVGDeleteScript className = "w-5 h-5" />
140+ < SVGDeleteScript className = "w-5 h-5" aria-hidden = "true" />
82141 </ ToolsSecondaryButton >
83142 </ div >
84- < div >
143+ < div role = "cell" aria-labelledby = "col-recipient-name" >
144+ < label htmlFor = { nameInputId } className = "sr-only" >
145+ Name (optional)
146+ </ label >
85147 < InputField
148+ id = { nameInputId }
86149 placeholder = "Fill in name (optional)"
87150 value = { name }
88151 onChange = { ( e : React . ChangeEvent < HTMLInputElement > ) =>
89152 onChangeName ( e . target . value )
90153 }
154+ aria-describedby = { `name-description-${ index } ` }
91155 />
156+ < div id = { `name-description-${ index } ` } className = "sr-only" >
157+ Optional display name for recipient { index + 1 }
158+ </ div >
92159 </ div >
93- < div className = "relative" >
160+ < div
161+ role = "cell"
162+ className = "relative"
163+ aria-labelledby = "col-payment-pointer"
164+ >
165+ < label htmlFor = { pointerInputId } className = "sr-only" >
166+ Wallet Address or Payment Pointer *
167+ </ label >
94168 < InputField
169+ id = { pointerInputId }
95170 placeholder = { placeholder }
96171 value = { pointer }
97172 onChange = { ( e : React . ChangeEvent < HTMLInputElement > ) =>
98173 onChangePointer ( e . target . value )
99174 }
175+ aria-invalid = { hasError }
176+ aria-describedby = { cx (
177+ `pointer-description-${ index } ` ,
178+ hasError ? `pointer-error-${ index } ` : ''
179+ ) }
180+ aria-required = "true"
100181 />
101- < p className = "absolute left-0 text-xs mt-2xs text-text-error" >
102- { hasError && 'Invalid payment pointer' }
103- </ p >
182+ < div id = { `pointer-description-${ index } ` } className = "sr-only" >
183+ Wallet address or payment pointer for recipient { index + 1 }
184+ </ div >
185+ { hasError && (
186+ < div
187+ id = { `pointer-error-${ index } ` }
188+ className = "absolute left-0 text-xs mt-2xs text-text-error"
189+ role = "alert"
190+ aria-live = "polite"
191+ >
192+ Invalid payment pointer
193+ </ div >
194+ ) }
104195 </ div >
105- < div className = { hasError ? 'mt-xs' : 'md:mt-0' } >
196+ < div
197+ role = "cell"
198+ className = { hasError ? 'mt-xs' : 'md:mt-0' }
199+ aria-labelledby = "col-weight"
200+ >
201+ < label htmlFor = { weightInputId } className = "sr-only" >
202+ Weight *
203+ </ label >
106204 < InputField
205+ id = { weightInputId }
107206 type = "number"
108207 value = { weight }
109208 min = { 0 }
@@ -112,10 +211,22 @@ export const ShareInput = React.memo(
112211 onChangeWeight ( Number ( e . target . value ) )
113212 }
114213 disabled = { weightDisabled }
214+ aria-disabled = { weightDisabled }
215+ aria-describedby = { `weight-description-${ index } ` }
216+ aria-required = "true"
115217 />
218+ < div id = { `weight-description-${ index } ` } className = "sr-only" >
219+ Weight value for revenue distribution calculation for
220+ recipient
221+ { index + 1 }
222+ </ div >
116223 </ div >
117- < div >
224+ < div role = "cell" aria-labelledby = "col-percent" >
225+ < label htmlFor = { percentInputId } className = "sr-only" >
226+ Percentage
227+ </ label >
118228 < InputField
229+ id = { percentInputId }
119230 type = "number"
120231 min = { 0 }
121232 max = { 100 }
@@ -125,15 +236,24 @@ export const ShareInput = React.memo(
125236 onChangePercent ( Number ( e . target . value ) / 100 )
126237 }
127238 disabled = { percentDisabled }
239+ aria-disabled = { percentDisabled }
240+ aria-describedby = { `percent-description-${ index } ` }
128241 />
242+ < div id = { `percent-description-${ index } ` } className = "sr-only" >
243+ Calculated percentage of total revenue for recipient { index + 1 }
244+ </ div >
129245 </ div >
130- < div className = "hidden md:block" >
246+ < div
247+ role = "cell"
248+ className = "hidden md:block"
249+ aria-labelledby = "col-action"
250+ >
131251 < ToolsSecondaryButton
132252 onClick = { onRemove }
133253 className = "border-none py-sm px-xs shrink-0"
134- aria-label = { `Remove recipient ${ index + 1 } ` }
254+ aria-label = { `Remove recipient ${ index + 1 } from revenue sharing table ` }
135255 >
136- < SVGDeleteScript className = "w-5 h-5" />
256+ < SVGDeleteScript className = "w-5 h-5" aria-hidden = "true" />
137257 </ ToolsSecondaryButton >
138258 </ div >
139259 </ div >
0 commit comments