Skip to content
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
1 change: 1 addition & 0 deletions angular/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export {
TextareaCustomEvent,
ToastOptions,
ToastButton,
ToastLayout,
ToggleChangeEventDetail,
ToggleCustomEvent,
} from '@ionic/core';
1 change: 1 addition & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,7 @@ ion-toast,prop,header,string | undefined,undefined,false,false
ion-toast,prop,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false
ion-toast,prop,icon,string | undefined,undefined,false,false
ion-toast,prop,keyboardClose,boolean,false,false,false
ion-toast,prop,layout,"baseline" | "stacked",'baseline',false,false
ion-toast,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-toast,prop,message,IonicSafeString | string | undefined,undefined,false,false
ion-toast,prop,mode,"ios" | "md",undefined,false,false
Expand Down
10 changes: 9 additions & 1 deletion core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { PickerInternalChangeEventDetail } from "./components/picker-internal/pi
import { PinFormatter } from "./components/range/range-interface";
import { NavigationHookCallback } from "./components/route/route-interface";
import { SelectCompareFn } from "./components/select/select-interface";
import { ToastAttributes, ToastPosition } from "./components/toast/toast-interface";
import { ToastAttributes, ToastLayout, ToastPosition } from "./components/toast/toast-interface";
export namespace Components {
interface IonAccordion {
/**
Expand Down Expand Up @@ -2976,6 +2976,10 @@ export namespace Components {
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
"keyboardClose": boolean;
/**
* 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.
*/
"layout": ToastLayout;
/**
* Animation to use when the toast is dismissed.
*/
Expand Down Expand Up @@ -6977,6 +6981,10 @@ declare namespace LocalJSX {
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
"keyboardClose"?: boolean;
/**
* 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.
*/
"layout"?: ToastLayout;
/**
* Animation to use when the toast is dismissed.
*/
Expand Down
57 changes: 57 additions & 0 deletions core/src/components/toast/test/layout/index.html
Original file line number Diff line number Diff line change
@@ -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>
15 changes: 15 additions & 0 deletions core/src/components/toast/test/layout/toast.e2e.ts
Original file line number Diff line number Diff line change
@@ -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`);
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions core/src/components/toast/toast-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface ToastOptions {
animated?: boolean;
icon?: string;
htmlAttributes?: ToastAttributes;
layout?: ToastLayout;

color?: Color;
mode?: Mode;
Expand All @@ -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;
Expand Down
12 changes: 10 additions & 2 deletions core/src/components/toast/toast.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
12 changes: 11 additions & 1 deletion core/src/components/toast/toast.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -134,6 +138,12 @@
display: flex;
}

.toast-layout-stacked .toast-button-group {
justify-content: end;

width: 100%;
}

.toast-button {
border: 0;

Expand Down
25 changes: 24 additions & 1 deletion core/src/components/toast/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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

Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -290,16 +300,29 @@ 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');
const mode = getIonMode(this);
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"
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export {
TextareaCustomEvent,
ToastOptions,
ToastButton,
ToastLayout,
ToggleChangeEventDetail,
ToggleCustomEvent,
} from '@ionic/core/components';
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/src/components/Overlays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const IonLoading = /*@__PURE__*/ defineOverlayContainer<JSX.IonLoading>('

export const IonPicker = /*@__PURE__*/ defineOverlayContainer<JSX.IonPicker>('ion-picker', defineIonPickerCustomElement, ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop'], pickerController);

export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);
export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);

export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']);

Expand Down
1 change: 1 addition & 0 deletions packages/vue/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export {
TextareaCustomEvent,
ToastOptions,
ToastButton,
ToastLayout,
ToggleChangeEventDetail,
ToggleCustomEvent,
} from "@ionic/core/components";