1
- import { ChangeEvent , useCallback , useRef , useState , VoidFunctionComponent } from 'react' ;
2
- import styled from 'styled-components' ;
1
+ import { ChangeEvent , Fragment , TransitionEvent , useCallback , useRef , useState , VoidFunctionComponent } from 'react' ;
2
+ import styled , { css } from 'styled-components' ;
3
3
import { useDataAttributes } from '../../hooks/use-data-attributes' ;
4
4
import { Tooltip , TooltipProps } from '../tooltip/tooltip' ;
5
5
import { RadioButton } from '../radio-button/radio-button' ;
@@ -9,6 +9,7 @@ const StyledFieldset = styled.fieldset`
9
9
border : none;
10
10
margin : 0 ;
11
11
padding : 0 ;
12
+ width : 100% ;
12
13
` ;
13
14
14
15
const StyledLegend = styled . legend < { isMobile : boolean } > `
@@ -30,13 +31,40 @@ const StyledTooltip = styled(Tooltip)`
30
31
margin-left: calc(var(--spacing-1x) * 1.5);
31
32
` ;
32
33
33
- const ContentWrapper = styled . div < { $isExpanded : boolean , $maxHeight ?: number , $transitionDuration : number } > ( ( { $isExpanded, $maxHeight = 500 , $transitionDuration } ) => `
34
- overflow: hidden;
34
+ interface ContentWrapperProps {
35
+ $isCollapsing : boolean ;
36
+ $isExpanded : boolean ;
37
+ $maxHeight ?: number ;
38
+ $transitionDuration : number ;
39
+ }
40
+
41
+ function getTransition ( { $isCollapsing, $transitionDuration } : ContentWrapperProps ) : string {
42
+ const maxHeightTransition = `max-height ${ $transitionDuration } ms ease-in-out` ;
43
+ if ( $isCollapsing ) {
44
+ const marginTransition = `margin-bottom ${ $transitionDuration / 2 } ms ${ $transitionDuration / 2 } ms ease-in-out` ;
45
+ return `${ maxHeightTransition } , ${ marginTransition } ` ;
46
+ }
47
+
48
+ return maxHeightTransition ;
49
+ }
50
+
51
+ const ContentWrapper = styled . div < ContentWrapperProps > ( ( {
52
+ $isExpanded,
53
+ $maxHeight = 500 ,
54
+ } ) => css `
35
55
max-height : ${ $isExpanded ? `${ $maxHeight } px` : '0' } ;
36
- transition: max-height ${ $transitionDuration } ms ease-in-out;
56
+ overflow : hidden;
57
+ transition : ${ getTransition } ;
58
+
59
+ : not (: last-child ) {
60
+ margin-bottom : ${ $isExpanded ? 'var(--spacing-1x)' : '0' } ;
61
+ }
37
62
` ) ;
38
63
39
- const InnerContent = styled . div < { $isExpanded : boolean , $transitionStarted : boolean } > ( ( { $isExpanded, $transitionStarted } ) => `
64
+ const InnerContent = styled . div < { $isExpanded : boolean , $transitionStarted : boolean } > ( ( {
65
+ $isExpanded,
66
+ $transitionStarted,
67
+ } ) => `
40
68
display: ${ $isExpanded || $transitionStarted ? 'block' : 'none' } ;
41
69
` ) ;
42
70
@@ -90,6 +118,7 @@ export const RadioButtonGroup: VoidFunctionComponent<RadioButtonGroupProps> = ({
90
118
) ;
91
119
const prevChecked = useRef ( currentChecked ) ;
92
120
const [ transitionStarted , setTransitionStarted ] = useState ( false ) ;
121
+ const [ collapsingElement , setCollapsingElement ] = useState < string > ( ) ;
93
122
const dataAttributes = useDataAttributes ( otherProps ) ;
94
123
const dataTestId = dataAttributes [ 'data-testid' ] ? dataAttributes [ 'data-testid' ] : 'radio-button-group' ;
95
124
@@ -109,23 +138,27 @@ export const RadioButtonGroup: VoidFunctionComponent<RadioButtonGroupProps> = ({
109
138
|| buttons . find ( ( b ) => b . value === currentChecked ) ?. content ;
110
139
111
140
if ( willHaveTransition ) {
141
+ setCollapsingElement ( prevChecked . current ) ;
112
142
setTransitionStarted ( true ) ;
113
143
}
114
144
newRefValue = currentChecked ;
115
145
}
116
146
117
- if ( newRefValue !== null ) {
147
+ if ( newRefValue !== undefined ) {
118
148
prevChecked . current = newRefValue ;
119
149
}
120
150
121
- const handleTransitionEnd = useCallback ( ( ) => {
122
- setTransitionStarted ( false ) ;
151
+ const handleTransitionEnd = useCallback ( ( event : TransitionEvent ) => {
152
+ if ( event . propertyName === 'max-height' ) {
153
+ setCollapsingElement ( undefined ) ;
154
+ setTransitionStarted ( false ) ;
155
+ }
123
156
} , [ ] ) ;
124
157
125
158
return (
126
- < StyledFieldset className = { className } >
159
+ < StyledFieldset key = { label } className = { className } >
127
160
{ label && (
128
- < StyledLegend isMobile = { isMobile } >
161
+ < StyledLegend key = "legend" isMobile = { isMobile } >
129
162
{ label }
130
163
{ /* eslint-disable-next-line react/jsx-props-no-spreading */ }
131
164
{ tooltip && < StyledTooltip { ...tooltip } /> }
@@ -135,7 +168,7 @@ export const RadioButtonGroup: VoidFunctionComponent<RadioButtonGroupProps> = ({
135
168
const isExpanded = currentChecked === button . value ;
136
169
137
170
return (
138
- < >
171
+ < Fragment key = { ` ${ groupName } - ${ button . value } -fragment` } >
139
172
< StyledRadioButton
140
173
key = { `${ groupName } -${ button . value } ` }
141
174
aria-label = { ariaLabel }
@@ -153,9 +186,11 @@ export const RadioButtonGroup: VoidFunctionComponent<RadioButtonGroupProps> = ({
153
186
/>
154
187
{ button . content && (
155
188
< ContentWrapper
189
+ key = { `${ groupName } -${ button . value } -content` }
156
190
data-testid = "content-wrapper"
157
191
$maxHeight = { button . content . maxHeight }
158
192
$isExpanded = { isExpanded }
193
+ $isCollapsing = { transitionStarted && collapsingElement === button . value }
159
194
$transitionDuration = { transitionDuration }
160
195
onTransitionEnd = { handleTransitionEnd }
161
196
>
@@ -167,7 +202,7 @@ export const RadioButtonGroup: VoidFunctionComponent<RadioButtonGroupProps> = ({
167
202
</ InnerContent >
168
203
</ ContentWrapper >
169
204
) }
170
- </ >
205
+ </ Fragment >
171
206
) ;
172
207
} ) }
173
208
</ StyledFieldset >
0 commit comments