Skip to content

Commit

Permalink
Add finalized documentation for how animations should work
Browse files Browse the repository at this point in the history
  • Loading branch information
cwoolum committed Sep 21, 2024
1 parent c818eec commit 27ae8e8
Show file tree
Hide file tree
Showing 19 changed files with 384 additions and 276 deletions.
5 changes: 5 additions & 0 deletions .changeset/hip-dancers-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik-ui/headless': patch
---

Add finalized documentation for how animations should work
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 ImportantAnimationCaveats = 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>
);
});
68 changes: 68 additions & 0 deletions apps/website/src/components/animations/compatability.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { component$ } from '@builder.io/qwik';
import { Note, NoteStatus } from '../note/note'; // Adjust the import path based on your structure
import { Badge } from '~/components/ui';

export const BrowserAnimationCompatability = component$(() => {
return (
<Note status={NoteStatus.Info}>
<div class="flex flex-col gap-2">
<h4>
<strong>Browser Compatability</strong>
</h4>
Browser versions that do not support the popover API natively 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.
<table class="w-full min-w-[540px] text-left sm:min-w-full">
<thead>
<tr>
<th>Browser</th>
<th>Minimum Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>Internet Explorer</td>
<td>
<Badge look="alert" class="w-12 justify-center">
N/A
</Badge>
</td>
</tr>
<tr>
<td>Firefox</td>
<td>
<Badge look="primary" class="w-12 justify-center">
125
</Badge>
</td>
</tr>
<tr>
<td>Chrome</td>
<td>
<Badge look="primary" class="w-12 justify-center">
114
</Badge>
</td>
</tr>
<tr>
<td>Safari</td>
<td>
<Badge look="primary" class="w-12 justify-center">
17
</Badge>
</td>
</tr>
<tr>
<td>Edge</td>
<td>
<Badge look="primary" class="w-12 justify-center">
114
</Badge>
</td>
</tr>
</tbody>
</table>
</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 @@ -10,6 +10,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 { ImportantAnimationCaveats } from '../animations/caveats';
import { BrowserAnimationCompatability } from '../animations/compatability';

export const components: Record<string, Component> = {
p: component$<PropsOf<'p'>>(({ ...props }) => {
Expand Down Expand Up @@ -132,4 +134,6 @@ export const components: Record<string, Component> = {
Note,
StatusBanner,
Showcase,
ImportantAnimationCaveats,
BrowserAnimationCompatability,
};
2 changes: 1 addition & 1 deletion apps/website/src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,7 @@
}

p code {
@apply rounded-sm border-b-2 bg-accent px-2 py-0.5;
@apply rounded-sm border-b-2 bg-accent/50 px-2 py-0.5;
}

pre code {
Expand Down
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
26 changes: 11 additions & 15 deletions apps/website/src/routes/docs/headless/modal/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,33 +170,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**.
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.

> 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.
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.

### Our current approach
### Keyframe Animation Example

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.
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:

### Adding a transition

<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.
<ImportantCaveats />

### 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
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 27ae8e8

Please sign in to comment.