Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a customizable, slottable day component for calendars #745

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,75 +74,4 @@
background-color: var(--gse-ui-calendarMenu-backgroundColor);
border-radius: var(--gse-ui-calendarMenu-single-body-borderRadius);
}

.gux-content-week {
.gux-content-date {
display: inline-block;
inline-size: var(--gse-ui-calendarMenu-day-date-size);
block-size: var(--gse-ui-calendarMenu-day-input-height);
font-family: var(--gse-ui-calendarMenu-date-currentText-fontFamily);
font-size: var(--gse-ui-calendarMenu-date-defaultText-fontSize);
font-style: normal;
font-weight: var(--gse-ui-calendarMenu-date-defaultText-fontWeight);
line-height: 32px;
color: #626e84;
text-align: center;
vertical-align: middle;
border: none;
outline: none;
opacity: var(--gse-ui-calendarMenu-disabled-opacity);

&.gux-current-month {
// TODO: Is this the correct token to use here?
color: var(--gse-ui-calendarMenu-month-default-foregroundColor);
opacity: 1;
}

&.gux-hidden {
display: none;
}

&.gux-disabled {
pointer-events: none;
}

&.gux-selected {
color: var(--gse-ui-calendarMenu-date-selected-foregroundColor);
background-color: var(
--gse-ui-calendarMenu-date-selected-backgroundColor
);
border: none;
border-radius: var(--gse-ui-calendarMenu-month-borderRadius);
outline: none;

&:hover {
background-color: var(
--gse-ui-calendarMenu-date-selected-hoverBackgroundColor
);
}
}

&:hover {
cursor: pointer;
background-color: var(--gse-ui-calendarMenu-date-hover-backgroundColor);
border-radius: var(--gse-ui-calendarMenu-month-borderRadius);
}

&:focus-visible {
border-radius: var(--gse-ui-calendarMenu-month-focusBorderRadius);
outline: var(--gse-semantic-focusOutline-md-borderWidth) solid
var(--gse-semantic-border-focus);
}

.gux-sr-only {
@include mixins.gux-sr-only-clip;
}

&.gux-current-date {
font-family: var(--gse-ui-calendarMenu-month-currentText-fontFamily);
font-size: var(--gse-ui-calendarMenu-date-currentText-fontSize);
font-weight: var(--gse-ui-calendarMenu-date-currentText-fontWeight);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Element, h, JSX, State } from '@stencil/core';
import { Component, Element, h, JSX, Listen, State } from '@stencil/core';
import { IWeekElement, GuxCalendarDayOfWeek } from '../../gux-calendar.types';
import { asIsoDate, fromIsoDate } from '@utils/date/iso-dates';
import {
Expand Down Expand Up @@ -32,6 +32,13 @@ export class GuxCalendar {
@State()
private maxValue: Date;

@Listen('guxdayselected')
onDaySelected(event: CustomEvent<string>) {
const selectedDate = fromIsoDate(event.detail);
this.selectDate(selectedDate);
event.stopPropagation();
}

private locale: string = 'en';

private i18n: GetI18nValue;
Expand Down Expand Up @@ -85,7 +92,7 @@ export class GuxCalendar {
}
}

private onDateClick(date: Date): void {
private selectDate(date: Date): void {
if (this.isInvalidDate(date)) {
return;
}
Expand Down Expand Up @@ -116,21 +123,24 @@ export class GuxCalendar {
* Focus the focused date
*/
private focusDate() {
const target: HTMLTableCellElement = this.root.shadowRoot.querySelector(
`.gux-content-date[data-date="${this.focusedValue.getTime()}"]`
);
const isoDateStr = asIsoDate(this.focusedValue);
// Find the rendered element for the slot for the focused date
const target: HTMLElement = this.root.shadowRoot
.querySelector<HTMLSlotElement>(`slot[name="${isoDateStr}"]`)
.assignedNodes({ flatten: true })[0] as HTMLElement;
if (target) {
target.focus();
}
}

private onKeyDown(event: KeyboardEvent): void {
console.log('KEYDOWN', event.key);
switch (event.key) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor this can be removed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will also look into why linting didn't catch this

case ' ':
break;
case 'Enter':
event.preventDefault();
this.onDateClick(this.getFocusedValue());
this.selectDate(this.getFocusedValue());
afterNextRenderTimeout(() => {
this.focusDate();
});
Expand Down Expand Up @@ -302,7 +312,7 @@ export class GuxCalendar {

private renderContent(): JSX.Element {
return (
<div>
<div onKeyDown={e => void this.onKeyDown(e)}>
<div class="gux-week-days">
{getWeekdays(this.locale, this.startDayOfWeek).map(
day => (<div class="gux-week-day">{day}</div>) as JSX.Element
Expand All @@ -314,37 +324,22 @@ export class GuxCalendar {
week =>
(
<div class="gux-content-week">
{week.dates.map(
day =>
(
<div
data-date={day.date.getTime()}
onClick={() => this.onDateClick(day.date)}
role="button"
{week.dates.map(day => {
const isoDateStr = asIsoDate(day.date);
return (
<slot key={isoDateStr} name={isoDateStr}>
<gux-day
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the key property valid on the slot element ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be, it's a JSX/Stencil feature, not a native one: https://stenciljs.com/docs/templating-jsx#dealing-with-children. We don't do a lot of removing/rearranging dates in the calendar, but I try to always add a key when creating children in a loop as a general best practice.

day={isoDateStr}
aria-current={day.selected ? 'true' : 'false'}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if the day property will work for example if you have a gux-day component like
<gux-day slot="2024-05-05" day="2024-05-10"></gux-day the calendar will be out of sync. I am not sure what the best way to handle that scenario would be. My suggestion would be either a single source of truth like keeping the slot attribute and somehow accessing the value in gux-day through an attribute on the parent maybe but I am not sure how that would work with how the calendar is rendered. My other sugguestion would be to just log a warning.

tabindex={day.selected || day.focused ? '0' : '-1'}
onKeyDown={e => void this.onKeyDown(e)}
aria-disabled={day.disabled ? 'true' : 'false'}
tabindex={day.selected || day.focused ? '0' : '-1'}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a developer slots their own gux-day they will be responsible for making sure aria-current, aria-disabled and tabindex are correct right?

class={{
'gux-content-date': true,
'gux-disabled': day.disabled,
'gux-current-month': day.inCurrentMonth,
'gux-selected': day.selected,
'gux-current-date': day.isCurrentDate
'gux-muted': !day.inCurrentMonth
}}
>
<span class="gux-non-sr" aria-hidden="true">
{day.date.getDate()}
</span>
<span class="gux-sr-only">
<gux-date-beta
datetime={day.date.toISOString()}
format="long"
></gux-date-beta>
</span>
</div>
) as JSX.Element
)}
></gux-day>
</slot>
) as JSX.Element;
})}
</div>
) as JSX.Element
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
### Depends on

- [gux-icon](../../../../stable/gux-icon)
- [gux-date-beta](../../../gux-date)
- [gux-day](../../../gux-day)

### Graph
```mermaid
graph TD;
gux-calendar-beta --> gux-icon
gux-calendar-beta --> gux-date-beta
gux-calendar-beta --> gux-day
style gux-calendar-beta fill:#f9f,stroke:#333,stroke-width:4px
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ <h2>Single Date Selection</h2>
<input type="date" value="2023-05-19" />
</gux-calendar-beta>

<h2>Customized Date Styles</h2>
<gux-calendar-beta oninput="notify(event)">
<input type="date" value="2023-05-19" />
<gux-day slot="2023-05-03" day="2023-05-03" style="background: red"></gux-day>
</gux-calendar-beta>

<h2>Empty Date Selection</h2>
<gux-calendar-beta oninput="notify(event)">
<input type="date" />
Expand Down Expand Up @@ -70,7 +76,8 @@ <h2>Languages</h2>
}

.language {
padding: 10px 25px;
padding-block: 10px;
padding-inline: 25px;
}
</style>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { E2EElement, E2EPage } from '@stencil/core/testing';
import { asIsoDate } from '@utils/date/iso-dates';

export async function validateSelectedDate(
element: E2EElement,
expectedMonthAndYear: string,
expectedDay: string
) {
const selectedDate = await element.find('pierce/.gux-selected .gux-non-sr');
const currentMonthAndYear = await element.find(
'pierce/.gux-header-month-and-year'
);
const selectedDate = await element.find(
'pierce/gux-day[aria-current="true"]'
);
expect(currentMonthAndYear.innerHTML).toBe(expectedMonthAndYear);
expect(selectedDate.innerHTML).toBe(expectedDay);
expect(selectedDate.shadowRoot).toEqualText(
`<button type="button"><slot>${expectedDay}</slot></button>`
);
}

export async function validateHeaderMonth(
Expand All @@ -23,15 +28,19 @@ export async function validateHeaderMonth(
expect(currentMonthAndYear.innerHTML).toBe(expectedMonthAndYear);
}

export async function getSelectedDateElement(
calendarElement: E2EElement
): Promise<E2EElement> {
return calendarElement.find('pierce/[aria-current="true"]');
}

export async function getContentDateElement(
element: E2EElement,
dateAsMonthDayYear: string
): Promise<E2EElement> {
return await element.find(
`pierce/.gux-content-date[data-date="${new Date(
dateAsMonthDayYear
).getTime()}"]`
);
const dateStr = asIsoDate(new Date(dateAsMonthDayYear));
console.log('Finding: ');
return await element.find(`pierce/slot[name="${dateStr}"] gux-day`);
}

export async function goToPreviousMonth(
Expand Down
Loading
Loading