-
Notifications
You must be signed in to change notification settings - Fork 2
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
Allow closed state to be passed to Dialog for proper transition handling #1117
Conversation
9be0cc5
to
8f4a1f8
Compare
import { Dialog } from '../../../../components/feedback'; | ||
import { Button } from '../../../../components/input'; | ||
import { Slider } from '../../../../components/transition'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit, but I typically import from the package entry point in pattern library page component modules.
Thanks for putting so much thought into this! I think I generally grok where you're coming from.
We definitely are, and very soon, in fact. If it helps to consider another use case: we're going to want transitions on toast messages. The new |
Thanks for the feedback @lyzadanger @robertknight made an interesting suggestion on trying to improve the internal sync of the three pieces of state:
This goes in line with one of my TODOs above: I'm adding a comment in the code to make sure we get reminded from time to time. I may also try to play a bit with extracting the transition component handling into a hook that can be reused in other components: const { closeHandler, Wrapper } = useTransitionComponent(transitionComponent, onClose, closed, ...); I think isolating that from the rest of the logic in |
996b1e6
to
e2cfb31
Compare
Codecov Report
@@ Coverage Diff @@
## main #1117 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 57 57
Lines 806 822 +16
Branches 312 320 +8
=========================================
+ Hits 806 822 +16
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
@@ -278,7 +284,7 @@ describe('Dialog', () => { | |||
// The onClose callback is not immediately invoked | |||
assert.notCalled(onClose); | |||
// Once the transition has ended, the callback should have been called | |||
await delay(60); // Transition finishes after 50ms | |||
await delay(70); // Transition finishes after 50ms |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit random, but the test was passing locally and failing in CI. I guess there's a chance there's a timing issue/race condition here, as we are talking about 10ms.
da65bf6
to
ef6e9d3
Compare
ef6e9d3
to
f598215
Compare
return () => { | ||
clearTimeout(timeout); | ||
}; | ||
}, [direction, onTransitionEnd]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stub TransitionComponent
was triggering this timeout on every render, which was not relevant for a component that was previously mounted and unmounted every time.
With the new logic to open/close, the component can render multiple times, triggering the setTimeout on each one of them. Hence, the addition of the useEffect
.
f598215
to
361463d
Compare
I have created a draft for this #1131 It still has the complexities introduced here, but at least they are all wrapped in a reusable hook instead of complicating It also allows to use transitions in other components if we want. |
019ea6d
to
13fc954
Compare
After multiple attempts, I came to realize this is not possible, because the different state changes are not "linear", or "instant" when a transition component is provided. There are potential "delays" between the change of one piece of state and the next one. I have slightly simplified it though, removing nested conditionals, so I think it's ready for a review again. |
13fc954
to
c29884d
Compare
After giving this a lot of thought, we have come to the conclusion that this approach introduces too much complexity to solve a niche use-case. We'll probably try something else. Once of the options is using This approach would expose a bit the transition handling implementation details, but make it much simpler to maintain in the long term. |
This PR introduces some changes on how the
Dialog
handles closed state, so that it can be passed to it for proper transition handling, but in a way that we can dynamically render theDialog
itself from consumer code, if aTransitionComponent
is not provided.How to use it
Up until now, we had to do something like this:
This works, as long as
setClosed(true)
is not called from outside theDialog
itself.01.-.broken.webm
In order to fix that, a new
closed
prop is introduced, letting theDialog
itself both wraponClose
, but also react to changes toclosed
, and properly handle transitions in both cases:02.-.fixed.webm
When no
transitionComponent
is provided, then both options above are valid.03.-.no-transition.webm
State change flow
The way this works is currently a bit messy (I want to put some extra thought on it), as it requires deriving two pieces of state from the new
closed
prop.The reason we cannot use the prop "directly" is that the prop might change sooner, but we want to delay the state to actually change, if there's a transition involved.
This explains how the different combinations of prop and state flows work:
With
TransitionComponent
Without
TransitionComponent
The conclusion is that from the 4 possible combinations, only the first one justifies the need to "derive"
isClosed
state fromclosed
prop.Considerations / thoughts / next steps
isClosed
feels like a too vague name for the internal state. It also might be confused with theclosed
prop.close
andisClosed
are true when not visible, whiletransitionComponentVisible
is true when visible. Perhaps we should make the three consistent, maybe renaming totransitionComponentHidden
.Removing theScratch that. Without that check, then theif (isClosed) return null
logic simplifies the rest of the interactions, but leaves some DOM nodes for preact to handle forever.close
prop does nothing when notransitionComponent
is provided, which would make it inconsistent.isClosed
andtransitionComponentVisible
, but haven't nailed it yet.Dialog
, we may want to extract some of the logic, but I would start by copy-pasting once, and then it will be easier to identify what needs to be reused.