feat(a11y): add dynamic font scaling (#28314)

Issue number: resolves #24638, resolves #18592

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Developers have requested that Ionic Framework support the dynamic type
feature on iOS for accessibility purposes. Ionic applications do not
respond to font scaling on iOS which can create inaccessible
applications particularly for users with low vision. Ionic apps on
Android devices currently support the Android equivalent due to
functionality in the Chromium webview.

Developers have also requested a way of adjusting the fonts in their
Ionic UI components consistently.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Ionic components now use `rem` instead of `px` where appropriate. This
means devs can change the font size on `html` and the text in supported
Ionic components will scale up/down appropriately
- Add support for Dynamic Type on iOS (the iOS version of Dynamic Font
Scaling)

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
Co-authored-by: Brandy Carney <brandyscarney@users.noreply.github.com>
Co-authored-by: Shawn Taylor <shawn@ionic.io>
Co-authored-by: ionitron <hi@ionicframework.com>
Co-authored-by: Sean Perkins <sean@ionic.io>
Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
Co-authored-by: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com>
This commit is contained in:
Liam DeBeasi
2023-10-10 17:38:09 -04:00
committed by GitHub
parent 57e2476370
commit f8067819ee
546 changed files with 2363 additions and 395 deletions

View File

@ -161,7 +161,7 @@
min-height: 24px;
font-size: 13px;
font-size: dynamic-font(13px);
}
// iOS Item Avatar

View File

@ -7,7 +7,7 @@
$item-ios-min-height: 44px !default;
/// @prop - Font size of the item
$item-ios-font-size: 16px !default;
$item-ios-font-size: dynamic-font(16px) !default;
/// @prop - Margin top of the item paragraph
$item-ios-paragraph-margin-top: 0 !default;
@ -22,7 +22,7 @@ $item-ios-paragraph-margin-bottom: 2px !default;
$item-ios-paragraph-margin-start: $item-ios-paragraph-margin-end !default;
/// @prop - Font size of the item paragraph
$item-ios-paragraph-font-size: 14px !default;
$item-ios-paragraph-font-size: dynamic-font(14px) !default;
/// @prop - Color of the item paragraph
$item-ios-paragraph-text-color: rgba($text-color-rgb, .4) !default;

View File

@ -1,3 +1,4 @@
@use "sass:math";
@import "./item";
@import "./item.md.vars";
@import "../label/label.md.vars";
@ -25,7 +26,7 @@
--highlight-color-valid: #{$item-md-input-highlight-color-valid};
--highlight-color-invalid: #{$item-md-input-highlight-color-invalid};
font-size: $item-md-font-size;
font-size: dynamic-font($item-md-font-size);
font-weight: normal;
text-transform: none;
@ -201,7 +202,13 @@
::slotted(ion-icon) {
color: $item-md-icon-slot-color;
font-size: $item-md-icon-slot-font-size;
// The icon's font size should use em units to support
// font scaling but evaluate to 24px at 100% font size.
// The value in em units is calculated by dividing
// the icon's font size in pixels by the item's
// font size in pixels.
// e.g. 24px / 16px = 1.5em
font-size: math.div($item-md-icon-slot-font-size, $item-md-font-size) * 1em;
}
:host(.ion-color:not(.item-fill-solid):not(.item-fill-outline)) ::slotted(ion-icon) {
@ -338,7 +345,7 @@
min-height: 25px;
font-size: 12px;
font-size: dynamic-font(12px);
}

View File

@ -165,7 +165,7 @@ $item-md-icon-end-slot-margin-end: null !default;
$item-md-icon-slot-color: rgba($text-color-rgb, 0.54) !default;
/// @prop - Font size of an icon in the start/end slot
$item-md-icon-slot-font-size: 24px !default;
$item-md-icon-slot-font-size: 24px !default;
// Label Slots
@ -188,7 +188,7 @@ $item-md-label-slot-end-margin-start: $item-md-label-slot-end-margin-end !
// --------------------------------------------------
/// @prop - Font size of a note in the start/end slot
$item-md-note-slot-font-size: 11px !default;
$item-md-note-slot-font-size: dynamic-font(11px) !default;
/// @prop - Padding top for a note in the start/end slot
$item-md-note-slot-padding-top: 18px !default;

