feat(radio): component can be used outside of ion-item (#26582)

This commit is contained in:
Sean Perkins
2023-01-10 21:19:21 -05:00
committed by GitHub
parent 162c3f076a
commit 9761b0a092
252 changed files with 1521 additions and 436 deletions

View File

@@ -1515,13 +1515,13 @@ export declare interface IonRadio extends Components.IonRadio {
@ProxyCmp({
defineCustomElementFn: undefined,
inputs: ['color', 'disabled', 'mode', 'name', 'value']
inputs: ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value']
})
@Component({
selector: 'ion-radio',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
inputs: ['color', 'disabled', 'mode', 'name', 'value']
inputs: ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value']
})
export class IonRadio {
protected el: HTMLElement;

View File

@@ -1004,6 +1004,9 @@ ion-progress-bar,part,track
ion-radio,shadow
ion-radio,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-radio,prop,disabled,boolean,false,false,false
ion-radio,prop,justify,"end" | "space-between" | "start",'space-between',false,false
ion-radio,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
ion-radio,prop,legacy,boolean | undefined,undefined,false,false
ion-radio,prop,mode,"ios" | "md",undefined,false,false
ion-radio,prop,name,string,this.inputId,false,false
ion-radio,prop,value,any,undefined,false,false

View File

@@ -2158,6 +2158,18 @@ export namespace Components {
* If `true`, the user cannot interact with the radio.
*/
"disabled": boolean;
/**
* How to pack the label and radio within a line. `'start'`: The label and radio will appear on the left in LTR and on the right in RTL. `'end'`: The label and radio will appear on the right in LTR and on the left in RTL. `'space-between'`: The label and radio will appear on opposite ends of the line with space between the two elements.
*/
"justify": 'start' | 'end' | 'space-between';
/**
* Where to place the label relative to the radio. `'start'`: The label will appear to the left of the radio in LTR and to the right in RTL. `'end'`: The label will appear to the right of the radio in LTR and to the left in RTL. `'fixed'`: The label has the same behavior as `'start'` except it also has a fixed width. Long text will be truncated with ellipses ("...").
*/
"labelPlacement": 'start' | 'end' | 'fixed';
/**
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
*/
"legacy"?: boolean;
/**
* The mode determines which platform styles to use.
*/
@@ -6066,6 +6078,18 @@ declare namespace LocalJSX {
* If `true`, the user cannot interact with the radio.
*/
"disabled"?: boolean;
/**
* How to pack the label and radio within a line. `'start'`: The label and radio will appear on the left in LTR and on the right in RTL. `'end'`: The label and radio will appear on the right in LTR and on the left in RTL. `'space-between'`: The label and radio will appear on opposite ends of the line with space between the two elements.
*/
"justify"?: 'start' | 'end' | 'space-between';
/**
* Where to place the label relative to the radio. `'start'`: The label will appear to the left of the radio in LTR and to the right in RTL. `'end'`: The label will appear to the right of the radio in LTR and to the left in RTL. `'fixed'`: The label has the same behavior as `'start'` except it also has a fixed width. Long text will be truncated with ellipses ("...").
*/
"labelPlacement"?: 'start' | 'end' | 'fixed';
/**
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
*/
"legacy"?: boolean;
/**
* The mode determines which platform styles to use.
*/

View File

@@ -6,11 +6,19 @@
:host {
--color-checked: #{ion-color(primary, base)};
}
// TODO FW-3125: Remove styles
:host(.legacy-radio) {
width: $radio-ios-icon-width;
height: $radio-ios-icon-height;
}
// TODO FW-3125: Move styles to :host selector
:host(:not(.legacy-radio)) {
min-height: 42px;
}
:host(.ion-color.radio-checked) .radio-inner {
border-color: current-color(base);
}
@@ -19,7 +27,6 @@
@include margin-horizontal(0, null);
}
// iOS Radio Checkmark: Checked
// -----------------------------------------
@@ -38,7 +45,6 @@
border-color: var(--color-checked);
}
// iOS Radio: Disabled
// -----------------------------------------
@@ -46,7 +52,6 @@
opacity: $radio-ios-disabled-opacity;
}
// iOS Radio: Keyboard Focus
// -----------------------------------------
@@ -63,20 +68,37 @@
background: $radio-ios-background-color-focused;
content: "";
opacity: .2;
opacity: 0.2;
}
// iOS Radio Within An Item
// -----------------------------------------
:host(.in-item) {
@include margin($radio-ios-item-end-margin-top, $radio-ios-item-end-margin-end, $radio-ios-item-end-margin-bottom, $radio-ios-item-end-margin-start);
:host(.in-item.legacy-radio) {
@include margin(
$radio-ios-item-end-margin-top,
$radio-ios-item-end-margin-end,
$radio-ios-item-end-margin-bottom,
$radio-ios-item-end-margin-start
);
display: block;
position: static;
}
:host(.in-item[slot="start"]) {
@include margin($radio-ios-item-start-margin-top, $radio-ios-item-start-margin-end, $radio-ios-item-start-margin-bottom, $radio-ios-item-start-margin-start);
:host(.in-item.legacy-radio[slot="start"]) {
@include margin(
$radio-ios-item-start-margin-top,
$radio-ios-item-start-margin-end,
$radio-ios-item-start-margin-bottom,
$radio-ios-item-start-margin-start
);
}
// Radio Native Wrapper
// ----------------------------------------------------------------
.native-wrapper .radio-icon {
width: $radio-ios-icon-width;
height: $radio-ios-icon-height;
}

View File

@@ -10,11 +10,19 @@
--border-width: #{$radio-md-icon-border-width};
--border-style: #{$radio-md-icon-border-style};
--border-radius: #{$radio-md-icon-border-radius};
}
// TODO FW-3125: Remove this style
:host(.legacy-radio) {
width: $radio-md-icon-width;
height: $radio-md-icon-height;
}
// TODO FW-3125: Move styles to :host selector
:host(:not(.legacy-radio)) {
min-height: 46px;
}
:host(.ion-color) .radio-inner {
background: current-color(base);
}
@@ -35,7 +43,6 @@
border-color: var(--color);
}
// Material Design Radio Inner Circle: Unchecked
// -----------------------------------------
@@ -52,7 +59,6 @@
background: var(--color-checked);
}
// Material Design Radio Outer Circle: Checked
// -----------------------------------------
@@ -60,7 +66,6 @@
border-color: var(--color-checked);
}
// Material Design Radio Inner Circle: Checked
// -----------------------------------------
@@ -68,7 +73,6 @@
transform: scale3d(1, 1, 1);
}
// Material Design Radio: Disabled
// -----------------------------------------
@@ -77,13 +81,15 @@
opacity: $radio-md-disabled-opacity;
}
// Material Design Radio: Keyboard Focus
// -----------------------------------------
:host(.ion-focused.legacy-radio) .radio-icon::after {
@include position(-12px, null, null, -12px);
}
:host(.ion-focused) .radio-icon::after {
@include border-radius(var(--inner-border-radius));
@include position(-12px, null, null, -12px);
display: block;
position: absolute;
@@ -94,20 +100,37 @@
background: $radio-md-background-color-focused;
content: "";
opacity: .2;
opacity: 0.2;
}
// Material Design Radio Within An Item
// -----------------------------------------
:host(.in-item) {
@include margin($radio-md-item-end-margin-top, $radio-md-item-end-margin-end, $radio-md-item-end-margin-bottom, $radio-md-item-end-margin-start);
:host(.in-item.legacy-radio) {
@include margin(
$radio-md-item-end-margin-top,
$radio-md-item-end-margin-end,
$radio-md-item-end-margin-bottom,
$radio-md-item-end-margin-start
);
display: block;
position: static;
}
:host(.in-item[slot="start"]) {
@include margin($radio-md-item-start-margin-top, $radio-md-item-start-margin-end, $radio-md-item-start-margin-bottom, $radio-md-item-start-margin-start);
:host(.in-item.legacy-radio[slot="start"]) {
@include margin(
$radio-md-item-start-margin-top,
$radio-md-item-start-margin-end,
$radio-md-item-start-margin-bottom,
$radio-md-item-start-margin-start
);
}
// Radio Native Wrapper
// ----------------------------------------------------------------
.native-wrapper .radio-icon {
width: $radio-md-icon-width;
height: $radio-md-icon-height;
}

View File

@@ -16,11 +16,20 @@
position: relative;
box-sizing: border-box;
max-width: 100%;
min-height: inherit;
user-select: none;
z-index: $z-index-item-input;
}
// TODO FW-3125: Remove :not selector and move styles to host
:host(:not(.legacy-radio)) {
cursor: pointer;
}
:host(.radio-disabled) {
pointer-events: none;
}
@@ -42,7 +51,8 @@
box-sizing: border-box;
}
label {
// TODO FW-3125: Remove this style
:host(.legacy-radio) label {
@include input-cover();
display: flex;
@@ -59,3 +69,139 @@ input {
:host(:focus) {
outline: none;
}
:host(.in-item:not(.legacy-radio)) {
width: 100%;
}
// Radio Wrapper
// --------------------------------------------------
.radio-wrapper {
display: flex;
position: relative;
flex-grow: 1;
align-items: center;
height: 100%;
min-height: inherit;
cursor: inherit;
}
// Radio Label
// ----------------------------------------------------------------
.label-text-wrapper {
/**
* This ensures that double tapping this text
* clicks the <label> and focuses the radio
* when a screen reader is enabled.
*/
pointer-events: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
/**
* If no label text is placed into the slot
* then the element should be hidden otherwise
* there will be additional margins added.
*/
.label-text-wrapper-hidden {
display: none;
}
// Radio Native Wrapper
// ----------------------------------------------------------------
.native-wrapper {
display: flex;
align-items: center;
}
// Radio Justify
// --------------------------------------------------
:host(.radio-justify-space-between) .radio-wrapper {
justify-content: space-between;
}
:host(.radio-justify-start) .radio-wrapper {
justify-content: start;
}
:host(.radio-justify-end) .radio-wrapper {
justify-content: end;
}
// Radio Label Placement - Start
// ----------------------------------------------------------------
/**
* Label is on the left of the radio in LTR and
* on the right in RTL.
*/
:host(.radio-label-placement-start) .radio-wrapper {
flex-direction: row;
}
:host(.radio-label-placement-start) .label-text-wrapper {
/**
* The margin between the label and
* the radio should be on the end
* when the label sits at the start.
*/
@include margin(0, 8px, 0, 0);
}
// Radio Label Placement - End
// ----------------------------------------------------------------
/**
* Label is on the right of the radio in LTR and
* on the left in RTL.
*/
:host(.radio-label-placement-end) .radio-wrapper {
flex-direction: row-reverse;
}
/**
* The margin between the label and
* the radio should be on the start
* when the label sits at the end.
*/
:host(.radio-label-placement-end) .label-text-wrapper {
@include margin(0, 0, 0, 8px);
}
// Radio Label Placement - Fixed
// ----------------------------------------------------------------
:host(.radio-label-placement-fixed) .label-text-wrapper {
/**
* The margin between the label and
* the radio should be on the end
* when the label sits at the start.
*/
@include margin(0, 8px, 0, 0);
}
/**
* Label is on the left of the radio in LTR and
* on the right in RTL. Label also has a fixed width.
*/
:host(.radio-label-placement-fixed) .label-text-wrapper {
flex: 0 0 100px;
width: 100px;
min-width: 100px;
}

View File

@@ -3,12 +3,18 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '
import { getIonMode } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
import { addEventListener, getAriaLabel, removeEventListener } from '../../utils/helpers';
import type { LegacyFormController } from '../../utils/forms';
import { createLegacyFormController } from '../../utils/forms';
import type { Attributes } from '../../utils/helpers';
import { addEventListener, getAriaLabel, inheritAriaAttributes, removeEventListener } from '../../utils/helpers';
import { printIonWarning } from '../../utils/logging';
import { createColorClasses, hostContext } from '../../utils/theme';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @slot - The label text to associate with the radio. Use the "labelPlacement" property to control where the label is placed relative to the radio.
*
* @part container - The container for the radio mark.
* @part mark - The checkmark or dot used to indicate the checked state.
*/
@@ -24,6 +30,11 @@ export class Radio implements ComponentInterface {
private inputId = `ion-rb-${radioButtonIds++}`;
private radioGroup: HTMLIonRadioGroupElement | null = null;
private nativeInput!: HTMLInputElement;
private legacyFormController!: LegacyFormController;
private inheritedAttributes: Attributes = {};
// This flag ensures we log the deprecation warning at most once.
private hasLoggedDeprecationWarning = false;
@Element() el!: HTMLIonRadioElement;
@@ -60,6 +71,37 @@ export class Radio implements ComponentInterface {
*/
@Prop() value?: any | null;
/**
* Where to place the label relative to the radio.
* `'start'`: The label will appear to the left of the radio in LTR and to the right in RTL.
* `'end'`: The label will appear to the right of the radio in LTR and to the left in RTL.
* `'fixed'`: The label has the same behavior as `'start'` except it also has a fixed width. Long text will be truncated with ellipses ("...").
*/
@Prop() labelPlacement: 'start' | 'end' | 'fixed' = 'start';
// TODO FW-3125: Remove the legacy property and implementation
/**
* Set the `legacy` property to `true` to forcibly use the legacy form control markup.
* Ionic will only opt components in to the modern form markup when they are
* using either the `aria-label` attribute or the default slot that contains
* the label text. As a result, the `legacy` property should only be used as
* an escape hatch when you want to avoid this automatic opt-in behavior.
* Note that this property will be removed in an upcoming major release
* of Ionic, and all form components will be opted-in to using the modern form markup.
*/
@Prop() legacy?: boolean;
/**
* How to pack the label and radio within a line.
* `'start'`: The label and radio will appear on the left in LTR and
* on the right in RTL.
* `'end'`: The label and radio will appear on the right in LTR and
* on the left in RTL.
* `'space-between'`: The label and radio will appear on opposite
* ends of the line with space between the two elements.
*/
@Prop() justify: 'start' | 'end' | 'space-between' = 'space-between';
/**
* Emitted when the styles change.
* @internal
@@ -92,6 +134,7 @@ export class Radio implements ComponentInterface {
}
connectedCallback() {
this.legacyFormController = createLegacyFormController(this.el);
if (this.value === undefined) {
this.value = this.inputId;
}
@@ -112,16 +155,24 @@ export class Radio implements ComponentInterface {
componentWillLoad() {
this.emitStyle();
if (!this.legacyFormController.hasLegacyControl()) {
this.inheritedAttributes = {
...inheritAriaAttributes(this.el),
};
}
}
@Watch('color')
@Watch('checked')
@Watch('disabled')
emitStyle() {
this.ionStyle.emit({
'radio-checked': this.checked,
'interactive-disabled': this.disabled,
});
if (this.legacyFormController.hasLegacyControl()) {
this.ionStyle.emit({
'radio-checked': this.checked,
'interactive-disabled': this.disabled,
});
}
}
private updateState = () => {
@@ -142,7 +193,89 @@ export class Radio implements ComponentInterface {
this.ionBlur.emit();
};
private get hasLabel() {
return this.el.textContent !== '';
}
private renderRadioControl() {
return (
<div class="radio-icon" part="container">
<div class="radio-inner" part="mark" />
<div class="radio-ripple"></div>
</div>
);
}
render() {
const { legacyFormController } = this;
return legacyFormController.hasLegacyControl() ? this.renderLegacyRadio() : this.renderRadio();
}
private renderRadio() {
const { checked, disabled, inputId, color, el, justify, labelPlacement, inheritedAttributes, hasLabel } = this;
const mode = getIonMode(this);
const inItem = hostContext('ion-item', el);
return (
<Host
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
'in-item': inItem,
'radio-checked': checked,
'radio-disabled': disabled,
[`radio-justify-${justify}`]: true,
[`radio-label-placement-${labelPlacement}`]: true,
// Focus and active styling should not apply when the radio is in an item
'ion-activatable': !inItem,
'ion-focusable': !inItem,
})}
>
<label class="radio-wrapper">
<div
class={{
'label-text-wrapper': true,
'label-text-wrapper-hidden': !hasLabel,
}}
>
<slot></slot>
</div>
<div class="native-wrapper">{this.renderRadioControl()}</div>
<input
type="radio"
checked={checked}
disabled={disabled}
id={inputId}
ref={(nativeEl) => (this.nativeInput = nativeEl as HTMLInputElement)}
{...inheritedAttributes}
/>
</label>
</Host>
);
}
private renderLegacyRadio() {
if (!this.hasLoggedDeprecationWarning) {
printIonWarning(
`Using ion-radio with an ion-label has been deprecated. To migrate, remove the ion-label and pass your label directly into ion-radio instead.
Example: <ion-radio>Option Label:</ion-radio>
For radios that do not have a visible label, developers should use "aria-label" so screen readers can announce the purpose of the radio.`,
this.el
);
if (this.legacy) {
printIonWarning(
`ion-radio is being used with the "legacy" property enabled which will forcibly enable the legacy form markup. This property will be removed in an upcoming major release of Ionic where this form control will use the modern form markup.
Developers can dismiss this warning by removing their usage of the "legacy" property and using the new radio syntax.`,
this.el
);
}
this.hasLoggedDeprecationWarning = true;
}
const { inputId, disabled, checked, color, el, buttonTabindex } = this;
const mode = getIonMode(this);
const { label, labelId, labelText } = getAriaLabel(el, inputId);
@@ -163,12 +296,10 @@ export class Radio implements ComponentInterface {
interactive: true,
'radio-checked': checked,
'radio-disabled': disabled,
'legacy-radio': true,
})}
>
<div class="radio-icon" part="container">
<div class="radio-inner" part="mark" />
<div class="radio-ripple"></div>
</div>
{this.renderRadioControl()}
<label htmlFor={inputId}>{labelText}</label>
<input
type="radio"

View File

@@ -3,139 +3,48 @@
<head>
<meta charset="UTF-8" />
<title>Radio - a11y</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<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>
.container {
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio - a11y</ion-title>
</ion-toolbar>
</ion-header>
<main>
<h1>Radio - a11y</h1>
<ion-content id="content" class="outer-content">
<div class="container">
<ion-radio>my label</ion-radio>
<ion-radio aria-label="my aria label"></ion-radio>
<ion-radio-group>
<ion-radio>my label in a group</ion-radio>
</ion-radio-group>
<ion-radio-group>
<ion-radio aria-label="my aria label in a group"></ion-radio>
</ion-radio-group>
<ion-list>
<ion-list-header>
<ion-label> Select a maintenance drone: </ion-label>
</ion-list-header>
<ion-radio-group id="first-group" value="huey">
<ion-item>
<ion-label>Huey</ion-label>
<ion-radio slot="start" value="huey"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Dewey</ion-label>
<ion-radio slot="start" value="dewey"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Fooey</ion-label>
<ion-radio slot="start" value="fooey" color="secondary" disabled></ion-radio>
</ion-item>
<ion-item>
<ion-label>Louie</ion-label>
<ion-radio slot="start" value="louie"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-item>
<ion-radio-group>
<ion-radio>my label in a group in a list</ion-radio>
</ion-radio-group>
</ion-item>
</ion-list>
<ion-list>
<ion-radio-group id="second-group" value="huey">
<ion-item>
<ion-label>Huey</ion-label>
<ion-radio slot="start" value="huey" color="danger"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Dewey</ion-label>
<ion-radio slot="start" value="dewey" color="secondary"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Fooey</ion-label>
<ion-radio slot="start" value="fooey" color="secondary" disabled></ion-radio>
</ion-item>
<ion-item>
<ion-label>Louie</ion-label>
<ion-radio slot="start" value="louie" color="tertiary"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-item>
<ion-radio-group>
<ion-radio aria-label="my aria label in a group in a list"></ion-radio>
</ion-radio-group>
</ion-item>
</ion-list>
<div style="padding: 10px 16px">
<ion-radio-group value="louie">
<h5>Custom Labels</h5>
<div>
<ion-radio id="custom-huey" value="huey"></ion-radio>
<label for="custom-huey">Huey</label>
</div>
<div>
<ion-radio id="custom-dewey" value="dewey"></ion-radio>
<label for="custom-dewey">Dewey</label>
</div>
<div>
<ion-radio id="custom-fooey" value="fooey" disabled></ion-radio>
<label for="custom-fooey">Fooey</label>
</div>
<div>
<ion-radio id="custom-louie" value="louie"></ion-radio>
<label for="custom-louie">Louie</label>
</div>
</ion-radio-group>
</div>
</ion-content>
</ion-app>
</div>
</main>
</body>
<style>
ion-list {
margin-bottom: 10px !important;
}
.native-radio-group {
background: white;
padding: 10px 16px;
margin-bottom: 10px;
}
.native-radio-group div {
margin: 10px 0;
}
</style>
<script>
const inputs = document.querySelectorAll('ion-radio');
for (var i = 0; i < inputs.length; i++) {
const input = inputs[i];
input.addEventListener('ionBlur', function () {
console.log('Listen ionBlur: fired');
});
input.addEventListener('ionFocus', function () {
console.log('Listen ionFocus: fired');
});
input.addEventListener('ionChange', function (ev) {
console.log('Listen ionChange: fired', ev.detail);
});
input.addEventListener('click', function () {
console.log('Listen click: fired');
});
}
</script>
</html>

View File

@@ -1,46 +1,17 @@
import AxeBuilder from '@axe-core/playwright';
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('radio: a11y', () => {
test.beforeEach(({ skip }) => {
test.beforeEach(async ({ skip }) => {
skip.rtl();
skip.mode('md');
});
test('tabbing should switch between radio groups', async ({ page, browserName }) => {
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
test('should not have accessibility violations', async ({ page }) => {
await page.goto(`/src/components/radio/test/a11y`);
const firstGroupRadios = page.locator('#first-group ion-radio');
const secondGroupRadios = page.locator('#second-group ion-radio');
await page.keyboard.press(tabKey);
await expect(firstGroupRadios.nth(0)).toBeFocused();
await page.keyboard.press(tabKey);
await expect(secondGroupRadios.nth(0)).toBeFocused();
await page.keyboard.press(`Shift+${tabKey}`);
await expect(firstGroupRadios.nth(0)).toBeFocused();
});
test('using arrow keys should move between enabled radios within group', async ({ page, browserName }) => {
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
await page.goto(`/src/components/radio/test/a11y`);
const firstGroupRadios = page.locator('#first-group ion-radio');
await page.keyboard.press(tabKey);
await expect(firstGroupRadios.nth(0)).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(firstGroupRadios.nth(1)).toBeFocused();
// firstGroupRadios.nth(2) is disabled so it should not receive focus.
await page.keyboard.press('ArrowDown');
await expect(firstGroupRadios.nth(3)).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(firstGroupRadios.nth(0)).toBeFocused();
await page.keyboard.press('ArrowUp');
await expect(firstGroupRadios.nth(3)).toBeFocused();
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
});

View File

@@ -22,252 +22,13 @@
</ion-toolbar>
</ion-header>
<ion-content id="content" class="radio-test outer-content">
<ion-list>
<ion-radio-group id="pizzaToppings">
<ion-item-divider>
<ion-label>Pizza Toppings (Unchecked Group)</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Pepperoni</ion-label>
<ion-radio slot="start" value="pepperoni" id="groupedRadio"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Sausage</ion-label>
<ion-radio slot="start" value="sausage"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Pineapple</ion-label>
<ion-radio slot="start" value="pineapple"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-list>
<ion-radio-group id="fruitRadio" value="grape">
<ion-item-divider>
<ion-label>Fruits (Group w/ values)</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Apple</ion-label>
<ion-radio slot="start" value="apple"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Grape, checked, disabled</ion-label>
<ion-radio slot="start" id="grapeChecked" value="grape" disabled></ion-radio>
</ion-item>
<ion-item>
<ion-label>Cherry</ion-label>
<ion-radio slot="start" color="danger" value="cherry"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-radio-group id="pizzaRadio" value="pepperoni">
<ion-item-divider>
<ion-label>Extra Pizza Topping (Group w/ no values)</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Pepperoni</ion-label>
<ion-radio slot="end" name="pepperoni" value="pepperoni" id="pepperoni-radio"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Sausage</ion-label>
<ion-radio slot="end" color="danger" name="sausage" value="sausage"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-radio-group id="veggiesRadio" value="broccoli" allow-empty-selection>
<ion-item-divider>
<ion-label>Veggies (Group w/ allow empty)</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Carrot</ion-label>
<ion-radio slot="end" value="carrot"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Tomato</ion-label>
<ion-radio slot="end" value="tomato"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Broccoli</ion-label>
<ion-radio id="dangerRadio" slot="end" color="danger" value="broccoli"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-radio-group allow-empty-selection value="custom">
<ion-item-divider>
<ion-label>Custom (Group w/ allow empty)</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Custom</ion-label>
<ion-radio
slot="end"
color="danger"
value="custom"
style="--border-radius: 2px; --inner-border-radius: 10px 0px 10px 0px"
></ion-radio>
</ion-item>
</ion-radio-group>
<ion-radio-group allow-empty-selection value="custom">
<ion-item-divider>
<ion-label>Part (Group w/ allow empty)</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Radio ::part</ion-label>
<ion-radio slot="end" value="custom" class="radio-part"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-item>
<ion-button slot="start" onClick="toggleBoolean('grapeChecked', 'checked')" fill="outline" size="small"
>Checked</ion-button
>
<ion-button slot="start" onClick="toggleBoolean('grapeChecked', 'disabled')" fill="outline" size="small"
>Disabled</ion-button
>
<ion-button slot="end" onClick="printRadioValues()" fill="outline" size="small">Print</ion-button>
<ion-button
slot="end"
onClick="toggleString('dangerRadio', 'color', 'danger', 'secondary')"
fill="outline"
size="small"
color="danger"
>Color</ion-button
>
</ion-item>
</ion-list>
<pre id="valuesCode"></pre>
<ion-list>
<ion-list-header>
<ion-label> No Radio Group </ion-label>
</ion-list-header>
<ion-item>
<ion-label>Kiwi, (ionChange) Secondary color</ion-label>
<ion-radio slot="start" color="secondary" id="ungroupedRadio"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Checkbox right, checked, really long text that should ellipsis</ion-label>
<ion-radio slot="end" color="danger"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Checkbox right side</ion-label>
<ion-radio slot="end"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Button w/ right side default icon, really long text that should ellipsis</ion-label>
<ion-icon name="information-circle" slot="end"></ion-icon>
</ion-item>
</ion-list>
<p>
<ion-radio id="standAloneChecked"></ion-radio>
Stand-alone checkbox:
<span id="standAloneCheckedSpan"></span>
</p>
<ion-item>
<ion-label>Checkbox / Toggle</ion-label>
<ion-radio slot="start" id="checked"></ion-radio>
<ion-toggle id="checked"></ion-toggle>
</ion-item>
<ion-radio-group>
<ion-item>
<ion-label>Checked</ion-label>
<ion-radio slot="start" checked></ion-radio>
<ion-toggle checked></ion-toggle>
</ion-item>
</ion-radio-group>
<ion-item>
<ion-label>Disabled</ion-label>
<ion-radio slot="start" disabled></ion-radio>
<ion-toggle disabled></ion-toggle>
</ion-item>
</ion-list>
<ion-content class="ion-padding">
<ion-radio
>Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable Notifications
Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable
Notifications</ion-radio
>
</ion-content>
<script>
const radio = document.getElementById('pepperoni-radio');
if (radio) {
radio.addEventListener('ionSelect', (ev) => {
console.log(ev.detail);
});
}
var radioValues = ['fruitRadio', 'pizzaRadio', 'veggiesRadio'];
printRadioValues();
function printRadioValues() {
var html = 'Values:<br>';
for (var i = 0; i < radioValues.length; i++) {
var radio = radioValues[i];
var el = document.getElementById(radio);
html += '<span>' + radio + ': ' + el.value + ' </span><br>';
}
var valueEle = document.getElementById('valuesCode');
valueEle.innerHTML = html;
}
function toggleBoolean(id, prop) {
var el = document.getElementById(id);
var isTrue = el[prop] ? false : true;
el[prop] = isTrue;
console.debug('in toggleBoolean, setting', prop, 'to', isTrue);
}
function toggleString(id, prop, firstVal, secondVal) {
var el = document.getElementById(id);
var stringVal = el[prop] == firstVal ? secondVal : firstVal;
el[prop] = stringVal;
console.debug('in toggleString, setting', prop, 'to', stringVal);
}
</script>
<style>
.outer-content {
--background: #f2f2f2;
}
.radio-test pre {
background: #f6f6f6;
border: 1px solid #ddd;
padding: 15px 10px;
margin-top: -20px;
}
.radio-part::part(container) {
background: rgba(255, 0, 0, 0.3);
border-color: darkred;
}
.radio-part::part(mark) {
background: hotpink;
}
.radio-part.radio-checked::part(container) {
background: rgba(0, 255, 0, 0.3);
border-color: darkgreen;
}
.radio-part.radio-checked::part(mark) {
background: purple;
}
</style>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio - Color</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>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio - Color</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-radio-group value="1">
<ion-radio color="danger" value="1">Label</ion-radio>
</ion-radio-group>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,29 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('radio: color', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
});
test('should apply color when checked', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio color="danger" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(`radio-color-checked-${page.getSnapshotSettings()}.png`);
});
test('should not apply color when unchecked', async ({ page }) => {
await page.setContent(`
<ion-radio-group>
<ion-radio color="danger" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(`radio-color-unchecked-${page.getSnapshotSettings()}.png`);
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio - Item</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(3, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
}
@media screen and (max-width: 800px) {
.grid {
grid-template-columns: 1fr;
padding: 0;
}
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio - Item</ion-title>
</ion-toolbar>
</ion-header>
<ion-content id="content" class="ion-padding">
<h1>Placement Start</h1>
<div class="grid">
<div class="grid-item">
<h2>Justify Start</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="start" justify="start">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
<div class="grid-item">
<h2>Justify End</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="start" justify="end">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
<div class="grid-item">
<h2>Justify Space Between</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="start" justify="space-between">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
</div>
<h1>Placement End</h1>
<div class="grid">
<div class="grid-item">
<h2>Justify Start</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="end" justify="start">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
<div class="grid-item">
<h2>Justify End</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="end" justify="end">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
<div class="grid-item">
<h2>Justify Space Between</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="end" justify="space-between">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
</div>
<h1>Placement Fixed</h1>
<div class="grid">
<div class="grid-item">
<h2>Justify Start</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="fixed" justify="start">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
<div class="grid-item">
<h2>Justify End</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="fixed" justify="end">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
<div class="grid-item">
<h2>Justify Space Between</h2>
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio label-placement="fixed" justify="space-between">Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</div>
</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,31 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('radio: item', () => {
test('should render correctly in list', async ({ page }) => {
await page.setContent(`
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio>Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
`);
const list = page.locator('ion-list');
expect(await list.screenshot()).toMatchSnapshot(`toggle-list-${page.getSnapshotSettings()}.png`);
});
test('should render correctly in inset list', async ({ page }) => {
await page.setContent(`
<ion-list inset="true">
<ion-radio-group>
<ion-item>
<ion-radio>Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
`);
const list = page.locator('ion-list');
expect(await list.screenshot()).toMatchSnapshot(`radio-inset-list-${page.getSnapshotSettings()}.png`);
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -0,0 +1,128 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio - Label Placement</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(3, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
}
@media screen and (max-width: 800px) {
.grid {
grid-template-columns: 1fr;
padding: 0;
}
}
ion-radio {
width: 100%;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio - Label Placement</ion-title>
</ion-toolbar>
</ion-header>
<ion-content id="content" class="ion-padding">
<h1>Placement Start</h1>
<div class="grid">
<div class="grid-item">
<h2>Justify Start</h2>
<ion-radio-group value="1">
<ion-radio label-placement="start" justify="start" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
<div class="grid-item">
<h2>Justify End</h2>
<ion-radio-group value="1">
<ion-radio label-placement="start" justify="end" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
<div class="grid-item">
<h2>Justify Space Between</h2>
<ion-radio-group value="1">
<ion-radio label-placement="start" justify="space-between" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
</div>
<h1>Placement End</h1>
<div class="grid">
<div class="grid-item">
<h2>Justify Start</h2>
<ion-radio-group value="1">
<ion-radio label-placement="end" justify="start" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
<div class="grid-item">
<h2>Justify End</h2>
<ion-radio-group value="1">
<ion-radio label-placement="end" justify="end" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
<div class="grid-item">
<h2>Justify Space Between</h2>
<ion-radio-group value="1">
<ion-radio label-placement="end" justify="space-between" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
</div>
<h1>Placement Fixed</h1>
<div class="grid">
<div class="grid-item">
<h2>Justify Start</h2>
<ion-radio-group value="1">
<ion-radio label-placement="fixed" justify="start" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
<div class="grid-item">
<h2>Justify End</h2>
<ion-radio-group value="1">
<ion-radio label-placement="fixed" justify="end" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
<div class="grid-item">
<h2>Justify Space Between</h2>
<ion-radio-group value="1">
<ion-radio label-placement="fixed" justify="space-between" value="1">Enable Notifications</ion-radio>
</ion-radio-group>
</div>
</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,127 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
/**
* By default ion-radio only takes up
* as much space as it needs. Justification is
* used for when the radio takes up the full
* line (such as in an ion-item). As a result,
* we set the width of the radio so we can
* see the justification results.
*/
test.describe('radio: label', () => {
test.describe('radio: start placement', () => {
test('should render a start justification with label in the start position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="start" justify="start" style="width: 200px" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-start-justify-start-${page.getSnapshotSettings()}.png`
);
});
test('should render an end justification with label in the start position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="start" justify="end" style="width: 200px" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-start-justify-end-${page.getSnapshotSettings()}.png`
);
});
test('should render a space between justification with label in the start position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="start" justify="space-between" style="width: 200px" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-start-justify-space-between-${page.getSnapshotSettings()}.png`
);
});
});
test.describe('radio: end placement', () => {
test('should render a start justification with label in the end position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="end" justify="start" style="width: 200px" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-end-justify-start-${page.getSnapshotSettings()}.png`
);
});
test('should render an end justification with label in the end position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="end" justify="end" style="width: 200px" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(`radio-label-end-justify-end-${page.getSnapshotSettings()}.png`);
});
test('should render a space between justification with label in the end position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="end" justify="space-between" style="width: 200px" value="1">Label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-end-justify-space-between-${page.getSnapshotSettings()}.png`
);
});
});
test.describe('radio: fixed placement', () => {
test('should render a start justification with label in the fixed position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="fixed" justify="start" style="width: 200px" value="1">This is a long label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-fixed-justify-start-${page.getSnapshotSettings()}.png`
);
});
test('should render an end justification with label in the fixed position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="fixed" justify="end" style="width: 200px" value="1">This is a long label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-fixed-justify-end-${page.getSnapshotSettings()}.png`
);
});
test('should render a space between justification with label in the fixed position', async ({ page }) => {
await page.setContent(`
<ion-radio-group value="1">
<ion-radio label-placement="fixed" justify="space-between" style="width: 200px" value="1">This is a long label</ion-radio>
</ion-radio-group>
`);
const radio = page.locator('ion-radio');
expect(await radio.screenshot()).toMatchSnapshot(
`radio-label-fixed-justify-space-between-${page.getSnapshotSettings()}.png`
);
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Some files were not shown because too many files have changed in this diff Show More