feat(radio): add ionic theme styles (#29805)

- Added `ionic` theme styles
- Added `ionic` to the tests
- Updated file structure
- Added a focus ring mixin
This commit is contained in:
Maria Hutt
2024-08-30 06:56:42 -07:00
committed by GitHub
parent 3656c8deba
commit ccf1f65892
150 changed files with 533 additions and 124 deletions

View File

@ -24,6 +24,14 @@ ion-list {
@include globals.margin(globals.$ionic-space-100);
}
// Ionic No Lines List
// --------------------------------------------------
.list-ionic-lines-none .item-lines-default {
--inner-border-width: 0px;
--border-width: 0px;
}
// Ionic Shapes
//
// The border radius is applied to the list, excluding

View File

@ -12,6 +12,12 @@
<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>
ion-radio {
width: 100%;
}
</style>
</head>
<body>
@ -23,29 +29,12 @@
</ion-header>
<ion-content>
<ion-list>
<ion-radio-group name="items" id="group" value="1">
<ion-list-header>
<ion-label>Radio Group Header</ion-label>
</ion-list-header>
<ion-item>
<ion-radio value="1">Item 1</ion-radio>
</ion-item>
<ion-item>
<ion-radio value="2">Item 2</ion-radio>
</ion-item>
<ion-item>
<ion-radio value="3">Item 3</ion-radio>
</ion-item>
<ion-item>
<ion-radio value="4">Item 4</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
<ion-radio-group name="items" id="group" value="1">
<ion-radio justify="space-between" value="1">Label</ion-radio><br />
<ion-radio justify="space-between" value="2">Label</ion-radio><br />
<ion-radio justify="space-between" value="3">Label</ion-radio><br />
<ion-radio justify="space-between" value="4">Label</ion-radio><br />
</ion-radio-group>
</ion-content>
</ion-app>
</body>

View File

@ -18,9 +18,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-radio-group>
`,
config
@ -34,9 +32,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-radio-group>
`,
config
@ -50,9 +46,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-radio-group>
`,
config
@ -66,9 +60,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
<ion-radio id="one" value="one">One</ion-radio>
</ion-radio-group>
`,
config
@ -82,17 +74,11 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.setContent(
`
<ion-radio-group value="1">
<ion-item>
<ion-radio value="1">Item 1</ion-radio>
</ion-item>
<ion-radio value="1">Item 1</ion-radio>
<ion-item>
<ion-radio value="2">Item 2</ion-radio>
</ion-item>
<ion-radio value="2">Item 2</ion-radio>
<ion-item>
<ion-radio value="3">Item 3</ion-radio>
</ion-item>
<ion-radio value="3">Item 3</ion-radio>
</ion-radio-group>
`,
config

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio Group - 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>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio Group - Item</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list lines="none">
<ion-radio-group name="items" id="group" value="1">
<ion-list-header>
<ion-label>Radio Group Header</ion-label>
</ion-list-header>
<ion-item>
<ion-radio value="1">Item 1</ion-radio>
</ion-item>
<ion-item>
<ion-radio value="2">Item 2</ion-radio>
</ion-item>
<ion-item>
<ion-radio value="3">Item 3</ion-radio>
</ion-item>
<ion-item>
<ion-radio value="4">Item 4</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -1,4 +1,5 @@
@import "../../themes/native/native.globals";
@import "../../themes/functions.string";
@import "../../themes/mixins";
@import "./radio.vars.scss";
// Radio
@ -24,10 +25,13 @@
user-select: none;
z-index: $z-index-item-input;
box-sizing: border-box;
}
input {
@include visually-hidden();
}
:host(.radio-disabled) {
pointer-events: none;
}
@ -49,10 +53,6 @@
box-sizing: border-box;
}
input {
@include visually-hidden();
}
:host(:focus) {
outline: none;
}
@ -98,29 +98,6 @@ input {
cursor: inherit;
}
// Radio Label
// ----------------------------------------------------------------
.label-text-wrapper {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
:host(.in-item) .label-text-wrapper {
@include margin($radio-item-label-margin-top, null, $radio-item-label-margin-bottom, null);
}
:host(.in-item.radio-label-placement-stacked) .label-text-wrapper {
@include margin($radio-item-label-margin-top, null, $form-control-label-margin, null);
}
:host(.in-item.radio-label-placement-stacked) .native-wrapper {
@include margin(null, null, $radio-item-label-margin-bottom, null);
}
/**
* If no label text is placed into the slot
* then the element should be hidden otherwise
@ -176,15 +153,6 @@ input {
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(null, $form-control-label-margin, null, 0);
}
// Radio Label Placement - End
// ----------------------------------------------------------------
@ -196,27 +164,9 @@ input {
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(null, 0, null, $form-control-label-margin);
}
// 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(null, $form-control-label-margin, null, 0);
}
/**
* Label is on the left of the radio in LTR and
* on the right in RTL. Label also has a fixed width.
@ -238,23 +188,6 @@ input {
flex-direction: column;
}
:host(.radio-label-placement-stacked) .label-text-wrapper {
@include transform(scale(#{$form-control-label-stacked-scale}));
/**
* The margin between the label and
* the radio should be on the bottom
* when the label sits on top.
*/
@include margin(null, 0, $form-control-label-margin, 0);
/**
* Label text should not extend
* beyond the bounds of the radio.
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}
:host(.radio-label-placement-stacked.radio-alignment-start) .label-text-wrapper {
@include transform-origin(start, top);
}

View File

@ -0,0 +1,171 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
@use "./radio.common";
// Ionic Radio
// --------------------------------------------------
:host {
--color: #{globals.$ionic-color-neutral-500};
--color-checked: #{globals.$ionic-color-primary-base};
--border-width: #{globals.$ionic-border-size-025};
--border-style: #{globals.$ionic-border-style-solid};
--border-radius: #{globals.$ionic-border-radius-full};
--focus-ring-color: #{globals.$ionic-state-focus-1};
--focus-ring-width: #{globals.$ionic-border-size-050};
@include globals.typography(globals.$ionic-body-md-regular);
min-height: globals.$ionic-scale-1200;
color: globals.$ionic-color-neutral-1200;
}
// Radio Color
// --------------------------------------------------
:host(.ion-color.radio-checked) .radio-icon {
border-color: globals.current-color(base);
background-color: globals.current-color(base);
}
// Radio Label
// ----------------------------------------------------------------
:host(.in-item.radio-label-placement-stacked) .native-wrapper {
@include globals.margin(null, null, globals.$ionic-space-250, null);
}
// Radio Native Wrapper
// ----------------------------------------------------------------
.native-wrapper .radio-icon {
width: globals.$ionic-scale-600;
height: globals.$ionic-scale-600;
}
// Ionic Radio Outer Circle: Unchecked
// -----------------------------------------
.radio-icon {
@include globals.margin(0);
@include globals.border-radius(var(--border-radius));
border-width: var(--border-width);
border-style: var(--border-style);
border-color: var(--color);
background-color: globals.$ionic-color-base-white;
}
// Ionic Radio Inner Circle: Unchecked
// -----------------------------------------
.radio-inner {
@include globals.border-radius(var(--inner-border-radius));
width: calc(32% + var(--border-width));
height: calc(32% + var(--border-width));
background-color: globals.$ionic-color-base-white;
}
// Ionic Radio Outer Circle: Checked
// -----------------------------------------
:host(.radio-checked) .radio-icon {
border-color: var(--color-checked);
background-color: var(--color-checked);
}
// Radio Label Placement - Start & Fixed
// ----------------------------------------------------------------
:host(.radio-label-placement-start) .label-text-wrapper,
: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 globals.margin(null, globals.$ionic-space-400, null, 0);
}
// Radio Label Placement - End
// ----------------------------------------------------------------
/**
* 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 globals.margin(null, 0, null, globals.$ionic-space-400);
}
// Radio Label Placement - Stacked
// ----------------------------------------------------------------
:host(.radio-label-placement-stacked) .label-text-wrapper {
@include globals.transform(scale(0.75));
/**
* The margin between the label and
* the radio should be on the bottom
* when the label sits on top.
*/
@include globals.margin(null, 0, globals.$ionic-space-400, 0);
/**
* Label text should not extend
* beyond the bounds of the radio.
*/
max-width: calc(100% / 0.75);
}
// Radio Not In Item
// -----------------------------------------
:host(:not(.in-item):not(:last-of-type)) {
@include globals.margin(null, null, globals.$ionic-space-200, null);
}
// Ionic Radio: Disabled
// -----------------------------------------
:host(.radio-disabled) .radio-icon::after {
@include globals.disabled-state();
@include globals.border-radius(inherit);
// The border-width is added to the width and height to ensure
// the disabled state covers the entire radio icon. The top and
// left are adjusted to ensure the disabled state is centered on
// the radio icon.
@include globals.position(calc(var(--border-width) * -1), 0, 0, calc(var(--border-width) * -1));
width: calc(100% + (2 * var(--border-width)));
height: calc(100% + (2 * var(--border-width)));
}
// Ionic Radio: Keyboard Focus
// -----------------------------------------
:host(.ion-focused) .radio-icon {
@include globals.focused-state(var(--focus-ring-width), null, var(--focus-ring-color));
}
// Ionic Radio: Pressed
// -----------------------------------------
:host(.ion-activated) .radio-icon::after {
@include globals.pressed-state();
@include globals.border-radius(inherit);
// The border-width is added to the width and height to ensure
// the activated state covers the entire radio icon. The top and
// left are adjusted to ensure the activated state is centered on
// the radio icon.
@include globals.position(calc(var(--border-width) * -1), 0, 0, calc(var(--border-width) * -1));
width: calc(100% + (2 * var(--border-width)));
height: calc(100% + (2 * var(--border-width)));
}

