Skip to content

refactor(material/badge): switch to tokens theming API #27492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 84 additions & 118 deletions src/material/badge/_badge-theme.scss
Original file line number Diff line number Diff line change
@@ -1,96 +1,87 @@
// This contains all of the styles for the badge
// rather than just the color/theme because of
// no style sheet support for directives.
@use 'sass:color';
@use 'sass:map';
@use 'sass:meta';
@use 'sass:math';
@use '@angular/cdk';

@use '../core/theming/theming';
@use '../core/typography/typography';
@use '../core/typography/typography-utils';
@use '../core/tokens/m2/mat/badge' as tokens-mat-badge;
@use '../core/tokens/token-utils';
@use '../core/style/sass-utils';

// TODO(crisbeto): some of these variables aren't used anymore and should be deleted.
$font-size: 12px;
$font-weight: 600;
$default-size: 22px !default;
$small-size: $default-size - 6;
$large-size: $default-size + 6;
$_badge-structure-emitted: false !default;

// Internally there are some builds that throw an error if they can't figure out the values
// of CSS variables during compliation. This flag temporarily enables fallbacks for these builds.
// Eventually we should clean them up.
$_emit-fallback-vars: true;

// Mixin for building offset given different sizes
@mixin _badge-size($size) {
@mixin _badge-size($size, $font-size-token) {
// This mixin isn't used in the context of a theme so we can disable the ampersand check.
// stylelint-disable material/no-ampersand-beyond-selector-start
.mat-badge-content {
width: $size;
height: $size;
line-height: $size;
}

&.mat-badge-above {
.mat-badge-content {
top: math.div(-$size, 2);
@if ($font-size-token) {
@include token-utils.use-tokens(tokens-mat-badge.$prefix,
tokens-mat-badge.get-token-slots()) {
@include token-utils.create-token-slot(font-size, $font-size-token, $_emit-fallback-vars);
}
}
}

&.mat-badge-below {
.mat-badge-content {
bottom: math.div(-$size, 2);
}
&.mat-badge-above .mat-badge-content {
top: math.div(-$size, 2);
}

&.mat-badge-before {
.mat-badge-content {
left: -$size;
}
&.mat-badge-below .mat-badge-content {
bottom: math.div(-$size, 2);
}

[dir='rtl'] &.mat-badge-before {
.mat-badge-content {
left: auto;
right: -$size;
}
&.mat-badge-before .mat-badge-content {
left: -$size;
}

&.mat-badge-after {
.mat-badge-content {
right: -$size;
}
[dir='rtl'] &.mat-badge-before .mat-badge-content {
left: auto;
right: -$size;
}

[dir='rtl'] &.mat-badge-after {
.mat-badge-content {
right: auto;
left: -$size;
}
&.mat-badge-after .mat-badge-content {
right: -$size;
}

[dir='rtl'] &.mat-badge-after .mat-badge-content {
right: auto;
left: -$size;
}

&.mat-badge-overlap {
&.mat-badge-before {
.mat-badge-content {
left: math.div(-$size, 2);
}
&.mat-badge-before .mat-badge-content {
left: math.div(-$size, 2);
}

[dir='rtl'] &.mat-badge-before {
.mat-badge-content {
left: auto;
right: math.div(-$size, 2);
}
[dir='rtl'] &.mat-badge-before .mat-badge-content {
left: auto;
right: math.div(-$size, 2);
}

&.mat-badge-after {
.mat-badge-content {
right: math.div(-$size, 2);
}
&.mat-badge-after .mat-badge-content {
right: math.div(-$size, 2);
}

[dir='rtl'] &.mat-badge-after {
.mat-badge-content {
right: auto;
left: math.div(-$size, 2);
}
[dir='rtl'] &.mat-badge-after .mat-badge-content {
right: auto;
left: math.div(-$size, 2);
}
}
// stylelint-enable
Expand All @@ -101,19 +92,13 @@ $_badge-structure-emitted: false !default;
@mixin _badge-structure {
.mat-badge {
position: relative;
}

// The badge should make sure its host is overflow visible so that the badge content
// can be rendered outside of the element. Some components such as <mat-icon> explicitly
// style `overflow: hidden` so this requires extra specificity so that it does not
// depend on style load order.
.mat-badge.mat-badge {
overflow: visible;
}

.mat-badge-hidden {
.mat-badge-content {
display: none;
// The badge should make sure its host is overflow visible so that the badge content
// can be rendered outside of the element. Some components such as <mat-icon> explicitly
// style `overflow: hidden` so this requires extra specificity so that it does not
// depend on style load order.
&.mat-badge {
overflow: visible;
}
}

Expand All @@ -128,6 +113,30 @@ $_badge-structure-emitted: false !default;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;

@include token-utils.use-tokens(tokens-mat-badge.$prefix, tokens-mat-badge.get-token-slots()) {
@include token-utils.create-token-slot(background-color, background-color);
@include token-utils.create-token-slot(color, text-color);
@include token-utils.create-token-slot(font-family, text-font, $_emit-fallback-vars);
@include token-utils.create-token-slot(font-size, text-size, $_emit-fallback-vars);
@include token-utils.create-token-slot(font-weight, text-weight, $_emit-fallback-vars);
}

@include cdk.high-contrast(active, off) {
outline: solid 1px;
border-radius: 0;
}
}

.mat-badge-disabled .mat-badge-content {
@include token-utils.use-tokens(tokens-mat-badge.$prefix, tokens-mat-badge.get-token-slots()) {
@include token-utils.create-token-slot(background-color, disabled-state-background-color);
@include token-utils.create-token-slot(color, disabled-state-text-color);
}
}

.mat-badge-hidden .mat-badge-content {
display: none;
}

.ng-animate-disabled .mat-badge-content,
Expand All @@ -143,89 +152,46 @@ $_badge-structure-emitted: false !default;
}

.mat-badge-small {
@include _badge-size($small-size);
@include _badge-size($small-size, small-size-text-size);
}

.mat-badge-medium {
@include _badge-size($default-size);
@include _badge-size($default-size, null);
}

.mat-badge-large {
@include _badge-size($large-size);
@include _badge-size($large-size, large-size-text-size);
}
}

@mixin color($config-or-theme) {
$config: theming.get-color-config($config-or-theme);
$accent: map.get($config, accent);
$warn: map.get($config, warn);
$primary: map.get($config, primary);
$background: map.get($config, background);
$foreground: map.get($config, foreground);

.mat-badge-content {
color: theming.get-color-from-palette($primary, default-contrast);
background: theming.get-color-from-palette($primary);

@include cdk.high-contrast(active, off) {
outline: solid 1px;
border-radius: 0;
}
@include sass-utils.current-selector-or-root() {
@include token-utils.create-token-values(tokens-mat-badge.$prefix,
tokens-mat-badge.get-color-tokens($config));
}

.mat-badge-accent {
.mat-badge-content {
background: theming.get-color-from-palette($accent);
color: theming.get-color-from-palette($accent, default-contrast);
}
@include token-utils.create-token-values(tokens-mat-badge.$prefix,
tokens-mat-badge.private-get-color-palette-color-tokens($accent));
}

.mat-badge-warn {
.mat-badge-content {
color: theming.get-color-from-palette($warn, default-contrast);
background: theming.get-color-from-palette($warn);
}
}

.mat-badge-disabled {
.mat-badge-content {
$app-background: theming.get-color-from-palette($background, 'background');
$badge-color: theming.get-color-from-palette($foreground, disabled-button);

// The disabled color usually has some kind of opacity, but because the badge is overlayed
// on top of something else, it won't look good if it's opaque. If it is a color *type*,
// we convert it into a solid color by taking the opacity from the rgba value and using
// the value to determine the percentage of the background to put into foreground when
// mixing the colors together.
@if (meta.type-of($badge-color) == color and meta.type-of($app-background) == color) {
$badge-opacity: opacity($badge-color);
background: color.mix($app-background, rgba($badge-color, 1), (1 - $badge-opacity) * 100%);
}
@else {
background: $badge-color;
}

color: theming.get-color-from-palette($foreground, disabled-text);
}
@include token-utils.create-token-values(tokens-mat-badge.$prefix,
tokens-mat-badge.private-get-color-palette-color-tokens($warn));
}
}

@mixin typography($config-or-theme) {
$config: typography.private-typography-to-2014-config(
theming.get-typography-config($config-or-theme));
.mat-badge-content {
font-weight: $font-weight;
font-size: $font-size;
font-family: typography-utils.font-family($config);
}

.mat-badge-small .mat-badge-content {
// Set the font size to 75% of the original.
font-size: $font-size * 0.75;
}

.mat-badge-large .mat-badge-content {
font-size: $font-size * 2;
@include sass-utils.current-selector-or-root() {
@include token-utils.create-token-values(tokens-mat-badge.$prefix,
tokens-mat-badge.get-typography-tokens($config));
}
}

Expand Down
78 changes: 78 additions & 0 deletions src/material/core/tokens/m2/mat/_badge.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@use 'sass:meta';
@use 'sass:map';
@use 'sass:color';
@use '../../token-utils';
@use '../../../typography/typography-utils';
@use '../../../theming/theming';
@use '../../../style/sass-utils';

// The prefix used to generate the fully qualified name for tokens in this file.
$prefix: (mat, badge);

// Tokens that can't be configured through Angular Material's current theming API,
// but may be in a future version of the theming API.
@function get-unthemable-tokens() {
@return ();
}

// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($config) {
$foreground: map.get($config, foreground);
$background: map.get($config, background);
$primary-color-tokens: private-get-color-palette-color-tokens(map.get($config, primary));
$app-background: theming.get-color-from-palette($background, 'background');
$disabled-background: theming.get-color-from-palette($foreground, disabled-button);

// The disabled color usually has some kind of opacity, but because the badge is overlayed
// on top of something else, it won't look good if it's opaque. If it is a color *type*,
// we convert it into a solid color by taking the opacity from the rgba value and using
// the value to determine the percentage of the background to put into foreground when
// mixing the colors together.
@if (meta.type-of($disabled-background) == color and meta.type-of($app-background) == color) {
$badge-opacity: opacity($disabled-background);
$disabled-background: color.mix($app-background,
rgba($disabled-background, 1), (1 - $badge-opacity) * 100%);
}

@return map.merge($primary-color-tokens, (
disabled-state-background-color: $disabled-background,
disabled-state-text-color: theming.get-color-from-palette($foreground, disabled-text),
));
}

// Generates the tokens used to theme the badge based on a palette.
@function private-get-color-palette-color-tokens($palette) {
@return (
background-color: theming.get-color-from-palette($palette),
text-color: theming.get-color-from-palette($palette, default-contrast),
);
}

// Tokens that can be configured through Angular Material's typography theming API.
@function get-typography-tokens($config) {
$base-size: 12px;

@return (
text-font: typography-utils.font-family($config),
text-size: $base-size,
text-weight: 600,
small-size-text-size: $base-size * 0.75,
large-size-text-size: $base-size * 2,
);
}

// Tokens that can be configured through Angular Material's density theming API.
@function get-density-tokens($config) {
@return ();
}

// Combines the tokens generated by the above functions into a single map with placeholder values.
// This is used to create token slots.
@function get-token-slots() {
@return sass-utils.deep-merge-all(
get-unthemable-tokens(),
get-color-tokens(token-utils.$placeholder-color-config),
get-typography-tokens(token-utils.$placeholder-typography-config),
get-density-tokens(token-utils.$placeholder-density-config)
);
}