Skip to content

Commit b5403d1

Browse files
authored
feat(datetime-button): add support for custom, disabled, and accessible buttons (#25622)
1 parent 270247d commit b5403d1

19 files changed

+97
-56
lines changed

core/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ ion-datetime-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary
414414
ion-datetime-button,prop,datetime,string | undefined,undefined,false,false
415415
ion-datetime-button,prop,disabled,boolean,false,false,true
416416
ion-datetime-button,prop,mode,"ios" | "md",undefined,false,false
417+
ion-datetime-button,part,native
417418

418419
ion-fab,shadow
419420
ion-fab,prop,activated,boolean,false,false,false

core/src/components/datetime-button/datetime-button.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
@include padding(6px, 12px, 6px, 12px);
1717
@include margin(0px, 2px, 0px, 2px);
1818

19+
position: relative;
20+
1921
transition: 150ms color ease-in-out;
2022

2123
border: none;
@@ -30,9 +32,19 @@
3032
cursor: pointer;
3133

3234
appearance: none;
35+
36+
overflow: hidden;
3337
}
3438

3539
:host(.time-active) #time-button,
3640
:host(.date-active) #date-button {
3741
color: current-color(base);
3842
}
43+
44+
:host(.datetime-button-disabled) {
45+
pointer-events: none;
46+
}
47+
48+
:host(.datetime-button-disabled) button {
49+
opacity: 0.4;
50+
}

