feat(checkbox): add helper and error text to the ionic theme (#30278)

- Adds the common stylesheet for the shared styles
- Adds design for helper and error text to the checkbox for the ionic
theme.
- Updates tests to add screenshots for ionic theme

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2025-03-25 10:17:46 -04:00
committed by GitHub
parent e24fcf530a
commit f0516e6569
52 changed files with 184 additions and 270 deletions

View File

@ -549,26 +549,40 @@ ion-checkbox,prop,value,any,'on',false,false
ion-checkbox,event,ionBlur,void,true
ion-checkbox,event,ionChange,CheckboxChangeEventDetail<any>,true
ion-checkbox,event,ionFocus,void,true
ion-checkbox,css-prop,--border-color,ionic
ion-checkbox,css-prop,--border-color,ios
ion-checkbox,css-prop,--border-color,md
ion-checkbox,css-prop,--border-color-checked,ionic
ion-checkbox,css-prop,--border-color-checked,ios
ion-checkbox,css-prop,--border-color-checked,md
ion-checkbox,css-prop,--border-radius,ionic
ion-checkbox,css-prop,--border-radius,ios
ion-checkbox,css-prop,--border-radius,md
ion-checkbox,css-prop,--border-style,ionic
ion-checkbox,css-prop,--border-style,ios
ion-checkbox,css-prop,--border-style,md
ion-checkbox,css-prop,--border-width,ionic
ion-checkbox,css-prop,--border-width,ios
ion-checkbox,css-prop,--border-width,md
ion-checkbox,css-prop,--checkbox-background,ios
ion-checkbox,css-prop,--checkbox-background,md
ion-checkbox,css-prop,--checkbox-background-checked,ionic
ion-checkbox,css-prop,--checkbox-background-checked,ios
ion-checkbox,css-prop,--checkbox-background-checked,md
ion-checkbox,css-prop,--checkmark-color,ionic
ion-checkbox,css-prop,--checkmark-color,ios
ion-checkbox,css-prop,--checkmark-color,md
ion-checkbox,css-prop,--checkmark-height,ionic
ion-checkbox,css-prop,--checkmark-width,ionic
ion-checkbox,css-prop,--checkmark-width,ios
ion-checkbox,css-prop,--checkmark-width,md
ion-checkbox,css-prop,--focus-ring-color,ionic
ion-checkbox,css-prop,--focus-ring-offset,ionic
ion-checkbox,css-prop,--focus-ring-width,ionic
ion-checkbox,css-prop,--size,ionic
ion-checkbox,css-prop,--size,ios
ion-checkbox,css-prop,--size,md
ion-checkbox,css-prop,--transition,ionic
ion-checkbox,css-prop,--transition,ios
ion-checkbox,css-prop,--transition,md
ion-checkbox,part,container

View File

@ -1,14 +1,12 @@
@import "../../themes/native/native.globals";
@import "./checkbox.vars.scss";
@use "../../themes/mixins" as mixins;
// Checkbox
// Checkbox: Common
// --------------------------------------------------
:host {
/**
* @prop --size: Size of the checkbox icon
*
* @prop --checkbox-background: Background of the checkbox icon
* @prop --checkbox-background-checked: Background of the checkbox icon when checked
*
* @prop --border-color: Border color of the checkbox icon
@ -22,10 +20,6 @@
* @prop --checkmark-color: Color of the checkbox checkmark when checked
* @prop --checkmark-width: Stroke width of the checkbox checkmark
*/
--checkbox-background-checked: #{ion-color(primary, base)};
--border-color-checked: #{ion-color(primary, base)};
--checkmark-color: #{ion-color(primary, contrast)};
--transition: none;
display: inline-block;
@ -34,7 +28,6 @@
cursor: pointer;
user-select: none;
z-index: $z-index-item-input;
}
:host(.in-item) {
@ -60,12 +53,6 @@
width: auto;
}
:host(.ion-color) {
--checkbox-background-checked: #{current-color(base)};
--border-color-checked: #{current-color(base)};
--checkmark-color: #{current-color(contrast)};
}
.checkbox-wrapper {
display: flex;
@ -87,21 +74,6 @@
overflow: hidden;
}
// Checkboxes that are not slotted inside an item and are not used with a
// stacked label should have margins equal to those of the label.
:host(.in-item) .label-text-wrapper,
:host(.in-item:not(.checkbox-label-placement-stacked):not([slot])) .native-wrapper {
@include margin($checkbox-item-label-margin-top, null, $checkbox-item-label-margin-bottom, null);
}
:host(.in-item.checkbox-label-placement-stacked) .label-text-wrapper {
@include margin($checkbox-item-label-margin-top, null, $form-control-label-margin, null);
}
:host(.in-item.checkbox-label-placement-stacked) .native-wrapper {
@include margin(null, null, $checkbox-item-label-margin-bottom, null);
}
/**
* If no label text is placed into the slot
* then the element should be hidden otherwise
@ -111,10 +83,6 @@
display: none;
}
input {
@include visually-hidden();
}
.native-wrapper {
display: flex;
@ -122,29 +90,10 @@ input {
}
.checkbox-icon {
@include border-radius(var(--border-radius));
position: relative;
width: var(--size);
height: var(--size);
transition: var(--transition);
border-width: var(--border-width);
border-style: var(--border-style);
border-color: var(--border-color);
background: var(--checkbox-background);
box-sizing: border-box;
}
.checkbox-icon path {
fill: none;
stroke: var(--checkmark-color);
stroke-width: var(--checkmark-width);
opacity: 0;
}
@ -152,21 +101,13 @@ input {
// ----------------------------------------------------------------
.checkbox-bottom {
@include padding(4px, null, null, null);
display: flex;
justify-content: space-between;
font-size: dynamic-font(12px);
white-space: normal;
}
:host(.checkbox-label-placement-stacked) .checkbox-bottom {
font-size: dynamic-font(16px);
}
// Checkbox Hint Text
// ----------------------------------------------------------------
@ -177,14 +118,10 @@ input {
*/
.checkbox-bottom .error-text {
display: none;
color: ion-color(danger, base);
}
.checkbox-bottom .helper-text {
display: block;
color: $text-color-step-300;
}
:host(.ion-touched.ion-invalid) .checkbox-bottom .error-text {
@ -206,15 +143,6 @@ input {
flex-direction: row;
}
:host(.checkbox-label-placement-start) .label-text-wrapper {
/**
* The margin between the label and
* the checkbox should be on the end
* when the label sits at the start.
*/
@include margin(null, $form-control-label-margin, null, 0);
}
// Label Placement - End
// ----------------------------------------------------------------
@ -228,27 +156,9 @@ input {
justify-content: start;
}
/**
* The margin between the label and
* the checkbox should be on the start
* when the label sits at the end.
*/
:host(.checkbox-label-placement-end) .label-text-wrapper {
@include margin(null, 0, null, $form-control-label-margin);
}
// Label Placement - Fixed
// ----------------------------------------------------------------
:host(.checkbox-label-placement-fixed) .label-text-wrapper {
/**
* The margin between the label and
* the checkbox 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 checkbox in LTR and
* on the right in RTL. Label also has a fixed width.
@ -273,29 +183,12 @@ input {
text-align: center;
}
:host(.checkbox-label-placement-stacked) .label-text-wrapper {
@include transform(scale(#{$form-control-label-stacked-scale}));
/**
* The margin between the label and
* the checkbox should be on the bottom
* when the label sits at the top.
*/
@include margin(null, 0, $form-control-label-margin, 0);
/**
* Label text should not extend
* beyond the bounds of the checkbox.
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}
:host(.checkbox-label-placement-stacked.checkbox-alignment-start) .label-text-wrapper {
@include transform-origin(start, top);
@include mixins.transform-origin(start, top);
}
:host(.checkbox-label-placement-stacked.checkbox-alignment-center) .label-text-wrapper {
@include transform-origin(center, top);
@include mixins.transform-origin(center, top);
}
// Justify Content
@ -355,7 +248,6 @@ input {
// Disabled Checkbox
// ---------------------------------------------
:host(.checkbox-disabled) {
pointer-events: none;
}

View File

@ -1,9 +1,18 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
@use "./checkbox.common";
// Ionic Checkbox
// --------------------------------------------------
:host {
/**
* @prop --checkmark-height: Stroke height of the checkbox checkmark
*
* @prop --focus-ring-color: Color of the focus ring
* @prop --focus-ring-width: Width of the focus ring
* @prop --focus-ring-offset: Offset of the focus ring
*/
// Border
--border-width: #{globals.$ion-border-size-025};
--border-style: #{globals.$ion-border-style-solid};
@ -23,13 +32,6 @@
--checkmark-color: #{globals.$ion-primitives-base-white};
--transition: none;
display: inline-block;
position: relative;
cursor: pointer;
user-select: none;
z-index: 2;
// Checkbox Target area
@ -56,49 +58,8 @@
}
}
:host(.in-item) {
flex: 1 1 0;
width: 100%;
height: 100%;
}
/**
* Checkbox can be slotted
* in components such as item and
* toolbar which is why we do not
* limit the below behavior to just ion-item.
*/
:host([slot="start"]),
:host([slot="end"]) {
// Reset the flex property when the checkbox
// is slotted to avoid growing the element larger
// than its content.
flex: initial;
width: auto;
}
.checkbox-wrapper {
display: flex;
flex-grow: 1;
align-items: center;
height: inherit;
cursor: inherit;
}
.label-text-wrapper {
color: globals.$ion-primitives-neutral-1200;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
:host(.in-item) .label-text-wrapper {
@ -113,15 +74,6 @@
@include globals.margin(null, null, globals.$ion-space-250, null);
}
/**
* 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;
}
input {
@include globals.visually-hidden();
}
@ -129,11 +81,8 @@ input {
.native-wrapper {
@include globals.border-radius(var(--border-radius));
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: var(--size);
@ -151,72 +100,32 @@ input {
}
.checkbox-icon {
position: relative;
width: var(--checkmark-width);
height: var(--checkmark-height);
}
.checkbox-icon path {
fill: var(--checkmark-color);
opacity: 0;
}
// TODO move all justify and alignment styles to the common file
// when it is created
// Checkbox Hint Text
// ----------------------------------------------------------------
// Justify Content
// ---------------------------------------------
:host(.checkbox-justify-space-between) .checkbox-wrapper {
justify-content: space-between;
.checkbox-bottom {
@include globals.typography(globals.$ion-body-md-regular);
}
:host(.checkbox-justify-start) .checkbox-wrapper {
justify-content: start;
.checkbox-bottom .error-text {
color: globals.$ion-semantics-danger-800;
}
: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;
.checkbox-bottom .helper-text {
color: globals.$ion-primitives-neutral-800;
}
// Label Placement - Start
// ----------------------------------------------------------------
/**
* Label is on the left of the checkbox in LTR and
* on the right in RTL.
*/
:host(.checkbox-label-placement-start) .checkbox-wrapper {
flex-direction: row;
}
:host(.checkbox-label-placement-start) .label-text-wrapper {
/**
* The margin between the label and
@ -229,14 +138,6 @@ input {
// Label Placement - End
// ----------------------------------------------------------------
/**
* Label is on the right of the checkbox in LTR and
* on the left in RTL.
*/
:host(.checkbox-label-placement-end) .checkbox-wrapper {
flex-direction: row-reverse;
}
/**
* The margin between the label and
* the checkbox should be on the start
@ -258,28 +159,9 @@ input {
@include globals.margin(null, globals.$ion-space-400, null, 0);
}
/**
* Label is on the left of the checkbox in LTR and
* on the right in RTL. Label also has a fixed width.
*/
:host(.checkbox-label-placement-fixed) .label-text-wrapper {
flex: 0 0 100px;
width: 100px;
min-width: 100px;
max-width: 200px;
}
// Label Placement - Stacked
// ----------------------------------------------------------------
/**
* Label is on top of the checkbox.
*/
:host(.checkbox-label-placement-stacked) .checkbox-wrapper {
flex-direction: column;
}
:host(.checkbox-label-placement-stacked) .label-text-wrapper {
@include globals.transform(scale(0.75));
@ -297,15 +179,6 @@ input {
max-width: calc(100% / 0.75);
}
// TODO(ROU-10796): uncomment this when the scss compilation issue is fixed
// :host(.checkbox-label-placement-stacked.checkbox-alignment-start) .label-text-wrapper {
// @include globals.transform-origin(start, top);
// }
// :host(.checkbox-label-placement-stacked.checkbox-alignment-center) .label-text-wrapper {
// @include globals.transform-origin(center, top);
// }
// Ionic Design Checkbox Sizes
// --------------------------------------------------
:host(.checkbox-size-small) {
@ -322,11 +195,6 @@ input {
background: var(--checkbox-background-checked);
}
:host(.checkbox-checked) .checkbox-icon path,
:host(.checkbox-indeterminate) .checkbox-icon path {
opacity: 1;
}
// Ionic Design Checkbox Invalid
// --------------------------------------------------
:host(.ion-invalid) {
@ -337,12 +205,6 @@ input {
}
}
// Checkbox Disabled
// --------------------------------------------------
:host(.checkbox-disabled) {
pointer-events: none;
}
// Checkbox overrides the disabled state mixin properties
// to fix positioning issues, as the top and left properties
// cause the overlay to start inside the border. We unset

View File

@ -1,4 +1,4 @@
@import "./checkbox";
@import "./checkbox.native";
@import "./checkbox.ios.vars";
// iOS Checkbox

View File

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

View File

@ -0,0 +1,146 @@
@import "../../themes/native/native.globals";
@import "./checkbox.vars.scss";
@import "./checkbox.common";
// Checkbox
// --------------------------------------------------
:host {
/**
* @prop --checkbox-background: Background of the checkbox icon
*/
--checkbox-background-checked: #{ion-color(primary, base)};
--border-color-checked: #{ion-color(primary, base)};
--checkmark-color: #{ion-color(primary, contrast)};
--transition: none;
z-index: $z-index-item-input;
}
:host(.ion-color) {
--checkbox-background-checked: #{current-color(base)};
--border-color-checked: #{current-color(base)};
--checkmark-color: #{current-color(contrast)};
}
// Checkboxes that are not slotted inside an item and are not used with a
// stacked label should have margins equal to those of the label.
:host(.in-item) .label-text-wrapper,
:host(.in-item:not(.checkbox-label-placement-stacked):not([slot])) .native-wrapper {
@include margin($checkbox-item-label-margin-top, null, $checkbox-item-label-margin-bottom, null);
}
:host(.in-item.checkbox-label-placement-stacked) .label-text-wrapper {
@include margin($checkbox-item-label-margin-top, null, $form-control-label-margin, null);
}
:host(.in-item.checkbox-label-placement-stacked) .native-wrapper {
@include margin(null, null, $checkbox-item-label-margin-bottom, null);
}
input {
@include visually-hidden();
}
.checkbox-icon {
@include border-radius(var(--border-radius));
width: var(--size);
height: var(--size);
transition: var(--transition);
border-width: var(--border-width);
border-style: var(--border-style);
border-color: var(--border-color);
background: var(--checkbox-background);
box-sizing: border-box;
}
.checkbox-icon path {
fill: none;
stroke: var(--checkmark-color);
stroke-width: var(--checkmark-width);
}
// Checkbox Bottom Content
// ----------------------------------------------------------------
.checkbox-bottom {
@include padding(4px, null, null, null);
font-size: dynamic-font(12px);
}
:host(.checkbox-label-placement-stacked) .checkbox-bottom {
font-size: dynamic-font(16px);
}
// Checkbox Hint Text
// ----------------------------------------------------------------
.checkbox-bottom .error-text {
color: ion-color(danger, base);
}
.checkbox-bottom .helper-text {
color: $text-color-step-300;
}
// Label Placement - Start
// ----------------------------------------------------------------
:host(.checkbox-label-placement-start) .label-text-wrapper {
/**
* The margin between the label and
* the checkbox should be on the end
* when the label sits at the start.
*/
@include margin(null, $form-control-label-margin, null, 0);
}
// Label Placement - End
// ----------------------------------------------------------------
/**
* The margin between the label and
* the checkbox should be on the start
* when the label sits at the end.
*/
:host(.checkbox-label-placement-end) .label-text-wrapper {
@include margin(null, 0, null, $form-control-label-margin);
}
// Label Placement - Fixed
// ----------------------------------------------------------------
:host(.checkbox-label-placement-fixed) .label-text-wrapper {
/**
* The margin between the label and
* the checkbox should be on the end
* when the label sits at the start.
*/
@include margin(null, $form-control-label-margin, null, 0);
}
// Label Placement - Stacked
// ----------------------------------------------------------------
:host(.checkbox-label-placement-stacked) .label-text-wrapper {
@include transform(scale(#{$form-control-label-stacked-scale}));
/**
* The margin between the label and
* the checkbox should be on the bottom
* when the label sits at the top.
*/
@include margin(null, 0, $form-control-label-margin, 0);
/**
* Label text should not extend
* beyond the bounds of the checkbox.
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -98,7 +98,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
/**
* Rendering is different across modes
*/
configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
configs({ modes: ['ios', 'md', 'ionic-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) => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB