diff --git a/apps/cookbook/src/app/examples/button-example/examples/disabled.ts b/apps/cookbook/src/app/examples/button-example/examples/disabled.ts
index a4731e4c7f..8dcdef94e7 100644
--- a/apps/cookbook/src/app/examples/button-example/examples/disabled.ts
+++ b/apps/cookbook/src/app/examples/button-example/examples/disabled.ts
@@ -9,10 +9,10 @@ const config = {
- The button can be rendered with an icon only and no text. This is useful for "close" buttons and - menu buttons. + The button can be rendered with an icon only and no visible text. This is useful for "close" + buttons and menu buttons. +
+
+ To render a button with an icon only you can either include an accessible name inside the button
+ next to the icon and hide it visually by setting
+ [showIconOnly]="true"
+ or you can omit any text and set an
+ aria-label
+ instead. Please refer to the section on
+ Accessible Icon Buttons
+ below.
To render a button with an icon only, include an icon within the button and omit any text.
- Note: If you do need to include a text for the button in the markup but still want to render
- the button as icon only, set
- [showIconOnly]="true"
- . This is also useful in scenarios where the button needs to toggle between the default state
- and a "collapsed" state, e.g. when used in an
+ Note: including a—visually hidden—label in the markup is also useful in scenarios where the
+ button needs to toggle between the default state and a "collapsed" state, e.g. when used in an
action group
.
+ When rendering a button with no visible text it's important to make the button accessible to + assistive technologies, such as screen readers. +
++ By including a visually hidden label in the markup as mentioned above, assistive technologies + will automatically infer the name of the button from its content. +
+
+ If you choose to omit the text inside the button you must set a meaningful
+ aria-label
+ instead.
+
+ In both cases the label should describe the action of the button, such as "Close", "Search", + "Settings" etc. +
The
disabled
@@ -150,8 +174,8 @@
- In many scenarios it's good practice to expose the button as disabled, but still make it available for
- users to find when navigating via the
+ In many scenarios it's good practice to expose the button as disabled, but still make it
+ available for users to find when navigating via the
Tab
key.
@@ -197,6 +221,17 @@
Please refer to:
+
- Segmented Control can be rendered in two different sizes when in default mode:
- sm
- and
- md
- .
-
- Use the
- mode
- property to control the rendering of each segment. In addiditon to the
- default
- mode, Segmented Control supports
- chip
- and
- compactChip
- mode.
-
- Segmented Control can be combined with a Button to create a related group of choices with one - option separate from the others—all mutually exclusive. Use this pattern to create a filter as - in the example below. -
-
-
- Note: If changing the
- size
- of the Segmented Control be sure to set the same
- size
- for the Button.
-
-
+ Segmented Control can be rendered in two different sizes when in default mode:
+ sm
+ and
+ md
+ .
+
+ Use the
+ mode
+ property to control the rendering of each segment. In addiditon to the
+ default
+ mode, Segmented Control supports
+ chip
+ and
+ compactChip
+ mode.
+
+ Segmented Control can be combined with a Button to create a related group of choices with one + option separate from the others—all mutually exclusive. Use this pattern to create a filter as in + the example below. +
+
+
+ Note: If changing the
+ size
+ of the Segmented Control be sure to set the same
+ size
+ for the Button.
+
+
+ Badges can be applied to Segmented Control in
+ default
+ mode. A badge can be added to any
+ SegmentItem
+ using the
+ badge
+ property.
+
+
-
+ The badge can contain either a plain text string through
+ badge.content
+ or an icon provided as the name of an icon in the
+ badge.icon
+ property.
+
+ When using an icon the
+ badge.description
+ property should be set which will be used as the
+ aria-label
+ of the badge for accessibility.
+
+ The background color of the badge can be controlled through
+ badge.themeColor
+ .
+
Segmented Control has support for theme colors when used within a Card.
+
+ Simply set the Card's
+ themeColor
+ property and Segmented Control will render in a light or dark variant accordingly.
+
- Badges can be applied to Segmented Control in
- default
- mode. A badge can be added to any
- SegmentItem
- using the
- badge
- property.
-
- - See SegmentItem on Github - -
-
- The badge can contain either a plain text string through
- badge.content
- or an icon provided as the name of an icon in the
- badge.icon
- property.
-
- When using an icon the
- badge.description
- property should be set which will be used as the
- aria-label
- of the badge for accessibility.
-
- The background color of the badge can be controlled through
- badge.themeColor
- .
-
+ The Segmented Control implements the + + Tabs Pattern from ARIA Authoring Practices Guide + +
++ The component has full keyboard support for navigating between and selecting segment buttons. + Keyboard navigation moves focus between segment buttons, and + Enter ↵ + or + Space + selects the element that is currently focused. +
+The following keys can be used to navigate within the control:
+ +Segmented Control has support for theme colors when used within a Card.
-
- Simply set the Card's
- themeColor
- property and Segmented Control will render in a light or dark variant accordingly.
-
- Note on size: - For all modes, the touch area will always be a minimum of 44px by 44px. If chip/segment is - smaller than this, the surrounding area will still be clickable, to preserve accessibility. -
-+ Note on size: + For all modes, the touch area will always be a minimum of 44px by 44px. If chip/segment is smaller + than this, the surrounding area will still be clickable, to preserve accessibility. +
diff --git a/libs/designsystem/button/src/button.component.scss b/libs/designsystem/button/src/button.component.scss index 2afb381ae3..9b5f8dd614 100644 --- a/libs/designsystem/button/src/button.component.scss +++ b/libs/designsystem/button/src/button.component.scss @@ -226,7 +226,8 @@ $button-margin: utils.size('xxxs'); .content-layer { @include utils.slotted(':not(kirby-icon)') { - display: none; + position: absolute; + scale: 0; } } } diff --git a/libs/designsystem/button/src/button.component.spec.ts b/libs/designsystem/button/src/button.component.spec.ts index bc9388ea06..cd7d8e5215 100644 --- a/libs/designsystem/button/src/button.component.spec.ts +++ b/libs/designsystem/button/src/button.component.spec.ts @@ -506,8 +506,11 @@ describe('ButtonComponent', () => { expect(contentLayer.lastChild).toBe(contentLayer.querySelector('kirby-icon')); }); - it('should hide the plain text', () => { - expect(contentLayer.firstChild).toBeHidden(); + it('should visually hide the plain text', () => { + expect(contentLayer.firstChild).toHaveComputedStyle({ scale: '0' }); + const hiddenTextRect = (contentLayer.firstChild as HTMLElement).getBoundingClientRect(); + expect(hiddenTextRect.height).toEqual(0); + expect(hiddenTextRect.width).toEqual(0); }); it('should render as icon only', () => { @@ -540,8 +543,11 @@ describe('ButtonComponent', () => { expect(contentLayer.firstChild).toBe(contentLayer.querySelector('kirby-icon')); }); - it('should hide the plain text', () => { - expect(contentLayer.lastChild).toBeHidden(); + it('should visually hide the plain text', () => { + expect(contentLayer.lastChild).toHaveComputedStyle({ scale: '0' }); + const hiddenTextRect = (contentLayer.lastChild as HTMLElement).getBoundingClientRect(); + expect(hiddenTextRect.height).toEqual(0); + expect(hiddenTextRect.width).toEqual(0); }); it('should render as icon only', () => { @@ -568,8 +574,11 @@ describe('ButtonComponent', () => { expect(firstChild.firstChild.nodeType).toBe(Node.TEXT_NODE); }); - it('should hide the text element', () => { - expect(contentLayer.firstChild).toBeHidden(); + it('should visually hide the text element', () => { + expect(contentLayer.firstChild).toHaveComputedStyle({ scale: '0' }); + const hiddenTextRect = (contentLayer.firstChild as HTMLElement).getBoundingClientRect(); + expect(hiddenTextRect.height).toEqual(0); + expect(hiddenTextRect.width).toEqual(0); }); it('should render as icon only', () => { @@ -596,8 +605,11 @@ describe('ButtonComponent', () => { expect(lastChild.firstChild.nodeType).toBe(Node.TEXT_NODE); }); - it('should hide the text element', () => { - expect(contentLayer.lastChild).toBeHidden(); + it('should visually hide the text element', () => { + expect(contentLayer.lastChild).toHaveComputedStyle({ scale: '0' }); + const hiddenTextRect = (contentLayer.lastChild as HTMLElement).getBoundingClientRect(); + expect(hiddenTextRect.height).toEqual(0); + expect(hiddenTextRect.width).toEqual(0); }); it('should render as icon only', () => { diff --git a/libs/designsystem/calendar/src/calendar.component.spec.ts b/libs/designsystem/calendar/src/calendar.component.spec.ts index 6033efeec5..52481596a0 100644 --- a/libs/designsystem/calendar/src/calendar.component.spec.ts +++ b/libs/designsystem/calendar/src/calendar.component.spec.ts @@ -1,7 +1,7 @@ import { LOCALE_ID } from '@angular/core'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; import { format, Locale, startOfDay, startOfMonth } from 'date-fns'; -import { zonedTimeToUtc } from 'date-fns-tz'; +import { fromZonedTime } from 'date-fns-tz'; import { TestHelper } from '@kirbydesign/designsystem/testing'; import { WindowRef } from '@kirbydesign/designsystem/types'; @@ -891,7 +891,7 @@ describe('CalendarComponent', () => { } function utcMidnightDate(yyyyMMdd) { - return zonedTimeToUtc(yyyyMMdd, 'UTC'); + return fromZonedTime(yyyyMMdd, 'UTC'); } function clickDayOfMonth(dateOneIndexed: number) { diff --git a/libs/designsystem/calendar/src/calendar.component.ts b/libs/designsystem/calendar/src/calendar.component.ts index b0ebb52937..59e13125af 100644 --- a/libs/designsystem/calendar/src/calendar.component.ts +++ b/libs/designsystem/calendar/src/calendar.component.ts @@ -31,7 +31,7 @@ import { startOfMonth, startOfWeek, } from 'date-fns'; -import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz'; +import { fromZonedTime, toZonedTime } from 'date-fns-tz'; import { da, enGB, enUS } from 'date-fns/locale'; import { capitalizeFirstLetter } from '@kirbydesign/core'; @@ -299,11 +299,11 @@ export class CalendarComponent implements OnInit, OnChanges { return dateLocalOrUTC; } if ( - startOfDay(utcToZonedTime(dateLocalOrUTC, this.timeZoneName)).getTime() === - utcToZonedTime(dateLocalOrUTC, this.timeZoneName).getTime() + startOfDay(toZonedTime(dateLocalOrUTC, this.timeZoneName)).getTime() === + toZonedTime(dateLocalOrUTC, this.timeZoneName).getTime() ) { // the date is a UTC midnight; create the equivalent local timezone midnight date - const normalizedUTCdate = utcToZonedTime(dateLocalOrUTC, this.timeZoneName); + const normalizedUTCdate = toZonedTime(dateLocalOrUTC, this.timeZoneName); return normalizedUTCdate; } // does not point to midnight so we make it @@ -438,7 +438,7 @@ export class CalendarComponent implements OnInit, OnChanges { let newDate = new Date(newDay.year, newDay.monthIndex, newDay.date); if (this.timezone === 'UTC') { - newDate = zonedTimeToUtc(this.subtractTimezoneOffset(newDate), this.timeZoneName); + newDate = fromZonedTime(this.subtractTimezoneOffset(newDate), this.timeZoneName); } const dateToEmit = newDate; @@ -546,7 +546,7 @@ export class CalendarComponent implements OnInit, OnChanges { if (!newDate) return; if (this.timezone === 'UTC') { - newDate = zonedTimeToUtc(this.subtractTimezoneOffset(newDate), this.timeZoneName); + newDate = fromZonedTime(this.subtractTimezoneOffset(newDate), this.timeZoneName); } const today = this.getTodayDate(); diff --git a/libs/designsystem/form-field/src/form-field.component.spec.ts b/libs/designsystem/form-field/src/form-field.component.spec.ts index cbb42b844e..8687e4df08 100644 --- a/libs/designsystem/form-field/src/form-field.component.spec.ts +++ b/libs/designsystem/form-field/src/form-field.component.spec.ts @@ -443,7 +443,7 @@ describe('FormFieldComponent', () => { describe('When nested inside a kirby-item', () => { describe('by default', () => { - beforeEach(() => { + beforeEach(async () => { spectator = createHost( `Section Header
Section Header
Section Header