feat(theming): add new color variant for the ionic theme

This commit is contained in:
Brandy Carney
2024-11-12 11:32:22 -05:00
parent a1f3fcc23b
commit 703f765482
9 changed files with 149 additions and 89 deletions

View File

@ -553,6 +553,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. * 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; "href": string | undefined;
/**
* Set to `"bold"` for a button with vibrant, bold colors or to `"subtle"` for a button with muted, subtle colors.
*/
"hue"?: 'bold' | 'subtle';
/** /**
* The mode determines the platform behaviors of the component. * The mode determines the platform behaviors of the component.
*/ */
@ -5916,6 +5920,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. * 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; "href"?: string | undefined;
/**
* Set to `"bold"` for a button with vibrant, bold colors or to `"subtle"` for a button with muted, subtle colors.
*/
"hue"?: 'bold' | 'subtle';
/** /**
* The mode determines the platform behaviors of the component. * The mode determines the platform behaviors of the component.
*/ */

View File

@ -52,6 +52,11 @@
--ripple-color: var(--background-activated); --ripple-color: var(--background-activated);
} }
:host(.button-solid.button-subtle) {
--background: #{globals.ion-color(primary, subtle)};
--color: #{globals.ion-color(primary, base)};
}
// Outline Button // Outline Button
// -------------------------------------------------- // --------------------------------------------------
@ -81,6 +86,11 @@
// Ripple Effect // Ripple Effect
// ------------------------------------------------------------------------------- // -------------------------------------------------------------------------------
:host(.button-solid.button-subtle.ion-color) .button-native {
background: globals.current-color(subtle);
color: globals.current-color(base);
}
:host(.button-solid.ion-color) ion-ripple-effect { :host(.button-solid.ion-color) ion-ripple-effect {
color: globals.current-color(shade); color: globals.current-color(shade);
} }

View File