core/src/components/datetime-button/datetime-button.tsx

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { parseDate } from '../datetime/utils/parse';
1515
*
1616
* @slot date-target - Content displayed inside of the date button.
1717
* @slot time-target - Content displayed inside of the time button.
18+
*
19+
* @part native - The native HTML button that wraps the slotted text.
1820
*/
1921
@Component({
2022
tag: 'ion-datetime-button',
@@ -254,7 +256,7 @@ export class DatetimeButton implements ComponentInterface {
254256
};
255257

256258
render() {
257-
const { color, dateText, timeText, selectedButton, datetimeActive } = this;
259+
const { color, dateText, timeText, selectedButton, datetimeActive, disabled } = this;
258260

259261
const mode = getIonMode(this);
260262

@@ -263,31 +265,35 @@ export class DatetimeButton implements ComponentInterface {
263265
class={createColorClasses(color, {
264266
[mode]: true,
265267
[`${selectedButton}-active`]: datetimeActive,
268+
['datetime-button-disabled']: disabled,
266269
})}
267270
>
268271
{dateText && (
269-
<div class="date-target-container" onClick={() => this.handleDateClick()}>
270-
<slot name="date-target">
271-
{/*
272-
The button is added inside of the <slot> so that
273-
devs do not create nested interactives if they
274-
decide to add in a custom ion-button.
275-
*/}
276-
<button id="date-button" aria-expanded={datetimeActive ? 'true' : 'false'}>
277-
{dateText}
278-
</button>
279-
</slot>
280-
</div>
272+
<button
273+
class="ion-activatable"
274+
id="date-button"
275+
aria-expanded={datetimeActive ? 'true' : 'false'}
276+
onClick={() => this.handleDateClick()}
277+
disabled={disabled}
278+
part="native"
279+
>
280+
<slot name="date-target">{dateText}</slot>
281+
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
282+
</button>
281283
)}
282284

283285
{timeText && (
284-
<div class="time-target-container" onClick={() => this.handleTimeClick()}>
285-
<slot name="time-target">
286-
<button id="time-button" aria-expanded={datetimeActive ? 'true' : 'false'}>
287-
{timeText}
288-
</button>
289-
</slot>
290-
</div>
286+
<button
287+
class="ion-activatable"
288+
id="time-button"
289+
aria-expanded={datetimeActive ? 'true' : 'false'}
290+
onClick={() => this.handleTimeClick()}
291+
disabled={disabled}
292+
part="native"
293+
>
294+
<slot name="time-target">{timeText}</slot>
295+
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
296+
</button>
291297
)}
292298
</Host>
293299
);

core/src/components/datetime-button/test/basic/datetime-button.e2e.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ test.describe('datetime-button: switching to correct view', () => {
1616
const datetime = page.locator('ion-datetime');
1717
expect(datetime).toHaveJSProperty('presentation', 'date-time');
1818

19-
await page.locator('.date-target-container').click();
19+
await page.locator('#date-button').click();
2020

2121
expect(datetime).toHaveJSProperty('presentation', 'date');
2222
});
2323
test('should switch to a time-only view when the time button is clicked', async ({ page }) => {
2424
const datetime = page.locator('ion-datetime');
2525
expect(datetime).toHaveJSProperty('presentation', 'date-time');
2626

27-
await page.locator('.time-target-container').click();
27+
await page.locator('#time-button').click();
2828

2929
expect(datetime).toHaveJSProperty('presentation', 'time');
3030
});
@@ -43,8 +43,8 @@ test.describe('datetime-button: labels', () => {
4343
`);
4444
await page.waitForSelector('.datetime-ready');
4545

46-
await expect(page.locator('.date-target-container')).toContainText('Jan 1, 2022');
47-
await expect(page.locator('.time-target-container')).toContainText('6:30 AM');
46+
await expect(page.locator('#date-button')).toContainText('Jan 1, 2022');
47+
await expect(page.locator('#time-button')).toContainText('6:30 AM');
4848
});
4949
test('should set only month and year', async ({ page }) => {
5050
await page.setContent(`
@@ -53,8 +53,8 @@ test.describe('datetime-button: labels', () => {
5353
`);
5454
await page.waitForSelector('.datetime-ready');
5555

56-
await expect(page.locator('.date-target-container')).toContainText('January 2022');
57-
await expect(page.locator('.time-target-container')).toBeHidden();
56+
await expect(page.locator('#date-button')).toContainText('January 2022');
57+
await expect(page.locator('#time-button')).toBeHidden();
5858
});
5959
test('should set only year', async ({ page }) => {
6060
await page.setContent(`
@@ -63,8 +63,8 @@ test.describe('datetime-button: labels', () => {
6363
`);
6464
await page.waitForSelector('.datetime-ready');
6565

66-
await expect(page.locator('.date-target-container')).toContainText('2022');
67-
await expect(page.locator('.time-target-container')).toBeHidden();
66+
await expect(page.locator('#date-button')).toContainText('2022');
67+
await expect(page.locator('#time-button')).toBeHidden();
6868
});
6969
test('should set only month', async ({ page }) => {
7070
await page.setContent(`
@@ -73,8 +73,8 @@ test.describe('datetime-button: labels', () => {
7373
`);
7474
await page.waitForSelector('.datetime-ready');
7575

76-
await expect(page.locator('.date-target-container')).toContainText('January');
77-
await expect(page.locator('.time-target-container')).toBeHidden();
76+
await expect(page.locator('#date-button')).toContainText('January');
77+
await expect(page.locator('#time-button')).toBeHidden();
7878
});
7979
test('should set only time', async ({ page }) => {
8080
await page.setContent(`
@@ -83,8 +83,8 @@ test.describe('datetime-button: labels', () => {
8383
`);
8484
await page.waitForSelector('.datetime-ready');
8585

86-
await expect(page.locator('.date-target-container')).toBeHidden();
87-
await expect(page.locator('.time-target-container')).toContainText('6:30 AM');
86+
await expect(page.locator('#time-button')).toContainText('6:30 AM');
87+
await expect(page.locator('#date-button')).toBeHidden();
8888
});
8989
test('should update the label when the value of the datetime changes', async ({ page }) => {
9090
await page.setContent(`
@@ -94,7 +94,7 @@ test.describe('datetime-button: labels', () => {
9494
await page.waitForSelector('.datetime-ready');
9595

9696
const datetime = page.locator('ion-datetime');
97-
const dateTarget = page.locator('.date-target-container');
97+
const dateTarget = page.locator('#date-button');
9898

9999
await expect(dateTarget).toContainText('Jan 1, 2022');
100100

@@ -123,8 +123,8 @@ test.describe('datetime-button: locale', () => {
123123
* a period after "ene". Just checking ene allows us to verify the
124124
* behavior while avoiding these cross browser differences.
125125
*/
126-
await expect(page.locator('.date-target-container')).toContainText(/ene/);
127-
await expect(page.locator('.time-target-container')).toContainText('6:30');
126+
await expect(page.locator('#date-button')).toContainText(/ene/);
127+
await expect(page.locator('#time-button')).toContainText('6:30');
128128
});
129129
test('should respect hour cycle even if different from locale default', async ({ page }) => {
130130
await page.setContent(`
@@ -133,7 +133,7 @@ test.describe('datetime-button: locale', () => {
133133
`);
134134
await page.waitForSelector('.datetime-ready');
135135

136-
await expect(page.locator('.time-target-container')).toContainText('16:30');
136+
await expect(page.locator('#time-button')).toContainText('16:30');
137137
});
138138
test('should ignore the timezone when selecting a date', async ({ page }) => {
139139
await page.setContent(`
@@ -142,7 +142,7 @@ test.describe('datetime-button: locale', () => {
142142
`);
143143
await page.waitForSelector('.datetime-ready');
144144

145-
const timeTarget = page.locator('.time-target-container');
145+
const timeTarget = page.locator('#time-button');
146146
await expect(timeTarget).toContainText('6:30');
147147

148148
const firstOfMonth = page.locator('ion-datetime .calendar-day[data-month="1"][data-day="1"]');
@@ -168,8 +168,8 @@ test.describe('datetime-button: wheel', () => {
168168
`);
169169
await page.waitForSelector('.datetime-ready');
170170

171-
await expect(page.locator('.date-target-container')).toContainText('Jan 1, 2022 6:30 AM');
172-
await expect(page.locator('.time-target-container')).not.toBeVisible();
171+
await expect(page.locator('#date-button')).toContainText('Jan 1, 2022 6:30 AM');
172+
await expect(page.locator('#time-button')).not.toBeVisible();
173173
});
174174
test('should only show a single date button when presentation="time-date" and prefer-wheel="true"', async ({
175175
page,
@@ -180,7 +180,7 @@ test.describe('datetime-button: wheel', () => {
180180
`);
181181
await page.waitForSelector('.datetime-ready');
182182

183-
await expect(page.locator('.date-target-container')).toContainText('Jan 1, 2022 6:30 AM');
184-
await expect(page.locator('.time-target-container')).not.toBeVisible();
183+
await expect(page.locator('#date-button')).toContainText('Jan 1, 2022 6:30 AM');
184+
await expect(page.locator('#time-button')).not.toBeVisible();
185185
});
186186
});

core/src/components/datetime-button/test/buttons/index.html

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,8 @@
2020
<ion-item>
2121
<ion-input value="March 15, 2022 at 12:43 AM"></ion-input>
2222
<ion-datetime-button slot="end" id="default-button" datetime="default-datetime">
23-
<ion-button id="custom-date-button" slot="date-target" fill="clear">
24-
<ion-icon slot="icon-only" name="calendar"></ion-icon>
25-
</ion-button>
26-
<ion-button id="custom-time-button" slot="time-target" fill="clear">
27-
<ion-icon name="time"></ion-icon>
28-
</ion-button>
23+
<ion-icon color="primary" id="custom-date-button" slot="date-target" name="calendar"></ion-icon>
24+
<ion-icon color="primary" id="custom-time-button" slot="time-target" name="time"></ion-icon>
2925
</ion-datetime-button>
3026
</ion-item>
3127

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { expect } from '@playwright/test';
2+
import { test } from '@utils/test/playwright';
3+
4+
test.describe('datetime-button: disabled buttons', () => {
5+
test('buttons should not be enabled when component is disabled', async ({ page }, testInfo) => {
6+
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests');
7+
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic');
8+
9+
await page.setContent(`
10+
<ion-datetime-button datetime="datetime" disabled="true"></ion-datetime-button>
11+
<ion-datetime id="datetime" presentation="date-time"></ion-datetime>
12+
`);
13+
await page.waitForSelector('.datetime-ready');
14+
15+
await expect(page.locator('#date-button')).toBeDisabled();
16+
await expect(page.locator('#time-button')).toBeDisabled();
17+
});
18+
test('buttons should visually be disabled', async ({ page }) => {
19+
await page.setContent(`
20+
<ion-datetime-button datetime="datetime" disabled="true"></ion-datetime-button>
21+
<ion-datetime id="datetime" presentation="date-time" value="2022-01-01T16:30:00"></ion-datetime>
22+
`);
23+
await page.waitForSelector('.datetime-ready');
24+
25+
const datetimeButton = page.locator('ion-datetime-button');
26+
expect(await datetimeButton.screenshot()).toMatchSnapshot(
27+
`datetime-button-disabled-${page.getSnapshotSettings()}.png`
28+
);
29+
});
30+
});

core/src/components/datetime-button/test/disabled/index.html

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,14 @@ <h2>Custom Button Disabled</h2>
5757

5858
<ion-item>
5959
<ion-label>Start Date</ion-label>
60-
<ion-datetime-button disabled="true" slot="end" id="year-button" datetime="year-datetime">
61-
<ion-button disabled="true" id="custom-date-button" slot="date-target" fill="clear">
62-
<ion-icon slot="icon-only" name="calendar"></ion-icon>
63-
</ion-button>
64-
<ion-button disabled="true" id="custom-time-button" slot="time-target" fill="clear">
65-
<ion-icon name="time"></ion-icon>
66-
</ion-button>
60+
<ion-datetime-button disabled="true" slot="end" id="custom-button" datetime="custom-datetime">
61+
<ion-icon color="primary" id="custom-date-button" slot="date-target" name="calendar"></ion-icon>
62+
<ion-icon color="primary" id="custom-time-button" slot="time-target" name="time"></ion-icon>
6763
</ion-datetime-button>
6864
</ion-item>
6965

70-
<ion-popover arrow="false" trigger="year-button">
71-
<ion-datetime locale="en-US" id="year-datetime" value="2022-03-15T00:43:00"></ion-datetime>
66+
<ion-popover arrow="false" trigger="custom-button">
67+
<ion-datetime locale="en-US" id="custom-datetime" value="2022-03-15T00:43:00"></ion-datetime>
7268
</ion-popover>
7369
</div>
7470
</div>

0 commit comments

Comments
 (0)