Skip to content

Commit

Permalink
Make hiddenUntilFound override keepMounted
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Nov 14, 2024
1 parent 169ba04 commit 02616e5
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 36 deletions.
6 changes: 2 additions & 4 deletions docs/data/components/accordion/accordion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,10 @@ Alternatively `keepMounted` can be passed to `Accordion.Panel`s directly to enab
Content hidden by `Accordion.Panel` components can be made accessible only to a browser's find-in-page functionality with the `hiddenUntilFound` prop to improve searchability:

```jsx
<Accordion.Root hiddenUntilFound keepMounted>
{/* accordion items */}
</Accordion.Root>
<Accordion.Root hiddenUntilFound>{/* accordion items */}</Accordion.Root>
```

Be aware that this must be used together with `keepMounted`.
When `hiddenUntilFound` is used, `Accordion.Panel`s will remain mounted even when closed, overriding the `keepMounted` prop on the root or the panel.

Alternatively `hiddenUntilFound` can be passed to `Accordion.Panel`s directly to enable this for only one `Item` instead of the whole accordion.

Expand Down
4 changes: 2 additions & 2 deletions docs/data/components/collapsible/collapsible.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ Content hidden in the `Collapsible.Panel` component can be made accessible only
```jsx
<Collapsible.Root>
<Collapsible.Trigger>Toggle</Collapsible.Trigger>
<Collapsible.Panel hiddenUntilFound keepMounted>
<Collapsible.Panel hiddenUntilFound>
When this component is closed, this sentence will only be accessible to the browser's native
find-in-page functionality
</Collapsible.Panel>
</Collapsible.Root>
```
Be aware that this must be used together with `keepMounted`.
When `hiddenUntilFound` is used, the `Panel` remains mounted even when closed, overriding the `keepMounted` prop.
We recommend using [CSS animations](#css-animations) for animated collapsibles that use this feature. Currently there is browser bug that does not highlight the found text inside elements that have a [CSS transition](#css-transitions) applied.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
"hiddenUntilFound": {
"description": "If <code>true</code>, sets <code>hidden=&quot;until-found&quot;</code> when closed. Requires setting <code>keepMounted</code> to <code>true</code>. If <code>false</code>, sets <code>hidden</code> when closed."
"description": "If <code>true</code>, sets <code>hidden=&quot;until-found&quot;</code> when closed. Accordion panels will remain mounted in the DOM when closed and overrides <code>keepMounted</code>. If <code>false</code>, sets <code>hidden</code> when closed."
},
"keepMounted": {
"description": "If <code>true</code>, accordion panels remains mounted when closed and is instead hidden using the <code>hidden</code> attribute. If <code>false</code>, accordion panels are unmounted when closed."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"disabled": { "description": "If <code>true</code>, the component is disabled." },
"hiddenUntilFound": {
"description": "If <code>true</code>, sets <code>hidden=&quot;until-found&quot;</code> when closed. Requires setting <code>keepMounted</code> to <code>true</code>. If <code>false</code>, sets <code>hidden</code> when closed."
"description": "If <code>true</code>, sets <code>hidden=&quot;until-found&quot;</code> when closed. Accordion panels will remain mounted in the DOM when closed and overrides <code>keepMounted</code>. If <code>false</code>, sets <code>hidden</code> when closed."
},
"keepMounted": {
"description": "If <code>true</code>, accordion panels remains mounted when closed and is instead hidden using the <code>hidden</code> attribute. If <code>false</code>, accordion panels are unmounted when closed."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
"hiddenUntilFound": {
"description": "If <code>true</code>, sets <code>hidden=&quot;until-found&quot;</code> when closed. Requires setting <code>keepMounted</code> to <code>true</code>. If <code>false</code>, sets <code>hidden</code> when closed."
"description": "If <code>true</code>, sets the hidden state using <code>hidden=&quot;until-found&quot;</code>. The panel remains mounted in the DOM when closed and overrides <code>keepMounted</code>. If <code>false</code>, sets the hidden state using <code>hidden</code>."
},
"keepMounted": {
"description": "If <code>true</code>, the panel remains mounted when closed and is instead hidden using the <code>hidden</code> attribute If <code>false</code>, the panel is unmounted when closed."
"description": "If <code>true</code>, the panel remains mounted when closed and is instead hidden using the <code>hidden</code> attribute If <code>false</code>, the panel is unmounted when closed. If the <code>hiddenUntilFound</code> prop is used, the panel overrides this prop and is remains mounted when closed."
},
"render": { "description": "A function to customize rendering of the component." }
},
Expand Down
2 changes: 0 additions & 2 deletions docs/src/app/experiments/collapsible-hidden-until-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export default function CollapsibleHiddenUntilFound() {
</Collapsible.Trigger>
<Collapsible.Panel
hiddenUntilFound
keepMounted
className={classNames(classes.panel, classes.animation)}
>
<p>This is the collapsed content</p>
Expand All @@ -66,7 +65,6 @@ export default function CollapsibleHiddenUntilFound() {
</Collapsible.Trigger>
<Collapsible.Panel
hiddenUntilFound
keepMounted
className={classNames(classes.panel, classes.transition)}
>
<p>This is the collapsed content</p>
Expand Down
17 changes: 14 additions & 3 deletions packages/mui-base/src/Accordion/Panel/AccordionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ const AccordionPanel = React.forwardRef(function AccordionPanel(

const { hiddenUntilFound, keepMounted } = useAccordionRootContext();

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (keepMountedProp === false && (hiddenUntilFoundProp ?? hiddenUntilFound)) {
console.warn(
'Base UI: The `keepMounted={false}` prop on a Accordion.Panel will be ignored when using `hiddenUntilFound` on the Panel or the Root since it requires the panel to remain mounted when closed.',
);
}
}, [hiddenUntilFoundProp, hiddenUntilFound, keepMountedProp]);
}

const { getRootProps, height, width, isOpen } = useCollapsiblePanel({
animated,
hiddenUntilFound: hiddenUntilFoundProp || hiddenUntilFound,
Expand Down Expand Up @@ -72,7 +83,7 @@ const AccordionPanel = React.forwardRef(function AccordionPanel(
customStyleHookMapping: accordionStyleHookMapping,
});

if (!(keepMountedProp || keepMounted) && !isOpen) {
if (!(keepMountedProp ?? keepMounted) && !isOpen) {
return null;
}

Expand Down Expand Up @@ -101,8 +112,8 @@ AccordionPanel.propTypes /* remove-proptypes */ = {
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* If `true`, sets `hidden="until-found"` when closed.
* Requires setting `keepMounted` to `true`.
* If `true`, sets `hidden="until-found"` when closed. Accordion panels
* will remain mounted in the DOM when closed and overrides `keepMounted`.
* If `false`, sets `hidden` when closed.
* @default false
*/
Expand Down
29 changes: 20 additions & 9 deletions packages/mui-base/src/Accordion/Root/AccordionRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const AccordionRoot = React.forwardRef(function AccordionRoot(
className,
direction,
disabled = false,
hiddenUntilFound = false,
keepMounted = false,
hiddenUntilFound: hiddenUntilFoundProp,
keepMounted: keepMountedProp,
loop,
onValueChange,
openMultiple = true,
Expand All @@ -42,6 +42,17 @@ const AccordionRoot = React.forwardRef(function AccordionRoot(
...otherProps
} = props;

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (hiddenUntilFoundProp && keepMountedProp === false) {
console.warn(
'Base UI: The `keepMounted={false}` prop on a Accordion.Root will be ignored when using `hiddenUntilFound` since it requires Panels to remain mounted when closed.',
);
}
}, [hiddenUntilFoundProp, keepMountedProp]);
}

// memoized to allow omitting both defaultValue and value
// which would otherwise trigger a warning in useControlled
const defaultValue = React.useMemo(() => {
Expand Down Expand Up @@ -76,11 +87,11 @@ const AccordionRoot = React.forwardRef(function AccordionRoot(
const contextValue: AccordionRootContext = React.useMemo(
() => ({
...accordion,
hiddenUntilFound,
keepMounted,
hiddenUntilFound: hiddenUntilFoundProp ?? false,
keepMounted: keepMountedProp ?? false,
ownerState,
}),
[accordion, hiddenUntilFound, keepMounted, ownerState],
[accordion, hiddenUntilFoundProp, keepMountedProp, ownerState],
);

const { renderElement } = useComponentRenderer({
Expand Down Expand Up @@ -111,8 +122,8 @@ export namespace AccordionRoot {
extends useAccordionRoot.Parameters,
Omit<BaseUIComponentProps<'div', OwnerState>, 'defaultValue'> {
/**
* If `true`, sets `hidden="until-found"` when closed.
* Requires setting `keepMounted` to `true`.
* If `true`, sets `hidden="until-found"` when closed. Accordion panels
* will remain mounted in the DOM when closed and overrides `keepMounted`.
* If `false`, sets `hidden` when closed.
* @default false
*/
Expand Down Expand Up @@ -165,8 +176,8 @@ AccordionRoot.propTypes /* remove-proptypes */ = {
*/
disabled: PropTypes.bool,
/**
* If `true`, sets `hidden="until-found"` when closed.
* Requires setting `keepMounted` to `true`.
* If `true`, sets `hidden="until-found"` when closed. Accordion panels
* will remain mounted in the DOM when closed and overrides `keepMounted`.
* If `false`, sets `hidden` when closed.
* @default false
*/
Expand Down
22 changes: 19 additions & 3 deletions packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ describe('<Collapsible.Panel />', () => {
const { queryByText } = await render(
<Collapsible.Root defaultOpen={false} animated={false} onOpenChange={handleOpenChange}>
<Collapsible.Trigger />
<Collapsible.Panel hiddenUntilFound keepMounted>
This is panel content
</Collapsible.Panel>
<Collapsible.Panel hiddenUntilFound>This is panel content</Collapsible.Panel>
</Collapsible.Root>,
);

Expand All @@ -114,5 +112,23 @@ describe('<Collapsible.Panel />', () => {
expect(handleOpenChange.callCount).to.equal(1);
expect(panel).to.have.attribute('data-open');
});

// toWarnDev doesn't work reliably with async rendering. To re-enable after it's fixed in the test-utils.
// eslint-disable-next-line mocha/no-skipped-tests
it.skip('warns when setting both `hiddenUntilFound` and `keepMounted={false}`', async function test() {
await expect(() =>
render(
<Collapsible.Root animated={false}>
<Collapsible.Trigger />
<Collapsible.Panel hiddenUntilFound keepMounted={false}>
This is panel content
</Collapsible.Panel>
</Collapsible.Root>,
),
).toWarnDev([
'Base UI: The `keepMounted={false}` prop on a Collapsible will be ignored when using `hiddenUntilFound` since it requires the Panel to remain mounted even when closed.',
'Base UI: The `keepMounted={false}` prop on a Collapsible will be ignored when using `hiddenUntilFound` since it requires the Panel to remain mounted even when closed.',
]);
});
});
});
33 changes: 28 additions & 5 deletions packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,26 @@ const CollapsiblePanel = React.forwardRef(function CollapsiblePanel(
props: CollapsiblePanel.Props,
forwardedRef: React.ForwardedRef<HTMLButtonElement>,
) {
const { className, hiddenUntilFound, keepMounted = false, render, ...otherProps } = props;
const {
className,
hiddenUntilFound,
keepMounted: keepMountedProp,
render,
...otherProps
} = props;

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (hiddenUntilFound && keepMountedProp === false) {
console.warn(
'Base UI: The `keepMounted={false}` prop on a Collapsible will be ignored when using `hiddenUntilFound` since it requires the Panel to remain mounted even when closed.',
);
}
}, [hiddenUntilFound, keepMountedProp]);
}

const keepMounted = keepMountedProp ?? false;

const { animated, mounted, open, panelId, setPanelId, setMounted, setOpen, ownerState } =
useCollapsibleRootContext();
Expand Down Expand Up @@ -55,7 +74,7 @@ const CollapsiblePanel = React.forwardRef(function CollapsiblePanel(
customStyleHookMapping: collapsibleStyleHookMapping,
});

if (!keepMounted && !isOpen) {
if (!keepMounted && !isOpen && !hiddenUntilFound) {
return null;
}

Expand All @@ -72,6 +91,8 @@ namespace CollapsiblePanel {
* If `true`, the panel remains mounted when closed and is instead
* hidden using the `hidden` attribute
* If `false`, the panel is unmounted when closed.
* If the `hiddenUntilFound` prop is used, the panel overrides this prop and
* is remains mounted when closed.
* @default false
*/
keepMounted?: boolean;
Expand All @@ -92,16 +113,18 @@ CollapsiblePanel.propTypes /* remove-proptypes */ = {
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* If `true`, sets `hidden="until-found"` when closed.
* Requires setting `keepMounted` to `true`.
* If `false`, sets `hidden` when closed.
* If `true`, sets the hidden state using `hidden="until-found"`. The panel
* remains mounted in the DOM when closed and overrides `keepMounted`.
* If `false`, sets the hidden state using `hidden`.
* @default false
*/
hiddenUntilFound: PropTypes.bool,
/**
* If `true`, the panel remains mounted when closed and is instead
* hidden using the `hidden` attribute
* If `false`, the panel is unmounted when closed.
* If the `hiddenUntilFound` prop is used, the panel overrides this prop and
* is remains mounted when closed.
* @default false
*/
keepMounted: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ export namespace useCollapsiblePanel {
*/
animated?: boolean;
/**
* If `true`, sets `hidden="until-found"` when closed.
* Requires setting `keepMounted` to `true`.
* If `false`, sets `hidden` when closed.
* If `true`, sets the hidden state using `hidden="until-found"`. The panel
* remains mounted in the DOM when closed and overrides `keepMounted`.
* If `false`, sets the hidden state using `hidden`.
* @default false
*/
hiddenUntilFound?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-base/src/Dialog/Root/DialogRoot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('<Dialog.Root />', () => {
});
});

// toWarnDev doesn't work reliably with async rendering. To re-eanble after it's fixed in the test-utils.
// toWarnDev doesn't work reliably with async rendering. To re-enable after it's fixed in the test-utils.
// eslint-disable-next-line mocha/no-skipped-tests
describe.skip('prop: modal', () => {
it('warns when the dialog is modal but no backdrop is present', async () => {
Expand Down

0 comments on commit 02616e5

Please sign in to comment.