@ -75,6 +75,12 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
*/ */
@Prop({ reflect: true }) expand?: 'full' | 'block'; @Prop({ reflect: true }) expand?: 'full' | 'block';
/**
* Set to `"bold"` for a button with vibrant, bold colors or to `"subtle"` for
* a button with muted, subtle colors.
*/
@Prop() hue?: 'bold' | 'subtle' = 'bold';
/** /**
* Set to `"clear"` for a transparent button that resembles a flat button, to `"outline"` * Set to `"clear"` for a transparent button that resembles a flat button, to `"outline"`
* for a transparent button with a border, or to `"solid"` for a button with a filled background. * for a transparent button with a border, or to `"solid"` for a button with a filled background.
@ -349,8 +355,20 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
}; };
render() { render() {
const { buttonType, type, disabled, rel, target, href, color, expand, hasIconOnly, strong, inheritedAttributes } = const {
this; buttonType,
type,
disabled,
rel,
target,
href,
color,
expand,
hue,
hasIconOnly,
strong,
inheritedAttributes,
} = this;
const theme = getIonTheme(this); const theme = getIonTheme(this);
const mode = getIonMode(this); const mode = getIonMode(this);
@ -394,6 +412,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
[theme]: true, [theme]: true,
[buttonType]: true, [buttonType]: true,
[`${buttonType}-${expand}`]: expand !== undefined, [`${buttonType}-${expand}`]: expand !== undefined,
[`${buttonType}-${hue}`]: hue !== undefined,
[`${buttonType}-${size}`]: size !== undefined, [`${buttonType}-${size}`]: size !== undefined,
[`${buttonType}-${shape}`]: true, [`${buttonType}-${shape}`]: true,
[`${buttonType}-${fill}`]: true, [`${buttonType}-${fill}`]: true,

View File

@ -12,6 +12,12 @@
<script src="../../../../../scripts/testing/scripts.js"></script> <script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script> <script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script> <script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
ion-button {
margin: 8px 4px;
}
</style>
</head> </head>
<body> <body>
@ -23,6 +29,8 @@
</ion-header> </ion-header>
<ion-content class="ion-padding ion-text-center" id="content" no-bounce> <ion-content class="ion-padding ion-text-center" id="content" no-bounce>
<h2>Bold (default) Buttons</h2>
<p> <p>
<ion-button id="default">Default</ion-button> <ion-button id="default">Default</ion-button>
<ion-button class="ion-focused">Default.focused</ion-button> <ion-button class="ion-focused">Default.focused</ion-button>
@ -83,48 +91,68 @@
<ion-button class="ion-activated" color="dark">Dark.activated</ion-button> <ion-button class="ion-activated" color="dark">Dark.activated</ion-button>
</p> </p>
<h2>Subtle Buttons</h2>
<p> <p>
<ion-button style="--opacity: 0.2">Opacity: 0.2</ion-button> <ion-button hue="subtle" id="default">Default</ion-button>
<ion-button hue="subtle" class="ion-focused">Default.focused</ion-button>
<ion-button hue="subtle" class="ion-activated">Default.activated</ion-button>
</p> </p>
<p> <p>
<ion-button expand="block" id="disabledButton" disabled>Button Disabled</ion-button> <ion-button hue="subtle" color="primary">Primary</ion-button>
<ion-button expand="block" color="secondary" disabled>Secondary Disabled</ion-button> <ion-button hue="subtle" color="primary" class="ion-focused">Primary.focused</ion-button>
<ion-button expand="block" color="tertiary" style="--opacity: 1" disabled>Disabled opacity: 1</ion-button> <ion-button hue="subtle" color="primary" class="ion-activated">Primary.activated</ion-button>
</p> </p>
<p> <p>
<ion-button expand="block" onclick="toggleDisabled()">Toggle Disabled</ion-button> <ion-button hue="subtle" color="secondary">Secondary</ion-button>
<ion-button hue="subtle" class="ion-focused" color="secondary">Secondary.focused</ion-button>
<ion-button hue="subtle" class="ion-activated" color="secondary">Secondary.activated</ion-button>
</p> </p>
<p> <p>
<ion-button id="dynamicColor1" onclick="changeColor(event)">Change Color</ion-button> <ion-button hue="subtle" color="tertiary">Tertiary</ion-button>
<ion-button id="dynamicColor2" onclick="changeColor(event)" fill="outline">Change Color</ion-button> <ion-button hue="subtle" class="ion-focused" color="tertiary">Tertiary.focused</ion-button>
<ion-button hue="subtle" class="ion-activated" color="tertiary">Tertiary.activated</ion-button>
</p>
<p>
<ion-button hue="subtle" color="success">Success</ion-button>
<ion-button hue="subtle" color="success" class="ion-focused">Success.focused</ion-button>
<ion-button hue="subtle" color="success" class="ion-activated">Success.activated</ion-button>
</p>
<p>
<ion-button hue="subtle" color="warning">Warning</ion-button>
<ion-button hue="subtle" color="warning" class="ion-focused">Warning.focused</ion-button>
<ion-button hue="subtle" color="warning" class="ion-activated">Warning.activated</ion-button>
</p>
<p>
<ion-button hue="subtle" color="danger">Danger</ion-button>
<ion-button hue="subtle" color="danger" class="ion-focused">Danger.focused</ion-button>
<ion-button hue="subtle" color="danger" class="ion-activated">Danger.activated</ion-button>
</p>
<p>
<ion-button hue="subtle" color="light">Light</ion-button>
<ion-button hue="subtle" color="light" class="ion-focused">Light.focused</ion-button>
<ion-button hue="subtle" color="light" class="ion-activated">Light.activated</ion-button>
</p>
<p>
<ion-button hue="subtle" color="medium">Medium</ion-button>
<ion-button hue="subtle" color="medium" class="ion-focused">Medium.focused</ion-button>
<ion-button hue="subtle" color="medium" class="ion-activated">Medium.activated</ion-button>
</p>
<p>
<ion-button hue="subtle" color="dark">Dark</ion-button>
<ion-button hue="subtle" color="dark" class="ion-focused">Dark.focused</ion-button>
<ion-button hue="subtle" color="dark" class="ion-activated">Dark.activated</ion-button>
</p> </p>
</ion-content> </ion-content>
</ion-app> </ion-app>
<script>
testingColors = ['primary', 'secondary', 'danger', 'dark'];
testingColorIndex = {
dynamicColor1: 0,
dynamicColor2: 0,
};
function changeColor(ev) {
el = ev.currentTarget;
testingColorIndex[el.id] =
testingColorIndex[el.id] >= testingColors.length - 1 ? 0 : testingColorIndex[el.id] + 1;
newColor = testingColors[testingColorIndex[el.id]];
el.color = newColor;
}
function toggleDisabled() {
var buttonEl = document.getElementById('disabledButton');
buttonEl.disabled = !buttonEl.disabled;
}
</script>
</body> </body>
</html> </html>

View File

@ -93,6 +93,7 @@
$contrast: map-get($value, contrast); $contrast: map-get($value, contrast);
$shade: map-get($value, shade); $shade: map-get($value, shade);
$tint: map-get($value, tint); $tint: map-get($value, tint);
$subtle: map-get($value, subtle);
--ion-color-base: var(--ion-color-#{$color-name}, #{$base}) !important; --ion-color-base: var(--ion-color-#{$color-name}, #{$base}) !important;
--ion-color-base-rgb: var(--ion-color-#{$color-name}-rgb, #{color-to-rgb-list($base)}) !important; --ion-color-base-rgb: var(--ion-color-#{$color-name}-rgb, #{color-to-rgb-list($base)}) !important;
@ -100,6 +101,7 @@
--ion-color-contrast-rgb: var(--ion-color-#{$color-name}-contrast-rgb, #{color-to-rgb-list($contrast)}) !important; --ion-color-contrast-rgb: var(--ion-color-#{$color-name}-contrast-rgb, #{color-to-rgb-list($contrast)}) !important;
--ion-color-shade: var(--ion-color-#{$color-name}-shade, #{$shade}) !important; --ion-color-shade: var(--ion-color-#{$color-name}-shade, #{$shade}) !important;
--ion-color-tint: var(--ion-color-#{$color-name}-tint, #{$tint}) !important; --ion-color-tint: var(--ion-color-#{$color-name}-tint, #{$tint}) !important;
--ion-color-subtle: var(--ion-color-#{$color-name}-subtle, #{$subtle}) !important;
} }
// Generates the CSS variables for each color // Generates the CSS variables for each color

View File

@ -17,77 +17,69 @@
// TODO(ROU-10778, ROU-10875): Sync the color names to the design system of // TODO(ROU-10778, ROU-10875): Sync the color names to the design system of
// ios and md. This will allow us to have a single color map. // ios and md. This will allow us to have a single color map.
$primary: #105cef;
$secondary: initial;
$tertiary: initial;
$success: #1fbd3b;
$warning: #e18300;
$danger: #bf2222;
$light: #f2f4fd;
$medium: initial;
$neutral: #626262;
$dark: initial;
$ionic-colors: ( $ionic-colors: (
primary: ( primary: (
base: $primary, base: globals.$ion-semantics-primary-base,
contrast: #fff, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($primary), shade: globals.$ion-semantics-primary-800,
tint: color.get-color-tint($primary), tint: globals.$ion-semantics-primary-500,
subtle: globals.$ion-semantics-primary-100,
), ),
secondary: ( secondary: (
base: $secondary, base: globals.$ion-semantics-info-base,
contrast: $secondary, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($secondary), shade: globals.$ion-semantics-info-800,
tint: color.get-color-tint($secondary), tint: globals.$ion-semantics-info-500,
subtle: globals.$ion-semantics-info-100,
), ),
tertiary: ( tertiary: (
base: $tertiary, base: globals.$ion-primitives-violet-700,
contrast: $tertiary, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($tertiary), shade: globals.$ion-primitives-violet-800,
tint: color.get-color-tint($tertiary), tint: globals.$ion-primitives-violet-500,
subtle: globals.$ion-primitives-violet-100,
), ),
success: ( success: (
base: $success, base: globals.$ion-semantics-success-base,
contrast: #000, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($success), shade: globals.$ion-semantics-success-800,
tint: color.get-color-tint($success), tint: globals.$ion-semantics-success-500,
subtle: globals.$ion-semantics-success-100,
), ),
warning: ( warning: (
base: $warning, base: globals.$ion-semantics-warning-base,
contrast: #000, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($warning), shade: globals.$ion-semantics-warning-800,
tint: color.get-color-tint($warning), tint: globals.$ion-semantics-warning-500,
subtle: globals.$ion-semantics-warning-100,
), ),
danger: ( danger: (
base: $danger, base: globals.$ion-semantics-danger-base,
contrast: #fff, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($danger), shade: globals.$ion-semantics-danger-800,
tint: color.get-color-tint($danger), tint: globals.$ion-semantics-danger-500,
subtle: globals.$ion-semantics-danger-100,
), ),
light: ( light: (
base: $light, base: globals.$ion-primitives-neutral-200,
contrast: #000, contrast: globals.$ion-primitives-base-black,
shade: color.get-color-shade($light), shade: globals.$ion-primitives-neutral-300,
tint: color.get-color-tint($light), tint: globals.$ion-primitives-neutral-100,
subtle: globals.$ion-primitives-neutral-100,
), ),
medium: ( medium: (
base: $medium, base: globals.$ion-primitives-neutral-700,
contrast: $medium, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($medium), shade: globals.$ion-primitives-neutral-800,
tint: color.get-color-tint($medium), tint: globals.$ion-primitives-neutral-500,
), subtle: globals.$ion-primitives-neutral-100,
neutral: (
base: $neutral,
contrast: #fff,
shade: color.get-color-shade($neutral),
tint: color.get-color-tint($neutral),
), ),
dark: ( dark: (
base: $dark, base: globals.$ion-primitives-neutral-1100,
contrast: $dark, contrast: globals.$ion-primitives-base-white,
shade: color.get-color-shade($dark), shade: globals.$ion-primitives-neutral-1200,
tint: color.get-color-tint($dark), tint: globals.$ion-primitives-neutral-900,
subtle: globals.$ion-primitives-neutral-100,
), ),
); );

View File

@ -345,14 +345,14 @@ export declare interface IonBreadcrumbs extends Components.IonBreadcrumbs {
@ProxyCmp({ @ProxyCmp({
inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'] inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type']
}) })
@Component({ @Component({
selector: 'ion-button', selector: 'ion-button',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>', template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'], inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'],
}) })
export class IonButton { export class IonButton {
protected el: HTMLElement; protected el: HTMLElement;

View File

@ -439,14 +439,14 @@ export declare interface IonBreadcrumbs extends Components.IonBreadcrumbs {
@ProxyCmp({ @ProxyCmp({
defineCustomElementFn: defineIonButton, defineCustomElementFn: defineIonButton,
inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'] inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type']
}) })
@Component({ @Component({
selector: 'ion-button', selector: 'ion-button',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>', template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'], inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'hue', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'],
standalone: true standalone: true
}) })
export class IonButton { export class IonButton {

View File

@ -162,6 +162,7 @@ export const IonButton = /*@__PURE__*/ defineContainer<JSX.IonButton>('ion-butto
'buttonType', 'buttonType',
'disabled', 'disabled',
'expand', 'expand',
'hue',
'fill', 'fill',
'routerDirection', 'routerDirection',
'routerAnimation', 'routerAnimation',