diff --git a/BREAKING.md b/BREAKING.md index 055d8c9ac93..968a8e47d19 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -118,11 +118,12 @@ The following breaking changes apply to `ion-button`: 9. The `ios` button follows iOS 17 for its font, icons and disabled state. [9](#version-10x-button-ios-17) 10. A badge without a `vertical` position now has a gap between it and the label, and no longer makes the button taller than its `min.height`. To remove the gap, set `IonButton.size..badge.margin.start` for global styles or `--ion-button-size--badge-margin-start` for component-specific styles to `0`. 11. A button with an icon in the `icon-only` slot now shows only that icon and any badge with a `vertical` position. Text, elements in the `start` and `end` slots, and other content are hidden. Text and elements without a slot are only visually hidden, so they still name the button for assistive technology. To show other content, remove the `icon-only` slot and use the `start` or `end` slot for the icon. -12. CSS variables have been replaced. [12](#version-10x-button-replaced-css-variables) -13. `--padding-*` CSS variables are no longer part of the documented public API (but remain functional). [13](#version-10x-button-padding-variables) -14. The fill, size, shape and expand classes have been renamed. [14](#version-10x-button-renamed-classes) -15. The `default` size has been deprecated. [15](#version-10x-button-deprecated-default-size) -16. Theme classes (`ion-button.md`, `ion-button.ios`) are no longer supported. [16](#version-10x-button-theme-classes) +12. The text of `outline` buttons in `md` and `ios` now uses the color's darker `foreground` variant instead of `base`, matching `clear` buttons, since it sits on the page rather than on a fill of the color. In `ios`, `clear` buttons with a `color` also keep `foreground` on hover and focus. To revert, set `IonButton.hue.bold.outline..color` (`.semantic..color` with a `color`) for global styles or `--ion-button-hue-bold-outline--color` (`--ion-button-hue-bold-outline-semantic--color`) for component-specific styles to the color's `base`. For `ios` `clear` buttons with a `color`, set `IonButton.hue.bold.clear.semantic..color` or `--ion-button-hue-bold-clear-semantic--color` for `hover` and `focus` to the color's `base` as well. +13. CSS variables have been replaced. [13](#version-10x-button-replaced-css-variables) +14. `--padding-*` CSS variables are no longer part of the documented public API (but remain functional). [14](#version-10x-button-padding-variables) +15. The fill, size, shape and expand classes have been renamed. [15](#version-10x-button-renamed-classes) +16. The `default` size has been deprecated. [16](#version-10x-button-deprecated-default-size) +17. Theme classes (`ion-button.md`, `ion-button.ios`) are no longer supported. [17](#version-10x-button-theme-classes)
Material Design 2 in md
diff --git a/core/api.txt b/core/api.txt index bcf367d65ba..feb0f8a9aac 100644 --- a/core/api.txt +++ b/core/api.txt @@ -439,6 +439,7 @@ ion-button,prop,expand,"block" | "full" | undefined,undefined,false,true ion-button,prop,fill,"clear" | "default" | "outline" | "solid" | undefined,undefined,false,true ion-button,prop,form,HTMLFormElement | string | undefined,undefined,false,false ion-button,prop,href,string | undefined,undefined,false,false +ion-button,prop,hue,"bold" | "subtle" | undefined,undefined,false,false ion-button,prop,mode,"ios" | "md",undefined,false,false ion-button,prop,rel,string | undefined,undefined,false,false ion-button,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false @@ -571,6 +572,126 @@ ion-button,css-prop,--ion-button-hue-bold-solid-semantic-hover-color ion-button,css-prop,--ion-button-hue-bold-solid-semantic-ripple-effect-color ion-button,css-prop,--ion-button-hue-bold-solid-semantic-ripple-effect-opacity ion-button,css-prop,--ion-button-hue-bold-solid-strong-font-weight +ion-button,css-prop,--ion-button-hue-subtle-clear-activated-background +ion-button,css-prop,--ion-button-hue-subtle-clear-activated-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-clear-activated-color +ion-button,css-prop,--ion-button-hue-subtle-clear-activated-opacity +ion-button,css-prop,--ion-button-hue-subtle-clear-default-background +ion-button,css-prop,--ion-button-hue-subtle-clear-default-border-color +ion-button,css-prop,--ion-button-hue-subtle-clear-default-border-style +ion-button,css-prop,--ion-button-hue-subtle-clear-default-border-width +ion-button,css-prop,--ion-button-hue-subtle-clear-default-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-clear-default-color +ion-button,css-prop,--ion-button-hue-subtle-clear-default-font-weight +ion-button,css-prop,--ion-button-hue-subtle-clear-disabled-background +ion-button,css-prop,--ion-button-hue-subtle-clear-disabled-border-color +ion-button,css-prop,--ion-button-hue-subtle-clear-disabled-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-clear-disabled-color +ion-button,css-prop,--ion-button-hue-subtle-clear-disabled-opacity +ion-button,css-prop,--ion-button-hue-subtle-clear-focus-background +ion-button,css-prop,--ion-button-hue-subtle-clear-focus-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-clear-focus-color +ion-button,css-prop,--ion-button-hue-subtle-clear-hover-background +ion-button,css-prop,--ion-button-hue-subtle-clear-hover-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-clear-hover-color +ion-button,css-prop,--ion-button-hue-subtle-clear-hover-opacity +ion-button,css-prop,--ion-button-hue-subtle-clear-ripple-effect-color +ion-button,css-prop,--ion-button-hue-subtle-clear-ripple-effect-opacity +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-activated-background +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-activated-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-default-background +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-default-border-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-default-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-disabled-background +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-disabled-border-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-disabled-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-focus-background +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-focus-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-hover-background +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-hover-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-ripple-effect-color +ion-button,css-prop,--ion-button-hue-subtle-clear-semantic-ripple-effect-opacity +ion-button,css-prop,--ion-button-hue-subtle-clear-strong-font-weight +ion-button,css-prop,--ion-button-hue-subtle-outline-activated-background +ion-button,css-prop,--ion-button-hue-subtle-outline-activated-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-outline-activated-color +ion-button,css-prop,--ion-button-hue-subtle-outline-activated-opacity +ion-button,css-prop,--ion-button-hue-subtle-outline-default-background +ion-button,css-prop,--ion-button-hue-subtle-outline-default-border-color +ion-button,css-prop,--ion-button-hue-subtle-outline-default-border-style +ion-button,css-prop,--ion-button-hue-subtle-outline-default-border-width +ion-button,css-prop,--ion-button-hue-subtle-outline-default-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-outline-default-color +ion-button,css-prop,--ion-button-hue-subtle-outline-default-font-weight +ion-button,css-prop,--ion-button-hue-subtle-outline-disabled-background +ion-button,css-prop,--ion-button-hue-subtle-outline-disabled-border-color +ion-button,css-prop,--ion-button-hue-subtle-outline-disabled-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-outline-disabled-color +ion-button,css-prop,--ion-button-hue-subtle-outline-disabled-opacity +ion-button,css-prop,--ion-button-hue-subtle-outline-focus-background +ion-button,css-prop,--ion-button-hue-subtle-outline-focus-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-outline-focus-color +ion-button,css-prop,--ion-button-hue-subtle-outline-hover-background +ion-button,css-prop,--ion-button-hue-subtle-outline-hover-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-outline-hover-color +ion-button,css-prop,--ion-button-hue-subtle-outline-hover-opacity +ion-button,css-prop,--ion-button-hue-subtle-outline-ripple-effect-color +ion-button,css-prop,--ion-button-hue-subtle-outline-ripple-effect-opacity +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-activated-background +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-activated-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-default-background +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-default-border-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-default-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-disabled-background +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-disabled-border-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-disabled-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-focus-background +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-focus-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-hover-background +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-hover-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-ripple-effect-color +ion-button,css-prop,--ion-button-hue-subtle-outline-semantic-ripple-effect-opacity +ion-button,css-prop,--ion-button-hue-subtle-outline-strong-font-weight +ion-button,css-prop,--ion-button-hue-subtle-solid-activated-background +ion-button,css-prop,--ion-button-hue-subtle-solid-activated-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-solid-activated-color +ion-button,css-prop,--ion-button-hue-subtle-solid-activated-opacity +ion-button,css-prop,--ion-button-hue-subtle-solid-default-background +ion-button,css-prop,--ion-button-hue-subtle-solid-default-border-color +ion-button,css-prop,--ion-button-hue-subtle-solid-default-border-style +ion-button,css-prop,--ion-button-hue-subtle-solid-default-border-width +ion-button,css-prop,--ion-button-hue-subtle-solid-default-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-solid-default-color +ion-button,css-prop,--ion-button-hue-subtle-solid-default-font-weight +ion-button,css-prop,--ion-button-hue-subtle-solid-disabled-background +ion-button,css-prop,--ion-button-hue-subtle-solid-disabled-border-color +ion-button,css-prop,--ion-button-hue-subtle-solid-disabled-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-solid-disabled-color +ion-button,css-prop,--ion-button-hue-subtle-solid-disabled-opacity +ion-button,css-prop,--ion-button-hue-subtle-solid-focus-background +ion-button,css-prop,--ion-button-hue-subtle-solid-focus-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-solid-focus-color +ion-button,css-prop,--ion-button-hue-subtle-solid-hover-background +ion-button,css-prop,--ion-button-hue-subtle-solid-hover-box-shadow +ion-button,css-prop,--ion-button-hue-subtle-solid-hover-color +ion-button,css-prop,--ion-button-hue-subtle-solid-hover-opacity +ion-button,css-prop,--ion-button-hue-subtle-solid-ripple-effect-color +ion-button,css-prop,--ion-button-hue-subtle-solid-ripple-effect-opacity +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-activated-background +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-activated-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-default-background +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-default-border-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-default-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-disabled-background +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-disabled-border-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-disabled-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-focus-background +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-focus-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-hover-background +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-hover-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-ripple-effect-color +ion-button,css-prop,--ion-button-hue-subtle-solid-semantic-ripple-effect-opacity +ion-button,css-prop,--ion-button-hue-subtle-solid-strong-font-weight ion-button,css-prop,--ion-button-letter-spacing ion-button,css-prop,--ion-button-margin-bottom ion-button,css-prop,--ion-button-margin-end diff --git a/core/src/components.d.ts b/core/src/components.d.ts index f7e6731d9dc..7addd399f83 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -625,6 +625,10 @@ export namespace Components { * Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. */ "href": string | undefined; + /** + * Set to `"bold"` for a button with vibrant, bold colors or to `"subtle"` for a button with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset. + */ + "hue"?: Hue; /** * The mode determines the platform behaviors of the component. */ @@ -6495,6 +6499,10 @@ declare namespace LocalJSX { * Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. */ "href"?: string | undefined; + /** + * Set to `"bold"` for a button with vibrant, bold colors or to `"subtle"` for a button with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset. + */ + "hue"?: Hue; /** * The mode determines the platform behaviors of the component. */ @@ -10515,6 +10523,7 @@ declare namespace LocalJSX { "disabled": boolean; "expand": IonButtonExpand; "fill": IonButtonFill | 'default'; + "hue": Hue; "routerDirection": RouterDirection; "download": string | undefined; "href": string | undefined; diff --git a/core/src/components/button/_button.docs.scss b/core/src/components/button/_button.docs.scss index 510f1952576..3940c0c99fa 100644 --- a/core/src/components/button/_button.docs.scss +++ b/core/src/components/button/_button.docs.scss @@ -13,6 +13,12 @@ * @prop --ion-button-hue-bold-outline-strong-font-weight: Font weight of the `bold` hue and `outline` fill combination on a `strong` button * @prop --ion-button-hue-bold-clear-default-font-weight: Font weight of the `bold` hue and `clear` fill combination in the default state * @prop --ion-button-hue-bold-clear-strong-font-weight: Font weight of the `bold` hue and `clear` fill combination on a `strong` button + * @prop --ion-button-hue-subtle-solid-default-font-weight: Font weight of the `subtle` hue and `solid` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-strong-font-weight: Font weight of the `subtle` hue and `solid` fill combination on a `strong` button + * @prop --ion-button-hue-subtle-outline-default-font-weight: Font weight of the `subtle` hue and `outline` fill combination in the default state + * @prop --ion-button-hue-subtle-outline-strong-font-weight: Font weight of the `subtle` hue and `outline` fill combination on a `strong` button + * @prop --ion-button-hue-subtle-clear-default-font-weight: Font weight of the `subtle` hue and `clear` fill combination in the default state + * @prop --ion-button-hue-subtle-clear-strong-font-weight: Font weight of the `subtle` hue and `clear` fill combination on a `strong` button * @prop --ion-button-size-small-min-width: Minimum width of the `small` button size * @prop --ion-button-size-small-min-height: Minimum height of the `small` button size, and the width and height of an icon-only button of that size * @prop --ion-button-size-small-font-size: Font size of the `small` button size @@ -264,4 +270,118 @@ * @prop --ion-button-hue-bold-clear-semantic-disabled-background: Background of the `bold` hue and `clear` fill combination in the disabled state when a semantic color is applied * @prop --ion-button-hue-bold-clear-semantic-disabled-color: Text color of the `bold` hue and `clear` fill combination in the disabled state when a semantic color is applied * @prop --ion-button-hue-bold-clear-semantic-disabled-border-color: Border color of the `bold` hue and `clear` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-default-border-color: Border color of the `subtle` hue and `outline` fill combination in the default state + * @prop --ion-button-hue-subtle-outline-default-color: Text color of the `subtle` hue and `outline` fill combination in the default state + * @prop --ion-button-hue-subtle-clear-default-color: Text color of the `subtle` hue and `clear` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-default-background: Background of the `subtle` hue and `solid` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-default-color: Text color of the `subtle` hue and `solid` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-default-border-width: Border width of the `subtle` hue and `solid` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-default-border-style: Border style of the `subtle` hue and `solid` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-default-border-color: Border color of the `subtle` hue and `solid` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-default-box-shadow: Box shadow of the `subtle` hue and `solid` fill combination in the default state + * @prop --ion-button-hue-subtle-solid-ripple-effect-color: Ripple color of the `subtle` hue and `solid` fill combination + * @prop --ion-button-hue-subtle-solid-ripple-effect-opacity: Ripple opacity of the `subtle` hue and `solid` fill combination + * @prop --ion-button-hue-subtle-solid-semantic-default-border-color: Border color of the `subtle` hue and `solid` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-default-background: Background of the `subtle` hue and `solid` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-default-color: Text color of the `subtle` hue and `solid` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-ripple-effect-color: Ripple color of the `subtle` hue and `solid` fill combination when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-ripple-effect-opacity: Ripple opacity of the `subtle` hue and `solid` fill combination when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-focus-color: Text color of the `subtle` hue and `solid` fill combination in the focus state + * @prop --ion-button-hue-subtle-solid-focus-background: Background of the `subtle` hue and `solid` fill combination in the focus state + * @prop --ion-button-hue-subtle-solid-focus-box-shadow: Box shadow of the `subtle` hue and `solid` fill combination in the focus state + * @prop --ion-button-hue-subtle-solid-semantic-focus-background: Background of the `subtle` hue and `solid` fill combination in the focus state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-focus-color: Text color of the `subtle` hue and `solid` fill combination in the focus state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-hover-color: Text color of the `subtle` hue and `solid` fill combination in the hover state + * @prop --ion-button-hue-subtle-solid-hover-opacity: Opacity of the whole button of the `subtle` hue and `solid` fill combination in the hover state + * @prop --ion-button-hue-subtle-solid-hover-background: Background of the `subtle` hue and `solid` fill combination in the hover state + * @prop --ion-button-hue-subtle-solid-hover-box-shadow: Box shadow of the `subtle` hue and `solid` fill combination in the hover state + * @prop --ion-button-hue-subtle-solid-semantic-hover-background: Background of the `subtle` hue and `solid` fill combination in the hover state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-hover-color: Text color of the `subtle` hue and `solid` fill combination in the hover state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-activated-color: Text color of the `subtle` hue and `solid` fill combination in the activated state + * @prop --ion-button-hue-subtle-solid-activated-opacity: Opacity of the whole button of the `subtle` hue and `solid` fill combination in the activated state + * @prop --ion-button-hue-subtle-solid-activated-background: Background of the `subtle` hue and `solid` fill combination in the activated state + * @prop --ion-button-hue-subtle-solid-activated-box-shadow: Box shadow of the `subtle` hue and `solid` fill combination in the activated state + * @prop --ion-button-hue-subtle-solid-semantic-activated-background: Background of the `subtle` hue and `solid` fill combination in the activated state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-activated-color: Text color of the `subtle` hue and `solid` fill combination in the activated state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-disabled-background: Background of the `subtle` hue and `solid` fill combination in the disabled state + * @prop --ion-button-hue-subtle-solid-disabled-opacity: Opacity of the whole button of the `subtle` hue and `solid` fill combination in the disabled state + * @prop --ion-button-hue-subtle-solid-disabled-color: Text color of the `subtle` hue and `solid` fill combination in the disabled state + * @prop --ion-button-hue-subtle-solid-disabled-border-color: Border color of the `subtle` hue and `solid` fill combination in the disabled state + * @prop --ion-button-hue-subtle-solid-disabled-box-shadow: Box shadow of the `subtle` hue and `solid` fill combination in the disabled state + * @prop --ion-button-hue-subtle-solid-semantic-disabled-background: Background of the `subtle` hue and `solid` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-disabled-color: Text color of the `subtle` hue and `solid` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-solid-semantic-disabled-border-color: Border color of the `subtle` hue and `solid` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-default-border-width: Border width of the `subtle` hue and `outline` fill combination in the default state + * @prop --ion-button-hue-subtle-outline-default-border-style: Border style of the `subtle` hue and `outline` fill combination in the default state + * @prop --ion-button-hue-subtle-outline-default-background: Background of the `subtle` hue and `outline` fill combination in the default state + * @prop --ion-button-hue-subtle-outline-default-box-shadow: Box shadow of the `subtle` hue and `outline` fill combination in the default state + * @prop --ion-button-hue-subtle-outline-ripple-effect-color: Ripple color of the `subtle` hue and `outline` fill combination + * @prop --ion-button-hue-subtle-outline-ripple-effect-opacity: Ripple opacity of the `subtle` hue and `outline` fill combination + * @prop --ion-button-hue-subtle-outline-semantic-default-border-color: Border color of the `subtle` hue and `outline` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-default-background: Background of the `subtle` hue and `outline` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-default-color: Text color of the `subtle` hue and `outline` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-ripple-effect-color: Ripple color of the `subtle` hue and `outline` fill combination when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-ripple-effect-opacity: Ripple opacity of the `subtle` hue and `outline` fill combination when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-focus-color: Text color of the `subtle` hue and `outline` fill combination in the focus state + * @prop --ion-button-hue-subtle-outline-focus-background: Background of the `subtle` hue and `outline` fill combination in the focus state + * @prop --ion-button-hue-subtle-outline-focus-box-shadow: Box shadow of the `subtle` hue and `outline` fill combination in the focus state + * @prop --ion-button-hue-subtle-outline-semantic-focus-background: Background of the `subtle` hue and `outline` fill combination in the focus state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-focus-color: Text color of the `subtle` hue and `outline` fill combination in the focus state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-hover-color: Text color of the `subtle` hue and `outline` fill combination in the hover state + * @prop --ion-button-hue-subtle-outline-hover-opacity: Opacity of the whole button of the `subtle` hue and `outline` fill combination in the hover state + * @prop --ion-button-hue-subtle-outline-hover-background: Background of the `subtle` hue and `outline` fill combination in the hover state + * @prop --ion-button-hue-subtle-outline-hover-box-shadow: Box shadow of the `subtle` hue and `outline` fill combination in the hover state + * @prop --ion-button-hue-subtle-outline-semantic-hover-background: Background of the `subtle` hue and `outline` fill combination in the hover state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-hover-color: Text color of the `subtle` hue and `outline` fill combination in the hover state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-activated-color: Text color of the `subtle` hue and `outline` fill combination in the activated state + * @prop --ion-button-hue-subtle-outline-activated-opacity: Opacity of the whole button of the `subtle` hue and `outline` fill combination in the activated state + * @prop --ion-button-hue-subtle-outline-activated-background: Background of the `subtle` hue and `outline` fill combination in the activated state + * @prop --ion-button-hue-subtle-outline-activated-box-shadow: Box shadow of the `subtle` hue and `outline` fill combination in the activated state + * @prop --ion-button-hue-subtle-outline-semantic-activated-background: Background of the `subtle` hue and `outline` fill combination in the activated state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-activated-color: Text color of the `subtle` hue and `outline` fill combination in the activated state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-disabled-background: Background of the `subtle` hue and `outline` fill combination in the disabled state + * @prop --ion-button-hue-subtle-outline-disabled-opacity: Opacity of the whole button of the `subtle` hue and `outline` fill combination in the disabled state + * @prop --ion-button-hue-subtle-outline-disabled-color: Text color of the `subtle` hue and `outline` fill combination in the disabled state + * @prop --ion-button-hue-subtle-outline-disabled-border-color: Border color of the `subtle` hue and `outline` fill combination in the disabled state + * @prop --ion-button-hue-subtle-outline-disabled-box-shadow: Box shadow of the `subtle` hue and `outline` fill combination in the disabled state + * @prop --ion-button-hue-subtle-outline-semantic-disabled-background: Background of the `subtle` hue and `outline` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-disabled-color: Text color of the `subtle` hue and `outline` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-outline-semantic-disabled-border-color: Border color of the `subtle` hue and `outline` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-default-border-width: Border width of the `subtle` hue and `clear` fill combination in the default state + * @prop --ion-button-hue-subtle-clear-default-border-style: Border style of the `subtle` hue and `clear` fill combination in the default state + * @prop --ion-button-hue-subtle-clear-default-border-color: Border color of the `subtle` hue and `clear` fill combination in the default state + * @prop --ion-button-hue-subtle-clear-default-background: Background of the `subtle` hue and `clear` fill combination in the default state + * @prop --ion-button-hue-subtle-clear-default-box-shadow: Box shadow of the `subtle` hue and `clear` fill combination in the default state + * @prop --ion-button-hue-subtle-clear-ripple-effect-color: Ripple color of the `subtle` hue and `clear` fill combination + * @prop --ion-button-hue-subtle-clear-ripple-effect-opacity: Ripple opacity of the `subtle` hue and `clear` fill combination + * @prop --ion-button-hue-subtle-clear-semantic-default-border-color: Border color of the `subtle` hue and `clear` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-default-background: Background of the `subtle` hue and `clear` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-default-color: Text color of the `subtle` hue and `clear` fill combination in the default state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-ripple-effect-color: Ripple color of the `subtle` hue and `clear` fill combination when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-ripple-effect-opacity: Ripple opacity of the `subtle` hue and `clear` fill combination when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-focus-color: Text color of the `subtle` hue and `clear` fill combination in the focus state + * @prop --ion-button-hue-subtle-clear-focus-background: Background of the `subtle` hue and `clear` fill combination in the focus state + * @prop --ion-button-hue-subtle-clear-focus-box-shadow: Box shadow of the `subtle` hue and `clear` fill combination in the focus state + * @prop --ion-button-hue-subtle-clear-semantic-focus-background: Background of the `subtle` hue and `clear` fill combination in the focus state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-focus-color: Text color of the `subtle` hue and `clear` fill combination in the focus state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-hover-color: Text color of the `subtle` hue and `clear` fill combination in the hover state + * @prop --ion-button-hue-subtle-clear-hover-opacity: Opacity of the whole button of the `subtle` hue and `clear` fill combination in the hover state + * @prop --ion-button-hue-subtle-clear-hover-background: Background of the `subtle` hue and `clear` fill combination in the hover state + * @prop --ion-button-hue-subtle-clear-hover-box-shadow: Box shadow of the `subtle` hue and `clear` fill combination in the hover state + * @prop --ion-button-hue-subtle-clear-semantic-hover-background: Background of the `subtle` hue and `clear` fill combination in the hover state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-hover-color: Text color of the `subtle` hue and `clear` fill combination in the hover state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-activated-color: Text color of the `subtle` hue and `clear` fill combination in the activated state + * @prop --ion-button-hue-subtle-clear-activated-opacity: Opacity of the whole button of the `subtle` hue and `clear` fill combination in the activated state + * @prop --ion-button-hue-subtle-clear-activated-background: Background of the `subtle` hue and `clear` fill combination in the activated state + * @prop --ion-button-hue-subtle-clear-activated-box-shadow: Box shadow of the `subtle` hue and `clear` fill combination in the activated state + * @prop --ion-button-hue-subtle-clear-semantic-activated-background: Background of the `subtle` hue and `clear` fill combination in the activated state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-activated-color: Text color of the `subtle` hue and `clear` fill combination in the activated state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-disabled-background: Background of the `subtle` hue and `clear` fill combination in the disabled state + * @prop --ion-button-hue-subtle-clear-disabled-opacity: Opacity of the whole button of the `subtle` hue and `clear` fill combination in the disabled state + * @prop --ion-button-hue-subtle-clear-disabled-color: Text color of the `subtle` hue and `clear` fill combination in the disabled state + * @prop --ion-button-hue-subtle-clear-disabled-border-color: Border color of the `subtle` hue and `clear` fill combination in the disabled state + * @prop --ion-button-hue-subtle-clear-disabled-box-shadow: Box shadow of the `subtle` hue and `clear` fill combination in the disabled state + * @prop --ion-button-hue-subtle-clear-semantic-disabled-background: Background of the `subtle` hue and `clear` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-disabled-color: Text color of the `subtle` hue and `clear` fill combination in the disabled state when a semantic color is applied + * @prop --ion-button-hue-subtle-clear-semantic-disabled-border-color: Border color of the `subtle` hue and `clear` fill combination in the disabled state when a semantic color is applied */ diff --git a/core/src/components/button/button.scss b/core/src/components/button/button.scss index 37d39cd71a0..479bac52aa4 100644 --- a/core/src/components/button/button.scss +++ b/core/src/components/button/button.scss @@ -4,6 +4,7 @@ // Button // -------------------------------------------------- +$hues: bold, subtle; $fills: solid, outline, clear; $sizes: small, medium, large; $shapes: crisp, soft, round, rectangular; @@ -137,15 +138,17 @@ $spinner-sizes: xsmall, small, medium, large, xlarge; // Button: Fill Typography // -------------------------------------------------- -@each $fill in $fills { - :host(.button-hue-bold.button-fill-#{$fill}) { - font-weight: var(--ion-button-hue-bold-#{$fill}-default-font-weight); +@each $hue in $hues { + @each $fill in $fills { + :host(.button-hue-#{$hue}.button-fill-#{$fill}) { + font-weight: var(--ion-button-hue-#{$hue}-#{$fill}-default-font-weight); + } } -} -@each $fill in $fills { - :host(.button-hue-bold.button-fill-#{$fill}.button-strong) { - font-weight: var(--ion-button-hue-bold-#{$fill}-strong-font-weight); + @each $fill in $fills { + :host(.button-hue-#{$hue}.button-fill-#{$fill}.button-strong) { + font-weight: var(--ion-button-hue-#{$hue}-#{$fill}-strong-font-weight); + } } } @@ -286,156 +289,158 @@ $spinner-sizes: xsmall, small, medium, large, xlarge; // Button: Hues and Fills // -------------------------------------------------- -/** - * Each fill has a resting look and one for each state. Without a color, - * the button reads the plain tokens. With a color, it reads the `semantic` - * tokens, which use the color's base, contrast and so on. - */ -@each $fill in $fills { - $token: --ion-button-hue-bold-#{$fill}; +@each $hue in $hues { + /** + * Each fill has a resting look and one for each state. Without a color, + * the button reads the plain tokens. With a color, it reads the `semantic` + * tokens, which use the color's base, contrast and so on. + */ + @each $fill in $fills { + $token: --ion-button-hue-#{$hue}-#{$fill}; - :host(.button-hue-bold.button-fill-#{$fill}) { - color: var(#{$token}-default-color); - } + :host(.button-hue-#{$hue}.button-fill-#{$fill}) { + color: var(#{$token}-default-color); + } - :host(.button-hue-bold.button-fill-#{$fill}) .button-native { - border-width: var(#{$token}-default-border-width); - border-style: var(#{$token}-default-border-style); - border-color: var(#{$token}-default-border-color); + :host(.button-hue-#{$hue}.button-fill-#{$fill}) .button-native { + border-width: var(#{$token}-default-border-width); + border-style: var(#{$token}-default-border-style); + border-color: var(#{$token}-default-border-color); - background: var(#{$token}-default-background); + background: var(#{$token}-default-background); - box-shadow: var(#{$token}-default-box-shadow); - } + box-shadow: var(#{$token}-default-box-shadow); + } - // The ripple sits in this shadow root, out of reach of the global token CSS - :host(.button-hue-bold.button-fill-#{$fill}) ion-ripple-effect { - // A variable, because the ripple animates its opacity in a keyframe - --ion-ripple-effect-opacity: var(#{$token}-ripple-effect-opacity); + // The ripple sits in this shadow root, out of reach of the global token CSS + :host(.button-hue-#{$hue}.button-fill-#{$fill}) ion-ripple-effect { + // A variable, because the ripple animates its opacity in a keyframe + --ion-ripple-effect-opacity: var(#{$token}-ripple-effect-opacity); - color: var(#{$token}-ripple-effect-color); - } + color: var(#{$token}-ripple-effect-color); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-color) .button-native { - border-color: var(#{$token}-semantic-default-border-color); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-color) .button-native { + border-color: var(#{$token}-semantic-default-border-color); - background: var(#{$token}-semantic-default-background); - color: var(#{$token}-semantic-default-color); - } + background: var(#{$token}-semantic-default-background); + color: var(#{$token}-semantic-default-color); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-color) ion-ripple-effect { - --ion-ripple-effect-opacity: var(#{$token}-semantic-ripple-effect-opacity); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-color) ion-ripple-effect { + --ion-ripple-effect-opacity: var(#{$token}-semantic-ripple-effect-opacity); - color: var(#{$token}-semantic-ripple-effect-color); + color: var(#{$token}-semantic-ripple-effect-color); + } } -} -/** - * The states are written in this order: focus -> hover -> activated -> disabled. - * Their rules share a specificity, so a later state overrides an earlier - * one: disabled overrides all, and activated overrides hover and focus. - */ + /** + * The states are written in this order: focus -> hover -> activated -> disabled. + * Their rules share a specificity, so a later state overrides an earlier + * one: disabled overrides all, and activated overrides hover and focus. + */ -// Focused -@each $fill in $fills { - $token: --ion-button-hue-bold-#{$fill}; + // Focused + @each $fill in $fills { + $token: --ion-button-hue-#{$hue}-#{$fill}; - :host(.button-hue-bold.button-fill-#{$fill}.ion-focused) { - color: var(#{$token}-focus-color); - } + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-focused) { + color: var(#{$token}-focus-color); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-focused) .button-native { - background: var(#{$token}-focus-background); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-focused) .button-native { + background: var(#{$token}-focus-background); - box-shadow: var(#{$token}-focus-box-shadow); - } + box-shadow: var(#{$token}-focus-box-shadow); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-color.ion-focused) .button-native { - background: var(#{$token}-semantic-focus-background); - color: var(#{$token}-semantic-focus-color); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-color.ion-focused) .button-native { + background: var(#{$token}-semantic-focus-background); + color: var(#{$token}-semantic-focus-color); + } } -} -// Hover -@media (any-hover: hover) { - @each $fill in $fills { - $token: --ion-button-hue-bold-#{$fill}; + // Hover + @media (any-hover: hover) { + @each $fill in $fills { + $token: --ion-button-hue-#{$hue}-#{$fill}; - :host(.button-hue-bold.button-fill-#{$fill}:hover) { - color: var(#{$token}-hover-color); - } + :host(.button-hue-#{$hue}.button-fill-#{$fill}:hover) { + color: var(#{$token}-hover-color); + } - // Disabled keeps its own opacity - :host(.button-hue-bold.button-fill-#{$fill}:not(.button-disabled):hover) { - opacity: var(#{$token}-hover-opacity); - } + // Disabled keeps its own opacity + :host(.button-hue-#{$hue}.button-fill-#{$fill}:not(.button-disabled):hover) { + opacity: var(#{$token}-hover-opacity); + } - :host(.button-hue-bold.button-fill-#{$fill}:hover) .button-native { - background: var(#{$token}-hover-background); + :host(.button-hue-#{$hue}.button-fill-#{$fill}:hover) .button-native { + background: var(#{$token}-hover-background); - box-shadow: var(#{$token}-hover-box-shadow); - } + box-shadow: var(#{$token}-hover-box-shadow); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-color:hover) .button-native { - background: var(#{$token}-semantic-hover-background); - color: var(#{$token}-semantic-hover-color); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-color:hover) .button-native { + background: var(#{$token}-semantic-hover-background); + color: var(#{$token}-semantic-hover-color); + } } } -} -// Activated -@each $fill in $fills { - $token: --ion-button-hue-bold-#{$fill}; + // Activated + @each $fill in $fills { + $token: --ion-button-hue-#{$hue}-#{$fill}; - :host(.button-hue-bold.button-fill-#{$fill}.ion-activated) { - color: var(#{$token}-activated-color, var(#{$token}-default-color)); - } + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-activated) { + color: var(#{$token}-activated-color, var(#{$token}-default-color)); + } - // Disabled keeps its own opacity - :host(.button-hue-bold.button-fill-#{$fill}.ion-activated:not(.button-disabled)) { - opacity: var(#{$token}-activated-opacity); - } + // Disabled keeps its own opacity + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-activated:not(.button-disabled)) { + opacity: var(#{$token}-activated-opacity); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-activated) .button-native { - background: var(#{$token}-activated-background, var(#{$token}-default-background)); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-activated) .button-native { + background: var(#{$token}-activated-background, var(#{$token}-default-background)); - box-shadow: var(#{$token}-activated-box-shadow); - } + box-shadow: var(#{$token}-activated-box-shadow); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-color.ion-activated) .button-native { - background: var(#{$token}-semantic-activated-background, var(#{$token}-semantic-default-background)); - color: var(#{$token}-semantic-activated-color, var(#{$token}-semantic-default-color)); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-color.ion-activated) .button-native { + background: var(#{$token}-semantic-activated-background, var(#{$token}-semantic-default-background)); + color: var(#{$token}-semantic-activated-color, var(#{$token}-semantic-default-color)); + } } -} -// Disabled -@each $fill in $fills { - $token: --ion-button-hue-bold-#{$fill}; + // Disabled + @each $fill in $fills { + $token: --ion-button-hue-#{$hue}-#{$fill}; - :host(.button-hue-bold.button-fill-#{$fill}.button-disabled) { - // Keeps the default color when the theme doesn't set one - color: var(#{$token}-disabled-color, var(#{$token}-default-color)); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.button-disabled) { + // Keeps the default color when the theme doesn't set one + color: var(#{$token}-disabled-color, var(#{$token}-default-color)); - opacity: var(#{$token}-disabled-opacity); - } + opacity: var(#{$token}-disabled-opacity); + } - :host(.button-hue-bold.button-fill-#{$fill}.button-disabled) .button-native { - // Keeps the default border color when the theme doesn't set one - border-color: var(#{$token}-disabled-border-color, var(#{$token}-default-border-color)); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.button-disabled) .button-native { + // Keeps the default border color when the theme doesn't set one + border-color: var(#{$token}-disabled-border-color, var(#{$token}-default-border-color)); - background: var(#{$token}-disabled-background); + background: var(#{$token}-disabled-background); - box-shadow: var(#{$token}-disabled-box-shadow); - } + box-shadow: var(#{$token}-disabled-box-shadow); + } - :host(.button-hue-bold.button-fill-#{$fill}.ion-color.button-disabled) .button-native { - // Keeps the default border color when the theme doesn't set one - border-color: var(#{$token}-semantic-disabled-border-color, var(#{$token}-semantic-default-border-color)); + :host(.button-hue-#{$hue}.button-fill-#{$fill}.ion-color.button-disabled) .button-native { + // Keeps the default border color when the theme doesn't set one + border-color: var(#{$token}-semantic-disabled-border-color, var(#{$token}-semantic-default-border-color)); - background: var(#{$token}-semantic-disabled-background); + background: var(#{$token}-semantic-disabled-background); - // Keeps the default color when the theme doesn't set one - color: var(#{$token}-semantic-disabled-color, var(#{$token}-semantic-default-color)); + // Keeps the default color when the theme doesn't set one + color: var(#{$token}-semantic-disabled-color, var(#{$token}-semantic-default-color)); + } } } diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index d9c0d44625f..b23341cbca3 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -96,6 +96,14 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf */ @Prop({ reflect: true, mutable: true }) fill?: IonButtonFill | 'default'; + /** + * Set to `"bold"` for a button with vibrant, bold colors or to `"subtle"` for + * a button with muted, subtle colors. + * + * Defaults to `"bold"` if both the hue property and theme config are unset. + */ + @Prop() hue?: Hue; + /** * When using a router, it specifies the transition direction when navigating to * another page using `href`. @@ -298,10 +306,13 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf } /** - * Gets the button hue from the theme config. Defaults to `bold` if it is unset. + * Gets the button hue. Uses the `hue` property if set, otherwise + * checks the theme config. Defaults to `bold` if neither is set. */ get hueValue(): Hue { - return config.getObjectValue('IonButton.hue', 'bold') as Hue; + const hueConfig = config.getObjectValue('IonButton.hue', 'bold') as Hue; + + return this.hue ?? hueConfig; } /** @@ -503,7 +514,6 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf [`${buttonType}-size-${sizeValue}`]: true, [`${buttonType}-shape-${shapeValue}`]: true, [`${buttonType}-fill-${fillValue}`]: true, - // TODO(FW-6838): read the hue prop first in the hue PR [`${buttonType}-hue-${hueValue}`]: true, [`${buttonType}-strong`]: strong, 'in-datetime': this.inDatetime, diff --git a/core/src/components/button/test/a11y/button.e2e.ts b/core/src/components/button/test/a11y/button.e2e.ts index f2080414d9e..11caf43e745 100644 --- a/core/src/components/button/test/a11y/button.e2e.ts +++ b/core/src/components/button/test/a11y/button.e2e.ts @@ -2,6 +2,11 @@ import AxeBuilder from '@axe-core/playwright'; import { expect } from '@playwright/test'; import { configs, test } from '@utils/test/playwright'; +import { ION_BUTTON_FILLS } from '../../button.interface'; + +/** + * This behavior does not vary across directions. + */ configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title, config }) => { test.describe(title('button: a11y for ion-color()'), () => { test('should not have accessibility violations', async ({ page }) => { @@ -288,6 +293,36 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { }); }); +/** + * TODO(FW-6417): the subtle palette fails contrast for success, warning and + * light, and in the high contrast palettes. Add them back once it is fixed. + */ +const COLORS = ['primary', 'secondary', 'tertiary', 'danger', 'medium', 'dark']; + +/** + * This behavior does not vary across directions. + */ +configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title, config }) => { + test.describe(title('button: subtle hue a11y'), () => { + test('should not have accessibility violations', async ({ page }) => { + await page.setContent( + ION_BUTTON_FILLS.map((fill) => + [undefined, ...COLORS] + .map( + (color) => + `Button` + ) + .join('') + ).join(''), + config + ); + + const results = await new AxeBuilder({ page }).analyze(); + expect(results.violations).toEqual([]); + }); + }); +}); + /** * Tab order does not vary across modes or directions */ diff --git a/core/src/components/button/test/button.spec.ts b/core/src/components/button/test/button.spec.ts index a0be15994d2..c05976f5cfb 100644 --- a/core/src/components/button/test/button.spec.ts +++ b/core/src/components/button/test/button.spec.ts @@ -212,6 +212,17 @@ describe('button: hue', () => { expect(page.root!.classList.contains('button-hue-subtle')).toBe(true); }); + + it('should use the hue when it is set', async () => { + config.reset({ customTheme: { config: { components: { IonButton: { hue: 'subtle' } } } } }); + + const page = await newSpecPage({ + components: [Button], + html: `Button`, + }); + + expect(page.root!.classList.contains('button-hue-bold')).toBe(true); + }); }); describe('button: ripple effect', () => { diff --git a/core/src/components/button/test/hue/button.e2e.ts b/core/src/components/button/test/hue/button.e2e.ts new file mode 100644 index 00000000000..65dd7ab7064 --- /dev/null +++ b/core/src/components/button/test/hue/button.e2e.ts @@ -0,0 +1,137 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +import { HUES } from '../../../../themes/themes.interfaces'; +import { ION_BUTTON_FILLS } from '../../button.interface'; + +const states = ['', 'class="ion-focused"', 'class="ion-activated"', 'disabled']; + +const colors = ['', 'primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', 'light', 'medium', 'dark']; + +/** + * This behavior does not vary across directions. + */ +configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('button: hue'), () => { + HUES.forEach((hue) => { + test(`should render ${hue} buttons in each fill and state`, async ({ page }) => { + const sections = ION_BUTTON_FILLS.map( + (fill) => ` +

${fill}

+ ${states + .map( + (state) => + `
${colors + .map( + (color) => + `${ + color || 'Default' + }` + ) + .join('')}
` + ) + .join('\n')} + ` + ).join('\n'); + + await page.setContent( + ` + + +
+ ${sections} +
+ `, + config + ); + + const container = page.locator('.hue-container'); + + // Firefox and Safari leave the area below the viewport transparent + const box = await container.boundingBox(); + await page.setViewportSize({ width: Math.ceil(box!.width), height: Math.ceil(box!.height) }); + + await expect(container).toHaveScreenshot(screenshot(`button-hue-${hue}`)); + }); + }); + }); +}); + +/** + * In the ionic theme, the ripple is the only pressed feedback, so pressing + * a hovered button keeps its text color. This behavior does not vary + * across directions. + */ +configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, config }) => { + test.describe(title('button: hue states'), () => { + /** + * `(any-hover: hover)` evaluates to "none" when the browser reports a + * touch device, so turn that off to apply the hover rules. Headless + * Firefox reports no hover capability regardless, so it skips the test. + */ + test.use({ + hasTouch: false, + isMobile: false, + }); + + HUES.forEach((hue) => { + ION_BUTTON_FILLS.forEach((fill) => { + ['', 'primary'].forEach((color) => { + test(`should keep the hover text color when pressing a ${hue} ${fill} button${ + color ? ` with a color` : '' + }`, async ({ page, browserName }) => { + test.skip(browserName === 'firefox', 'Headless Firefox reports no hover capability'); + + await page.setContent( + ` + + + Button + `, + config + ); + + const button = page.locator('ion-button'); + const native = button.locator('.button-native'); + const getColor = (el: Element) => getComputedStyle(el).color; + + await button.hover(); + + const hovered = await native.evaluate(getColor); + + await button.evaluate((el) => el.classList.add('ion-activated')); + + expect(await native.evaluate(getColor)).toBe(hovered); + }); + }); + }); + }); + }); +}); diff --git a/core/src/components/button/test/hue/index.html b/core/src/components/button/test/hue/index.html new file mode 100644 index 00000000000..4f9cfd9383f --- /dev/null +++ b/core/src/components/button/test/hue/index.html @@ -0,0 +1,97 @@ + + + + + Button - Hue + + + + + + + + + + + + + + + Button - Hue + + + + + + + + + diff --git a/core/src/components/button/test/spinner/button.e2e.ts b/core/src/components/button/test/spinner/button.e2e.ts index 627a3051d78..62c971bcc05 100644 --- a/core/src/components/button/test/spinner/button.e2e.ts +++ b/core/src/components/button/test/spinner/button.e2e.ts @@ -1,6 +1,9 @@ import { expect } from '@playwright/test'; import { configs, test } from '@utils/test/playwright'; +/** + * This behavior does not vary across directions. + */ configs({ modes: ['ionic-md', 'md', 'ios'], directions: ['ltr'] }).forEach(({ config, screenshot, title }) => { test.describe(title('button: spinner'), () => { test('should not have visual regressions', async ({ page }) => { diff --git a/core/src/components/button/test/states/button.e2e.ts b/core/src/components/button/test/states/button.e2e.ts index d68676545f0..4097342b59e 100644 --- a/core/src/components/button/test/states/button.e2e.ts +++ b/core/src/components/button/test/states/button.e2e.ts @@ -5,6 +5,9 @@ import { ION_BUTTON_FILLS } from '../../button.interface'; const STATES = ['activated', 'focused']; +/** + * This behavior does not vary across directions. + */ configs({ directions: ['ltr'], modes: ['ionic-md', 'md', 'ios'] }).forEach(({ title, screenshot, config }) => { test.describe(title('button: states'), () => { STATES.forEach((state) => { diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 4756a1e76cd..c76d78a99ed 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -1959,10 +1959,12 @@ export class Datetime implements ComponentInterface { ['has-clear-button']: this.showClearButton, }} > + {/* The datetime styles its buttons through the `bold` hue tokens, so a hue from the config can't restyle them */} {showDefaultButtons && ( this.cancel(true)} disabled={isButtonDisabled} @@ -1974,6 +1976,7 @@ export class Datetime implements ComponentInterface { {showClearButton && ( clearButtonClick()} disabled={isButtonDisabled} @@ -1984,6 +1987,7 @@ export class Datetime implements ComponentInterface { {showDefaultButtons && ( this.confirm(true)} @@ -2595,8 +2599,10 @@ export class Datetime implements ComponentInterface { + {/* The datetime styles its buttons through the `bold` hue tokens, so a hue from the config can't restyle them */}
this.prevMonth()} @@ -2612,6 +2618,7 @@ export class Datetime implements ComponentInterface { > this.nextMonth()} diff --git a/core/src/components/datetime/test/basic/datetime.spec.ts b/core/src/components/datetime/test/basic/datetime.spec.ts new file mode 100644 index 00000000000..db9b458f16c --- /dev/null +++ b/core/src/components/datetime/test/basic/datetime.spec.ts @@ -0,0 +1,41 @@ +import { newSpecPage } from '@stencil/core/testing'; + +import { config } from '../../../../global/config'; +import { Button } from '../../../button/button'; +import { Datetime } from '../../datetime'; + +describe('datetime: buttons', () => { + beforeEach(() => { + const mockIntersectionObserver = jest.fn(); + mockIntersectionObserver.mockReturnValue({ + observe: () => null, + unobserve: () => null, + disconnect: () => null, + }); + global.IntersectionObserver = mockIntersectionObserver; + }); + + afterEach(() => { + config.reset({}); + }); + + it('should keep the bold hue when the theme config sets another hue', async () => { + config.reset({ customTheme: { config: { components: { IonButton: { hue: 'subtle' } } } } }); + + const page = await newSpecPage({ + components: [Datetime, Button], + html: ``, + }); + + const datetime = page.body.querySelector('ion-datetime')!; + const buttons = Array.from(datetime.shadowRoot!.querySelectorAll('ion-button')); + + expect(buttons.map((button) => button.classList.contains('button-hue-bold'))).toEqual([ + true, + true, + true, + true, + true, + ]); + }); +}); diff --git a/core/src/components/input-password-toggle/input-password-toggle.tsx b/core/src/components/input-password-toggle/input-password-toggle.tsx index 075f7222097..10d04f3273a 100644 --- a/core/src/components/input-password-toggle/input-password-toggle.tsx +++ b/core/src/components/input-password-toggle/input-password-toggle.tsx @@ -150,8 +150,10 @@ export class InputPasswordToggle implements ComponentInterface { [mode]: true, })} > + {/* Themes color the toggle through its `bold` hue tokens, so a hue from the config can't restyle it */} { + afterEach(() => { + config.reset({}); + }); + it('should toggle input type when clicked', async () => { const page = await newSpecPage({ components: [Input, InputPasswordToggle, Button], @@ -112,4 +117,22 @@ describe('input password toggle', () => { // color is an actual prop so we can access the element property expect(button.color).toBe('danger'); }); + + it('should keep the bold hue when the theme config sets another hue', async () => { + config.reset({ customTheme: { config: { components: { IonButton: { hue: 'subtle' } } } } }); + + const page = await newSpecPage({ + components: [Input, InputPasswordToggle, Button], + template: () => ( + + + + ), + }); + + const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!; + const button = inputPasswordToggle.shadowRoot!.querySelector('ion-button')!; + + expect(button.classList.contains('button-hue-bold')).toBe(true); + }); }); diff --git a/core/src/themes/ionic/default.tokens.ts b/core/src/themes/ionic/default.tokens.ts index 68840c4c814..2719dd1b1a3 100644 --- a/core/src/themes/ionic/default.tokens.ts +++ b/core/src/themes/ionic/default.tokens.ts @@ -538,6 +538,214 @@ export const defaultTheme: DefaultTheme = { }, }, }, + + subtle: { + solid: { + strong: { + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + default: { + background: ionColor('primary', 'base', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + focus: { + background: ionColor('primary', 'base', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + }, + + hover: { + background: ionColor('primary', 'shade', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + }, + + // A white wash over the button + disabled: { + background: ionColor('primary', 'base', { subtle: true }), + + opacity: '0.4', + }, + + rippleEffect: { + color: ionColor('primary', 'shade', { subtle: true }), + opacity: '1', + }, + + // Any of the semantic colors like primary, secondary, etc. + semantic: { + default: { + background: currentColor('base', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + focus: { + background: currentColor('base', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + hover: { + background: currentColor('shade', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + disabled: { + background: currentColor('base', { subtle: true }), + }, + + rippleEffect: { + color: currentColor('shade', { subtle: true }), + opacity: '1', + }, + }, + }, + + outline: { + strong: { + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + default: { + background: baseColors.backgroundColor, + color: ionColor('primary', 'foreground', { subtle: true }), + + border: { + color: ionColor('primary', 'shade', { subtle: true }), + style: 'solid', + width: 'var(--ion-border-width-xxxs)', + }, + + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + focus: { + background: baseColors.backgroundColor, + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + hover: { + background: ionColor('primary', 'shade', { subtle: true }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + // A white wash over the button + disabled: { + background: baseColors.backgroundColor, + + opacity: '0.4', + }, + + rippleEffect: { + color: ionColor('primary', 'shade', { subtle: true }), + opacity: '1', + }, + + semantic: { + default: { + background: baseColors.backgroundColor, + color: currentColor('foreground', { subtle: true }), + + border: { + color: currentColor('shade', { subtle: true }), + }, + }, + + focus: { + background: baseColors.backgroundColor, + color: currentColor('foreground', { subtle: true }), + }, + + hover: { + background: currentColor('shade', { subtle: true }), + color: currentColor('foreground', { subtle: true }), + }, + + disabled: { + background: baseColors.backgroundColor, + }, + + rippleEffect: { + color: currentColor('shade', { subtle: true }), + opacity: '1', + }, + }, + }, + + clear: { + strong: { + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + default: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + focus: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + hover: { + background: ionColor('primary', 'shade', { subtle: true }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + // A white wash over the button + disabled: { + background: 'transparent', + + opacity: '0.4', + }, + + rippleEffect: { + color: ionColor('primary', 'shade', { subtle: true }), + opacity: '1', + }, + + semantic: { + default: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + }, + + focus: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + }, + + hover: { + background: currentColor('shade', { subtle: true }), + color: currentColor('foreground', { subtle: true }), + }, + + disabled: { + background: 'transparent', + }, + + rippleEffect: { + color: currentColor('shade', { subtle: true }), + opacity: '1', + }, + }, + }, + }, }, // Sizes diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts index 54f0f56032f..de4a84255c1 100644 --- a/core/src/themes/ios/default.tokens.ts +++ b/core/src/themes/ios/default.tokens.ts @@ -417,7 +417,7 @@ export const defaultTheme: DefaultTheme = { default: { background: 'transparent', - color: ionColor('primary', 'base'), + color: ionColor('primary', 'foreground'), border: { color: ionColor('primary', 'base'), @@ -432,13 +432,13 @@ export const defaultTheme: DefaultTheme = { focus: { background: ionColor('primary', 'base', { alpha: 0.1 }), - color: ionColor('primary', 'base'), + color: ionColor('primary', 'foreground'), }, // Fades the whole button instead of changing its color hover: { background: 'transparent', - color: ionColor('primary', 'base'), + color: ionColor('primary', 'foreground'), opacity: '0.6', }, @@ -460,7 +460,7 @@ export const defaultTheme: DefaultTheme = { semantic: { default: { background: 'transparent', - color: currentColor('base'), + color: currentColor('foreground'), border: { color: currentColor('base'), @@ -469,12 +469,12 @@ export const defaultTheme: DefaultTheme = { focus: { background: currentColor('base', { alpha: 0.1 }), - color: currentColor('base'), + color: currentColor('foreground'), }, hover: { background: 'transparent', - color: currentColor('base'), + color: currentColor('foreground'), }, activated: { @@ -542,12 +542,12 @@ export const defaultTheme: DefaultTheme = { focus: { background: currentColor('base', { alpha: 0.1 }), - color: currentColor('base'), + color: currentColor('foreground'), }, hover: { background: 'transparent', - color: currentColor('base'), + color: currentColor('foreground'), }, activated: { @@ -562,6 +562,227 @@ export const defaultTheme: DefaultTheme = { }, }, }, + + subtle: { + solid: { + strong: { + font: { + weight: 'var(--ion-font-weight-semi-bold)', + }, + }, + + default: { + background: ionColor('primary', 'base', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + + font: { + weight: 'var(--ion-font-weight-normal)', + }, + }, + + focus: { + background: ionColor('primary', 'shade', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + }, + + hover: { + background: ionColor('primary', 'tint', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + }, + + activated: { + background: ionColor('primary', 'shade', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + }, + + disabled: { + background: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.08), + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + + // Any of the semantic colors like primary, secondary, etc. + semantic: { + default: { + background: currentColor('base', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + focus: { + background: currentColor('shade', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + hover: { + background: currentColor('tint', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + activated: { + background: currentColor('shade', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + disabled: { + background: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.08), + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + }, + }, + + outline: { + strong: { + font: { + weight: 'var(--ion-font-weight-semi-bold)', + }, + }, + + default: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + + border: { + color: ionColor('primary', 'shade', { subtle: true }), + style: 'solid', + width: 'var(--ion-border-width-25)', + }, + + font: { + weight: 'var(--ion-font-weight-normal)', + }, + }, + + focus: { + background: ionColor('primary', 'base', { alpha: 0.1 }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + // Fades the whole button instead of changing its color + hover: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + opacity: '0.6', + }, + + activated: { + background: ionColor('primary', 'base', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + }, + + disabled: { + background: 'transparent', + + border: { + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + + semantic: { + default: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + + border: { + color: currentColor('shade', { subtle: true }), + }, + }, + + focus: { + background: currentColor('base', { alpha: 0.1 }), + color: currentColor('foreground', { subtle: true }), + }, + + hover: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + }, + + activated: { + background: currentColor('base', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + disabled: { + background: 'transparent', + + border: { + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + }, + }, + + clear: { + strong: { + font: { + weight: 'var(--ion-font-weight-semi-bold)', + }, + }, + + default: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + + // The clear fill uses a regular weight font + font: { + weight: 'normal', + }, + }, + + focus: { + background: ionColor('primary', 'base', { alpha: 0.1 }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + // Fades the whole button instead of changing its color + hover: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + opacity: '0.6', + }, + + activated: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + opacity: '0.4', + }, + + disabled: { + background: 'transparent', + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + + semantic: { + default: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + }, + + focus: { + background: currentColor('base', { alpha: 0.1 }), + color: currentColor('foreground', { subtle: true }), + }, + + hover: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + }, + + activated: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + }, + + disabled: { + background: 'transparent', + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.25), + }, + }, + }, + }, }, // Sizes diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts index a4cb1a03297..c28e5b5e63c 100644 --- a/core/src/themes/md/default.tokens.ts +++ b/core/src/themes/md/default.tokens.ts @@ -446,7 +446,7 @@ export const defaultTheme: DefaultTheme = { default: { background: 'transparent', - color: ionColor('primary', 'base'), + color: ionColor('primary', 'foreground'), border: { color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), @@ -461,12 +461,12 @@ export const defaultTheme: DefaultTheme = { focus: { background: ionColor('primary', 'base', { alpha: 0.12 }), - color: ionColor('primary', 'base'), + color: ionColor('primary', 'foreground'), }, hover: { background: ionColor('primary', 'base', { alpha: 0.04 }), - color: ionColor('primary', 'base'), + color: ionColor('primary', 'foreground'), }, disabled: { @@ -486,7 +486,7 @@ export const defaultTheme: DefaultTheme = { semantic: { default: { background: 'transparent', - color: currentColor('base'), + color: currentColor('foreground'), border: { color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), @@ -495,12 +495,12 @@ export const defaultTheme: DefaultTheme = { focus: { background: currentColor('base', { alpha: 0.12 }), - color: currentColor('base'), + color: currentColor('foreground'), }, hover: { background: currentColor('base', { alpha: 0.04 }), - color: currentColor('base'), + color: currentColor('foreground'), }, disabled: { @@ -583,6 +583,257 @@ export const defaultTheme: DefaultTheme = { }, }, }, + + subtle: { + solid: { + strong: { + font: { + weight: 'var(--ion-font-weight-bold)', + }, + }, + + default: { + background: ionColor('primary', 'base', { subtle: true }), + color: ionColor('primary', 'contrast', { subtle: true }), + box: { + shadow: + '0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12)', + }, + + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + focus: { + background: overlay( + ionColor('primary', 'base', { subtle: true }), + ionColor('primary', 'contrast', { alpha: 0.24, subtle: true }) + ), + color: ionColor('primary', 'contrast', { subtle: true }), + + box: { + shadow: + '0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12)', + }, + }, + + hover: { + background: overlay( + ionColor('primary', 'base', { subtle: true }), + ionColor('primary', 'contrast', { alpha: 0.08, subtle: true }) + ), + + color: ionColor('primary', 'contrast', { subtle: true }), + + box: { + shadow: + '0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12)', + }, + }, + + // The ripple shows the press, and the button lifts + activated: { + box: { + shadow: + '0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12)', + }, + }, + + disabled: { + background: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), + + box: { + shadow: 'none', + }, + + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.38), + }, + + rippleEffect: { + opacity: '0.24', + }, + + // Any of the semantic colors like primary, secondary, etc. + semantic: { + default: { + background: currentColor('base', { subtle: true }), + color: currentColor('contrast', { subtle: true }), + }, + + focus: { + background: overlay( + currentColor('base', { subtle: true }), + currentColor('contrast', { alpha: 0.24, subtle: true }) + ), + color: currentColor('contrast', { subtle: true }), + }, + + hover: { + background: overlay( + currentColor('base', { subtle: true }), + currentColor('contrast', { alpha: 0.08, subtle: true }) + ), + color: currentColor('contrast', { subtle: true }), + }, + + disabled: { + background: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.38), + }, + + rippleEffect: { + opacity: '0.24', + }, + }, + }, + + outline: { + strong: { + font: { + weight: 'var(--ion-font-weight-bold)', + }, + }, + + default: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + + border: { + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), + style: 'solid', + width: 'var(--ion-border-width-xxxs)', + }, + + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + focus: { + background: ionColor('primary', 'base', { alpha: 0.12 }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + hover: { + background: ionColor('primary', 'base', { alpha: 0.04 }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + disabled: { + background: 'transparent', + + border: { + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), + }, + + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.38), + }, + + rippleEffect: { + opacity: '0.12', + }, + + semantic: { + default: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + + border: { + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), + }, + }, + + focus: { + background: currentColor('base', { alpha: 0.12 }), + color: currentColor('foreground', { subtle: true }), + }, + + hover: { + background: currentColor('base', { alpha: 0.04 }), + color: currentColor('foreground', { subtle: true }), + }, + + disabled: { + background: 'transparent', + + border: { + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.12), + }, + + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.38), + }, + + rippleEffect: { + opacity: '0.12', + }, + }, + }, + + clear: { + strong: { + font: { + weight: 'var(--ion-font-weight-bold)', + }, + }, + + default: { + background: 'transparent', + color: ionColor('primary', 'foreground', { subtle: true }), + + font: { + weight: 'var(--ion-font-weight-medium)', + }, + }, + + focus: { + background: ionColor('primary', 'base', { alpha: 0.12 }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + hover: { + background: ionColor('primary', 'base', { alpha: 0.04 }), + color: ionColor('primary', 'foreground', { subtle: true }), + }, + + disabled: { + background: 'transparent', + + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.38), + }, + + rippleEffect: { + opacity: '0.12', + }, + + semantic: { + default: { + background: 'transparent', + color: currentColor('foreground', { subtle: true }), + }, + + focus: { + background: currentColor('base', { alpha: 0.12 }), + color: currentColor('foreground', { subtle: true }), + }, + + hover: { + background: currentColor('base', { alpha: 0.04 }), + color: currentColor('foreground', { subtle: true }), + }, + + disabled: { + background: 'transparent', + + color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.38), + }, + + rippleEffect: { + opacity: '0.12', + }, + }, + }, + }, }, // Sizes diff --git a/packages/angular/src/lazy/directives/proxies.ts b/packages/angular/src/lazy/directives/proxies.ts index 6b458d73f83..8a45edb8682 100644 --- a/packages/angular/src/lazy/directives/proxies.ts +++ b/packages/angular/src/lazy/directives/proxies.ts @@ -385,14 +385,14 @@ export declare interface IonBreadcrumbs extends Components.IonBreadcrumbs { @ProxyCmp({ - inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'type'] + inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'type'] }) @Component({ selector: 'ion-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['buttonType', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', { name: 'strong', transform: nullableBooleanAttribute }, 'target', 'type'], + inputs: ['buttonType', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', { name: 'strong', transform: nullableBooleanAttribute }, 'target', 'type'], outputs: ['ionFocus', 'ionBlur'], standalone: false }) diff --git a/packages/angular/src/standalone/directives/ion-button.ts b/packages/angular/src/standalone/directives/ion-button.ts index 3e2d0a4c863..02547f53074 100644 --- a/packages/angular/src/standalone/directives/ion-button.ts +++ b/packages/angular/src/standalone/directives/ion-button.ts @@ -11,14 +11,14 @@ import { defineCustomElement as defineIonButton } from '@ionic/core/components/i @ProxyCmp({ defineCustomElementFn: defineIonButton, - inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'type'] + inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'type'] }) @Component({ selector: 'ion-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['buttonType', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', { name: 'strong', transform: nullableBooleanAttribute }, 'target', 'type'], + inputs: ['buttonType', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', { name: 'strong', transform: nullableBooleanAttribute }, 'target', 'type'], outputs: ['ionFocus', 'ionBlur'], }) export class IonButton { diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index feeeadddac1..21448c2899c 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -182,6 +182,7 @@ export const IonButton: StencilVueComponent = /*@__PURE__*/ defin 'disabled', 'expand', 'fill', + 'hue', 'routerDirection', 'routerAnimation', 'download',