feat(toast): add stacked buttons functionality (#26790)

This commit is contained in:
Liam DeBeasi
2023-02-15 09:56:02 -05:00
committed by GitHub
parent daa89a26ac
commit fc5fcc064d
24 changed files with 134 additions and 6 deletions

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Toast - Layout</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<script type="module">
import { toastController } from '../../../../dist/ionic/index.esm.js';
window.toastController = toastController;
</script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Toast - Layout</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button id="baseline" onclick="openToast(baselineConfig)">Open Baseline Layout Toast</ion-button>
<ion-button id="stacked" onclick="openToast(stackedConfig)">Open Stacked Layout Toast</ion-button>
</ion-content>
<script>
async function openToast(opts) {
const toast = await toastController.create(opts);
await toast.present();
}
const baselineConfig = {
icon: 'globe',
header: 'Toast Header',
message: 'This is an inline layout toast.',
buttons: [
{ side: 'start', text: 'Start Button', icon: 'alarm' },
{ side: 'end', text: 'End Button', icon: 'bonfire' },
],
};
const stackedConfig = {
...baselineConfig,
message: 'This is a stacked layout toast.',
layout: 'stacked',
};
</script>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,15 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('toast: stacked layout', () => {
test('should render stacked buttons', async ({ page }) => {
await page.goto('/src/components/toast/test/layout');
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
await page.click('#stacked');
await ionToastDidPresent.next();
const toastWrapper = page.locator('ion-toast .toast-wrapper');
expect(await toastWrapper.screenshot()).toMatchSnapshot(`toast-stacked-${page.getSnapshotSettings()}.png`);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -12,6 +12,7 @@ export interface ToastOptions {
animated?: boolean;
icon?: string;
htmlAttributes?: ToastAttributes;
layout?: ToastLayout;
color?: Color;
mode?: Mode;
@ -27,6 +28,8 @@ export interface ToastOptions {
*/
export type ToastAttributes = { [key: string]: any };
export type ToastLayout = 'baseline' | 'stacked';
export interface ToastButton {
text?: string;
icon?: string;

View File

@ -49,14 +49,22 @@
// --------------------------------------------------
.toast-button-group-start {
.toast-layout-baseline .toast-button-group-start {
@include margin(null, null, null, 8px);
}
.toast-button-group-end {
.toast-layout-stacked .toast-button-group-start {
@include margin(8px, 8px, null, null);
}
.toast-layout-baseline .toast-button-group-end {
@include margin(null, 8px, null, null);
}
.toast-layout-stacked .toast-button-group-end {
@include margin(null, 8px, 8px, null);
}
.toast-button {
@include padding($toast-md-button-padding-top, $toast-md-button-padding-end, $toast-md-button-padding-bottom, $toast-md-button-padding-start);

View File

@ -112,7 +112,11 @@
contain: content;
}
.toast-content {
.toast-layout-stacked .toast-container {
flex-wrap: wrap;
}
.toast-layout-baseline .toast-content {
display: flex;
flex: 1;
@ -134,6 +138,12 @@
display: flex;
}
.toast-layout-stacked .toast-button-group {
justify-content: end;
width: 100%;
}
.toast-button {
border: 0;

View File

@ -11,6 +11,7 @@ import type {
OverlayInterface,
ToastButton,
} from '../../interface';
import { printIonWarning } from '../../utils/logging';
import { dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
import type { IonicSafeString } from '../../utils/sanitization';
import { sanitizeDOMString } from '../../utils/sanitization';
@ -20,7 +21,7 @@ import { iosEnterAnimation } from './animations/ios.enter';
import { iosLeaveAnimation } from './animations/ios.leave';
import { mdEnterAnimation } from './animations/md.enter';
import { mdLeaveAnimation } from './animations/md.leave';
import type { ToastAttributes, ToastPosition } from './toast-interface';
import type { ToastAttributes, ToastPosition, ToastLayout } from './toast-interface';
// TODO(FW-2832): types
@ -87,6 +88,15 @@ export class Toast implements ComponentInterface, OverlayInterface {
*/
@Prop() header?: string;
/**
* Defines how the message and buttons are laid out in the toast.
* 'baseline': The message and the buttons will appear on the same line.
* Message text may wrap within the message container.
* 'stacked': The buttons containers and message will stack on top
* of each other. Use this if you have long text in your buttons.
*/
@Prop() layout: ToastLayout = 'baseline';
/**
* Message to be shown in the toast.
*/
@ -290,6 +300,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
}
render() {
const { layout, el } = this;
const allButtons = this.getButtons();
const startButtons = allButtons.filter((b) => b.side === 'start');
const endButtons = allButtons.filter((b) => b.side !== 'start');
@ -297,9 +308,21 @@ export class Toast implements ComponentInterface, OverlayInterface {
const wrapperClass = {
'toast-wrapper': true,
[`toast-${this.position}`]: true,
[`toast-layout-${layout}`]: true,
};
const role = allButtons.length > 0 ? 'dialog' : 'status';
/**
* Stacked buttons are only meant to be
* used with one type of button.
*/
if (layout === 'stacked' && startButtons.length > 0 && endButtons.length > 0) {
printIonWarning(
'This toast is using start and end buttons with the stacked toast layout. We recommend following the best practice of using either start or end buttons with the stacked toast layout.',
el
);
}
return (
<Host
aria-live="polite"