feat(checkbox): add ionic theme styling (#29335)

Issue number: N/A

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Checkbox does not have an ionic theme implementation.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Adds the ionic theme styles and initial features for checkbox. 
- This PR is a combination of all the individual PRs already reviewed.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

We should confirm that the visual styles and behavior function as we
expect with all the changes combined in this PR.

---------

Co-authored-by: João Ferreira <60441552+JoaoFerreira-FrontEnd@users.noreply.github.com>
Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
Sean Perkins
2024-04-16 11:19:33 -04:00
committed by GitHub
parent 62a3e115ef
commit ea1207174d
152 changed files with 351 additions and 6 deletions

View File

@ -322,6 +322,8 @@ ion-checkbox,prop,justify,"end" | "space-between" | "start",'space-between',fals
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
ion-checkbox,prop,mode,"ios" | "md",undefined,false,false
ion-checkbox,prop,name,string,this.inputId,false,false
ion-checkbox,prop,shape,"rectangular" | "soft" | undefined,'soft',false,false
ion-checkbox,prop,size,"small" | undefined,undefined,false,false
ion-checkbox,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-checkbox,prop,value,any,'on',false,false
ion-checkbox,event,ionBlur,void,true

View File

@ -728,6 +728,14 @@ export namespace Components {
* The name of the control, which is submitted with the form data.
*/
"name": string;
/**
* Set to `"soft"` for a checkbox with more rounded corners. Only available when the theme is `"ionic"`.
*/
"shape"?: 'soft' | 'rectangular';
/**
* Set to `"small"` for a checkbox with less height and padding.
*/
"size"?: 'small';
/**
* The theme determines the visual appearance of the component.
*/
@ -5972,6 +5980,14 @@ declare namespace LocalJSX {
* Emitted when the checkbox has focus.
*/
"onIonFocus"?: (event: IonCheckboxCustomEvent<void>) => void;
/**
* Set to `"soft"` for a checkbox with more rounded corners. Only available when the theme is `"ionic"`.
*/
"shape"?: 'soft' | 'rectangular';
/**
* Set to `"small"` for a checkbox with less height and padding.
*/
"size"?: 'small';
/**
* The theme determines the visual appearance of the component.
*/

View File

@ -0,0 +1,137 @@
@import "./checkbox";
@import "./checkbox.ionic.vars";
// Ionic Checkbox
// --------------------------------------------------
:host {
// Border
--border-radius: #{$checkbox-ionic-border-radius};
--border-width: #{$checkbox-ionic-border-width};
--border-style: #{$checkbox-ionic-border-style};
--border-color: #{$checkbox-ionic-background-color-off};
--checkmark-width: 3;
// Focus
--focus-ring-color: #9ec4fd;
--focus-ring-width: 2px;
--focus-ring-offset: 2px;
// Size
--size: #{$checkbox-ionic-size};
// Checkbox Target area
// --------------------------------------------------
&::after {
@include position(50%, 0, null, 0);
position: absolute;
height: 100%;
min-height: 48px;
transform: translateY(-50%);
content: "";
cursor: pointer;
z-index: 1;
}
.native-wrapper {
position: relative;
}
}
// Ionic Design Checkbox Sizes
// --------------------------------------------------
:host(.checkbox-size-small) {
// Size
--size: #{$checkbox-ionic-small-size};
}
// Ionic Design Checkbox Invalid
// --------------------------------------------------
:host(.ion-invalid) {
--focus-ring-color: #ffafaf;
.checkbox-icon {
border-color: #f72c2c;
}
}
// Checkbox Disabled
// --------------------------------------------------
// disabled, indeterminate checkbox
:host(.checkbox-disabled.checkbox-indeterminate) .checkbox-icon {
/* TODO(FW-6183): Use design token variables */
border-width: 0;
background-color: #{$background-color-step-600};
}
// disabled, unchecked checkbox
:host(.checkbox-disabled) .checkbox-icon {
/* TODO(FW-6183): Use design token variables */
border-color: #c9c9c9;
background-color: #f5f5f5; // mix of #f5f5f5 with 60% #FFF
}
// disabled, checked checkbox
:host(.checkbox-disabled.checkbox-checked) .checkbox-icon {
border-width: 0;
background-color: $background-color-step-100;
}
// Checkbox Hover
// --------------------------------------------------------
@media (any-hover: hover) {
:host(:hover) .checkbox-icon {
/* TODO(FW-6183): Use design token variables */
background-color: #ececec; // mix of 'white', '#121212', 0.08, 'rgb'
}
:host(:hover.checkbox-checked) .checkbox-icon,
:host(:hover.checkbox-indeterminate) .checkbox-icon {
/* TODO(FW-6183): Use design token variables */
background-color: #1061da; // mix of '#1068eb', '#121212', 0.08, 'rgb'
}
}
// Checkbox Focus
// --------------------------------------------------
// Only show the focus ring when the checkbox is focused and not disabled
:host(.ion-focused:not(.checkbox-disabled)) .checkbox-icon {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
// Checkbox: Active
// --------------------------------------------------------
:host(.ion-activated) .checkbox-icon {
/* TODO(FW-6183): Use design token variables */
background-color: #e3e3e3; // mix of 'white', '#121212', 0.12, 'rgb'
}
:host(.ion-activated.checkbox-checked) .checkbox-icon,
:host(.ion-activated.checkbox-indeterminate) .checkbox-icon {
/* TODO(FW-6183): Use design token variables */
background-color: #105ed1; // mix of '#1068eb', '#121212', 0.12, 'rgb'
}
// Ionic Design Checkbox Shapes
// --------------------------------------------------
:host(.checkbox-shape-soft) {
--border-radius: #{$checkbox-ionic-border-radius};
}
:host(.checkbox-shape-rectangular) {
--border-radius: #{$checkbox-ionic-rectangular-border};
}
.checkbox-wrapper {
min-height: 48px;
}

View File

@ -0,0 +1,31 @@
@import "../../themes/ionic.globals.ionic";
// Ionic Checkbox Variables
// --------------------------------------------------
/// @prop - The default width and height of the checkbox
$checkbox-ionic-size: 24px !default;
/// @prop - The background color of the checkbox when the checkbox is unchecked
$checkbox-ionic-background-color-off: $background-color-step-400 !default;
/// @prop - Border style of the checkbox
$checkbox-ionic-border-style: solid !default;
/// @prop - Border width of the checkbox
$checkbox-ionic-border-width: 1px !default;
/// @prop - The border radius of the checkbox
/// With a default size of 24px, the border radius is calculated as 24px / 4 - 2px = 4px
/// With a small size of 16px, the border radius is calculated as 16px / 4 - 2px = 2px;
$checkbox-ionic-border-radius: calc(var(--size) / 4 - 2px) !default;
/// @prop - Icon size of the checkbox for the small size
$checkbox-ionic-small-size: 16px !default;
// Checkbox Shapes
// -------------------------------------------------------------------------------
/* Rectangular */
/// @prop - Rectangular border radius of the checkbox
$checkbox-ionic-rectangular-border: 0 !default;

View File

@ -24,7 +24,7 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
styleUrls: {
ios: 'checkbox.ios.scss',
md: 'checkbox.md.scss',
ionic: 'checkbox.md.scss',
ionic: 'checkbox.ionic.scss',
},
shadow: true,
})
@ -98,6 +98,16 @@ export class Checkbox implements ComponentInterface {
*/
@Prop() alignment: 'start' | 'center' = 'center';
/**
* Set to `"small"` for a checkbox with less height and padding.
*/
@Prop() size?: 'small';
/**
* Set to `"soft"` for a checkbox with more rounded corners. Only available when the theme is `"ionic"`.
*/
@Prop() shape?: 'soft' | 'rectangular' = 'soft';
/**
* Emitted when the checked property has changed
* as a result of a user action such as a click.
@ -181,6 +191,8 @@ export class Checkbox implements ComponentInterface {
name,
value,
alignment,
size,
shape,
} = this;
const theme = getIonTheme(this);
@ -201,6 +213,8 @@ export class Checkbox implements ComponentInterface {
[`checkbox-justify-${justify}`]: true,
[`checkbox-alignment-${alignment}`]: true,
[`checkbox-label-placement-${labelPlacement}`]: true,
[`checkbox-size-${size}`]: size !== undefined,
[`checkbox-shape-${shape}`]: true,
})}
onClick={this.onClick}
>
@ -252,6 +266,12 @@ export class Checkbox implements ComponentInterface {
) : (
<path d="M1.73,12.91 8.1,19.28 22.79,4.59" part="mark" />
);
} else if (theme === 'ionic') {
path = indeterminate ? (
<path d="M6.5 12H17.5" stroke-linecap="round" part="mark" />
) : (
<path d="M6 12.5L10 16.5L18.5 8" stroke-linecap="round" stroke-linejoin="round" part="mark" />
);
}
return path;

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('checkbox: basic visual tests'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
@ -123,3 +123,92 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
});
});
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('checkbox: basic visual tests'), () => {
test('should have a small size applied correctly', async ({ page }) => {
await page.setContent(
`
<div id="checkboxes">
<ion-checkbox size="small">Small</ion-checkbox>
<ion-checkbox size="small" checked="true">Small - Checked</ion-checkbox>
</div>
`,
config
);
const checkboxes = page.locator('#checkboxes');
await expect(checkboxes).toHaveScreenshot(screenshot(`checkbox-small`));
});
test('should have an invalid visual applied correctly', async ({ page }) => {
await page.setContent(
`
<div id="checkboxes" style="padding: 8px">
<ion-checkbox class="ion-invalid">Invalid</ion-checkbox>
<ion-checkbox class="ion-invalid ion-focused">Invalid</ion-checkbox>
</div>
`,
config
);
const checkboxes = page.locator('#checkboxes');
await expect(checkboxes).toHaveScreenshot(screenshot(`checkbox-invalid`));
});
});
test.describe(title('checkbox: safe area'), () => {
test('should click the safe area of a small checkbox', async ({ page }) => {
await page.setContent(`<ion-checkbox size="small">Small</ion-checkbox>`, config);
const checkbox = page.locator('ion-checkbox');
const box = await checkbox.boundingBox();
if (box !== null) {
await page.mouse.click(box.x + box.width / 2, box.y + 47);
}
await expect(checkbox).toBeFocused();
});
test('should click the safe area of a default checkbox', async ({ page }) => {
await page.setContent(`<ion-checkbox>Default</ion-checkbox>`, config);
const checkbox = page.locator('ion-checkbox');
const box = await checkbox.boundingBox();
if (box !== null) {
await page.mouse.click(box.x + box.width / 2, box.y + 47);
}
await expect(checkbox).toBeFocused();
});
});
test.describe(title('checkbox: shapes'), () => {
test('should have a soft shape applied correctly', async ({ page }) => {
await page.setContent(
`
<div id="checkboxes">
<ion-checkbox >soft</ion-checkbox>
<ion-checkbox shape="soft">Soft</ion-checkbox>
</div>
`,
config
);
const checkboxes = page.locator('#checkboxes');
await expect(checkboxes).toHaveScreenshot(screenshot(`checkbox-shape-soft`));
});
test('should have a rectangular shape applied correctly', async ({ page }) => {
await page.setContent(
`
<div id="checkboxes">
<ion-checkbox shape="rectangular">Rectangular</ion-checkbox>
</div>
`,
config
);
const checkboxes = page.locator('#checkboxes');
await expect(checkboxes).toHaveScreenshot(screenshot(`checkbox-shape-rectangular`));
});
});
});

View File

@ -9,7 +9,7 @@ import { configs, test } from '@utils/test/playwright';
* we set the width of the checkbox so we can
* see the justification results.
*/
configs().forEach(({ title, screenshot, config }) => {
configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('checkbox: label'), () => {
test.describe('checkbox: start placement', () => {
test('should render a start justification with label in the start position', async ({ page }) => {

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