Skip to content

Commit 23b3f43

Browse files
authored
fix(Disclosure): Prevent incorrectly showing content when isDisabled is toggled (#9045)
Fixes #9004
1 parent 3ee3fd8 commit 23b3f43

File tree

4 files changed

+104
-16
lines changed

4 files changed

+104
-16
lines changed

packages/@react-aria/disclosure/src/useDisclosure.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export function useDisclosure(props: AriaDisclosureProps, state: DisclosureState
155155
role: 'group',
156156
'aria-labelledby': triggerId,
157157
'aria-hidden': !state.isExpanded,
158-
hidden: (isSSR || isDisabled) ? (isDisabled || !state.isExpanded) : undefined
158+
hidden: !state.isExpanded || undefined
159159
}
160160
};
161161
}

packages/@react-aria/disclosure/test/useDisclosure.test.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,25 @@ describe('useDisclosure', () => {
9090
expect(result.current.state.isExpanded).toBe(false);
9191
});
9292

93+
it('should keep panel hidden when toggling disabled state', () => {
94+
let {result, rerender} = renderHook(({isDisabled}: {isDisabled: boolean}) => {
95+
let state = useDisclosureState({});
96+
return useDisclosure({isDisabled}, state, ref);
97+
}, {initialProps: {isDisabled: false}});
98+
99+
act(() => {
100+
rerender({isDisabled: true});
101+
});
102+
103+
expect(result.current.panelProps.hidden).toBe(true);
104+
105+
act(() => {
106+
rerender({isDisabled: false});
107+
});
108+
109+
expect(result.current.panelProps.hidden).toBe(true);
110+
});
111+
93112
it('should set correct IDs for accessibility', () => {
94113
let {result} = renderHook(() => {
95114
let state = useDisclosureState({});
@@ -111,27 +130,27 @@ describe('useDisclosure', () => {
111130
writable: true,
112131
configurable: true
113132
});
114-
133+
115134
const ref = {current: document.createElement('div')};
116-
135+
117136
const {result} = renderHook(() => {
118137
const state = useDisclosureState({});
119138
const disclosure = useDisclosure({}, state, ref);
120139
return {state, disclosure};
121140
});
122-
141+
123142
expect(result.current.state.isExpanded).toBe(false);
124143
expect(ref.current.getAttribute('hidden')).toBe('until-found');
125-
144+
126145
// Simulate the 'beforematch' event
127146
act(() => {
128147
const event = new Event('beforematch', {bubbles: true});
129148
ref.current.dispatchEvent(event);
130149
});
131-
150+
132151
expect(result.current.state.isExpanded).toBe(true);
133152
expect(ref.current.hasAttribute('hidden')).toBe(false);
134-
153+
135154
Object.defineProperty(document.body, 'onbeforematch', {
136155
value: originalOnBeforeMatch,
137156
writable: true,
@@ -148,31 +167,31 @@ describe('useDisclosure', () => {
148167
writable: true,
149168
configurable: true
150169
});
151-
170+
152171
const ref = {current: document.createElement('div')};
153-
172+
154173
const onExpandedChange = jest.fn();
155-
174+
156175
const {result} = renderHook(() => {
157176
const state = useDisclosureState({isExpanded: false, onExpandedChange});
158177
const disclosure = useDisclosure({isExpanded: false}, state, ref);
159178
return {state, disclosure};
160179
});
161-
180+
162181
expect(result.current.state.isExpanded).toBe(false);
163182
expect(ref.current.getAttribute('hidden')).toBe('until-found');
164-
183+
165184
// Simulate the 'beforematch' event
166185
act(() => {
167186
const event = new Event('beforematch', {bubbles: true});
168187
ref.current.dispatchEvent(event);
169188
});
170-
189+
171190
expect(result.current.state.isExpanded).toBe(false);
172191
expect(ref.current.getAttribute('hidden')).toBe('until-found');
173192
expect(onExpandedChange).toHaveBeenCalledTimes(1);
174193
expect(onExpandedChange).toHaveBeenCalledWith(true);
175-
194+
176195
Object.defineProperty(document.body, 'onbeforematch', {
177196
value: originalOnBeforeMatch,
178197
writable: true,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2024 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {Button, Heading} from 'react-aria-components';
14+
import {
15+
Disclosure,
16+
DisclosureGroup,
17+
DisclosurePanel
18+
} from '../src/Disclosure';
19+
import {Meta, StoryFn} from '@storybook/react';
20+
import React from 'react';
21+
import './styles.css';
22+
23+
export default {
24+
title: 'React Aria Components/DisclosureGroup',
25+
component: DisclosureGroup
26+
} as Meta<typeof DisclosureGroup>;
27+
28+
export type DisclosureGroupStory = StoryFn<typeof DisclosureGroup>;
29+
30+
export const DisclosureGroupExample: DisclosureGroupStory = (args) => {
31+
const [isDisabled, setIsDisabled] = React.useState(false);
32+
const toggleDisabled = () => setIsDisabled((d) => !d);
33+
34+
return (
35+
<>
36+
<Button onPress={toggleDisabled}>Toggle Disabled</Button>
37+
<DisclosureGroup {...args}>
38+
<Disclosure isDisabled={isDisabled}>
39+
{({isExpanded}) => (
40+
<>
41+
<Heading level={3}>
42+
<Button slot="trigger">
43+
{isExpanded ? '⬇️' : '➡️'} This is a disclosure header
44+
</Button>
45+
</Heading>
46+
<DisclosurePanel>
47+
<p>This is the content of the disclosure panel.</p>
48+
</DisclosurePanel>
49+
</>
50+
)}
51+
</Disclosure>
52+
<Disclosure isDisabled={isDisabled}>
53+
{({isExpanded}) => (
54+
<>
55+
<Heading level={3}>
56+
<Button slot="trigger">
57+
{isExpanded ? '⬇️' : '➡️'} This is a disclosure header
58+
</Button>
59+
</Heading>
60+
<DisclosurePanel>
61+
<p>This is the content of the disclosure panel.</p>
62+
</DisclosurePanel>
63+
</>
64+
)}
65+
</Disclosure>
66+
</DisclosureGroup>
67+
</>
68+
);
69+
};

packages/react-aria-components/test/Disclosure.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('Disclosure', () => {
9898
expect(panel).not.toBeVisible();
9999
});
100100

101-
it('should not expand a disabled disclosure via isExpanded', () => {
101+
it('should expand a disabled disclosure via isExpanded', () => {
102102
const {getByTestId, queryByText} = render(
103103
<Disclosure data-testid="disclosure" isDisabled isExpanded>
104104
<Heading level={3}>
@@ -112,7 +112,7 @@ describe('Disclosure', () => {
112112
const disclosure = getByTestId('disclosure');
113113
expect(disclosure).toHaveAttribute('data-disabled', 'true');
114114
const panel = queryByText('Content');
115-
expect(panel).not.toBeVisible();
115+
expect(panel).toBeVisible();
116116
});
117117

118118
it('should support controlled isExpanded prop', async () => {

0 commit comments

Comments
 (0)