Skip to content

Commit 5f2e195

Browse files
committed
feat(toast): add subtle hue for the ionic theme
1 parent 8fc775f commit 5f2e195

File tree

6 files changed

+189
-5
lines changed

6 files changed

+189
-5
lines changed

core/src/components.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3832,6 +3832,10 @@ export namespace Components {
38323832
* Additional attributes to pass to the toast.
38333833
*/
38343834
"htmlAttributes"?: { [key: string]: any };
3835+
/**
3836+
* Set to `"bold"` for a toast with vibrant, bold colors or to `"subtle"` for a toast with muted, subtle colors.
3837+
*/
3838+
"hue"?: 'bold' | 'subtle';
38353839
/**
38363840
* The name of the icon to display, or the path to a valid SVG file. See `ion-icon`. https://ionic.io/ionicons
38373841
*/
@@ -9360,6 +9364,10 @@ declare namespace LocalJSX {
93609364
* Additional attributes to pass to the toast.
93619365
*/
93629366
"htmlAttributes"?: { [key: string]: any };
9367+
/**
9368+
* Set to `"bold"` for a toast with vibrant, bold colors or to `"subtle"` for a toast with muted, subtle colors.
9369+
*/
9370+
"hue"?: 'bold' | 'subtle';
93639371
/**
93649372
* The name of the icon to display, or the path to a valid SVG file. See `ion-icon`. https://ionic.io/ionicons
93659373
*/
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Toast - Colors</title>
6+
<meta
7+
name="viewport"
8+
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
9+
/>
10+
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
11+
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
12+
<script src="../../../../../scripts/testing/scripts.js"></script>
13+
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
14+
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
15+
</head>
16+
17+
<script type="module">
18+
import { toastController } from '../../../../dist/ionic/index.esm.js';
19+
window.toastController = toastController;
20+
</script>
21+
22+
<body>
23+
<ion-app>
24+
<ion-header>
25+
<ion-toolbar>
26+
<ion-title>Toast - Colors</ion-title>
27+
</ion-toolbar>
28+
</ion-header>
29+
30+
<ion-content class="ion-padding" id="content">
31+
<p>
32+
Toasts are presented indefinitely but can be closed by clicking the backdrop. The backdrop has been added for
33+
demo purposes only.
34+
</p>
35+
36+
<button class="expand" onclick="openAllToasts(false)">Show All Bold Toasts</button>
37+
<button class="expand" onclick="openAllToasts(true)">Show All Subtle Toasts</button>
38+
</ion-content>
39+
40+
<div id="toastBackdrop" class="backdrop"></div>
41+
</ion-app>
42+
43+
<script>
44+
const colorIconMap = {
45+
primary: { color: 'primary', icon: 'alert-circle' },
46+
secondary: { color: 'secondary', icon: 'information-circle' },
47+
tertiary: { color: 'tertiary', icon: 'alert-circle' },
48+
success: { color: 'success', icon: 'checkmark-circle' },
49+
warning: { color: 'warning', icon: 'warning' },
50+
danger: { color: 'danger', icon: 'alert-circle' },
51+
light: { color: 'light', icon: 'alert-circle' },
52+
medium: { color: 'medium', icon: 'alert-circle' },
53+
dark: { color: 'dark', icon: 'alert-circle' },
54+
};
55+
let toastCount = 0;
56+
let activeToasts = [];
57+
58+
// Show backdrop when toasts are presented
59+
function showBackdrop() {
60+
const backdrop = document.getElementById('toastBackdrop');
61+
if (toastCount === 1) {
62+
backdrop.style.display = 'block';
63+
}
64+
backdrop.addEventListener('click', closeAllToasts);
65+
}
66+
67+
// Hide backdrop when all toasts are dismissed
68+
function hideBackdrop() {
69+
const backdrop = document.getElementById('toastBackdrop');
70+
if (toastCount === 0) {
71+
backdrop.style.display = 'none';
72+
backdrop.removeEventListener('click', closeAllToasts);
73+
}
74+
}
75+
76+
// Close all toasts when the backdrop is clicked
77+
function closeAllToasts() {
78+
activeToasts.forEach((toast) => {
79+
toast.dismiss();
80+
});
81+
toastCount = 0;
82+
activeToasts = [];
83+
hideBackdrop();
84+
}
85+
86+
async function openToast(opts, delay = 0) {
87+
setTimeout(async () => {
88+
const yOffset = toastCount * 60;
89+
toastCount++;
90+
91+
const toast = await toastController.create({
92+
...opts,
93+
position: 'top',
94+
animated: false,
95+
});
96+
97+
await toast.present();
98+
activeToasts.push(toast);
99+
100+
showBackdrop();
101+
102+
requestAnimationFrame(() => {
103+
const toastEl = document.querySelector('ion-toast:last-of-type');
104+
if (toastEl) {
105+
toastEl.style.position = 'absolute';
106+
toastEl.style.left = '50%';
107+
toastEl.style.transform = 'translateX(-50%)';
108+
toastEl.style.top = `${10 + yOffset}px`;
109+
}
110+
});
111+
112+
toast.onDidDismiss().then(() => {
113+
toastCount--;
114+
activeToasts = activeToasts.filter((t) => t !== toast);
115+
hideBackdrop();
116+
});
117+
}, delay);
118+
}
119+
120+
// Show all toasts sequentially with a slight delay between each
121+
function openAllToasts(isSubtle) {
122+
toastCount = 0;
123+
activeToasts = [];
124+
125+
Object.values(colorIconMap).forEach((item, index) => {
126+
openToast(
127+
{
128+
message: `This is a ${item.color} toast.`,
129+
buttons: [{ text: 'Action' }, { icon: 'close', role: 'cancel' }],
130+
color: item.color,
131+
icon: item.icon,
132+
...(isSubtle ? { hue: 'subtle' } : {}),
133+
},
134+
index * 150
135+
);
136+
});
137+
}
138+
</script>
139+
140+
<style>
141+
.backdrop {
142+
position: fixed;
143+
top: 0;
144+
left: 0;
145+
width: 100%;
146+
height: 100%;
147+
background-color: rgba(0, 0, 0, 0.5);
148+
display: none;
149+
z-index: 9998;
150+
pointer-events: all;
151+
backdrop-filter: blur(5px);
152+
}
153+
</style>
154+
</body>
155+
</html>

core/src/components/toast/toast.ionic.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,17 @@
132132
.toast-button-icon {
133133
font-size: globals.$ion-scale-600;
134134
}
135+
136+
// Subtle Toast
137+
// --------------------------------------------------
138+
139+
:host(.toast-hue-subtle) .toast-wrapper {
140+
--background: #{globals.ion-color(primary, base, $subtle: true)};
141+
--background-activated: #{globals.ion-color(primary, shade, $subtle: true)};
142+
--color: #{globals.ion-color(primary, contrast, $subtle: true)};
143+
}
144+
145+
:host(.toast-hue-subtle.ion-color) .toast-wrapper {
146+
background: globals.current-color(base, $subtle: true);
147+
color: globals.current-color(contrast, $subtle: true);
148+
}

core/src/components/toast/toast.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ export class Toast implements ComponentInterface, OverlayInterface {
135135
*/
136136
@Prop() header?: string;
137137

138+
/**
139+
* Set to `"bold"` for a toast with vibrant, bold colors or to `"subtle"` for
140+
* a toast with muted, subtle colors.
141+
*/
142+
@Prop() hue?: 'bold' | 'subtle' = 'bold';
143+
138144
/**
139145
* Defines how the message and buttons are laid out in the toast.
140146
* 'baseline': The message and the buttons will appear on the same line.
@@ -715,7 +721,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
715721
}
716722

717723
render() {
718-
const { layout, el, revealContentToScreenReader, header, message } = this;
724+
const { layout, el, revealContentToScreenReader, header, hue, message } = this;
719725
const allButtons = this.getButtons();
720726
const startButtons = allButtons.filter((b) => b.side === 'start');
721727
const endButtons = allButtons.filter((b) => b.side !== 'start');
@@ -753,6 +759,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
753759
'overlay-hidden': true,
754760
'toast-translucent': this.translucent,
755761
[`toast-shape-${shape}`]: shape !== undefined,
762+
[`toast-hue-${hue}`]: hue !== undefined,
756763
})}
757764
onIonToastWillDismiss={this.dispatchCancelHandler}
758765
>

packages/angular/src/directives/proxies.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2439,15 +2439,15 @@ export declare interface IonTitle extends Components.IonTitle {}
24392439

24402440

24412441
@ProxyCmp({
2442-
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
2442+
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
24432443
methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss']
24442444
})
24452445
@Component({
24462446
selector: 'ion-toast',
24472447
changeDetection: ChangeDetectionStrategy.OnPush,
24482448
template: '<ng-content></ng-content>',
24492449
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2450-
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
2450+
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
24512451
})
24522452
export class IonToast {
24532453
protected el: HTMLIonToastElement;

packages/angular/standalone/src/directives/proxies.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,15 +2202,15 @@ export declare interface IonTitle extends Components.IonTitle {}
22022202

22032203
@ProxyCmp({
22042204
defineCustomElementFn: defineIonToast,
2205-
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
2205+
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
22062206
methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss']
22072207
})
22082208
@Component({
22092209
selector: 'ion-toast',
22102210
changeDetection: ChangeDetectionStrategy.OnPush,
22112211
template: '<ng-content></ng-content>',
22122212
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2213-
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
2213+
inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'],
22142214
standalone: true
22152215
})
22162216
export class IonToast {

0 commit comments

Comments
 (0)