feat(checkbox): add helperText and errorText properties (#30140)
Issue number: resolves #29810 --------- ## What is the current behavior? Checkbox does not support helper and error text. ## What is the new behavior? Adds support for helper and error text, similar to input and textarea. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information - [Bottom Content: Preview](https://ionic-framework-git-rou-11141-ionic1.vercel.app/src/components/checkbox/test/bottom-content) - [Item: Preview](https://ionic-framework-git-rou-11141-ionic1.vercel.app/src/components/checkbox/test/item) --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
@ -148,44 +148,51 @@ input {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// Justify Content
|
||||
// ---------------------------------------------
|
||||
// Checkbox Bottom Content
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.checkbox-bottom {
|
||||
@include padding(4px, null, null, null);
|
||||
|
||||
display: flex;
|
||||
|
||||
:host(.checkbox-justify-space-between) .checkbox-wrapper {
|
||||
justify-content: space-between;
|
||||
|
||||
font-size: dynamic-font(12px);
|
||||
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
:host(.checkbox-justify-start) .checkbox-wrapper {
|
||||
justify-content: start;
|
||||
:host(.checkbox-label-placement-stacked) .checkbox-bottom {
|
||||
font-size: dynamic-font(16px);
|
||||
}
|
||||
|
||||
:host(.checkbox-justify-end) .checkbox-wrapper {
|
||||
justify-content: end;
|
||||
// Checkbox Hint Text
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error text should only be shown when .ion-invalid is
|
||||
* present on the checkbox. Otherwise the helper text should
|
||||
* be shown.
|
||||
*/
|
||||
.checkbox-bottom .error-text {
|
||||
display: none;
|
||||
|
||||
color: ion-color(danger, base);
|
||||
}
|
||||
|
||||
// Align Items
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.checkbox-alignment-start) .checkbox-wrapper {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
:host(.checkbox-alignment-center) .checkbox-wrapper {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Justify Content & Align Items
|
||||
// ---------------------------------------------
|
||||
|
||||
// The checkbox should be displayed as block when either justify
|
||||
// or alignment is set; otherwise, these properties will have no
|
||||
// visible effect.
|
||||
:host(.checkbox-justify-space-between),
|
||||
:host(.checkbox-justify-start),
|
||||
:host(.checkbox-justify-end),
|
||||
:host(.checkbox-alignment-start),
|
||||
:host(.checkbox-alignment-center) {
|
||||
.checkbox-bottom .helper-text {
|
||||
display: block;
|
||||
|
||||
color: $text-color-step-300;
|
||||
}
|
||||
|
||||
:host(.ion-touched.ion-invalid) .checkbox-bottom .error-text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host(.ion-touched.ion-invalid) .checkbox-bottom .helper-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Label Placement - Start
|
||||
@ -217,6 +224,8 @@ input {
|
||||
*/
|
||||
:host(.checkbox-label-placement-end) .checkbox-wrapper {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -260,6 +269,8 @@ input {
|
||||
*/
|
||||
:host(.checkbox-label-placement-stacked) .checkbox-wrapper {
|
||||
flex-direction: column;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:host(.checkbox-label-placement-stacked) .label-text-wrapper {
|
||||
@ -287,6 +298,46 @@ input {
|
||||
@include transform-origin(center, top);
|
||||
}
|
||||
|
||||
// Justify Content
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.checkbox-justify-space-between) .checkbox-wrapper {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:host(.checkbox-justify-start) .checkbox-wrapper {
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
:host(.checkbox-justify-end) .checkbox-wrapper {
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
// Align Items
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.checkbox-alignment-start) .checkbox-wrapper {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
:host(.checkbox-alignment-center) .checkbox-wrapper {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Justify Content & Align Items
|
||||
// ---------------------------------------------
|
||||
|
||||
// The checkbox should be displayed as block when either justify
|
||||
// or alignment is set; otherwise, these properties will have no
|
||||
// visible effect.
|
||||
:host(.checkbox-justify-space-between),
|
||||
:host(.checkbox-justify-start),
|
||||
:host(.checkbox-justify-end),
|
||||
:host(.checkbox-alignment-start),
|
||||
:host(.checkbox-alignment-center) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Checked / Indeterminate Checkbox
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
@ -17,6 +17,9 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
|
||||
* @part container - The container for the checkbox mark.
|
||||
* @part label - The label text describing the checkbox.
|
||||
* @part mark - The checkmark used to indicate the checked state.
|
||||
* @part supporting-text - Supporting text displayed beneath the checkbox label.
|
||||
* @part helper-text - Supporting text displayed beneath the checkbox label when the checkbox is valid.
|
||||
* @part error-text - Supporting text displayed beneath the checkbox label when the checkbox is invalid and touched.
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-checkbox',
|
||||
@ -28,6 +31,8 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
|
||||
})
|
||||
export class Checkbox implements ComponentInterface {
|
||||
private inputId = `ion-cb-${checkboxIds++}`;
|
||||
private helperTextId = `${this.inputId}-helper-text`;
|
||||
private errorTextId = `${this.inputId}-error-text`;
|
||||
private focusEl?: HTMLElement;
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
@ -60,6 +65,16 @@ export class Checkbox implements ComponentInterface {
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* Text that is placed under the checkbox label and displayed when an error is detected.
|
||||
*/
|
||||
@Prop() errorText?: string;
|
||||
|
||||
/**
|
||||
* Text that is placed under the checkbox label and displayed when no error is detected.
|
||||
*/
|
||||
@Prop() helperText?: string;
|
||||
|
||||
/**
|
||||
* The value of the checkbox does not mean if it's checked or not, use the `checked`
|
||||
* property for that.
|
||||
@ -174,6 +189,48 @@ export class Checkbox implements ComponentInterface {
|
||||
this.toggleChecked(ev);
|
||||
};
|
||||
|
||||
private getHintTextID(): string | undefined {
|
||||
const { el, helperText, errorText, helperTextId, errorTextId } = this;
|
||||
|
||||
if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
|
||||
return errorTextId;
|
||||
}
|
||||
|
||||
if (helperText) {
|
||||
return helperTextId;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for rendering helper text and error text.
|
||||
* This element should only be rendered if hint text is set.
|
||||
*/
|
||||
private renderHintText() {
|
||||
const { helperText, errorText, helperTextId, errorTextId } = this;
|
||||
|
||||
/**
|
||||
* undefined and empty string values should
|
||||
* be treated as not having helper/error text.
|
||||
*/
|
||||
const hasHintText = !!helperText || !!errorText;
|
||||
if (!hasHintText) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="checkbox-bottom">
|
||||
<div id={helperTextId} class="helper-text" part="supporting-text helper-text">
|
||||
{helperText}
|
||||
</div>
|
||||
<div id={errorTextId} class="error-text" part="supporting-text error-text">
|
||||
{errorText}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
color,
|
||||
@ -199,6 +256,8 @@ export class Checkbox implements ComponentInterface {
|
||||
return (
|
||||
<Host
|
||||
aria-checked={indeterminate ? 'mixed' : `${checked}`}
|
||||
aria-describedby={this.getHintTextID()}
|
||||
aria-invalid={this.getHintTextID() === this.errorTextId}
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
@ -237,6 +296,7 @@ export class Checkbox implements ComponentInterface {
|
||||
part="label"
|
||||
>
|
||||
<slot></slot>
|
||||
{this.renderHintText()}
|
||||
</div>
|
||||
<div class="native-wrapper">
|
||||
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">
|
||||
|
||||
207
core/src/components/checkbox/test/bottom-content/checkbox.e2e.ts
Normal file
@ -0,0 +1,207 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* Functionality is the same across modes & directions
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('checkbox: bottom content functionality'), () => {
|
||||
test('should not render bottom content if no hint is enabled', async ({ page }) => {
|
||||
await page.setContent(`<ion-checkbox>Label</ion-checkbox>`, config);
|
||||
|
||||
const bottomEl = page.locator('ion-checkbox .checkbox-bottom');
|
||||
await expect(bottomEl).toHaveCount(0);
|
||||
});
|
||||
test('helper text should be visible initially', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox helper-text="Helper text" error-text="Error text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-checkbox .helper-text');
|
||||
const errorText = page.locator('ion-checkbox .error-text');
|
||||
await expect(helperText).toBeVisible();
|
||||
await expect(helperText).toHaveText('Helper text');
|
||||
await expect(errorText).toBeHidden();
|
||||
});
|
||||
test('checkbox should have an aria-describedby attribute when helper text is present', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox helper-text="Helper text" error-text="Error text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
const helperText = page.locator('ion-checkbox .helper-text');
|
||||
const helperTextId = await helperText.getAttribute('id');
|
||||
const ariaDescribedBy = await checkbox.getAttribute('aria-describedby');
|
||||
|
||||
expect(ariaDescribedBy).toBe(helperTextId);
|
||||
});
|
||||
test('error text should be visible when checkbox is invalid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-checkbox .helper-text');
|
||||
const errorText = page.locator('ion-checkbox .error-text');
|
||||
await expect(helperText).toBeHidden();
|
||||
await expect(errorText).toBeVisible();
|
||||
await expect(errorText).toHaveText('Error text');
|
||||
});
|
||||
|
||||
test('checkbox should have an aria-describedby attribute when error text is present', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
const errorText = page.locator('ion-checkbox .error-text');
|
||||
const errorTextId = await errorText.getAttribute('id');
|
||||
const ariaDescribedBy = await checkbox.getAttribute('aria-describedby');
|
||||
|
||||
expect(ariaDescribedBy).toBe(errorTextId);
|
||||
});
|
||||
test('checkbox should have aria-invalid attribute when checkbox is invalid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
|
||||
await expect(checkbox).toHaveAttribute('aria-invalid');
|
||||
});
|
||||
test('checkbox should not have aria-invalid attribute when checkbox is valid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox helper-text="Helper text" error-text="Error text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
|
||||
await expect(checkbox).not.toHaveAttribute('aria-invalid');
|
||||
});
|
||||
test('checkbox should not have aria-describedby attribute when no hint or error text is present', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setContent(`<ion-checkbox>Label</ion-checkbox>`, config);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
|
||||
await expect(checkbox).not.toHaveAttribute('aria-describedby');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Rendering is different across modes
|
||||
*/
|
||||
configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('checkbox: helper text rendering'), () => {
|
||||
// Check the default label placement, end, and stacked
|
||||
[undefined, 'end', 'stacked'].forEach((labelPlacement) => {
|
||||
test(`${
|
||||
labelPlacement ? `${labelPlacement} label - ` : ''
|
||||
}should not have visual regressions when rendering helper text`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox ${
|
||||
labelPlacement ? `label-placement="${labelPlacement}"` : ''
|
||||
} helper-text="Helper text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-checkbox');
|
||||
await expect(bottomEl).toHaveScreenshot(
|
||||
screenshot(`checkbox-helper-text${labelPlacement ? `-${labelPlacement}` : ''}`)
|
||||
);
|
||||
});
|
||||
|
||||
test(`${
|
||||
labelPlacement ? `${labelPlacement} label - ` : ''
|
||||
}should not have visual regressions when rendering helper text with wrapping text`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox ${
|
||||
labelPlacement ? `label-placement="${labelPlacement}"` : ''
|
||||
} helper-text="Helper text helper text helper text helper text helper text helper text helper text helper text helper text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-checkbox');
|
||||
await expect(bottomEl).toHaveScreenshot(
|
||||
screenshot(`checkbox-helper-text${labelPlacement ? `-${labelPlacement}` : ''}-wrapping`)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('checkbox: error text rendering'), () => {
|
||||
test('should not have visual regressions when rendering error text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox class="ion-invalid ion-touched" error-text="Error text">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-checkbox');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`checkbox-error-text`));
|
||||
});
|
||||
test('should not have visual regressions when rendering error text with a stacked label', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-checkbox class="ion-invalid ion-touched" error-text="Error text" label-placement="stacked">Label</ion-checkbox>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-checkbox');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`checkbox-error-text-stacked-label`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Customizing supporting text is the same across modes and directions
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('checkbox: supporting text customization'), () => {
|
||||
test('should not have visual regressions when rendering helper text with custom css', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-checkbox::part(supporting-text) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
ion-checkbox::part(helper-text) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<ion-checkbox helper-text="Helper text">Label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-checkbox');
|
||||
await expect(helperText).toHaveScreenshot(screenshot(`checkbox-helper-text-custom-css`));
|
||||
});
|
||||
test('should not have visual regressions when rendering error text with custom css', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-checkbox::part(supporting-text) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
ion-checkbox::part(error-text) {
|
||||
color: purple;
|
||||
}
|
||||
</style>
|
||||
<ion-checkbox class="ion-invalid ion-touched" error-text="Error text">Label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const errorText = page.locator('ion-checkbox');
|
||||
await expect(errorText).toHaveScreenshot(screenshot(`checkbox-error-text-custom-css`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
134
core/src/components/checkbox/test/bottom-content/index.html
Normal file
@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Checkbox - Bottom Content</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
|
||||
color: #6f7378;
|
||||
|
||||
margin-top: 10px;
|
||||
}
|
||||
ion-checkbox {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Checkbox - Bottom Content</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>No Hint</h2>
|
||||
<ion-checkbox>Label</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>No Hint: Stacked</h2>
|
||||
<ion-checkbox label-placement="stacked">Label</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Label Start</h2>
|
||||
<ion-checkbox helper-text="Helper text" error-text="Error text">Label</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Label End</h2>
|
||||
<ion-checkbox label-placement="end" helper-text="Helper text" error-text="Error text">Label</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Label Stacked</h2>
|
||||
<ion-checkbox label-placement="stacked" helper-text="Helper text" error-text="Error text"
|
||||
>Label</ion-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Label Fixed</h2>
|
||||
<ion-checkbox label-placement="fixed" helper-text="Helper text" error-text="Error text">Label</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text: Label Start</h2>
|
||||
<ion-checkbox helper-text="Helper text" error-text="Error text" class="ion-invalid ion-touched"
|
||||
>Label</ion-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text: Label End</h2>
|
||||
<ion-checkbox
|
||||
label-placement="end"
|
||||
helper-text="Helper text"
|
||||
error-text="Error text"
|
||||
class="ion-invalid ion-touched"
|
||||
>Label</ion-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text: Label Stacked</h2>
|
||||
<ion-checkbox
|
||||
label-placement="stacked"
|
||||
helper-text="Helper text"
|
||||
error-text="Error text"
|
||||
class="ion-invalid ion-touched"
|
||||
>Label</ion-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text: Label Fixed</h2>
|
||||
<ion-checkbox
|
||||
label-placement="fixed"
|
||||
helper-text="Helper text"
|
||||
error-text="Error text"
|
||||
class="ion-invalid ion-touched"
|
||||
>Label</ion-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button onclick="toggleValid()" class="expand">Toggle error</button>
|
||||
|
||||
<script>
|
||||
const checkboxes = document.querySelectorAll('ion-checkbox[helper-text]');
|
||||
|
||||
function toggleValid() {
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.classList.toggle('ion-invalid');
|
||||
checkbox.classList.toggle('ion-touched');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
@ -53,7 +53,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('checkbox: long label in item'), () => {
|
||||
test('should render margins correctly when using long label in item', async ({ page }) => {
|
||||
test('should not have visual regressions when using long label in item', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-list>
|
||||
@ -69,7 +69,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
const list = page.locator('ion-list');
|
||||
await expect(list).toHaveScreenshot(screenshot(`checkbox-long-label-in-item`));
|
||||
});
|
||||
test('should render margins correctly when using long label in item with start alignment', async ({
|
||||
test('should not have visual regressions when using long label in item with start alignment', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
@ -93,8 +93,25 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('checkbox: end label in item'), () => {
|
||||
test('should not have visual regressions when using end label in item', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="end">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const list = page.locator('ion-list');
|
||||
await expect(list).toHaveScreenshot(screenshot(`checkbox-end-label-in-item`));
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('checkbox: stacked label in item'), () => {
|
||||
test('should render margins correctly when using stacked label in item', async ({ page }) => {
|
||||
test('should not have visual regressions when using stacked label in item', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-list>
|
||||
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@ -49,6 +49,15 @@
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<h1>Placement Start</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default Justify</h2>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="start">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Justify Start</h2>
|
||||
<ion-list>
|
||||
@ -79,6 +88,15 @@
|
||||
|
||||
<h1>Placement End</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default Justify</h2>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="end">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Justify Start</h2>
|
||||
<ion-list>
|
||||
@ -109,6 +127,15 @@
|
||||
|
||||
<h1>Placement Fixed</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default Justify</h2>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="fixed">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Justify Start</h2>
|
||||
<ion-list>
|
||||
@ -139,6 +166,15 @@
|
||||
|
||||
<h1>Placement Stacked</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default Align</h2>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="stacked">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Align Start</h2>
|
||||
<ion-list>
|
||||
@ -190,6 +226,24 @@
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="end">
|
||||
<ion-label class="ion-text-wrap">
|
||||
Enable Notifications Enable Notifications Enable Notifications
|
||||
</ion-label>
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="end" alignment="start">
|
||||
<ion-label class="ion-text-wrap">
|
||||
Enable Notifications Enable Notifications Enable Notifications
|
||||
</ion-label>
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||