feat(toast): add hue property for the ionic theme (#30333)
Issue number: internal --------- ## What is the current behavior? The toast component does not support the `hue` property. ## What is the new behavior? Adds support for the `subtle` hue for the toast. Defaults to `subtle`. ## Does this introduce a breaking change? - [ ] Yes - [x] No
8
core/src/components.d.ts
vendored
@@ -3844,6 +3844,10 @@ export namespace Components {
|
||||
* Additional attributes to pass to the toast.
|
||||
*/
|
||||
"htmlAttributes"?: { [key: string]: any };
|
||||
/**
|
||||
* Set to `"bold"` for a toast with vibrant, bold colors or to `"subtle"` for a toast with muted, subtle colors. Only applies to the `ionic` theme.
|
||||
*/
|
||||
"hue"?: 'bold' | 'subtle';
|
||||
/**
|
||||
* The name of the icon to display, or the path to a valid SVG file. See `ion-icon`. https://ionic.io/ionicons
|
||||
*/
|
||||
@@ -9384,6 +9388,10 @@ declare namespace LocalJSX {
|
||||
* Additional attributes to pass to the toast.
|
||||
*/
|
||||
"htmlAttributes"?: { [key: string]: any };
|
||||
/**
|
||||
* Set to `"bold"` for a toast with vibrant, bold colors or to `"subtle"` for a toast with muted, subtle colors. Only applies to the `ionic` theme.
|
||||
*/
|
||||
"hue"?: 'bold' | 'subtle';
|
||||
/**
|
||||
* The name of the icon to display, or the path to a valid SVG file. See `ion-icon`. https://ionic.io/ionicons
|
||||
*/
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
123
core/src/components/toast/test/hue/index.html
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Toast - Hue</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>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Toast - Hue</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding" id="content">
|
||||
<p>
|
||||
Toasts are presented indefinitely but can be closed by clicking the backdrop. The backdrop has been added for
|
||||
demo purposes only.
|
||||
</p>
|
||||
|
||||
<button id="show-subtle-toasts" class="expand">Show All Subtle Toasts</button>
|
||||
<button id="show-bold-toasts" class="expand">Show All Bold Toasts</button>
|
||||
</ion-content>
|
||||
|
||||
<div id="backdrop" class="backdrop"></div>
|
||||
|
||||
<ion-toast position="top"></ion-toast>
|
||||
<ion-toast color="primary" position="top"></ion-toast>
|
||||
<ion-toast color="secondary" position="top"></ion-toast>
|
||||
<ion-toast color="tertiary" position="top"></ion-toast>
|
||||
<ion-toast color="success" position="top"></ion-toast>
|
||||
<ion-toast color="warning" position="top"></ion-toast>
|
||||
<ion-toast color="danger" position="top"></ion-toast>
|
||||
<ion-toast color="light" position="top"></ion-toast>
|
||||
<ion-toast color="medium" position="top"></ion-toast>
|
||||
<ion-toast color="dark" position="top"></ion-toast>
|
||||
</ion-app>
|
||||
|
||||
<script type="module">
|
||||
let lastToast = null;
|
||||
let toastOffset = 10;
|
||||
|
||||
// Show all toasts when the button is clicked
|
||||
function openAllToasts(isBold) {
|
||||
toastOffset = 10;
|
||||
|
||||
const toasts = document.querySelectorAll('ion-toast');
|
||||
|
||||
toasts.forEach((toast, index) => {
|
||||
toast.removeAttribute('hue');
|
||||
toast.hue = isBold ? 'bold' : 'subtle';
|
||||
const hue = toast.hue;
|
||||
|
||||
const color = toast.color || 'default';
|
||||
toast.message = `This is a ${color} toast.`;
|
||||
|
||||
toast.icon = 'information-circle';
|
||||
|
||||
toast.buttons = [
|
||||
{
|
||||
text: 'Action',
|
||||
},
|
||||
{
|
||||
icon: 'close',
|
||||
role: 'cancel',
|
||||
},
|
||||
];
|
||||
|
||||
// Set dynamic position for each toast to ensure they don't overlap
|
||||
toast.style.position = 'absolute';
|
||||
toast.style.top = `${toastOffset}px`;
|
||||
toast.style.left = '50%';
|
||||
toast.style.transform = 'translateX(-50%)';
|
||||
|
||||
toast.present();
|
||||
|
||||
// Update the toastOffset for the next toast to ensure it's positioned below the previous one
|
||||
toastOffset += 60;
|
||||
});
|
||||
|
||||
document.getElementById('backdrop').style.display = 'block';
|
||||
}
|
||||
|
||||
// Dismiss all toasts when backdrop is clicked
|
||||
function dismissAllToasts() {
|
||||
const toasts = document.querySelectorAll('ion-toast');
|
||||
toasts.forEach((toast) => toast.dismiss());
|
||||
|
||||
document.getElementById('backdrop').style.display = 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('show-subtle-toasts').addEventListener('click', () => openAllToasts(false));
|
||||
document.getElementById('show-bold-toasts').addEventListener('click', () => openAllToasts(true));
|
||||
|
||||
document.getElementById('backdrop').addEventListener('click', dismissAllToasts);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
59
core/src/components/toast/test/hue/toast.e2e.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { E2EPage, E2EPageOptions, ScreenshotFn, EventSpy } from '@utils/test/playwright';
|
||||
import { configs, test } 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/hue`, 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'),
|
||||
};
|
||||
}
|
||||
|
||||
async screenshot(screenshotModifier: string, screenshot: ScreenshotFn) {
|
||||
const { page } = this;
|
||||
|
||||
const screenshotString = screenshot(`toast-${screenshotModifier}`);
|
||||
|
||||
await expect(page).toHaveScreenshot(screenshotString);
|
||||
}
|
||||
}
|
||||
|
||||
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('toast: hue'), () => {
|
||||
let toastFixture: ToastFixture;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
toastFixture = new ToastFixture(page);
|
||||
await toastFixture.goto(config);
|
||||
});
|
||||
|
||||
test('should show all subtle toasts', async () => {
|
||||
await toastFixture.openToast('#show-subtle-toasts');
|
||||
await toastFixture.screenshot('subtle', screenshot);
|
||||
});
|
||||
|
||||
test('should show all bold toasts', async () => {
|
||||
await toastFixture.openToast('#show-bold-toasts');
|
||||
await toastFixture.screenshot('bold', screenshot);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
@@ -5,10 +5,7 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
--background: #{globals.$ion-primitives-neutral-1200};
|
||||
--box-shadow: #{globals.$ion-elevation-4};
|
||||
--button-color: #{globals.$ion-primitives-base-white};
|
||||
--color: #{globals.$ion-primitives-base-white};
|
||||
--max-width: 343px;
|
||||
--start: 8px;
|
||||
--end: 8px;
|
||||
@@ -132,3 +129,41 @@
|
||||
.toast-button-icon {
|
||||
font-size: globals.$ion-scale-600;
|
||||
}
|
||||
|
||||
// Bold Toast
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.toast-hue-bold) {
|
||||
--background: #{globals.$ion-bg-neutral-boldest-default};
|
||||
--background-activated: #{globals.$ion-bg-neutral-boldest-press};
|
||||
--color: #{globals.$ion-text-inverse};
|
||||
--button-color: #{globals.$ion-text-inverse};
|
||||
}
|
||||
|
||||
:host(.toast-hue-bold.ion-color) .toast-wrapper {
|
||||
background: globals.current-color(base);
|
||||
color: globals.current-color(contrast);
|
||||
}
|
||||
|
||||
:host(.toast-hue-bold.ion-color) .toast-button {
|
||||
color: globals.current-color(contrast);
|
||||
}
|
||||
|
||||
// Subtle Toast
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.toast-hue-subtle) {
|
||||
--background: #{globals.$ion-bg-neutral-subtlest-default};
|
||||
--background-activated: #{globals.$ion-bg-neutral-subtlest-press};
|
||||
--color: #{globals.$ion-text-default};
|
||||
--button-color: #{globals.$ion-text-link-default};
|
||||
}
|
||||
|
||||
:host(.toast-hue-subtle) .toast-button-cancel {
|
||||
color: #{globals.$ion-icon-subtlest};
|
||||
}
|
||||
|
||||
:host(.toast-hue-subtle.ion-color) .toast-wrapper {
|
||||
background: globals.current-color(base, $subtle: true);
|
||||
color: globals.current-color(contrast, $subtle: true);
|
||||
}
|
||||
|
||||
@@ -135,6 +135,14 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
||||
*/
|
||||
@Prop() header?: string;
|
||||
|
||||
/**
|
||||
* Set to `"bold"` for a toast with vibrant, bold colors or to `"subtle"` for
|
||||
* a toast with muted, subtle colors.
|
||||
*
|
||||
* Only applies to the `ionic` theme.
|
||||
*/
|
||||
@Prop() hue?: 'bold' | 'subtle' = 'subtle';
|
||||
|
||||
/**
|
||||
* Defines how the message and buttons are laid out in the toast.
|
||||
* 'baseline': The message and the buttons will appear on the same line.
|
||||
@@ -715,7 +723,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { layout, el, revealContentToScreenReader, header, message } = this;
|
||||
const { layout, el, revealContentToScreenReader, header, hue, message } = this;
|
||||
const allButtons = this.getButtons();
|
||||
const startButtons = allButtons.filter((b) => b.side === 'start');
|
||||
const endButtons = allButtons.filter((b) => b.side !== 'start');
|
||||
@@ -753,6 +761,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
||||
'overlay-hidden': true,
|
||||
'toast-translucent': this.translucent,
|
||||
[`toast-shape-${shape}`]: shape !== undefined,
|
||||
[`toast-hue-${hue}`]: hue !== undefined,
|
||||
})}
|
||||
onIonToastWillDismiss={this.dispatchCancelHandler}
|
||||
>
|
||||
|
||||