11import React from 'react'
2+ import styled from 'styled-components'
23import Box from '../Box'
3- import { useSSRSafeId } from '../utils/ssr'
44import ValidationAnimationContainer from '../_ValidationAnimationContainer'
5+ import { get } from '../constants'
6+ import { useSSRSafeId } from '../utils/ssr'
57import CheckboxOrRadioGroupCaption from './_CheckboxOrRadioGroupCaption'
68import CheckboxOrRadioGroupLabel from './_CheckboxOrRadioGroupLabel'
79import CheckboxOrRadioGroupValidation from './_CheckboxOrRadioGroupValidation'
8- import { Slots } from './slots'
9- import styled from 'styled-components'
10- import { get } from '../constants'
11- import CheckboxOrRadioGroupContext from './_CheckboxOrRadioGroupContext'
1210import VisuallyHidden from '../_VisuallyHidden'
11+ import { useSlots } from '../hooks/useSlots'
1312import { SxProp } from '../sx'
1413
1514export type CheckboxOrRadioGroupProps = {
@@ -37,6 +36,8 @@ export type CheckboxOrRadioGroupContext = {
3736 captionId ?: string
3837} & CheckboxOrRadioGroupProps
3938
39+ export const CheckboxOrRadioGroupContext = React . createContext < CheckboxOrRadioGroupContext > ( { } )
40+
4041const Body = styled . div `
4142 display: flex;
4243 flex-direction: column;
@@ -57,6 +58,11 @@ const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGrou
5758 required = false ,
5859 sx,
5960} ) => {
61+ const [ slots , rest ] = useSlots ( children , {
62+ caption : CheckboxOrRadioGroupCaption ,
63+ label : CheckboxOrRadioGroupLabel ,
64+ validation : CheckboxOrRadioGroupValidation ,
65+ } )
6066 const labelChild = React . Children . toArray ( children ) . find (
6167 child => React . isValidElement ( child ) && child . type === CheckboxOrRadioGroupLabel ,
6268 )
@@ -67,8 +73,8 @@ const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGrou
6773 React . isValidElement ( child ) && child . type === CheckboxOrRadioGroupCaption ? child : null ,
6874 )
6975 const id = useSSRSafeId ( idProp )
70- const validationMessageId = validationChild && `${ id } -validationMessage`
71- const captionId = captionChild && `${ id } -caption`
76+ const validationMessageId = validationChild ? `${ id } -validationMessage` : undefined
77+ const captionId = captionChild ? `${ id } -caption` : undefined
7278
7379 if ( ! labelChild && ! ariaLabelledby ) {
7480 // eslint-disable-next-line no-console
@@ -77,79 +83,73 @@ const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGrou
7783 )
7884 }
7985
86+ const isLegendVisible = React . isValidElement ( labelChild ) && ! labelChild . props . visuallyHidden
87+
8088 return (
81- < Slots
82- context = { {
89+ < CheckboxOrRadioGroupContext . Provider
90+ value = { {
8391 disabled,
8492 required,
8593 captionId,
8694 validationMessageId,
8795 } }
8896 >
89- { slots => {
90- const isLegendVisible = React . isValidElement ( labelChild ) && ! labelChild . props . visuallyHidden
91-
92- return (
93- < CheckboxOrRadioGroupContext . Provider value = { { disabled} } >
94- < div >
95- < Box
96- border = "none"
97- margin = { 0 }
98- mb = { validationChild ? 2 : undefined }
99- padding = { 0 }
100- { ...( labelChild && {
101- as : 'fieldset' ,
102- disabled,
103- } ) }
104- sx = { sx }
105- >
106- { labelChild ? (
107- /*
108- Placing the caption text and validation text in the <legend> provides a better user
109- experience for more screenreaders.
97+ < div >
98+ < Box
99+ border = "none"
100+ margin = { 0 }
101+ mb = { validationChild ? 2 : undefined }
102+ padding = { 0 }
103+ { ...( labelChild && {
104+ as : 'fieldset' ,
105+ disabled,
106+ } ) }
107+ sx = { sx }
108+ >
109+ { labelChild ? (
110+ /*
111+ Placing the caption text and validation text in the <legend> provides a better user
112+ experience for more screenreaders.
110113
111- Reference: https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/
112- */
113- < Box as = "legend" mb = { isLegendVisible ? 2 : undefined } padding = { 0 } >
114- { slots . Label }
115- { slots . Caption }
116- { React . isValidElement ( slots . Validation ) && slots . Validation . props . children && (
117- < VisuallyHidden > { slots . Validation . props . children } </ VisuallyHidden >
118- ) }
119- </ Box >
120- ) : (
121- /*
122- If CheckboxOrRadioGroup.Label wasn't passed as a child, we don't render a <legend>
123- but we still want to render a caption
124- */
125- slots . Caption
126- ) }
127-
128- < Body
129- { ...( ! labelChild && {
130- [ 'aria-labelledby' ] : ariaLabelledby ,
131- [ 'aria-describedby' ] : [ validationMessageId , captionId ] . filter ( Boolean ) . join ( ' ' ) ,
132- as : 'div' ,
133- role : 'group' ,
134- } ) }
135- >
136- { React . Children . toArray ( children ) . filter ( child => React . isValidElement ( child ) ) }
137- </ Body >
138- </ Box >
139- { validationChild && (
140- < ValidationAnimationContainer
141- // If we have CheckboxOrRadioGroup.Label as a child, we render a screenreader-accessible validation message in the <legend>
142- aria-hidden = { Boolean ( labelChild ) }
143- show
144- >
145- { slots . Validation }
146- </ ValidationAnimationContainer >
114+ Reference: https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/
115+ */
116+ < Box as = "legend" mb = { isLegendVisible ? 2 : undefined } padding = { 0 } >
117+ { slots . label }
118+ { slots . caption }
119+ { React . isValidElement ( slots . validation ) && slots . validation . props . children && (
120+ < VisuallyHidden > { slots . validation . props . children } </ VisuallyHidden >
147121 ) }
148- </ div >
149- </ CheckboxOrRadioGroupContext . Provider >
150- )
151- } }
152- </ Slots >
122+ </ Box >
123+ ) : (
124+ /*
125+ If CheckboxOrRadioGroup.Label wasn't passed as a child, we don't render a <legend>
126+ but we still want to render a caption
127+ */
128+ slots . caption
129+ ) }
130+
131+ < Body
132+ { ...( ! labelChild && {
133+ [ 'aria-labelledby' ] : ariaLabelledby ,
134+ [ 'aria-describedby' ] : [ validationMessageId , captionId ] . filter ( Boolean ) . join ( ' ' ) ,
135+ as : 'div' ,
136+ role : 'group' ,
137+ } ) }
138+ >
139+ { React . Children . toArray ( rest ) . filter ( child => React . isValidElement ( child ) ) }
140+ </ Body >
141+ </ Box >
142+ { validationChild && (
143+ < ValidationAnimationContainer
144+ // If we have CheckboxOrRadioGroup.Label as a child, we render a screenreader-accessible validation message in the <legend>
145+ aria-hidden = { Boolean ( labelChild ) }
146+ show
147+ >
148+ { slots . validation }
149+ </ ValidationAnimationContainer >
150+ ) }
151+ </ div >
152+ </ CheckboxOrRadioGroupContext . Provider >
153153 )
154154}
155155
0 commit comments