remove slot detection from card, dialog, drawer

This commit is contained in:
Cory LaViska
2024-03-21 14:47:29 -04:00
parent 58d14ed1d2
commit 252d710175
17 changed files with 125 additions and 238 deletions

View File

@@ -328,7 +328,7 @@ wa-select[label="Signet"]::part(form-control-help-text) {
</div>
</form>
<wa-dialog id="icon-chooser" label="Browse Icons">
<wa-dialog id="icon-chooser" label="Browse Icons" with-header>
<div style="display: grid; grid-template-rows: minmax(0, auto) minmax(0, 1fr); height: 100%; gap: 1rem;">
<div style="display: flex; gap: 1.25rem;">
<wa-input name="icon-search" placeholder="Search Icons" clearable style="flex: 1 1 auto;">

View File

@@ -5,7 +5,7 @@ layout: ../../../layouts/ComponentLayout.astro
---
```html:preview
<wa-card class="card-overview">
<wa-card with-image with-footer class="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
@@ -62,7 +62,7 @@ const css = `
const App = () => (
<>
<WaCard className="card-overview">
<WaCard with-image with-footer className="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
@@ -129,7 +129,7 @@ const App = () => (
Headers can be used to display titles and more.
```html:preview
<wa-card class="card-header">
<wa-card with-header class="card-header">
<div slot="header">
Header Title
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
@@ -185,7 +185,7 @@ const css = `
const App = () => (
<>
<WaCard className="card-header">
<WaCard with-header className="card-header">
<div slot="header">
Header Title
<WaIconButton name="gear" variant="solid"></WaIconButton>
@@ -203,7 +203,7 @@ const App = () => (
Footers can be used to display actions, summaries, or other relevant content.
```html:preview
<wa-card class="card-footer">
<wa-card with-footer class="card-footer">
This card has a footer. You can put all sorts of things in it!
<div slot="footer">
@@ -244,7 +244,7 @@ const css = `
const App = () => (
<>
<WaCard className="card-footer">
<WaCard with-footer className="card-footer">
This card has a footer. You can put all sorts of things in it!
<div slot="footer">
<WaRating></WaRating>
@@ -264,7 +264,7 @@ const App = () => (
Cards accept an `image` slot. The image is displayed atop the card and stretches to fit.
```html:preview
<wa-card class="card-image">
<wa-card with-image class="card-image">
<img
slot="image"
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"
@@ -291,7 +291,7 @@ const css = `
const App = () => (
<>
<WaCard className="card-image">
<WaCard with-image className="card-image">
<img
slot="image"
src="https://images.unsplash.com/photo-1547191783-94d5f8f6d8b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=80"

View File

@@ -7,7 +7,7 @@ layout: ../../../layouts/ComponentLayout.astro
<!-- cspell:dictionaries lorem-ipsum -->
```html:preview
<wa-dialog label="Dialog" class="dialog-overview">
<wa-dialog label="Dialog" with-header with-footer class="dialog-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -34,7 +34,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -54,7 +54,7 @@ const App = () => {
Use the `--width` custom property to set the dialog's width.
```html:preview
<wa-dialog label="Dialog" class="dialog-width" style="--width: 50vw;">
<wa-dialog label="Dialog" with-header with-footer class="dialog-width" style="--width: 50vw;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -81,7 +81,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" open={open} style={{ '--width': '50vw' }} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer open={open} style={{ '--width': '50vw' }} onWaAfterHide={() => setOpen(false)}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -99,7 +99,7 @@ const App = () => {
By design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user.
```html:preview
<wa-dialog label="Dialog" class="dialog-scrolling">
<wa-dialog label="Dialog" with-header with-footer class="dialog-scrolling">
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
<p>Scroll down and give it a try! 👇</p>
</div>
@@ -128,7 +128,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<div
style={{
height: '150vh',
@@ -155,7 +155,7 @@ const App = () => {
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
```html:preview
<wa-dialog label="Dialog" class="dialog-header-actions">
<wa-dialog label="Dialog" with-header with-footer class="dialog-header-actions">
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
@@ -186,7 +186,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaIconButton
class="new-window"
slot="header-actions"
@@ -214,7 +214,7 @@ To keep the dialog open in such cases, you can cancel the `wa-request-close` eve
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
```html:preview
<wa-dialog label="Dialog" class="dialog-deny-close">
<wa-dialog label="Dialog" with-header with-footer class="dialog-deny-close">
This dialog will not close when you click on the overlay.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -255,7 +255,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
This dialog will not close when you click on the overlay.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -273,7 +273,7 @@ const App = () => {
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
```html:preview
<wa-dialog label="Dialog" class="dialog-focus">
<wa-dialog label="Dialog" with-header with-footer class="dialog-focus">
<wa-input autofocus placeholder="I will have focus when the dialog is opened"></wa-input>
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-dialog>
@@ -302,7 +302,7 @@ const App = () => {
return (
<>
<WaDialog label="Dialog" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDialog label="Dialog" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaInput autofocus placeholder="I will have focus when the dialog is opened" />
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close

View File

@@ -7,7 +7,7 @@ layout: ../../../layouts/ComponentLayout.astro
<!-- cspell:dictionaries lorem-ipsum -->
```html:preview
<wa-drawer label="Drawer" class="drawer-overview">
<wa-drawer label="Drawer" with-header with-footer class="drawer-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -34,7 +34,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -54,7 +54,7 @@ const App = () => {
By default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`.
```html:preview
<wa-drawer label="Drawer" placement="start" class="drawer-placement-start">
<wa-drawer label="Drawer" placement="start" with-header with-footer class="drawer-placement-start">
This drawer slides in from the start.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -81,7 +81,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" placement="start" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="start" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
This drawer slides in from the start.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -99,7 +99,7 @@ const App = () => {
To make the drawer slide in from the top, set the `placement` attribute to `top`.
```html:preview
<wa-drawer label="Drawer" placement="top" class="drawer-placement-top">
<wa-drawer label="Drawer" placement="top" with-header with-footer class="drawer-placement-top">
This drawer slides in from the top.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -126,7 +126,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" placement="top" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="top" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
This drawer slides in from the top.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -144,7 +144,7 @@ const App = () => {
To make the drawer slide in from the bottom, set the `placement` attribute to `bottom`.
```html:preview
<wa-drawer label="Drawer" placement="bottom" class="drawer-placement-bottom">
<wa-drawer label="Drawer" placement="bottom" with-header with-footer class="drawer-placement-bottom">
This drawer slides in from the bottom.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -171,7 +171,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" placement="bottom" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" placement="bottom" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
This drawer slides in from the bottom.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -184,84 +184,12 @@ const App = () => {
};
```
### Contained to an Element
By default, drawers slide out of their [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make a drawer slide out of a parent element, add the `contained` attribute to the drawer and apply `position: relative` to its parent.
Unlike normal drawers, contained drawers are not modal. This means they do not show an overlay, they do not trap focus, and they are not dismissible with [[Escape]]. This is intentional to allow users to interact with elements outside of the drawer.
```html:preview
<div
style="position: relative; border: solid 2px var(--wa-color-surface-border); height: 300px; padding: 1rem; margin-bottom: 1rem;"
>
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer opens.
<wa-drawer label="Drawer" contained class="drawer-contained" style="--size: 50%;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
</div>
<wa-button>Toggle Drawer</wa-button>
<script>
const drawer = document.querySelector('.drawer-contained');
const openButton = drawer.parentElement.nextElementSibling;
const closeButton = drawer.querySelector('wa-button[variant="brand"]');
openButton.addEventListener('click', () => (drawer.open = !drawer.open));
closeButton.addEventListener('click', () => drawer.hide());
</script>
```
```jsx:react
import { useState } from 'react';
import WaButton from '@shoelace-style/shoelace/dist/react/button';
import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer';
const App = () => {
const [open, setOpen] = useState(false);
return (
<>
<div
style={{
position: 'relative',
border: 'solid 2px var(--wa-color-surface-border)',
height: '300px',
padding: '1rem',
marginBottom: '1rem'
}}
>
The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer
opens.
<WaDrawer
label="Drawer"
contained
no-modal
open={open}
onWaAfterHide={() => setOpen(false)}
style={{ '--size': '50%' }}
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
</WaButton>
</WaDrawer>
</div>
<WaButton onClick={() => setOpen(true)}>Open Drawer</WaButton>
</>
);
};
```
### Custom Size
Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.
```html:preview
<wa-drawer label="Drawer" class="drawer-custom-size" style="--size: 50vw;">
<wa-drawer label="Drawer" with-header with-footer class="drawer-custom-size" style="--size: 50vw;">
This drawer is always 50% of the viewport.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -288,7 +216,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)} style={{ '--size': '50vw' }}>
This drawer is always 50% of the viewport.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close
@@ -306,7 +234,7 @@ const App = () => {
By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.
```html:preview
<wa-drawer label="Drawer" class="drawer-scrolling">
<wa-drawer label="Drawer" with-header with-footer class="drawer-scrolling">
<div style="height: 150vh; border: dashed 2px var(--wa-color-surface-border); padding: 0 1rem;">
<p>Scroll down and give it a try! 👇</p>
</div>
@@ -335,7 +263,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<div
style={{
height: '150vh',
@@ -361,7 +289,7 @@ const App = () => {
The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
```html:preview
<wa-drawer label="Drawer" class="drawer-header-actions">
<wa-drawer label="Drawer" with-header with-footer class="drawer-header-actions">
<wa-icon-button class="new-window" slot="header-actions" name="arrow-up-right-from-square" variant="solid"></wa-icon-button>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-button slot="footer" variant="brand">Close</wa-button>
@@ -392,7 +320,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaIconButton slot="header-actions" name="arrow-up-right-from-square" onClick={() => window.open(location.href)} />
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
@@ -415,7 +343,7 @@ To keep the drawer open in such cases, you can cancel the `wa-request-close` eve
You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
```html:preview
<wa-drawer label="Drawer" class="drawer-deny-close">
<wa-drawer label="Drawer" with-header with-footer class="drawer-deny-close">
This drawer will not close when you click on the overlay.
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -456,7 +384,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaRequestClose={handleRequestClose} onWaAfterHide={() => setOpen(false)}>
This drawer will not close when you click on the overlay.
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Save &amp; Close
@@ -474,7 +402,7 @@ const App = () => {
By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
```html:preview
<wa-drawer label="Drawer" class="drawer-focus">
<wa-drawer label="Drawer" with-header with-footer class="drawer-focus">
<wa-input autofocus placeholder="I will have focus when the drawer is opened"></wa-input>
<wa-button slot="footer" variant="brand">Close</wa-button>
</wa-drawer>
@@ -503,7 +431,7 @@ const App = () => {
return (
<>
<WaDrawer label="Drawer" open={open} onWaAfterHide={() => setOpen(false)}>
<WaDrawer label="Drawer" with-header with-footer open={open} onWaAfterHide={() => setOpen(false)}>
<WaInput autofocus placeholder="I will have focus when the drawer is opened" />
<WaButton slot="footer" variant="brand" onClick={() => setOpen(false)}>
Close

View File

@@ -6,7 +6,7 @@ description: TODO
## Card
```html:preview
<wa-card class="card-overview">
<wa-card with-image class="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"

View File

@@ -281,7 +281,7 @@ Internally, each component uses the [BEM methodology](http://getbem.com/) for cl
### Boolean Props
Boolean props should _always_ default to `false`, otherwise there's no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as `noHeader` and a corresponding kebab-case attribute such as `no-header`.
Boolean props should _always_ default to `false`, otherwise there's no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as `noValue` and a corresponding kebab-case attribute such as `no-value`.
When naming boolean props that hide or disable things, prefix them with `no-`, e.g. `no-spin-buttons` and avoid using other verbs such as `hide-` and `disable-` for consistency.

View File

@@ -15,7 +15,7 @@
fun.
</p>
<wa-dialog id="dialog"> I'm just a lowly dialog. </wa-dialog>
<wa-dialog id="dialog" label="Dialog" with-header> I'm just a lowly dialog. </wa-dialog>
<wa-button>Open Dialog</wa-button>
</wa-page>

View File

@@ -133,7 +133,7 @@
text-align: center;
"
>
<wa-card class="wa-card--muted" style="--padding: 8px">
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Total listening time</h3>
<p>
@@ -146,7 +146,7 @@
</p>
</wa-card>
<wa-card class="wa-card--muted" style="--padding: 8px">
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Total songs played</h3>
<p>
@@ -160,7 +160,7 @@
</p>
</wa-card>
<wa-card class="wa-card--muted" style="--padding: 8px">
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Average listening session</h3>
<p>
@@ -174,7 +174,7 @@
</p>
</wa-card>
<wa-card class="wa-card--muted" style="--padding: 8px">
<wa-card with-header class="wa-card--muted" style="--padding: 8px">
<h3 slot="header">Average track listening time</h3>
<p>

View File

@@ -1,6 +1,6 @@
import { classMap } from 'lit/directives/class-map.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import styles from './card.styles.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
@@ -34,7 +34,14 @@ import type { CSSResultGroup } from 'lit';
export default class WaCard extends WebAwesomeElement {
static styles: CSSResultGroup = [componentStyles, styles];
private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'image');
/** Renders the card with a header */
@property({ attribute: 'with-header', type: Boolean }) withHeader = false;
/** Renders the card with an image */
@property({ attribute: 'with-image', type: Boolean }) withImage = false;
/** Renders the card with a footer */
@property({ attribute: 'with-footer', type: Boolean }) withFooter = false;
render() {
return html`
@@ -42,9 +49,9 @@ export default class WaCard extends WebAwesomeElement {
part="base"
class=${classMap({
card: true,
'card--has-footer': this.hasSlotController.test('footer'),
'card--has-image': this.hasSlotController.test('image'),
'card--has-header': this.hasSlotController.test('header')
'card--has-footer': this.withFooter,
'card--has-image': this.withImage,
'card--has-header': this.withHeader
})}
>
<slot name="image" part="image" class="card__image"></slot>

View File

@@ -37,6 +37,8 @@ export default css`
.card__image::slotted(img) {
display: block;
width: 100%;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.card:not(.card--has-image) .card__image {

View File

@@ -29,7 +29,7 @@ describe('<wa-card>', () => {
describe('when provided an element in the slot "header" to render a header', () => {
before(async () => {
el = await fixture<WaCard>(
html`<wa-card>
html`<wa-card with-header>
<div slot="header">Header Title</div>
This card has a header. You can put all sorts of things in it!
</wa-card>`
@@ -65,7 +65,7 @@ describe('<wa-card>', () => {
describe('when provided an element in the slot "footer" to render a footer', () => {
before(async () => {
el = await fixture<WaCard>(
html`<wa-card>
html`<wa-card with-footer>
This card has a footer. You can put all sorts of things in it!
<div slot="footer">Footer Content</div>
@@ -102,7 +102,7 @@ describe('<wa-card>', () => {
describe('when provided an element in the slot "image" to render a image', () => {
before(async () => {
el = await fixture<WaCard>(
html`<wa-card>
html`<wa-card with-image>
<img
slot="image"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"

View File

@@ -1,7 +1,6 @@
import { animateTo, stopAnimations } from '../../internal/animate.js';
import { classMap } from 'lit/directives/class-map.js';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { LocalizeController } from '../../utilities/localize.js';
@@ -71,7 +70,6 @@ export default class WaDialog extends WebAwesomeElement {
'wa-icon-button': WaIconButton
};
private readonly hasSlotController = new HasSlotController(this, 'footer');
private readonly localize = new LocalizeController(this);
private originalTrigger: HTMLElement | null;
public modal = new Modal(this);
@@ -88,16 +86,16 @@ export default class WaDialog extends WebAwesomeElement {
@property({ type: Boolean, reflect: true }) open = false;
/**
* The dialog's label as displayed in the header. You should always include a relevant label even when using
* `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead.
* The dialog's label as displayed in the header. You should always include a relevant label, as it is required for
* proper accessibility. If you need to display HTML, use the `label` slot instead.
*/
@property({ reflect: true }) label = '';
/**
* Disables the header. This will also remove the default close button, so please ensure you provide an easy,
* accessible way for users to dismiss the dialog.
*/
@property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;
/** Renders the dialog with a header. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;
/** Renders the dialog with a footer. */
@property({ attribute: 'with-footer', type: Boolean, reflect: true }) withFooter = false;
firstUpdated() {
this.dialog.hidden = !this.open;
@@ -272,7 +270,8 @@ export default class WaDialog extends WebAwesomeElement {
class=${classMap({
dialog: true,
'dialog--open': this.open,
'dialog--has-footer': this.hasSlotController.test('footer')
'dialog--with-header': this.withHeader,
'dialog--with-footer': this.withFooter
})}
>
<div part="overlay" class="dialog__overlay" @click=${() => this.requestClose('overlay')} tabindex="-1"></div>
@@ -283,11 +282,11 @@ export default class WaDialog extends WebAwesomeElement {
role="dialog"
aria-modal="true"
aria-hidden=${this.open ? 'false' : 'true'}
aria-label=${ifDefined(this.noHeader ? this.label : undefined)}
aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
aria-label=${ifDefined(this.withHeader ? undefined : this.label)}
aria-labelledby=${ifDefined(this.withHeader ? 'title' : undefined)}
tabindex="-1"
>
${!this.noHeader
${this.withHeader
? html`
<header part="header" class="dialog__header">
<h2 part="title" class="dialog__title" id="title">
@@ -314,9 +313,13 @@ export default class WaDialog extends WebAwesomeElement {
}
<div part="body" class="dialog__body" tabindex="-1"><slot></slot></div>
<footer part="footer" class="dialog__footer">
<slot name="footer"></slot>
</footer>
${this.withFooter
? html`
<footer part="footer" class="dialog__footer">
<slot name="footer"></slot>
</footer>
`
: ''}
</div>
</div>
`;

View File

@@ -99,10 +99,6 @@ export default css`
margin-inline-start: var(--wa-spacing-xs);
}
.dialog:not(.dialog--has-footer) .dialog__footer {
display: none;
}
.dialog__overlay {
position: fixed;
top: 0;

View File

@@ -6,10 +6,10 @@ import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import type WaDialog from './dialog.js';
describe('<wa-dialog>', () => {
describe('<wa-dialog with-header>', () => {
it('should be visible with the open attribute', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
@@ -18,7 +18,7 @@ describe('<wa-dialog>', () => {
it('should not be visible without the open attribute', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
@@ -27,7 +27,7 @@ describe('<wa-dialog>', () => {
it('should emit wa-show and wa-after-show when calling show()', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const showHandler = sinon.spy();
@@ -47,7 +47,7 @@ describe('<wa-dialog>', () => {
it('should emit wa-hide and wa-after-hide when calling hide()', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const hideHandler = sinon.spy();
@@ -67,7 +67,7 @@ describe('<wa-dialog>', () => {
it('should emit wa-show and wa-after-show when setting open = true', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const showHandler = sinon.spy();
@@ -87,7 +87,7 @@ describe('<wa-dialog>', () => {
it('should emit wa-hide and wa-after-hide when setting open = false', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
const hideHandler = sinon.spy();
@@ -107,7 +107,7 @@ describe('<wa-dialog>', () => {
it('should not close when wa-request-close is prevented', async () => {
const el = await fixture<WaDialog>(html`
<wa-dialog open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
`);
const overlay = el.shadowRoot!.querySelector<HTMLElement>('[part~="overlay"]')!;
@@ -120,7 +120,7 @@ describe('<wa-dialog>', () => {
});
it('should allow initial focus to be set', async () => {
const el = await fixture<WaDialog>(html` <wa-dialog><input /></wa-dialog> `);
const el = await fixture<WaDialog>(html` <wa-dialog with-header><input /></wa-dialog> `);
const input = el.querySelector('input')!;
const initialFocusHandler = sinon.spy((event: Event) => {
event.preventDefault();
@@ -137,7 +137,7 @@ describe('<wa-dialog>', () => {
});
it('should close when pressing Escape', async () => {
const el = await fixture<WaDialog>(html` <wa-dialog open></wa-dialog> `);
const el = await fixture<WaDialog>(html` <wa-dialog with-header open></wa-dialog> `);
const hideHandler = sinon.spy();
el.addEventListener('wa-hide', hideHandler);
@@ -162,7 +162,7 @@ describe('<wa-dialog>', () => {
render() {
return html`
<h1>Dialog Example</h1>
<wa-dialog label="Dialog" class="dialog-overview">
<wa-dialog with-header label="Dialog" class="dialog-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<br />
<label><input type="checkbox" />A</label>
@@ -200,7 +200,7 @@ describe('<wa-dialog>', () => {
const dialog = container.shadowRoot?.querySelector('wa-dialog');
if (!dialog) {
throw Error('Could not find <wa-dialog> element.');
throw Error('Could not find <wa-dialog with-header> element.');
}
const closeButton = dialog.shadowRoot?.querySelector('wa-icon-button');

View File

@@ -95,36 +95,27 @@ export default class WaDrawer extends WebAwesomeElement {
@property({ type: Boolean, reflect: true }) open = false;
/**
* The drawer's label as displayed in the header. You should always include a relevant label even when using
* `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead.
* The drawer's label as displayed in the header. You should always include a relevant label, as it is required for
* proper accessibility. If you need to display HTML, use the `label` slot instead.
*/
@property({ reflect: true }) label = '';
/** The direction from which the drawer will open. */
@property({ reflect: true }) placement: 'top' | 'end' | 'bottom' | 'start' = 'end';
/**
* By default, the drawer slides out of its containing block (usually the viewport). To make the drawer slide out of
* its parent element, set this attribute and add `position: relative` to the parent.
*/
@property({ type: Boolean, reflect: true }) contained = false;
/** Renders the drawer with a header. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;
/**
* Removes the header. This will also remove the default close button, so please ensure you provide an easy,
* accessible way for users to dismiss the drawer.
*/
@property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;
/** Renders the drawer with a footer. */
@property({ attribute: 'with-footer', type: Boolean, reflect: true }) withFooter = false;
firstUpdated() {
this.drawer.hidden = !this.open;
if (this.open) {
this.addOpenListeners();
if (!this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
this.modal.activate();
lockBodyScrolling(this);
}
}
@@ -152,10 +143,8 @@ export default class WaDrawer extends WebAwesomeElement {
private addOpenListeners() {
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
if (!this.contained) {
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => this.requestClose('keyboard');
}
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => this.requestClose('keyboard');
} else {
document.addEventListener('keydown', this.handleDocumentKeyDown);
this.closeWatcher?.destroy();
@@ -167,11 +156,6 @@ export default class WaDrawer extends WebAwesomeElement {
}
private handleDocumentKeyDown = (event: KeyboardEvent) => {
// Contained drawers aren't modal and don't response to the escape key
if (this.contained) {
return;
}
if (event.key === 'Escape' && this.modal.isActive() && this.open) {
event.stopImmediatePropagation();
this.requestClose('keyboard');
@@ -185,12 +169,8 @@ export default class WaDrawer extends WebAwesomeElement {
this.emit('wa-show');
this.addOpenListeners();
this.originalTrigger = document.activeElement as HTMLElement;
// Lock body scrolling only if the drawer isn't contained
if (!this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
this.modal.activate();
lockBodyScrolling(this);
// When the drawer is shown, Safari will attempt to set focus on whatever element has autofocus. This causes the
// drawer's animation to jitter, so we'll temporarily remove the attribute, call `focus({ preventScroll: true })`
@@ -239,11 +219,8 @@ export default class WaDrawer extends WebAwesomeElement {
// Hide
this.emit('wa-hide');
this.removeOpenListeners();
if (!this.contained) {
this.modal.deactivate();
unlockBodyScrolling(this);
}
this.modal.deactivate();
unlockBodyScrolling(this);
await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]);
const panelAnimation = getAnimation(this, `drawer.hide${uppercaseFirstLetter(this.placement)}`, {
@@ -279,19 +256,6 @@ export default class WaDrawer extends WebAwesomeElement {
}
}
@watch('contained', { waitUntilFirstUpdate: true })
handleNoModalChange() {
if (this.open && !this.contained) {
this.modal.activate();
lockBodyScrolling(this);
}
if (this.open && this.contained) {
this.modal.deactivate();
unlockBodyScrolling(this);
}
}
/** Shows the drawer. */
async show() {
if (this.open) {
@@ -323,10 +287,9 @@ export default class WaDrawer extends WebAwesomeElement {
'drawer--end': this.placement === 'end',
'drawer--bottom': this.placement === 'bottom',
'drawer--start': this.placement === 'start',
'drawer--contained': this.contained,
'drawer--fixed': !this.contained,
'drawer--rtl': this.localize.dir() === 'rtl',
'drawer--has-footer': this.hasSlotController.test('footer')
'drawer--with-header': this.withHeader,
'drawer--with-footer': this.withFooter
})}
>
<div part="overlay" class="drawer__overlay" @click=${() => this.requestClose('overlay')} tabindex="-1"></div>
@@ -337,11 +300,11 @@ export default class WaDrawer extends WebAwesomeElement {
role="dialog"
aria-modal="true"
aria-hidden=${this.open ? 'false' : 'true'}
aria-label=${ifDefined(this.noHeader ? this.label : undefined)}
aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
aria-label=${ifDefined(this.withHeader ? undefined : this.label)}
aria-labelledby=${ifDefined(this.withHeader ? 'title' : undefined)}
tabindex="0"
>
${!this.noHeader
${this.withHeader
? html`
<header part="header" class="drawer__header">
<h2 part="title" class="drawer__title" id="title">
@@ -367,9 +330,13 @@ export default class WaDrawer extends WebAwesomeElement {
<slot part="body" class="drawer__body"></slot>
<footer part="footer" class="drawer__footer">
<slot name="footer"></slot>
</footer>
${this.withFooter
? html`
<footer part="footer" class="drawer__footer">
<slot name="footer"></slot>
</footer>
`
: ''}
</div>
</div>
`;

View File

@@ -11,6 +11,8 @@ export default css`
}
.drawer {
position: fixed;
z-index: var(--wa-z-index-drawer);
top: 0;
inset-inline-start: 0;
width: 100%;
@@ -19,16 +21,6 @@ export default css`
overflow: hidden;
}
.drawer--contained {
position: absolute;
z-index: initial;
}
.drawer--fixed {
position: fixed;
z-index: var(--wa-z-index-drawer);
}
.drawer__panel {
position: absolute;
display: flex;
@@ -129,10 +121,6 @@ export default css`
margin-inline-end: var(--wa-spacing-xs);
}
.drawer:not(.drawer--has-footer) .drawer__footer {
display: none;
}
.drawer__overlay {
display: block;
position: fixed;
@@ -144,10 +132,6 @@ export default css`
pointer-events: all;
}
.drawer--contained .drawer__overlay {
display: none;
}
@media (forced-colors: active) {
.drawer__panel {
border: solid 1px white;

View File

@@ -147,7 +147,7 @@ it('Should allow tabbing to slotted elements', async () => {
it('Should account for when focus is changed from outside sources (like clicking)', async () => {
const dialog = await fixture(html`
<wa-dialog open="" label="Dialog" class="dialog-overview">
<wa-dialog open="" label="Dialog" with-header with-footer class="dialog-overview">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<wa-input placeholder="tab to me"></wa-input>
<wa-button slot="footer" variant="primary">Close</wa-button>
@@ -188,12 +188,12 @@ it('Should respect nested modal instances', async () => {
await fixture(html`
<div>
<wa-button id="open-dialog-1" @click=${() => dialogOne().show()}></wa-button>
<wa-dialog id="dialog-1" label="Dialog 1">
<wa-dialog id="dialog-1" label="Dialog 1" with-header with-footer>
<wa-button @click=${() => dialogTwo().show()} id="open-dialog-2">Open Dialog 2</wa-button>
<wa-button slot="footer" variant="primary">Close</wa-button>
</wa-dialog>
<wa-dialog id="dialog-2" label="Dialog 2">
<wa-dialog id="dialog-2" label="Dialog 2" with-header with-footer>
<wa-input id="focus-1" autofocus="" placeholder="I will have focus when the dialog is opened"></wa-input>
<wa-input id="focus-2" placeholder="Second input"></wa-input>
<wa-button slot="footer" variant="primary" class="close-2">Close</wa-button>