feat(core): component level theming

This commit is contained in:
Maria Hutt
2025-04-29 14:18:57 -07:00
parent b1ecd4748b
commit 9a431da0ee
6 changed files with 115 additions and 56 deletions

View File

@@ -1,21 +1,61 @@
// @import "../../themes/native/native.globals";
@use "../../themes/functions.color" as color;
@use "../../themes/mixins" as mixins;
:host {
/**
* @prop --background: Background of the chip
* @prop --color: Color of the chip
* @prop --border-radius: Border radius of the chip
* @prop --focus-ring-color: Color of the focus ring
* @prop --focus-ring-width: Width of the focus ring
*/
--focus-ring-color: var(--ion-color-blue-50);
--focus-ring-width: var(--ion-spacing-xs);
* @prop --ion-chip-focus-ring-color: Color of the focus ring
* @prop --ion-chip-focus-ring-width: Width of the focus ring
* @prop --ion-chip-padding-horizontal: Padding top and bottom of the chip
* @prop --ion-chip-padding-vertical: Padding start and end of the chip
* @prop --ion-chip-font-weight: Font weight of the chip
* @prop --ion-chip-line-height: Line height of the chip
* @prop --ion-chip-gap: Gap between the chip and the text
* @prop --ion-chip-border-width: Border width of the chip
*
* @prop --ion-chip-shape-soft-border-radius: Border radius of the chip for the soft shape
* @prop --ion-chip-shape-round-border-radius: Border radius of the chip for the round shape
* @prop --ion-chip-shape-rectangular-border-radius: Border radius of the chip for the rectangular shape
*
* @prop --ion-chip-size-small-min-height: Minimum height of the chip for the small size
* @prop --ion-chip-size-large-min-height: Minimum height of the chip for the large size
* @prop --ion-chip-size-small-font-size: Font size of the chip for the small size
* @prop --ion-chip-size-large-font-size: Font size of the chip for the large size
*
* @prop --ion-chip-hue-subtle-bg: Background of the chip for the subtle hue
* @prop --ion-chip-hue-subtle-color: Color of the chip for the subtle hue
* @prop --ion-chip-hue-subtle-border-color: Border color of the chip for the subtle hue
* @prop --ion-chip-hue-bold-bg: Background of the chip for the bold hue
* @prop --ion-chip-hue-bold-color: Color of the chip for the bold hue
* @prop --ion-chip-hue-bold-border-color: Border color of the chip for the bold hue
*/
--ion-chip-focus-ring-color: var(--ion-color-blue-50);
--ion-chip-focus-ring-width: var(--ion-spacing-xs);
--ion-chip-padding-horizontal: var(--ion-spacing-xs);
--ion-chip-padding-vertical: var(--ion-spacing-sm);
--ion-chip-font-weight: var(--ion-font-weights-normal);
--ion-chip-line-height: var(--ion-line-heights-md);
--ion-chip-gap: var(--ion-spacing-xs);
--ion-chip-border-width: var(--ion-spacing-xxs);
--ion-chip-shape-soft-border-radius: var(--ion-radii-lg);
--ion-chip-shape-round-border-radius: var(--ion-radii-xxl);
--ion-chip-shape-rectangular-border-radius: var(--ion-radii-none);
--ion-chip-size-small-min-height: var(--ion-spacing-lg);
--ion-chip-size-large-min-height: var(--ion-spacing-xxl);
--ion-chip-size-small-font-size: var(--ion-font-sizes-sm-rem);
--ion-chip-size-large-font-size: var(--ion-font-sizes-lg-rem);
--ion-chip-hue-subtle-bg: var(--ion-color-gray-100);
--ion-chip-hue-subtle-color: var(--ion-color-gray-800);
--ion-chip-hue-subtle-border-color: var(--ion-color-gray-300);
--ion-chip-hue-bold-bg: var(--ion-color-gray-700);
--ion-chip-hue-bold-color: var(--ion-color-white);
--ion-chip-hue-bold-border-color: var(--ion-color-gray-800);
@include mixins.font-smoothing();
@include mixins.padding(var(--ion-spacing-xs), var(--ion-spacing-sm));
@include mixins.border-radius(var(--border-radius));
@include mixins.padding(var(--ion-chip-padding-horizontal), var(--ion-chip-padding-vertical));
@include mixins.border-radius(var(--ion-chip-border-radius));
display: inline-flex;
@@ -24,22 +64,21 @@
align-items: center;
justify-content: center;
background: var(--background);
color: var(--color);
font-weight: var(--ion-font-weights-normal);
line-height: var(--ion-line-heights-md);
background: var(--ion-chip-background);
color: var(--ion-chip-color);
cursor: pointer;
font-weight: var(--ion-chip-font-weight);
line-height: var(--ion-chip-line-height);
gap: var(--ion-chip-gap);
overflow: hidden;
vertical-align: middle;
gap: var(--ion-spacing-xs);
box-sizing: border-box;
vertical-align: middle;
}
:host(.chip-disabled) {
@@ -51,70 +90,62 @@
// ---------------------------------------------
:host(.chip-outline) {
border-width: var(--ion-spacing-xxs);
border-width: var(--ion-chip-border-width);
border-style: solid; // Do we need to use a variable here? ionic uses tokens but all themes use solid so it's not really necessary
}
// Chip: Focus
// ---------------------------------------------
:host(.ion-focused) {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-width);
}
// Chip Shapes
// ---------------------------------------------
:host(.chip-soft) {
--border-radius: var(--ion-radii-lg);
--ion-chip-border-radius: var(--ion-chip-shape-soft-border-radius);
}
:host(.chip-round) {
--border-radius: var(--ion-radii-xxl);
--ion-chip-border-radius: var(--ion-chip-shape-round-border-radius);
}
:host(.chip-rectangular) {
--border-radius: var(--ion-radii-none);
--ion-chip-border-radius: var(--ion-chip-shape-rectangular-border-radius);
}
// Size
// ---------------------------------------------
:host(.chip-small) {
min-height: var(--ion-spacing-lg);
min-height: var(--ion-chip-size-small-min-height);
font-size: var(--ion-font-sizs-sm);
font-size: var(--ion-chip-size-small-font-size);
}
:host(.chip-large) {
min-height: var(--ion-spacing-xxl);
min-height: var(--ion-chip-size-large-min-height);
font-size: var(--ion-font-sizes-lg);
font-size: var(--ion-chip-size-large-font-size);
}
// Subtle Chip
// ---------------------------------------------
:host(.chip-subtle) {
--background: var(--ion-color-gray-100);
--color: var(--ion-color-gray-800);
--ion-chip-background: var(--ion-chip-hue-subtle-bg);
--ion-chip-color: var(--ion-chip-hue-subtle-color);
}
:host(.chip-outline.chip-subtle) {
border-color: var(--ion-color-gray-300);
border-color: var(--ion-chip-hue-subtle-border-color);
}
// Bold Chip
// ---------------------------------------------
:host(.chip-bold) {
--background: var(--ion-color-gray-700);
--color: var(--ion-color-white);
--ion-chip-background: var(--ion-chip-hue-bold-bg);
--ion-chip-color: var(--ion-chip-hue-bold-color);
}
:host(.chip-outline.chip-bold) {
border-color: var(--ion-color-gray-800);
border-color: var(--ion-chip-hue-bold-border-color);
}
// Chip Colors

View File

@@ -16,6 +16,8 @@ import { generateCSSVars } from '../../themes/base/generate-css-vars';
shadow: true,
})
export class MyChip implements ComponentInterface {
private isThemed = false;
@Element() el!: HTMLElement;
/**
@@ -71,9 +73,11 @@ export class MyChip implements ComponentInterface {
// check if componentTheme is not an empty object or undefined
if (componentTheme !== undefined || Object.keys(componentTheme).length > 0) {
this.isThemed = true;
// apply a style tag to this component
const style = document.createElement('style');
style.innerHTML = [generateCSSVars(componentTheme, '--ion-', ':host')].join('\n\n');
style.innerHTML = [generateCSSVars(componentTheme, '--ion-chip-', ':host(.chip-themed)')].join('\n\n');
// Attach to Shadow Root if available, otherwise Light DOM
const root = this.el.shadowRoot ?? this.el;
@@ -89,6 +93,9 @@ export class MyChip implements ComponentInterface {
<Host
aria-disabled={this.disabled ? 'true' : null}
class={createColorClasses(this.color, {
// Needed to properly add the custom styles else
// the styles would be ignored due to styling order
'chip-themed': this.isThemed,
[`chip-${shape}`]: shape !== undefined,
'chip-outline': this.outline,
'chip-disabled': this.disabled,

View File

@@ -73,18 +73,18 @@
},
components: {
IonChip: {
spacing: {
sm: '12px',
},
palette: {
light: {
color: {
gray: {
800: 'pink',
},
},
hue: {
subtle: {
bg: 'red',
color: 'white',
borderColor: 'black'
},
},
bold: {
bg: 'blue',
color: 'white',
borderColor: 'black'
}
}
},
},
},

View File

@@ -41,7 +41,10 @@ html {
/**
* Includes fallback if Dynamic Type is not enabled.
*/
font: var(--ion-dynamic-font, 16px var(--ion-font-family));
// ion-font-sizes-root needs a fallback since the new theming isn't fully
// implemented in the framework yet.
font: var(--ion-dynamic-font, var(--ion-font-sizes-root, 16px) var(--ion-font-family));
// font: var(--ion-dynamic-font, var(--ion-font-sizes-root) var(--ion-font-family));
}
}

View File

@@ -30,6 +30,8 @@ export const defaultTheme = {
xl: '16px',
xxl: '32px',
},
dynamicFont: '-apple-system-body',
fontFamily: 'Roboto, "Helvetica Neue", sans-serif',
fontWeights: {
thin: '100',
extraLight: '200',
@@ -42,6 +44,7 @@ export const defaultTheme = {
black: '900',
},
fontSizes: {
root: '16px',
xs: '12px',
sm: '14px',
md: '16px',

View File

@@ -8,6 +8,21 @@ export function generateCSSVars(theme: any, themePrefix = '--ion-', selector = '
if (key.match(/([a-z])([A-Z])/g)) {
key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
// If it's a font-sizes key, create rem version
// This is necessary to support the dynamic font size feature
if (key === 'font-sizes' && typeof val === 'object' && val !== null) {
const fontSizeBase = parseFloat(theme.fontSizes.root);
return Object.entries(val).map(([sizeKey, sizeValue]) => {
const remValue = `${parseFloat(sizeValue) / fontSizeBase}rem`;
// Need a check to determine if the value is already in rem
return [
`${prefix}${key}-${sizeKey}: ${sizeValue};`, // original px value
`${prefix}${key}-${sizeKey}-rem: ${remValue};` // rem value
].join('\n');
}).join('\n');
}
return typeof val === 'object' && val !== null
? flatten(val, `${prefix}${key}-`)
: [`${prefix}${key}: ${val};`];