Skip to content

Commit

Permalink
Calendar: enable keyboard interaction and improve semantics (#3689)
Browse files Browse the repository at this point in the history
Co-authored-by: Jakob Engelbrecht <jakob@basher.dk>
  • Loading branch information
RasmusKjeldgaard and jakobe authored Nov 12, 2024
1 parent 56a2256 commit ebc156b
Show file tree
Hide file tree
Showing 13 changed files with 577 additions and 335 deletions.
15 changes: 15 additions & 0 deletions apps/cookbook/src/app/showcase/_showcase.shared.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,18 @@ cookbook-code-viewer {
padding-block: 0;
}
}

// Style the Keyboard Input element. Borrowed from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd
kbd {
border-radius: 3px;
border: 1px solid var(--kirby-semi-dark);
box-shadow:
0 2px 1px var(--kirby-dark-overlay-20),
0 2px 1px 0 var(--kirby-white) inset;
color: var(--kirby-black);
font-size: 0.85em;
font-weight: var(--kirby-font-weight-bold);
line-height: 1;
padding: 2px 4px;
white-space: nowrap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,52 @@ <h2>Calendar without background</h2>
</div>
</cookbook-example-viewer>

<h2>Specifying locale</h2>
<h2>Accessibility</h2>
<p>
The calendar uses a table to provide an easily navigable grid of dates. Screen-reader users can
navigate using standard table navigation commands.
</p>

<p>
When using the calendar on another background than a white
<code>kirby-card</code>
, additional care should be taken by implementers to ensure proper contrast and readability of the
date grid and controls.
</p>

<h2>Keyboard support</h2>
<p>The calendar has full keyboard support to make it easier to navigate and select dates.</p>
<p>The following keys can be used to move the currently focussed date:</p>
<!-- prettier-ignore -->
<ul>
<li>
<b>Arrow keys</b>
<ul>
<li>
<kbd>&#8592;</kbd> and <kbd>&#8594;</kbd> moves focus to previous and next day
</li>
<li>
<kbd>&#8593;</kbd> and <kbd>&#8595;</kbd> moves focus to same weekday of previous and next week
</li>
</ul>
</li>
<li>
<kbd>Home</kbd> and <kbd>End</kbd> moves focus to first day (Monday) and last day (Sunday) of the current week
</li>
<li>
<kbd>PageUp</kbd> and <kbd>PageDown</kbd> moves focus to same day of previous and next month
</li>
<li>
<kbd>Shift</kbd> + <kbd>PageUp</kbd> and <kbd>Shift</kbd> + <kbd>PageDown</kbd> moves focus to same day and month of previous and next year
</li>
</ul>

<!-- prettier-ignore -->
<p>
When focussed, a date can be selected with <kbd>Enter &#8629;</kbd> and <kbd>Space</kbd>.
</p>

<h2>Specifying locale</h2>
<p>
The locale of the calendar (i.e. day and month names) can be set by providing a
<code>LOCALE_ID</code>
Expand Down
5 changes: 4 additions & 1 deletion libs/core/src/scss/interaction-state/_focus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@
$stroke-width: utils.size('xxxxs');

transition: interaction-state.transition();
box-shadow: #{$shadow}, 0 0 0 $gap #{utils.get-color('background-color')},
box-shadow:
#{$shadow},
0 0 0 $gap #{utils.get-color('background-color')},
0 0 0 $gap + $stroke-width utils.$focus-ring-color;
z-index: utils.z('default');
}
58 changes: 37 additions & 21 deletions libs/designsystem/calendar/src/calendar.component.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
<div class="header">
<div class="month-navigator">
<button
type="button"
[disabled]="!_canNavigateBack"
(click)="_changeMonth(-1)"
kirby-button
[noDecoration]="true"
type="button"
class="no-margin"
aria-label="Previous month"
[attr.aria-disabled]="_canNavigateBack ? null : true"
[noDecoration]="true"
(click)="_changeMonth(-1)"
>
<kirby-icon name="arrow-back"></kirby-icon>
<kirby-icon name="arrow-back" aria-hidden="true"></kirby-icon>
</button>

<div class="month-and-year">
<div class="month-and-year" [id]="_tableMonthId" aria-live="polite" aria-atomic="true">
<span class="month">{{ activeMonthName }}</span>
<span *ngIf="!_hasYearNavigator" class="year">{{ activeYear }}</span>
</div>

<button
type="button"
[disabled]="!_canNavigateForward"
(click)="_changeMonth(1)"
kirby-button
[noDecoration]="true"
type="button"
class="no-margin"
aria-label="Next month"
[attr.aria-disabled]="_canNavigateForward ? null : true"
[noDecoration]="true"
(click)="_changeMonth(1)"
>
<kirby-icon name="arrow-more"></kirby-icon>
<kirby-icon name="arrow-more" aria-hidden="true"></kirby-icon>
</button>
</div>
<kirby-dropdown
Expand All @@ -37,27 +39,41 @@
></kirby-dropdown>
</div>

