-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Collapsible does not always handle growing children #7318
Comments
As I investigate this, it turns out that the component relies on |
From investigation it looks like the problem is this change: #6100 Specifically, this change incorrectly fixed a bug in Secondary Navigation Items where the Collapsible would not change size if the children changed. However the root cause of that bug is the use of When in the Unfortunately, this caused a couple of bugs. One is that the re-measure process when the component is already open is a bit janky, which is why this PR was made (see the janky animation here. The other is that the PR made a Why did the original PR author not notice this? Because in Polaris by default, a TL;DR: The linked PR introduced this bug by trying to re-measure on children change. The correct fix is instead to avoid animating if |
Note: If we choose to add a feature for "animate while open" it will need more thought. The way this component is designed today only handles animating between "open" and "closed" states. |
… machine (#7364) ### WHY are these changes introduced? Fixes #7318 Today the Collapsible component can get stuck in the "animating" state for 2 reasons, both of which cause the `onTransitionEnd` event not to fire. 1. The prop `transition={{ duration: "0ms" }}` is used as [Navigation Items](https://github.com/Shopify/polaris/blob/b0445cf9b036752062af215da57347ce4b8f6f17/polaris-react/src/components/Navigation/components/Item/components/Secondary/Secondary.tsx#L19) does. 2. The children of the Collapsible have no margin and it is rendered for the first time with `open=true` (as raised in the linked issue). This causes the `div` to have `max-height` not change, which means the transition does not occur. Fundamentally the issue with this component is that it relies on the `onTransitionEnd` event to move out of the `animating` state. There are 2 ways that this event won't fire, similarly related: 1. If the animation is started but the height doesn't change (e.g. from 0px to 0px) 2. If the animation is started but the duration is set to 0 (`0ms`, `0s` or `-some-var-that-evaluates-to-0`) This PR does not solve the dependency on the `onTransitionEnd` event. Instead it: 1. Adds formal support for disabling the transition by setting `transition={false}`. This prevents the need to pass `0ms` duration. (Note this does not stop the bug occurring) 2. Handles if the user passes `0ms` or `0s` to also disable the transition. 3. Removes the "re-measure on child update" logic, as this was a workaround for the fact that the component was getting stuck in the animating state. As such there will still be a bug if the computed height of `children` is set to 0px. However I'm choosing not to address this in this PR as the chances are rare. To trigger this bug you must: 1. Render the Collapsible in closed state (`open={false}`) 2. Toggle the open state to true, while the children is `0px` high as computed by `element.scrollHeight` 4. Change the children to have content ### WHAT is this pull request doing? 1. It reverts the changes from #6283 and #6100 which caused the linked bug. 5. Adds support for passing `false` to the `transition` prop to disable the transition. 6. Changes the `Navigation` `Secondary` component to use `transition={false}` ### How to 🎩 🖥 [Local development instructions](https://github.com/Shopify/polaris/blob/main/README.md#local-development) 🗒 [General tophatting guidelines](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md) 📄 [Changelog guidelines](https://github.com/Shopify/polaris/blob/main/.github/CONTRIBUTING.md#changelog) <details> <summary>Copy-paste this code in <code>playground/Playground.tsx</code>:</summary> ```jsx import {HomeMinor, OrdersMinor, ProductsMinor} from '@shopify/polaris-icons'; import React, {useState} from 'react'; import {Button, Card, Collapsible, Frame, Navigation} from '../src'; const list1 = [ {label: 'a', url: 'path/to/one'}, {label: 'b', url: 'path/to/two'}, ]; const list2 = [ {label: 'one', url: 'path/to/one'}, {label: 'two', url: 'path/to/two'}, {label: 'three', url: 'path/to/three'}, ]; const list3 = [ {label: '1', url: 'path/to/one'}, {label: '2', url: 'path/to/two'}, {label: '3', url: 'path/to/three'}, {label: '4', url: 'path/to/four'}, {label: '5', url: 'path/to/five'}, ]; export function Playground() { const [open, setOpen] = useState(true); const toggleOpen = () => { setOpen(!open); }; const scenario1 = ( <div> <h1>Scenario 1</h1> <Collapsible open={open} id="1"> <ul> <li>List item</li> </ul> </Collapsible> </div> ); const scenario2 = ( <div> <h1>Scenario 2</h1> <button onClick={toggleOpen}>Toggle open</button> <Collapsible open={open} id="2"> <div>Hello</div> </Collapsible> </div> ); const [selected, setSelected] = useState('home'); const [subitems, setSubitems] = useState(list2); const scenario3 = ( <Frame> <button onClick={() => setSubitems(list1)}>list 1</button> <button onClick={() => setSubitems(list2)}>list 2</button> <button onClick={() => setSubitems(list3)}>list 3</button> <Navigation location="/"> <Navigation.Section items={[ { url: '/', label: 'Home', icon: HomeMinor, selected: selected === 'home', onClick: () => setSelected('home'), }, { url: '/', label: 'Orders', icon: OrdersMinor, subNavigationItems: subitems, selected: selected === 'orders', onClick: () => setSelected('orders'), }, { url: '/', label: 'Products', icon: ProductsMinor, selected: selected === 'products', onClick: () => setSelected('products'), }, ]} /> <Navigation.Item url="/" label="Expected List" subNavigationItems={subitems} /> </Navigation> </Frame> ); const scenario4 = ( <Card> Scenario 4 <Collapsible id="4" open={open} transition={{duration: '100ms'}}> Content goes here </Collapsible> </Card> ); const [firstOpen, setFirstOpen] = useState(false); const [secondOpen, setSecondOpen] = useState(false); const scenario5 = ( <Card> <h1>Scenario 5</h1> <button onClick={() => setFirstOpen(!firstOpen)}>First</button> <button onClick={() => setSecondOpen(!secondOpen)}>Second</button> <Collapsible id="5" open={firstOpen} transition={{duration: '100ms'}}> <Collapsible id="5.1" open={secondOpen}> <div style={{width: '100px', height: '100px', backgroundColor: 'purple'}} /> </Collapsible> </Collapsible> </Card> ); const scenario6 = ( <Card title="Scenario 6"> <Collapsible id="6" open={open}> <div style={{height: 0}} /> </Collapsible> </Card> ); return ( <div> <button onClick={toggleOpen}>Toggle open</button> {scenario1} {scenario2} {scenario3} {scenario4} {scenario5} {scenario6} </div> ); } ``` </details> The code has 6 scenarios: - A `Collapsible` with `ul` as its child (this has margin-top and margin-bottom) - A `Collapsible` with `div` as its child (this has no margins) - The original scenario from #6100 to show that this does not cause a regression. - Duration 100ms explicitly set - Nested collapsibles - A `Collapsible` that has children that is 0px high (not fixed by this PR) ### 🎩 checklist - [ ] Tested on [mobile](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md#cross-browser-testing) - [x] Tested on [multiple browsers](https://help.shopify.com/en/manual/shopify-admin/supported-browsers) - [x] Tested for [accessibility](https://github.com/Shopify/polaris/blob/main/documentation/Accessibility%20testing.md) - [ ] Updated the component's `README.md` with documentation changes - [ ] [Tophatted documentation](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting%20documentation.md) changes in the style guide
Issue summary
Collapsible occasionally does not recompute the size of its children even if they change their size appropriately within the React event cycle.
Expected behavior
As the children grow, all expanded Collapsible containers show their children
Actual behavior
Gif recording of last Collapsible element not resizing
Steps to reproduce the problem
A reproduction scenario is here
If you want to recreate the repro:
Reduced test case
See the problem here: https://codesandbox.io/s/quiet-leaf-bsrk37?file=/App.js
Specifications
The text was updated successfully, but these errors were encountered: