fix(many): update form controls (radio, checkbox, toggle, input, select) to have consistent disabled opacity (#27396)
Issue number: resolves #27184 --------- ## What is the current behavior? The modern form controls do not use the same opacity for their labels when disabled, resulting in inconsistent UI when using two different types in the same view (select vs checkbox, for example). ## What is the new behavior? The checkbox, input, radio, range, select, textarea and toggle labels have been updated on both modes to use the same opacity as each other when disabled. The checkbox and radio icons have been updated to use a different opacity than the label for `md` mode. - Updates `ios` mode so all form controls use the same opacity of `0.3` - I could not find any guidelines by Apple for what color these should be, so I decided to just make them the same as what is most commonly used & match item - Updates `md` mode so all form control labels use the same opacity of `0.38` - I used the [Material Design 3 documentation](https://m2.material.io/components) to get this number as well as the opacity of the disabled selection controls in the [Material Design 2 figma design kit](https://www.figma.com/community/file/778763161265841481). The Material Design 2 documentation does not mention the opacity, but this is also the number used by Material Design 1 so it's safe to assume it is what the disabled form labels should also use for Material Design 2. - Updates the `md` range so the slotted elements are also included when setting the opacity on the label - Updates the range, radio & checkbox tests to make sure there are screenshots in the different disabled states - Updates the item/disabled test to include radio & textarea so you can see all form controls side by side ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information I downloaded screenshots of the item disabled tests and put them side by side for `main` and this branch in order to see the differences in the labels. The grey boxes to the left of each item are just a bigger version of the label color for that item, so it's easier to see when they aren't the same.   --------- Co-authored-by: ionitron <hi@ionicframework.com>
@ -21,6 +21,9 @@
|
||||
|
||||
// iOS Checkbox: Disabled
|
||||
// -----------------------------------------
|
||||
// The checkbox and label should use the
|
||||
// same opacity and match the other form
|
||||
// controls
|
||||
|
||||
:host(.checkbox-disabled) {
|
||||
opacity: $checkbox-ios-disabled-opacity;
|
||||
|
||||
@ -54,7 +54,7 @@ $checkbox-ios-checkmark-width: math.div($checkbox-ios-icon-size, 6) +
|
||||
$checkbox-ios-checkmark-height: $checkbox-ios-icon-size * 0.5 !default;
|
||||
|
||||
/// @prop - Opacity of the disabled checkbox
|
||||
$checkbox-ios-disabled-opacity: .3 !default;
|
||||
$checkbox-ios-disabled-opacity: $form-control-ios-disabled-opacity !default;
|
||||
|
||||
/// @prop - Margin top of the left checkbox item
|
||||
$checkbox-ios-item-start-margin-top: 8px !default;
|
||||
|
||||
@ -40,11 +40,19 @@
|
||||
|
||||
// Material Design Checkbox: Disabled
|
||||
// --------------------------------------------------------
|
||||
// The checkbox itself should use the disabled
|
||||
// opacity set by its spec, while the label
|
||||
// should match the other form controls
|
||||
|
||||
:host(.checkbox-disabled) {
|
||||
:host(.legacy-checkbox.checkbox-disabled),
|
||||
:host(.checkbox-disabled) .label-text-wrapper {
|
||||
opacity: $checkbox-md-disabled-opacity;
|
||||
}
|
||||
|
||||
:host(.checkbox-disabled) .native-wrapper {
|
||||
opacity: $checkbox-md-icon-disabled-opacity;
|
||||
}
|
||||
|
||||
|
||||
// Material Design Checkbox Within An Item
|
||||
// TODO(FW-3100): remove this
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
// Material Design Checkbox
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Opacity of the disabled checkbox
|
||||
$checkbox-md-disabled-opacity: .3 !default;
|
||||
/// @prop - Opacity of the disabled checkbox label
|
||||
$checkbox-md-disabled-opacity: $form-control-md-disabled-opacity !default;
|
||||
|
||||
/// @prop - Background color of the checkbox icon when off
|
||||
$checkbox-md-icon-background-color-off: $item-md-background !default;
|
||||
@ -72,3 +72,12 @@ $checkbox-md-item-end-margin-bottom: $checkbox-md-item-end-margin-top !defau
|
||||
|
||||
/// @prop - Margin start of the end checkbox item
|
||||
$checkbox-md-item-end-margin-start: 0 !default;
|
||||
|
||||
/// @prop - Opacity of the disabled checkbox
|
||||
/// This value is used because the checkbox color is set to
|
||||
/// `rgb(0, 0, 0, 0.60)` when enabled and we need it to be
|
||||
/// `rgb(0, 0, 0, 0.38)` when disabled but the disabled
|
||||
/// opacity is applied on top of the transparent color so
|
||||
/// this opacity gets us the equivalent of applying `0.38`
|
||||
/// on top of an opaque checkbox `rgb(0, 0, 0, 1.0)`
|
||||
$checkbox-md-icon-disabled-opacity: 0.63 !default;
|
||||
|
||||
@ -3,42 +3,6 @@ import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs().forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('checkbox: basic visual tests'), () => {
|
||||
test('should render unchecked checkbox correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox>Unchecked</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-unchecked`));
|
||||
});
|
||||
|
||||
test('should render checked checkbox correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox checked>Checked</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-checked`));
|
||||
});
|
||||
|
||||
test('should render disabled checkbox correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox checked disabled>Disabled</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-disabled`));
|
||||
});
|
||||
|
||||
test('should render custom checkmark-width correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
54
core/src/components/checkbox/test/states/checkbox.e2e.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('checkbox: states'), () => {
|
||||
test('should render disabled checkbox correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox disabled>Label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
expect(await checkbox.screenshot()).toMatchSnapshot(screenshot(`checkbox-disabled`));
|
||||
});
|
||||
|
||||
test('should render disabled checked checkbox correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox checked disabled>Label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
expect(await checkbox.screenshot()).toMatchSnapshot(screenshot(`checkbox-checked-disabled`));
|
||||
});
|
||||
|
||||
test('should render checked checkbox correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox checked>Label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
expect(await checkbox.screenshot()).toMatchSnapshot(screenshot(`checkbox-checked`));
|
||||
});
|
||||
|
||||
test('should render unchecked checkbox correctly', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox>Label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
expect(await checkbox.screenshot()).toMatchSnapshot(screenshot(`checkbox-unchecked`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 950 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1004 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 811 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 2.7 KiB |
78
core/src/components/checkbox/test/states/index.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Checkbox - States</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(4, 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-checkbox {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Checkbox - States</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Unchecked</h2>
|
||||
<ion-checkbox>Enable Notifications</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Checked</h2>
|
||||
<ion-checkbox checked>Enable Notifications</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Disabled, Unchecked</h2>
|
||||
<ion-checkbox disabled="true">Enable Notifications</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Disabled, Checked</h2>
|
||||
<ion-checkbox disabled="true" checked>Enable Notifications</ion-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||