View File

@ -67,7 +67,7 @@
--show-full-highlight: 0;
--show-inset-highlight: 0;
--detail-icon-color: initial;
--detail-icon-font-size: 20px;
--detail-icon-font-size: 1.25em;
--detail-icon-opacity: 0.25;
--color-activated: var(--color);
--color-focused: var(--color);
@ -581,7 +581,7 @@ ion-ripple-effect {
.item-counter {
padding-top: 5px;
font-size: 12px;
font-size: dynamic-font(12px);
z-index: 1;
}

View File

@ -2,7 +2,7 @@ import AxeBuilder from '@axe-core/playwright';
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs().forEach(({ title, config }) => {
configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('item: axe'), () => {
test('should not have accessibility violations', async ({ page }) => {
await page.goto(`/src/components/item/test/a11y`, config);
@ -30,4 +30,150 @@ configs().forEach(({ title, config }) => {
expect(await item2.getAttribute('aria-label')).toEqual('test');
});
});
test.describe(title('item: font scaling'), () => {
test('should scale text on larger font sizes', async ({ page }) => {
await page.setContent(
`
<style>
html {
font-size: 310%;
}
</style>
<ion-item>
<ion-label>Item</ion-label>
</ion-item>
`,
config
);
const item = page.locator('ion-item');
await expect(item).toHaveScreenshot(screenshot(`item-scale`));
});
test('should scale slotted icons on larger font sizes', async ({ page }) => {
await page.setContent(
`
<style>
html {
font-size: 310%;
}
</style>
<ion-item>
<ion-icon slot="start" name="star"></ion-icon>
<ion-label>Item</ion-label>
<ion-icon slot="end" name="flag"></ion-icon>
</ion-item>
`,
config
);
const item = page.locator('ion-item');
await expect(item).toHaveScreenshot(screenshot(`item-icons-scale`));
});
test('should scale detail icon on larger font sizes', async ({ page }) => {
await page.setContent(
`
<style>
html {
font-size: 310%;
}
</style>
<ion-item detail="true">
<ion-label>Item</ion-label>
</ion-item>
`,
config
);
const item = page.locator('ion-item');
await expect(item).toHaveScreenshot(screenshot(`item-detail-icon-scale`));
});
test('should scale counter text on larger font sizes', async ({ page }) => {
await page.setContent(
`
<style>
html {
font-size: 310%;
}
</style>
<ion-item counter="true">
<ion-label position="stacked">Counter</ion-label>
<ion-input maxlength="20"></ion-input>
</ion-item>
`,
config
);
const item = page.locator('ion-item');
await expect(item).toHaveScreenshot(screenshot(`item-counter-text-scale`));
});
test('should scale helper and error text on larger font sizes', async ({ page }) => {
await page.setContent(
`
<style>
html {
font-size: 310%;
}
</style>
<ion-list>
<ion-item>
<ion-label>Helper</ion-label>
<ion-input></ion-input>
<ion-note slot="helper">Helper Text</ion-note>
</ion-item>
<ion-item class="ion-invalid">
<ion-label>Error</ion-label>
<ion-input></ion-input>
<ion-note slot="error">Error Text</ion-note>
</ion-item>
</ion-list>
`,
config
);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`item-helper-error-text-scale`));
});
test('should scale buttons in an item on larger font sizes', async ({ page }) => {
await page.setContent(
`
<style>
html {
font-size: 310%;
}
</style>
<ion-list>
<ion-item>
<ion-label>Item</ion-label>
<ion-button>Default</ion-button>
</ion-item>
<ion-item>
<ion-label>Item</ion-label>
<ion-button size="small">Small</ion-button>
</ion-item>
<ion-item>
<ion-label>Item</ion-label>
<ion-button size="large">Large</ion-button>
</ion-item>
</ion-list>
`,
config
);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`item-buttons-scale`));
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB