Skip to content

Commit f69ec47

Browse files
feat(toast): add wrapper and content parts (#30737)
Issue number: resolves #30735 --------- ## What is the current behavior? Toast's wrapper and content divs do not have part attributes. ## What is the new behavior? - Adds `wrapper` and `content` parts to Toast. ## Does this introduce a breaking change? - [ ] Yes - [x] No --------- Co-authored-by: Brandy Smith <[email protected]>
1 parent 9ae41ef commit f69ec47

File tree

3 files changed

+142
-5
lines changed

3 files changed

+142
-5
lines changed

core/api.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,9 +2016,11 @@ ion-toast,css-prop,--width,md
20162016
ion-toast,part,button
20172017
ion-toast,part,button cancel
20182018
ion-toast,part,container
2019+
ion-toast,part,content
20192020
ion-toast,part,header
20202021
ion-toast,part,icon
20212022
ion-toast,part,message
2023+
ion-toast,part,wrapper
20222024

20232025
ion-toggle,shadow
20242026
ion-toggle,prop,alignment,"center" | "start" | undefined,undefined,false,false
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { expect } from '@playwright/test';
2+
import { configs, test } from '@utils/test/playwright';
3+
4+
/**
5+
* This behavior does not vary across directions
6+
*/
7+
configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
8+
test.describe(title('toast: custom'), () => {
9+
test('should be able to customize toast wrapper, container, and content using css parts', async ({ page }) => {
10+
await page.setContent(
11+
`
12+
<ion-toast is-open="true" header="Header" message="Hello World"></ion-toast>
13+
14+
<style>
15+
ion-toast::part(wrapper) {
16+
background-color: red;
17+
}
18+
ion-toast::part(container) {
19+
background-color: green;
20+
}
21+
ion-toast::part(content) {
22+
background-color: blue;
23+
}
24+
</style>
25+
`,
26+
config
27+
);
28+
29+
const wrapperColor = await page.locator('ion-toast').evaluate((el: any) => {
30+
const partEl = el.shadowRoot?.querySelector('[part="wrapper"]') as HTMLElement | null;
31+
return partEl ? getComputedStyle(partEl).backgroundColor : null;
32+
});
33+
34+
expect(wrapperColor).toBe('rgb(255, 0, 0)');
35+
36+
const containerColor = await page.locator('ion-toast').evaluate((el: any) => {
37+
const partEl = el.shadowRoot?.querySelector('[part="container"]') as HTMLElement | null;
38+
return partEl ? getComputedStyle(partEl).backgroundColor : null;
39+
});
40+
41+
expect(containerColor).toBe('rgb(0, 128, 0)');
42+
43+
const contentColor = await page.locator('ion-toast').evaluate((el: any) => {
44+
const partEl = el.shadowRoot?.querySelector('[part="content"]') as HTMLElement | null;
45+
return partEl ? getComputedStyle(partEl).backgroundColor : null;
46+
});
47+
48+
expect(contentColor).toBe('rgb(0, 0, 255)');
49+
});
50+
51+
test('should be able to customize toast header and message using css parts', async ({ page }) => {
52+
await page.setContent(
53+
`
54+
<ion-toast is-open="true" header="Header" message="Hello World"></ion-toast>
55+
56+
<style>
57+
ion-toast::part(header) {
58+
color: red;
59+
}
60+
ion-toast::part(message) {
61+
color: green;
62+
}
63+
</style>
64+
`,
65+
config
66+
);
67+
68+
const headerColor = await page.locator('ion-toast').evaluate((el: any) => {
69+
const partEl = el.shadowRoot?.querySelector('[part="header"]') as HTMLElement | null;
70+
return partEl ? getComputedStyle(partEl).color : null;
71+
});
72+
73+
expect(headerColor).toBe('rgb(255, 0, 0)');
74+
75+
const messageColor = await page.locator('ion-toast').evaluate((el: any) => {
76+
const partEl = el.shadowRoot?.querySelector('[part="message"]') as HTMLElement | null;
77+
return partEl ? getComputedStyle(partEl).color : null;
78+
});
79+
80+
expect(messageColor).toBe('rgb(0, 128, 0)');
81+
});
82+
83+
test('should be able to customize toast icon, button, and button cancel using css parts', async ({ page }) => {
84+
await page.setContent(
85+
`
86+
<ion-toast is-open="true" header="Header" message="Hello World" icon="alert"></ion-toast>
87+
88+
<style>
89+
ion-toast::part(icon) {
90+
color: red;
91+
}
92+
ion-toast::part(button) {
93+
color: green;
94+
}
95+
ion-toast::part(button cancel) {
96+
color: blue;
97+
}
98+
</style>
99+
100+
<script>
101+
const toast = document.querySelector('ion-toast');
102+
toast.buttons = [
103+
{ text: 'Cancel', role: 'cancel' },
104+
{ text: 'OK' }
105+
];
106+
</script>
107+
`,
108+
config
109+
);
110+
111+
const iconColor = await page.locator('ion-toast').evaluate((el: any) => {
112+
const partEl = el.shadowRoot?.querySelector('[part="icon"]') as HTMLElement | null;
113+
return partEl ? getComputedStyle(partEl).color : null;
114+
});
115+
116+
expect(iconColor).toBe('rgb(255, 0, 0)');
117+
118+
const buttonColor = await page.locator('ion-toast').evaluate((el: any) => {
119+
const partEl = el.shadowRoot?.querySelector('[part="button"]') as HTMLElement | null;
120+
return partEl ? getComputedStyle(partEl).color : null;
121+
});
122+
123+
expect(buttonColor).toBe('rgb(0, 128, 0)');
124+
125+
const buttonCancelColor = await page.locator('ion-toast').evaluate((el: any) => {
126+
const partEl = el.shadowRoot?.querySelector('[part="button cancel"]') as HTMLElement | null;
127+
return partEl ? getComputedStyle(partEl).color : null;
128+
});
129+
130+
expect(buttonCancelColor).toBe('rgb(0, 0, 255)');
131+
});
132+
});
133+
});

core/src/components/toast/toast.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ import type {
4747
/**
4848
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
4949
*
50-
* @part button - Any button element that is displayed inside of the toast.
51-
* @part button cancel - Any button element with role "cancel" that is displayed inside of the toast.
52-
* @part container - The element that wraps all child elements.
50+
* @part wrapper - The outer wrapper for the toast overlay.
51+
* @part container - Groups the icon, content, and buttons.
52+
* @part content - The live region that contains the header and message.
5353
* @part header - The header text of the toast.
5454
* @part message - The body text of the toast.
5555
* @part icon - The icon that appears next to the toast content.
56+
* @part button - Any button element that is displayed inside of the toast.
57+
* @part button cancel - Any button element with role "cancel" that is displayed inside of the toast.
5658
*/
5759
@Component({
5860
tag: 'ion-toast',
@@ -727,7 +729,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
727729
})}
728730
onIonToastWillDismiss={this.dispatchCancelHandler}
729731
>
730-
<div class={wrapperClass}>
732+
<div class={wrapperClass} part="wrapper">
731733
<div class="toast-container" part="container">
732734
{this.renderButtons(startButtons, 'start')}
733735

@@ -746,7 +748,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
746748
not interrupt the user which is why this has
747749
a "status" role and a "polite" presentation.
748750
*/}
749-
<div class="toast-content" role="status" aria-atomic="true" aria-live="polite">
751+
<div class="toast-content" part="content" role="status" aria-atomic="true" aria-live="polite">
750752
{/*
751753
This logic below is done to improve consistency
752754
across platforms when showing and updating live regions.

0 commit comments

Comments
 (0)