View File

@ -1,4 +1,4 @@
@import "./radio";
@import "./radio.native";
@import "./radio.ios.vars";
// iOS Radio

View File

@ -1,4 +1,4 @@
@import "./radio";
@import "./radio.native";
@import "./radio.md.vars";
// Material Design Radio

View File

@ -0,0 +1,89 @@
@import "../../themes/native/native.globals";
@import "./radio.common";
@import "./radio.vars.scss";
// Radio
// --------------------------------------------------
:host {
z-index: $z-index-item-input;
}
// Radio Label
// ----------------------------------------------------------------
.label-text-wrapper {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
:host(.in-item) .label-text-wrapper {
@include margin($radio-item-label-margin-top, null, $radio-item-label-margin-bottom, null);
}
:host(.in-item.radio-label-placement-stacked) .label-text-wrapper {
@include margin($radio-item-label-margin-top, null, $form-control-label-margin, null);
}
:host(.in-item.radio-label-placement-stacked) .native-wrapper {
@include margin(null, null, $radio-item-label-margin-bottom, null);
}
// Radio Label Placement - Start
// ----------------------------------------------------------------
: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(null, $form-control-label-margin, null, 0);
}
// Radio Label Placement - End
// ----------------------------------------------------------------
/**
* 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(null, 0, null, $form-control-label-margin);
}
// 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(null, $form-control-label-margin, null, 0);
}
// Radio Label Placement - Stacked
// ----------------------------------------------------------------
:host(.radio-label-placement-stacked) .label-text-wrapper {
@include transform(scale(#{$form-control-label-stacked-scale}));
/**
* The margin between the label and
* the radio should be on the bottom
* when the label sits on top.
*/
@include margin(null, 0, $form-control-label-margin, 0);
/**
* Label text should not extend
* beyond the bounds of the radio.
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}

View File

@ -22,7 +22,7 @@ import type { Color } from '../../interface';
styleUrls: {
ios: 'radio.ios.scss',
md: 'radio.md.scss',
ionic: 'radio.md.scss',
ionic: 'radio.ionic.scss',
},
shadow: true,
})

View File

@ -43,6 +43,11 @@
<ion-radio-group value="radio">
<ion-radio value="radio" justify="start" label-placement="end" disabled>Disabled, Checked</ion-radio>
</ion-radio-group>
<ion-radio-group>
<ion-radio value="radio" justify="start" label-placement="end"
>This is a very very very very very very very very long</ion-radio
>
</ion-radio-group>
</div>
</ion-content>
</ion-app>

View File

@ -0,0 +1,30 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('radio'), () => {
test('should render multiple correctly', async ({ page }) => {
await page.setContent(
`
<style>
/* The radio checks are cut off without a container margin */
#container {
margin-top: 20px;
margin-bottom: 20px;
}
</style>
<div id="container">
<ion-radio-group>
<ion-radio>Enable Notifications</ion-radio><br />
<ion-radio>Enable Notifications</ion-radio>
</ion-radio-group>
</div>
`,
config
);
const container = page.locator('#container');
await expect(container).toHaveScreenshot(screenshot(`radio-multiple`));
});
});
});

View File

@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('radio: color'), () => {
test('should apply color when checked', async ({ page }) => {
await page.setContent(

View File

@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('radio: item'), () => {
test('should render correctly in list', async ({ page }) => {
await page.setContent(
@ -19,6 +19,25 @@ configs().forEach(({ title, screenshot, config }) => {
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`radio-list`));
});
test('should render multiple correctly in list', async ({ page }) => {
await page.setContent(
`
<ion-list>
<ion-radio-group>
<ion-item>
<ion-radio>Enable Notifications</ion-radio>
</ion-item>
<ion-item>
<ion-radio>Enable Notifications</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
`,
config
);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`radio-list-multiple`));
});
test('should render correctly in inset list', async ({ page }) => {
await page.setContent(
`

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -10,7 +10,7 @@ import { configs, test } from '@utils/test/playwright';
* see the justification results.
*/
configs().forEach(({ title, screenshot, config }) => {
configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('radio: label'), () => {
test.describe('radio: start placement', () => {
test('should render a start justification with label in the start position', async ({ page }) => {
@ -184,3 +184,19 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config, screen
});
});
});
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
test.describe(title('radio: long label'), () => {
test('long label should wrap', async ({ page }) => {
await page.setContent(
`
<ion-radio style="width: 200px">Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable Notifications</ion-radio>
`,
config
);
const radio = page.locator('ion-radio');
await expect(radio).toHaveScreenshot(screenshot(`radio-label-long-label`));
});
});
});

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