diff --git a/core/api.txt b/core/api.txt index 3805cae0ac..ea4944a2be 100644 --- a/core/api.txt +++ b/core/api.txt @@ -2376,6 +2376,7 @@ ion-toast,prop,message,IonicSafeString | string | undefined,undefined,false,fals ion-toast,prop,mode,"ios" | "md",undefined,false,false ion-toast,prop,position,"bottom" | "middle" | "top",'bottom',false,false ion-toast,prop,positionAnchor,HTMLElement | string | undefined,undefined,false,false +ion-toast,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false ion-toast,prop,swipeGesture,"vertical" | undefined,undefined,false,false ion-toast,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-toast,prop,translucent,boolean,false,false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index f8c88b745b..77444d293f 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -3771,6 +3771,10 @@ export namespace Components { * Present the toast overlay after it has been created. */ "present": () => Promise; + /** + * Set to `"soft"` for a toast with slightly rounded corners, `"round"` for a toast with fully rounded corners, or `"rectangular"` for a toast without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes. + */ + "shape"?: 'soft' | 'round' | 'rectangular'; /** * If set to 'vertical', the Toast can be dismissed with a swipe gesture. The swipe direction is determined by the value of the `position` property: `top`: The Toast can be swiped up to dismiss. `bottom`: The Toast can be swiped down to dismiss. `middle`: The Toast can be swiped up or down to dismiss. */ @@ -9161,6 +9165,10 @@ declare namespace LocalJSX { * The element to anchor the toast's position to. Can be set as a direct reference or the ID of the element. With `position="bottom"`, the toast will sit above the chosen element. With `position="top"`, the toast will sit below the chosen element. With `position="middle"`, the value of `positionAnchor` is ignored. */ "positionAnchor"?: HTMLElement | string; + /** + * Set to `"soft"` for a toast with slightly rounded corners, `"round"` for a toast with fully rounded corners, or `"rectangular"` for a toast without rounded corners. Defaults to `"round"` for the `ionic` theme, undefined for all other themes. + */ + "shape"?: 'soft' | 'round' | 'rectangular'; /** * If set to 'vertical', the Toast can be dismissed with a swipe gesture. The swipe direction is determined by the value of the `position` property: `top`: The Toast can be swiped up to dismiss. `bottom`: The Toast can be swiped down to dismiss. `middle`: The Toast can be swiped up or down to dismiss. */ diff --git a/core/src/components/toast/test/shape/index.html b/core/src/components/toast/test/shape/index.html new file mode 100644 index 0000000000..bcf9a150cf --- /dev/null +++ b/core/src/components/toast/test/shape/index.html @@ -0,0 +1,66 @@ + + + + + Toast - Shape + + + + + + + + + + + + + Toast - Shape + + + + + (Only available for ionic theme) + + + + + + + + + + + + diff --git a/core/src/components/toast/test/shape/toast.e2e.ts b/core/src/components/toast/test/shape/toast.e2e.ts new file mode 100644 index 0000000000..7ddeecc33a --- /dev/null +++ b/core/src/components/toast/test/shape/toast.e2e.ts @@ -0,0 +1,78 @@ +import { expect, type Locator } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; +import type { E2EPage, E2EPageOptions, ScreenshotFn, EventSpy } from '@utils/test/playwright'; + +class ToastFixture { + readonly page: E2EPage; + + private ionToastDidPresent!: EventSpy; + + constructor(page: E2EPage) { + this.page = page; + } + + async goto(config: E2EPageOptions) { + const { page } = this; + await page.goto(`/src/components/toast/test/shape`, config); + this.ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); + } + + async openToast(selector: string) { + const { page, ionToastDidPresent } = this; + const button = page.locator(selector); + await button.click(); + + await ionToastDidPresent.next(); + + return { + toast: page.locator('ion-toast'), + container: page.locator('ion-toast .toast-container'), + }; + } + + async screenshot(screenshotModifier: string, screenshot: ScreenshotFn, el?: Locator) { + const { page } = this; + + const screenshotString = screenshot(`toast-${screenshotModifier}`); + + if (el === undefined) { + await expect(page).toHaveScreenshot(screenshotString); + } else { + await expect(el).toHaveScreenshot(screenshotString); + } + } +} + +/** + * This behavior does not vary across directions. + */ +configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ config, screenshot, title }) => { + test.describe(title('toast: shape'), () => { + let toastFixture: ToastFixture; + + test.beforeEach(async ({ page }) => { + toastFixture = new ToastFixture(page); + await toastFixture.goto(config); + }); + + test('should render the default toast', async () => { + await toastFixture.openToast('#default-toast'); + await toastFixture.screenshot('shape-round', screenshot); + }); + + test('should render a soft toast', async () => { + await toastFixture.openToast('#soft-shape-toast'); + await toastFixture.screenshot('shape-soft', screenshot); + }); + + test('should render a round toast', async () => { + await toastFixture.openToast('#round-shape-toast'); + await toastFixture.screenshot('shape-round', screenshot); + }); + + test('should render a rectangular toast', async () => { + await toastFixture.openToast('#rect-shape-toast'); + await toastFixture.screenshot('shape-rectangular', screenshot); + }); + }); +}); diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a5f71ee19c Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..872777d841 Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4f02a860f7 Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..14702fbc6e Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..c45f0407bf Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 0000000000..10dd94804a Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-round-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..058a292211 Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..2d465bfb99 Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 0000000000..acbf3f8d43 Binary files /dev/null and b/core/src/components/toast/test/shape/toast.e2e.ts-snapshots/toast-shape-soft-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/toast.ionic.scss b/core/src/components/toast/toast.ionic.scss index 3b2f74bd41..2f3bfced63 100644 --- a/core/src/components/toast/toast.ionic.scss +++ b/core/src/components/toast/toast.ionic.scss @@ -6,7 +6,6 @@ :host { --background: #{globals.$ionic-color-neutral-1200}; - --border-radius: #{globals.$ionic-border-radius-400}; --box-shadow: #{globals.$ionic-elevation-400}; --button-color: #{globals.$ionic-color-base-white}; --color: #{globals.$ionic-color-base-white}; @@ -36,6 +35,21 @@ @include globals.padding(globals.$ionic-space-300, globals.$ionic-space-400); } +// Toast Shapes +// -------------------------------------------------- + +:host(.toast-shape-soft) { + --border-radius: #{globals.$ionic-border-radius-200}; +} + +:host(.toast-shape-round) { + --border-radius: #{globals.$ionic-border-radius-400}; +} + +:host(.toast-shape-rectangular) { + --border-radius: #{globals.$ionic-border-radius-0}; +} + // Toast Header // -------------------------------------------------- diff --git a/core/src/components/toast/toast.tsx b/core/src/components/toast/toast.tsx index 7b8461e717..bcffad5ca5 100644 --- a/core/src/components/toast/toast.tsx +++ b/core/src/components/toast/toast.tsx @@ -172,6 +172,15 @@ export class Toast implements ComponentInterface, OverlayInterface { */ @Prop() positionAnchor?: HTMLElement | string; + /** + * Set to `"soft"` for a toast with slightly rounded corners, + * `"round"` for a toast with fully rounded corners, or `"rectangular"` + * for a toast without rounded corners. + * + * Defaults to `"round"` for the `ionic` theme, undefined for all other themes. + */ + @Prop() shape?: 'soft' | 'round' | 'rectangular'; + /** * An array of buttons for the toast. */ @@ -484,6 +493,21 @@ export class Toast implements ComponentInterface, OverlayInterface { return buttons; } + private getShape(): string | undefined { + const { shape } = this; + + // TODO(ROU-11300): Remove theme check when shapes are defined for all themes. + if (getIonTheme(this) !== 'ionic') { + return undefined; + } + + if (shape === undefined) { + return 'round'; + } + + return shape; + } + /** * Returns the element specified by the positionAnchor prop, * or undefined if prop's value is an ID string and the element @@ -696,6 +720,7 @@ export class Toast implements ComponentInterface, OverlayInterface { const startButtons = allButtons.filter((b) => b.side === 'start'); const endButtons = allButtons.filter((b) => b.side !== 'start'); const theme = getIonTheme(this); + const shape = this.getShape(); const wrapperClass = { 'toast-wrapper': true, [`toast-${this.position}`]: true, @@ -725,6 +750,7 @@ export class Toast implements ComponentInterface, OverlayInterface { ...getClassMap(this.cssClass), 'overlay-hidden': true, 'toast-translucent': this.translucent, + [`toast-shape-${shape}`]: shape !== undefined, })} onIonToastWillDismiss={this.dispatchCancelHandler} > diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index a6377d16c0..e38fa10e0e 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -2341,7 +2341,7 @@ export declare interface IonTitle extends Components.IonTitle {} @ProxyCmp({ - inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'swipeGesture', 'theme', 'translucent', 'trigger'], + inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'], methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'] }) @Component({ @@ -2349,7 +2349,7 @@ export declare interface IonTitle extends Components.IonTitle {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'swipeGesture', 'theme', 'translucent', 'trigger'], + inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'], }) export class IonToast { protected el: HTMLElement; diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index 10223039d1..0f97609f21 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -2092,7 +2092,7 @@ export declare interface IonTitle extends Components.IonTitle {} @ProxyCmp({ defineCustomElementFn: defineIonToast, - inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'swipeGesture', 'theme', 'translucent', 'trigger'], + inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'], methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'] }) @Component({ @@ -2100,7 +2100,7 @@ export declare interface IonTitle extends Components.IonTitle {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'swipeGesture', 'theme', 'translucent', 'trigger'], + inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'], standalone: true }) export class IonToast { diff --git a/packages/vue/src/components/Overlays.ts b/packages/vue/src/components/Overlays.ts index 23921286d1..0506a1c914 100644 --- a/packages/vue/src/components/Overlays.ts +++ b/packages/vue/src/components/Overlays.ts @@ -25,7 +25,7 @@ export const IonLoading = /*@__PURE__*/ defineOverlayContainer(' export const IonPickerLegacy = /*@__PURE__*/ defineOverlayContainer('ion-picker-legacy', defineIonPickerLegacyCustomElement, ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop', 'theme', 'trigger']); -export const IonToast = /*@__PURE__*/ defineOverlayContainer('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'swipeGesture', 'theme', 'translucent', 'trigger']); +export const IonToast = /*@__PURE__*/ defineOverlayContainer('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger']); export const IonModal = /*@__PURE__*/ defineOverlayContainer('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'focusTrap', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'shape', 'showBackdrop', 'theme', 'trigger'], true);