diff --git a/angular/src/index.ts b/angular/src/index.ts index a28a12dfef..38d2130b7b 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -130,6 +130,7 @@ export { TextareaCustomEvent, ToastOptions, ToastButton, + ToastLayout, ToggleChangeEventDetail, ToggleCustomEvent, } from '@ionic/core'; diff --git a/core/api.txt b/core/api.txt index c22df0bde2..8eb31c0604 100644 --- a/core/api.txt +++ b/core/api.txt @@ -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 diff --git a/core/src/components.d.ts b/core/src/components.d.ts index f5f6867991..1164b07ad0 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -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 { /** @@ -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. */ @@ -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. */ diff --git a/core/src/components/toast/test/layout/index.html b/core/src/components/toast/test/layout/index.html new file mode 100644 index 0000000000..3562accab7 --- /dev/null +++ b/core/src/components/toast/test/layout/index.html @@ -0,0 +1,57 @@ + + + + + Toast - Layout + + + + + + + + + + + + + Toast - Layout + + + + + Open Baseline Layout Toast + Open Stacked Layout Toast + + + + + + diff --git a/core/src/components/toast/test/layout/toast.e2e.ts b/core/src/components/toast/test/layout/toast.e2e.ts new file mode 100644 index 0000000000..f5c22cd1b1 --- /dev/null +++ b/core/src/components/toast/test/layout/toast.e2e.ts @@ -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`); + }); +}); diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..1a906abf68 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..d7f8266d09 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..1d24d41c61 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..1b0edc0efa Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..5108314254 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Safari-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..fdeff15643 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..1ba4b083e3 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..ecab85e31f Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Safari-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..d5827d5ca9 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Chrome-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..5a20ad3558 Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Firefox-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..379adbf2bd Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Safari-linux.png b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..e592c426bd Binary files /dev/null and b/core/src/components/toast/test/layout/toast.e2e.ts-snapshots/toast-stacked-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/toast-interface.ts b/core/src/components/toast/toast-interface.ts index 9b417e3741..364ee9f2cd 100644 --- a/core/src/components/toast/toast-interface.ts +++ b/core/src/components/toast/toast-interface.ts @@ -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; diff --git a/core/src/components/toast/toast.md.scss b/core/src/components/toast/toast.md.scss index 7581c4b8af..968afe913a 100644 --- a/core/src/components/toast/toast.md.scss +++ b/core/src/components/toast/toast.md.scss @@ -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); diff --git a/core/src/components/toast/toast.scss b/core/src/components/toast/toast.scss index 0a44e69808..0418e86c7c 100644 --- a/core/src/components/toast/toast.scss +++ b/core/src/components/toast/toast.scss @@ -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; diff --git a/core/src/components/toast/toast.tsx b/core/src/components/toast/toast.tsx index cc941008e7..50812b7a06 100644 --- a/core/src/components/toast/toast.tsx +++ b/core/src/components/toast/toast.tsx @@ -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 ( (' export const IonPicker = /*@__PURE__*/ defineOverlayContainer('ion-picker', defineIonPickerCustomElement, ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop'], pickerController); -export const IonToast = /*@__PURE__*/ defineOverlayContainer('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController); +export const IonToast = /*@__PURE__*/ defineOverlayContainer('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('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']); diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 4a7bab011f..b3af281c14 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -121,6 +121,7 @@ export { TextareaCustomEvent, ToastOptions, ToastButton, + ToastLayout, ToggleChangeEventDetail, ToggleCustomEvent, } from "@ionic/core/components";