<table>
<table [attr.aria-labelledby]="_tableMonthId" role="grid">
<thead>
<tr>
<th *ngFor="let weekDay of _weekDays">{{ weekDay }}</th>
<th *ngFor="let weekDay of _weekDays" scope="col">
<span aria-hidden="true">{{ weekDay.firstLetterCapitalized }}</span>
<span class="visually-hidden">{{ weekDay.fullName }}</span>
</th>
</tr>
</thead>

<tbody>
<tr *ngFor="let week of _month">
<td *ngFor="let day of week">
<div
<td *ngFor="let day of week" [attr.aria-selected]="day.isSelected ? true : false">
<button
kirby-button
type="button"
(click)="_onDateSelected(day)"
class="{{ day.cssClasses }} contain-state-layer"
(keydown)="_onDateKeydown($event)"
class="day"
[class.current-month]="day.isCurrentMonth"
[class.weekend]="day.isWeekend"
[class.today]="day.isToday"
[class.selectable]="day.isSelectable"
[class.selected]="day.isSelected"
[class.focussed]="day.isFocussed"
[class.past]="day.isPast"
[attr.aria-label]="day.ariaLabel"
[attr.aria-disabled]="day.isSelectable ? null : true"
[noDecoration]="true"
[disabled]="day.isFocusable ? null : true"
[tabIndex]="day.isFocussed ? 0 : -1"
>
<span class="state-layer" aria-hidden="true"></span>
<span class="content-layer">{{ day.date }}</span>
</div>
{{ day.date }}
</button>
</td>
</tr>
</tbody>
</table>

<!-- <iframe src="kirby/components/calendar/calendar.webview.html" #calendarContainer style="width: 320px; height: 304px; border: 0"> -->
60 changes: 19 additions & 41 deletions libs/designsystem/calendar/src/calendar.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

$month-navigator-width: 80px;

:host {
font-size: utils.font-size('n');
}

table {
width: 100%;
border-collapse: collapse;
Expand Down Expand Up @@ -47,18 +51,13 @@ td {
flex-grow: 1;
align-items: center;
justify-content: space-between;

button:disabled {
opacity: 0.5;
pointer-events: none;
}
}

.month-and-year {
user-select: none;
font-weight: utils.font-weight('bold');

.month {
font-weight: utils.font-weight('bold');
margin-right: utils.size('xxs');
}
}
Expand Down Expand Up @@ -86,55 +85,34 @@ td {
align-items: center;
justify-content: center;
border-radius: $day-width * 0.5;
min-width: $day-width;
min-height: $day-width;
width: $day-width;
height: $day-width;
margin: utils.size('xxxs') 0;
color: var(--color, #{utils.get-color('black')});
background-color: var(--background-color, transparent);
font-size: utils.font-size('n');
}

.day.disabled,
.day:not(.selectable) {
button[aria-disabled='true'] {
// Hardcoded color because we don't want it to be part of a shared color palette
--color: #b4b4b4;

pointer-events: none;
color: #b4b4b4;
}

.day.selectable:not(.current-month) {
--color: #{utils.get-text-color('semi-dark')};
.day.selectable:not(.current-month, .selected) {
color: #{utils.get-text-color('semi-dark')};
}

.day.today {
@include interaction-state.apply-hover;
@include interaction-state.apply-active('s');

// Wrap declarations to avoid mixing with nested rules.
// See: https://sass-lang.com/documentation/breaking-changes/mixed-decls/
/* stylelint-disable no-duplicate-selectors */
& {
color: utils.get-color('medium-contrast');
background-color: utils.get-color('medium');
}
/* stylelint-enable no-duplicate-selectors */
color: utils.get-color('medium-contrast');
background-color: utils.get-color('medium');
}

.day.selected {
@include interaction-state.apply-hover('l', $make-lighter: true);
@include interaction-state.apply-active('xxxl', $make-lighter: true);

// Wrap declarations to avoid mixing with nested rules.
// See: https://sass-lang.com/documentation/breaking-changes/mixed-decls/
/* stylelint-disable no-duplicate-selectors */
& {
color: utils.get-color('black-contrast');
background-color: utils.get-color('black');
}
/* stylelint-enable no-duplicate-selectors */
color: utils.get-color('black-contrast');
background-color: utils.get-color('black');
}

.contain-state-layer {
@include interaction-state.initialize-layer;
@include interaction-state.apply-hover;
@include interaction-state.apply-active('s');
.visually-hidden {
position: absolute;
scale: 0;
}
Loading

0 comments on commit ebc156b

Please sign in to comment.