Skip to content

Commit

Permalink
Update animations functionality to only support the native popover ap…
Browse files Browse the repository at this point in the history
…proach (qwikifiers#970)

* Change how popover animations work and update docs

* fix: modal route not working + naming

* fix: modal route not working + naming #2

* fix: p code style

* chore: remove unnecessary import

* refactor: make compatibility note easier to maintain

---------

Co-authored-by: maiieul <[email protected]>
  • Loading branch information
cwoolum and maiieul authored Oct 2, 2024
1 parent 9cb3c96 commit 7e87d33
Show file tree
Hide file tree
Showing 25 changed files with 384 additions and 305 deletions.
5 changes: 5 additions & 0 deletions .changeset/two-jeans-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik-ui/headless': minor
---

We are removing the existing popover animations shimming and instead wil now only support native popover animations. This is considered a breaking change but will be more reliable overall.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ node_modules
dist
coverage
.eslintrc.*
vite.config.ts
vite.config.ts
packages/kit-headless/browsers/**
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
jobs:
test:
runs-on: ubuntu-latest
name: Test NodeJS ${{ matrix.node_version }}

strategy:
matrix:
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
],
"editor.codeActionsOnSave": {
"source.removeUnusedImports": "explicit"
}
},
"vitest.disableWorkspaceWarning": true
}
49 changes: 49 additions & 0 deletions apps/website/src/components/animations/caveats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { component$ } from '@builder.io/qwik';
import { Note, NoteStatus } from '../note/note'; // Adjust the import path based on your structure

export const TopLayerAnimationsCaveats = component$(() => {
return (
<Note status={NoteStatus.Warning}>
<strong>Important Caveats for Animating Discrete Properties</strong>

<ul class="mt-4 list-disc bg-gradient-to-b pl-4">
<li>
<strong>
Animating <code>display</code> and <code>overlay</code>:
</strong>
<p>
The <code>display</code> property must be included in the transitions list to
ensure the element remains visible throughout the animation. The value flips
from <code>none</code> to <code>block</code> at 0% of the animation, ensuring
visibility for the entire duration. The&nbsp;
<code>overlay</code> ensures the element stays in the top layer until the
animation completes.
</p>
</li>
<li>
<strong>
Using <code>transition-behavior: allow-discrete</code>:
</strong>
<p>
This property is essential when animating discrete properties like{' '}
<code>display</code> and <code>overlay</code>, which are not typically
animatable. It ensures smooth transitions for these discrete properties.
</p>
</li>
<li>
<strong>
Setting Starting Styles with <code>@starting-style</code>:
</strong>
<p>
CSS transitions are only triggered when a property changes on a visible
element. The&nbsp;
<code>@starting-style</code> at-rule allows you to set initial styles (e.g.,{' '}
<code>opacity</code> and
<code>transform</code>) when the element first appears, ensuring that the
animation behaves predictably.
</p>
</li>
</ul>
</Note>
);
});
22 changes: 22 additions & 0 deletions apps/website/src/components/animations/compatability.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { component$ } from '@builder.io/qwik';
import { Note, NoteStatus } from '../note/note'; // Adjust the import path based on your structure

export const BrowserAnimationsCompatability = component$(() => {
return (
<Note status={NoteStatus.Info}>
<div class="flex flex-col gap-2">
<h4>
<strong>Browser Compatability</strong>
</h4>
<p>
<a href="https://caniuse.com/?search=popover%20API">
Browser versions that do not support the popover API natively
</a>{' '}
have known issues when trying to use animations or transitions. If you need to
support legacy versions of browsers, please be sure to test this functionality
independently.
</p>
</div>
</Note>
);
});
4 changes: 4 additions & 0 deletions apps/website/src/components/mdx-components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { KeyboardInteractionTable } from '../keyboard-interaction-table/keyboard
import { Note } from '../note/note';
import { Showcase } from '../showcase/showcase';
import { StatusBanner } from '../status-banner/status-banner';
import { TopLayerAnimationsCaveats } from '../animations/caveats';
import { BrowserAnimationsCompatability } from '../animations/compatability';

export const components: Record<string, Component> = {
p: component$<PropsOf<'p'>>(({ ...props }) => {
Expand Down Expand Up @@ -134,4 +136,6 @@ export const components: Record<string, Component> = {
StatusBanner,
Showcase,
AutoAPI,
TopLayerAnimationsCaveats,
BrowserAnimationsCompatability,
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
import { component$, useStyles$ } from '@builder.io/qwik';
import { Modal, Label } from '@qwik-ui/headless';
import styles from '../snippets/animation.css?inline';

export default component$(() => {
useStyles$(styles);
useStyles$(`
.modal-animation {
animation: modalClose 0.35s ease-in-out forwards;
}
.modal-animation:popover-open {
animation: modalOpen 0.75s ease-in-out forwards;
}
@keyframes modalOpen {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes modalClose {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.9);
}
}`);

return (
<Modal.Root>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { component$, useStyles$ } from '@builder.io/qwik';
import { Modal, Label } from '@qwik-ui/headless';

export default component$(() => {
useStyles$(`
.modal-animation[open]::backdrop {
animation: backdropFadeIn 0.75s ease-in-out forwards;
}
@keyframes backdropFadeIn {
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: rgba(0, 0, 0, 0.65);
}
}`);

return (
<Modal.Root>
<Modal.Trigger class="modal-trigger">Open Modal</Modal.Trigger>
<Modal.Panel class="modal-panel modal-animation">
<Modal.Title>Edit Profile</Modal.Title>
<Modal.Description>
You can update your profile here. Hit the save button when finished.
</Modal.Description>
<Label>
Name
<input type="text" placeholder="John Doe" />
</Label>
<Label>
Email
<input type="text" placeholder="[email protected]" />
</Label>
<footer>
<Modal.Close class="modal-close">Cancel</Modal.Close>
<Modal.Close class="modal-close">Save Changes</Modal.Close>
</footer>
</Modal.Panel>
</Modal.Root>
);
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import { component$, useStyles$ } from '@builder.io/qwik';
import { Modal, Label } from '@qwik-ui/headless';
import styles from '../snippets/animation.css?inline';

export default component$(() => {
useStyles$(styles);
useStyles$(`
.modal-transition {
opacity: 0;
transform: scale(0.9);
transition:
opacity 0.35s ease-in-out,
transform 0.35s ease-in-out,
display 0.35s,
overlay 0.35s;
transition-behavior: allow-discrete;
}
.modal-transition:popover-open {
opacity: 1;
transform: scale(1);
}
@starting-style {
.modal-transition:popover-open {
opacity: 0;
transform: scale(0.9);
}
}`);

return (
<Modal.Root>
Expand Down
28 changes: 11 additions & 17 deletions apps/website/src/routes/docs/headless/modal/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ title: Qwik UI | Modal
---

import topLayer from '../../../../../public/images/top-layer.webp';

import { statusByComponent } from '~/_state/component-statuses';

<StatusBanner status={statusByComponent.headless.Modal} />
import {FeatureList} from '~/components/feature-list/feature-list';

# Modal

Expand Down Expand Up @@ -170,33 +168,29 @@ This is done in a separate layer so that styles are easily overridable in consum

## Animations

Animating things to display none has historically been a significant challenge on the web. This is because display none is a `discrete` property, and is **unanimatable**.

> There is currently efforts to solve this problem. [New CSS properties](https://developer.chrome.com/blog/entry-exit-animations/) have been introduced, but currently do not provide good enough browser support.
Modals require smooth entry and exit animations to enhance user experience. However, animating properties like display and overlay can be challenging because they are discrete properties and not traditionally animatable.

### Our current approach
Modern browsers have introduced discrete animation capabilities, allowing us to animate these properties effectively. Below, we'll explore how to implement animations and transitions for modals using keyframe animations and CSS transitions.

Qwik UI automatically detects any `animation` or `transition` declarations under the hood and waits for them to finish before closing the modal. If there is no animation, then it will close normally.
### Keyframe Animation Example

### Adding a transition
Keyframes are ideal for handling the entry and exit of the modal. Here's an example using modalOpen for opening and modalClose for closing the modal:

<Showcase name="transition" />
<Showcase name="animatable" />

To add an transition, use the `data-open`, `data-closing` and `data-closed` data attributes. Above is a snippet where we transition both the modal and backdrop's opacity.
### Transition Declarations

### Adding an animation
Transitions are useful for animating properties like opacity and transform. Here's how to implement transitions for the modal:

<Showcase name="animatable" />
<Showcase name="transition" />

To add an animation, it's the same as with transitions, using the `data-open` and `data-closing` data attributes. Below is a snippet of the animation example above.
<TopLayerAnimationsCaveats />

### Backdrop animations

Backdrop animations have also made significant progress in the last year, with support provided in over half of the major browsers, and close to 70% of users.

To add a backdrop animation, make sure to use the `::backdrop` pseudo selector to the end of the `data-closing` or `data-open` classes.
To animate the modal's backdrop, use the `::backdrop` pseudo-element and include it in your keyframes or transitions:

> Firefox currently does not support backdrop animations. The fallback for browsers that do not support animated backdrops is the same as a non-animated backdrop.
<Showcase name="backdrop-animatable" />

## Sheets

Expand Down
25 changes: 25 additions & 0 deletions apps/website/src/routes/docs/headless/popover/auto-api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const api = {
popover: [
{
floating: [],
},
{
'popover-panel-arrow': [],
},
{
'popover-panel-impl': [],
},
{
'popover-panel': [],
},
{
'popover-root': [],
},
{
'popover-trigger': [],
},
{
'use-popover': [],
},
],
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
import { component$ } from '@builder.io/qwik';
import { component$, useStyles$ } from '@builder.io/qwik';
import { Popover } from '@qwik-ui/headless';

export default component$(() => {
useStyles$(`
.popover-animation {
animation: popover-shrink 0.4s ease-in-out forwards;
}
/* For exit animation */
.popover-animation:popover-open {
animation: popover-grow 0.5s ease-in-out forwards;
}
@keyframes popover-shrink {
from {
transform: scale(1);
display: block;
}
to {
transform: scale(0);
display: none;
}
}
@keyframes popover-grow {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
`);

return (
<Popover.Root>
<Popover.Trigger class="popover-trigger">Popover Trigger</Popover.Trigger>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
import { component$ } from '@builder.io/qwik';
import { component$, useStyles$ } from '@builder.io/qwik';
import { Popover } from '@qwik-ui/headless';

export default component$(() => {
useStyles$(`
.popover-transition {
opacity: 0;
transform: scale(0.5);
transition:
opacity 0.3s ease-out,
transform 0.3s ease-out,
display 0.3s,
overlay 0.3s;
transition-behavior: allow-discrete;
}
.popover-transition:popover-open {
opacity: 1;
transform: scale(1);
transition:
opacity 0.3s ease-out,
transform 0.3s ease-out,
display 0.3s,
overlay 0.3s;
}
@starting-style {
.popover-transition:popover-open {
opacity: 0;
transform: scale(0.5);
}
}`);

return (
<Popover.Root>
<Popover.Trigger class="popover-trigger">Popover Trigger</Popover.Trigger>
Expand Down
Loading

0 comments on commit 7e87d33

Please sign in to comment.