Skip to content

Commit 056e222

Browse files
authored
fix(material-experimental/theming): implement M3 badge (#28460)
Aligns the badge with the M3 spec. This required a few issues to be fixed: 1. The badge was being sized with `width` and `height`. This is incorrect, because it prevents the badge from scaling with the text. I've resolved it while keeping the old behavior by introducing new tokens that target `min-width` and `min-height` while the old ones still target `width` and `height`. 2. The badge was being positioned purely with `top`, `bottom`, `left` and `right`. This is problematic, because it anchors the badge to the specific point in the host, causing the content to grow inwards instead of outwards. I've fixed it by using the opposite dimensions to position the badge (e.g. `bottom: 100%` instead of `top: -20px`) and then using a negative margin to offset the badge from there. This approach also has the advantage of producing less CSS. 3. The badge didn't have a padding which made the content look off if it's more than one character.
1 parent 14aeb00 commit 056e222

File tree

4 files changed

+111
-69
lines changed

4 files changed

+111
-69
lines changed

src/material-experimental/theming/_custom-tokens.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,25 @@
5959
text-size: map.get($systems, md-sys-typescale, label-small-size),
6060
text-weight: map.get($systems, md-sys-typescale, label-small-weight),
6161
small-size-text-size: _hardcode(0, $exclude-hardcoded),
62+
container-shape: map.get($systems, md-sys-shape, corner-full),
63+
container-size: _hardcode(16px, $exclude-hardcoded),
64+
legacy-container-size: _hardcode(unset, $exclude-hardcoded),
65+
legacy-small-size-container-size: _hardcode(unset, $exclude-hardcoded),
66+
small-size-container-size: _hardcode(6px, $exclude-hardcoded),
67+
container-padding: _hardcode(0 4px, $exclude-hardcoded),
68+
small-size-container-padding: _hardcode(0, $exclude-hardcoded),
69+
container-offset: -12px 0,
70+
small-size-container-offset: -6px 0,
71+
container-overlap-offset: -12px,
72+
small-size-container-overlap-offset: -6px,
73+
74+
// This size isn't in the M3 spec so we emit the same values as for `medium`.
75+
large-size-container-size: _hardcode(16px, $exclude-hardcoded),
76+
large-size-container-offset: -12px 0,
77+
large-size-container-overlap-offset: -12px,
6278
large-size-text-size: map.get($systems, md-sys-typescale, label-small-size),
79+
large-size-container-padding: _hardcode(0 4px, $exclude-hardcoded),
80+
legacy-large-size-container-size: _hardcode(unset, $exclude-hardcoded),
6381
), (
6482
primary: (
6583
background-color: map.get($systems, md-sys-color, primary),

src/material/badge/_badge-theme.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
@if inspection.get-theme-version($theme) == 1 {
1414
@include _theme-from-tokens(inspection.get-theme-tokens($theme, base));
1515
}
16-
@else {}
16+
@else {
17+
@include sass-utils.current-selector-or-root() {
18+
@include token-utils.create-token-values(tokens-mat-badge.$prefix,
19+
tokens-mat-badge.get-unthemable-tokens());
20+
}
21+
}
1722
}
1823

1924
/// Outputs color theme styles for the mat-badge.

src/material/badge/badge.scss

Lines changed: 60 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
@use 'sass:color';
2-
@use 'sass:math';
32
@use '@angular/cdk';
43
@use '../core/tokens/m2/mat/badge' as tokens-mat-badge;
54
@use '../core/tokens/token-utils';
@@ -8,65 +7,33 @@ $default-size: 22px !default;
87
$small-size: $default-size - 6;
98
$large-size: $default-size + 6;
109

11-
12-
// Mixin for building offset given different sizes
13-
@mixin _badge-size($size, $font-size-token) {
14-
.mat-badge-content {
15-
width: $size;
16-
height: $size;
17-
line-height: $size;
18-
19-
@if ($font-size-token) {
20-
@include token-utils.use-tokens(tokens-mat-badge.$prefix,
21-
tokens-mat-badge.get-token-slots()) {
22-
@include token-utils.create-token-slot(font-size, $font-size-token);
23-
}
24-
}
25-
}
26-
27-
&.mat-badge-above .mat-badge-content {
28-
top: math.div(-$size, 2);
29-
}
30-
31-
&.mat-badge-below .mat-badge-content {
32-
bottom: math.div(-$size, 2);
33-
}
34-
35-
&.mat-badge-before .mat-badge-content {
36-
left: -$size;
37-
}
38-
39-
[dir='rtl'] &.mat-badge-before .mat-badge-content {
40-
left: auto;
41-
right: -$size;
42-
}
43-
44-
&.mat-badge-after .mat-badge-content {
45-
right: -$size;
46-
}
47-
48-
[dir='rtl'] &.mat-badge-after .mat-badge-content {
49-
right: auto;
50-
left: -$size;
51-
}
52-
53-
&.mat-badge-overlap {
54-
&.mat-badge-before .mat-badge-content {
55-
left: math.div(-$size, 2);
56-
}
57-
58-
[dir='rtl'] &.mat-badge-before .mat-badge-content {
59-
left: auto;
60-
right: math.div(-$size, 2);
61-
}
62-
63-
&.mat-badge-after .mat-badge-content {
64-
right: math.div(-$size, 2);
10+
@mixin _badge-size($size) {
11+
@include token-utils.use-tokens(tokens-mat-badge.$prefix, tokens-mat-badge.get-token-slots()) {
12+
$prefix: if($size == 'medium', '', $size + '-size-');
13+
$legacy-size-var: token-utils.get-token-variable('legacy-#{$prefix}container-size');
14+
$size-var: token-utils.get-token-variable('#{$prefix}container-size');
15+
16+
.mat-badge-content {
17+
// The M2 badge is implemented incorrectly because it uses `width` and `height` for its
18+
// size which causes the text to be truncated. For M3 we want to fix this by emitting
19+
// two declarations:
20+
// * `legacy-container-size` token - targets width/height as in M2. In M3 the token is
21+
// emitted as `unset`.
22+
// * `container-size` token - In M2 the token is emitted as `unset` to preserve the legacy
23+
// behavior while in M3 it targets `min-width` and `min-height` which allows the badge to
24+
// grow with the content.
25+
width: var(#{$legacy-size-var}, unset);
26+
height: var(#{$legacy-size-var}, unset);
27+
min-width: var(#{$size-var}, unset);
28+
min-height: var(#{$size-var}, unset);
29+
line-height: var($legacy-size-var, var(#{$size-var}));
30+
@include token-utils.create-token-slot(padding, '#{$prefix}container-padding');
31+
@include token-utils.create-token-slot(font-size, '#{$prefix}text-size');
32+
@include token-utils.create-token-slot(margin, '#{$prefix}container-offset');
6533
}
6634

67-
[dir='rtl'] &.mat-badge-after .mat-badge-content {
68-
right: auto;
69-
left: math.div(-$size, 2);
35+
&.mat-badge-overlap .mat-badge-content {
36+
@include token-utils.create-token-slot(margin, '#{$prefix}container-overlap-offset');
7037
}
7138
}
7239
}
@@ -87,25 +54,51 @@ $large-size: $default-size + 6;
8754
position: absolute;
8855
text-align: center;
8956
display: inline-block;
90-
border-radius: 50%;
9157
transition: transform 200ms ease-in-out;
9258
transform: scale(0.6);
9359
overflow: hidden;
9460
white-space: nowrap;
9561
text-overflow: ellipsis;
62+
box-sizing: border-box;
9663
pointer-events: none;
9764

65+
@include cdk.high-contrast(active, off) {
66+
outline: solid 1px;
67+
border-radius: 0;
68+
}
69+
9870
@include token-utils.use-tokens(tokens-mat-badge.$prefix, tokens-mat-badge.get-token-slots()) {
9971
@include token-utils.create-token-slot(background-color, background-color);
10072
@include token-utils.create-token-slot(color, text-color);
10173
@include token-utils.create-token-slot(font-family, text-font);
102-
@include token-utils.create-token-slot(font-size, text-size);
10374
@include token-utils.create-token-slot(font-weight, text-weight);
104-
}
75+
@include token-utils.create-token-slot(border-radius, container-shape);
10576

106-
@include cdk.high-contrast(active, off) {
107-
outline: solid 1px;
108-
border-radius: 0;
77+
.mat-badge-above & {
78+
bottom: 100%;
79+
}
80+
81+
.mat-badge-below & {
82+
top: 100%;
83+
}
84+
85+
.mat-badge-before & {
86+
right: 100%;
87+
}
88+
89+
[dir='rtl'] .mat-badge-before & {
90+
right: auto;
91+
left: 100%;
92+
}
93+
94+
.mat-badge-after & {
95+
left: 100%;
96+
}
97+
98+
[dir='rtl'] .mat-badge-after & {
99+
left: auto;
100+
right: 100%;
101+
}
109102
}
110103
}
111104

@@ -133,13 +126,13 @@ $large-size: $default-size + 6;
133126
}
134127

135128
.mat-badge-small {
136-
@include _badge-size($small-size, small-size-text-size);
129+
@include _badge-size('small');
137130
}
138131

139132
.mat-badge-medium {
140-
@include _badge-size($default-size, null);
133+
@include _badge-size('medium');
141134
}
142135

143136
.mat-badge-large {
144-
@include _badge-size($large-size, large-size-text-size);
137+
@include _badge-size('large');
145138
}

src/material/core/tokens/m2/mat/_badge.scss

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@use 'sass:meta';
22
@use 'sass:map';
3+
@use 'sass:math';
34
@use 'sass:color';
45
@use '../../token-utils';
56
@use '../../../theming/inspection';
@@ -11,7 +12,32 @@ $prefix: (mat, badge);
1112
// Tokens that can't be configured through Angular Material's current theming API,
1213
// but may be in a future version of the theming API.
1314
@function get-unthemable-tokens() {
14-
@return ();
15+
$default-size: 22px;
16+
$small-size: $default-size - 6;
17+
$large-size: $default-size + 6;
18+
19+
@return (
20+
container-shape: 50%,
21+
container-size: unset,
22+
small-size-container-size: unset,
23+
large-size-container-size: unset,
24+
25+
legacy-container-size: $default-size,
26+
legacy-small-size-container-size: $small-size,
27+
legacy-large-size-container-size: $large-size,
28+
29+
container-offset: math.div($default-size, -2) 0,
30+
small-size-container-offset: math.div($small-size, -2) 0,
31+
large-size-container-offset: math.div($large-size, -2) 0,
32+
33+
container-overlap-offset: math.div($default-size, -2),
34+
small-size-container-overlap-offset: math.div($small-size, -2),
35+
large-size-container-overlap-offset: math.div($large-size, -2),
36+
37+
container-padding: 0,
38+
small-size-container-padding: 0,
39+
large-size-container-padding: 0,
40+
);
1541
}
1642

1743
// Tokens that can be configured through Angular Material's color theming API.

0 commit comments

Comments
 (0)