Skip to content
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

Update animations functionality to only support the native popover approach #970

Merged
merged 6 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 }}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this to make the actions page a bit more readable.


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
Loading