Skip to content

Commit b67259e

Browse files
JoaoFerreira-FrontEndbrandyscarneythetaPC
authored
feat(datetime): add showAdjacentDays to display days from the previous and next months (#30262)
Issue number: Internal ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> Adds a new property to datetime, showAdjacentDays, that when true will show the last days of the previous month and the first days of the next month. This will just occupy empty "cells" at the beginning of the month "table" and add rows to the table until a maximum of 6 rows are displayed. ## Changes - add styles for adjacent day button - add `showAdjacentDays` property to datetime component - change month generation to respect new property - add visual tests to new feature ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-rou-11118v2-ionic1.vercel.app/src/components/datetime/test/show-adjacent-days) --------- Co-authored-by: Brandy Smith <[email protected]> Co-authored-by: Brandy Smith <[email protected]> Co-authored-by: Maria Hutt <[email protected]>
1 parent b8ce56b commit b67259e

File tree

58 files changed

+493
-27
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+493
-27
lines changed

core/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ ion-datetime,prop,name,string,this.inputId,false,false
534534
ion-datetime,prop,preferWheel,boolean,false,false,false
535535
ion-datetime,prop,presentation,"date" | "date-time" | "month" | "month-year" | "time" | "time-date" | "year",'date-time',false,false
536536
ion-datetime,prop,readonly,boolean,false,false,false
537+
ion-datetime,prop,showAdjacentDays,boolean,false,false,false
537538
ion-datetime,prop,showClearButton,boolean,false,false,false
538539
ion-datetime,prop,showDefaultButtons,boolean,false,false,false
539540
ion-datetime,prop,showDefaultTimeLabel,boolean,true,false,false

core/src/components.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,10 @@ export namespace Components {
944944
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
945945
*/
946946
"reset": (startDate?: string) => Promise<void>;
947+
/**
948+
* If `true`, the datetime calendar displays a six-week (42-day) layout, including days from the previous and next months to fill the grid. These adjacent days are selectable unless disabled.
949+
*/
950+
"showAdjacentDays": boolean;
947951
/**
948952
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
949953
*/
@@ -5779,6 +5783,10 @@ declare namespace LocalJSX {
57795783
* If `true`, the datetime appears normal but the selected date cannot be changed.
57805784
*/
57815785
"readonly"?: boolean;
5786+
/**
5787+
* If `true`, the datetime calendar displays a six-week (42-day) layout, including days from the previous and next months to fill the grid. These adjacent days are selectable unless disabled.
5788+
*/
5789+
"showAdjacentDays"?: boolean;
57825790
/**
57835791
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
57845792
*/

core/src/components/datetime/datetime-interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface DatetimeParts {
1515
hour?: number;
1616
minute?: number;
1717
ampm?: 'am' | 'pm';
18+
isAdjacentDay?: boolean;
1819
}
1920

2021
export type DatetimePresentation = 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year';

core/src/components/datetime/datetime.ios.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@
267267
color: current-color(contrast);
268268
}
269269

270+
:host .calendar-day.calendar-day-adjacent-day {
271+
color: $text-color-step-700;
272+
}
273+
270274
// Time / Header
271275
// -----------------------------------
272276
:host .datetime-time {

core/src/components/datetime/datetime.md.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,17 @@
121121
color: current-color(contrast);
122122
}
123123

124-
.calendar-day.calendar-day-active {
124+
.calendar-day.calendar-day-active,
125+
.calendar-day.calendar-day-active:focus {
125126
border: 1px solid current-color(base);
126127

127128
background: current-color(base);
128129
}
129130

131+
:host .calendar-day.calendar-day-adjacent-day {
132+
color: $text-color-step-500;
133+
}
134+
130135
// Time / Header
131136
// -----------------------------------
132137
:host .datetime-time {

core/src/components/datetime/datetime.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@
364364
opacity: 0.4;
365365
}
366366

367-
.calendar-day:focus {
367+
368+
.calendar-day:not(.calendar-day-adjacent-day):focus {
368369
background: current-color(base, 0.2);
369370

370371
box-shadow: 0px 0px 0px 4px current-color(base, 0.2);

core/src/components/datetime/datetime.tsx

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export class Datetime implements ComponentInterface {
139139
hour: 13,
140140
minute: 52,
141141
ampm: 'pm',
142+
isAdjacentDay: false,
142143
};
143144

144145
@Element() el!: HTMLIonDatetimeElement;
@@ -207,6 +208,13 @@ export class Datetime implements ComponentInterface {
207208
*/
208209
@Prop() isDateEnabled?: (dateIsoString: string) => boolean;
209210

211+
/**
212+
* If `true`, the datetime calendar displays a six-week (42-day) layout,
213+
* including days from the previous and next months to fill the grid.
214+
* These adjacent days are selectable unless disabled.
215+
*/
216+
@Prop() showAdjacentDays = false;
217+
210218
@Watch('disabled')
211219
protected disabledChanged() {
212220
this.emitStyle();
@@ -805,12 +813,17 @@ export class Datetime implements ComponentInterface {
805813

806814
private focusWorkingDay = (currentMonth: Element) => {
807815
/**
808-
* Get the number of padding days so
816+
* Get the number of offset days so
809817
* we know how much to offset our next selector by
810818
* to grab the correct calendar-day element.
811819
*/
812-
const padding = currentMonth.querySelectorAll('.calendar-day-padding');
813-
const { day } = this.workingParts;
820+
821+
const { day, month, year } = this.workingParts;
822+
const firstOfMonth = new Date(`${month}/1/${year}`).getDay();
823+
const offset =
824+
firstOfMonth >= this.firstDayOfWeek
825+
? firstOfMonth - this.firstDayOfWeek
826+
: 7 - (this.firstDayOfWeek - firstOfMonth);
814827

815828
if (day === null) {
816829
return;
@@ -821,7 +834,7 @@ export class Datetime implements ComponentInterface {
821834
* and focus it.
822835
*/
823836
const dayEl = currentMonth.querySelector(
824-
`.calendar-day-wrapper:nth-of-type(${padding.length + day}) .calendar-day`
837+
`.calendar-day-wrapper:nth-of-type(${offset + day}) .calendar-day`
825838
) as HTMLElement | null;
826839
if (dayEl) {
827840
dayEl.focus();
@@ -2226,10 +2239,34 @@ export class Datetime implements ComponentInterface {
22262239
}}
22272240
>
22282241
<div class="calendar-month-grid">
2229-
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => {
2230-
const { day, dayOfWeek } = dateObject;
2231-
const { el, highlightedDates, isDateEnabled, multiple } = this;
2232-
const referenceParts = { month, day, year };
2242+
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7, this.showAdjacentDays).map((dateObject, index) => {
2243+
const { day, dayOfWeek, isAdjacentDay } = dateObject;
2244+
const { el, highlightedDates, isDateEnabled, multiple, showAdjacentDays } = this;
2245+
let _month = month;
2246+
let _year = year;
2247+
if (showAdjacentDays && isAdjacentDay && day !== null) {
2248+
if (day > 20) {
2249+
// Leading with the adjacent day from the previous month
2250+
// if its a adjacent day and is higher than '20' (last week even in feb)
2251+
if (month === 1) {
2252+
_year = year - 1;
2253+
_month = 12;
2254+
} else {
2255+
_month = month - 1;
2256+
}
2257+
} else if (day < 15) {
2258+
// Leading with the adjacent day from the next month
2259+
// if its a adjacent day and is lower than '15' (first two weeks)
2260+
if (month === 12) {
2261+
_year = year + 1;
2262+
_month = 1;
2263+
} else {
2264+
_month = month + 1;
2265+
}
2266+
}
2267+
}
2268+
2269+
const referenceParts = { month: _month, day, year: _year, isAdjacentDay };
22332270
const isCalendarPadding = day === null;
22342271
const {
22352272
isActive,
@@ -2284,18 +2321,20 @@ export class Datetime implements ComponentInterface {
22842321
* Custom highlight styles should not override the style for selected dates,
22852322
* nor apply to "filler days" at the start of the grid.
22862323
*/
2287-
if (highlightedDates !== undefined && !isActive && day !== null) {
2324+
if (highlightedDates !== undefined && !isActive && day !== null && !isAdjacentDay) {
22882325
dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
22892326
}
22902327

22912328
let dateParts = undefined;
22922329

22932330
// "Filler days" at the beginning of the grid should not get the calendar day
22942331
// CSS parts added to them
2295-
if (!isCalendarPadding) {
2332+
if (!isCalendarPadding && !isAdjacentDay) {
22962333
dateParts = `calendar-day${isActive ? ' active' : ''}${isToday ? ' today' : ''}${
22972334
isCalDayDisabled ? ' disabled' : ''
22982335
}`;
2336+
} else if (isAdjacentDay) {
2337+
dateParts = `calendar-day${isCalDayDisabled ? ' disabled' : ''}`;
22992338
}
23002339

23012340
return (
@@ -2319,8 +2358,8 @@ export class Datetime implements ComponentInterface {
23192358
}}
23202359
tabindex="-1"
23212360
data-day={day}
2322-
data-month={month}
2323-
data-year={year}
2361+
data-month={_month}
2362+
data-year={_year}
23242363
data-index={index}
23252364
data-day-of-week={dayOfWeek}
23262365
disabled={isButtonDisabled}
@@ -2330,6 +2369,7 @@ export class Datetime implements ComponentInterface {
23302369
'calendar-day-active': isActive,
23312370
'calendar-day-constrained': isCalDayConstrained,
23322371
'calendar-day-today': isToday,
2372+
'calendar-day-adjacent-day': isAdjacentDay,
23332373
}}
23342374
part={dateParts}
23352375
aria-hidden={isCalendarPadding ? 'true' : null}
@@ -2340,29 +2380,37 @@ export class Datetime implements ComponentInterface {
23402380
return;
23412381
}
23422382

2383+
if (isAdjacentDay) {
2384+
// The user selected a day outside the current month. Ignore this button, as the month will be re-rendered.
2385+
this.el.blur();
2386+
}
2387+
23432388
this.setWorkingParts({
23442389
...this.workingParts,
2345-
month,
2390+
month: _month,
23462391
day,
2347-
year,
2392+
year: _year,
2393+
isAdjacentDay,
23482394
});
23492395

23502396
// multiple only needs date info, so we can wipe out other fields like time
23512397
if (multiple) {
23522398
this.setActiveParts(
23532399
{
2354-
month,
2400+
month: _month,
23552401
day,
2356-
year,
2402+
year: _year,
2403+
isAdjacentDay,
23572404
},
23582405
isActive
23592406
);
23602407
} else {
23612408
this.setActiveParts({
23622409
...activePart,
2363-
month,
2410+
month: _month,
23642411
day,
2365-
year,
2412+
year: _year,
2413+
isAdjacentDay,
23662414
});
23672415
}
23682416
}}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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(({ title, screenshot, config }) => {
8+
test.describe(title('datetime: show adjacent days'), () => {
9+
test('should not have visual regressions', async ({ page }) => {
10+
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
11+
const datetime = page.locator('#default');
12+
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days`));
13+
});
14+
15+
test('should not have visual regressions with a custom styled calendar', async ({ page }) => {
16+
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
17+
const datetime = page.locator('#custom-calendar-days');
18+
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-custom-calendar`));
19+
});
20+
21+
test('should not have visual regressions with specific date disabled', async ({ page }) => {
22+
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
23+
const datetime = page.locator('#specificDate');
24+
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-specific-date-disabled`));
25+
});
26+
27+
test('should not have visual regressions with weekends disabled', async ({ page }) => {
28+
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
29+
const datetime = page.locator('#weekends');
30+
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-weekends-disabled`));
31+
});
32+
33+
test('should not have visual regressions with date range disabled', async ({ page }) => {
34+
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
35+
const datetime = page.locator('#dateRange');
36+
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-date-range-disabled`));
37+
});
38+
39+
test('should not have visual regressions with month disabled', async ({ page }) => {
40+
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
41+
const datetime = page.locator('#month');
42+
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-month-disabled`));
43+
});
44+
45+
test('should not have visual regressions with display specified', async ({ page }) => {
46+
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
47+
const datetime = page.locator('#display');
48+
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-display`));
49+
});
50+
});
51+
});

0 commit comments

Comments
 (0)