Skip to content

Commit b39e13e

Browse files
asynclizcopybara-github
authored andcommitted
feat(labs): add expressive button utility class component
PiperOrigin-RevId: 897356229
1 parent 7269a3c commit b39e13e

File tree

8 files changed

+930
-4
lines changed

8 files changed

+930
-4
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
//
2+
// Copyright 2026 Google LLC
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
@mixin root {
7+
--container-color: transparent;
8+
--container-height: auto;
9+
--container-elevation: var(--md-sys-elevation-shadow-0);
10+
--container-shape: calc(var(--container-height) / 2);
11+
--outline-width: 0;
12+
--outline-color: transparent;
13+
--icon-label-space: 8px;
14+
--icon-color: currentColor;
15+
--icon-size: 20px;
16+
--label-text: var(--md-sys-typescale-label-lg);
17+
--label-text-tracking: var(--md-sys-typescale-label-lg-tracking);
18+
--label-text-color: currentColor;
19+
--leading-space: 0;
20+
--state-layer-color: transparent;
21+
--trailing-space: 0;
22+
}
23+
24+
@mixin selected {
25+
--container-shape: var(--md-sys-shape-corner-md);
26+
}
27+
28+
@mixin square {
29+
--container-shape: var(--md-sys-shape-corner-md);
30+
}
31+
32+
@mixin selected-square {
33+
--container-shape: calc(var(--container-height) / 2);
34+
}
35+
36+
@mixin pressed {
37+
--container-shape: var(--md-sys-shape-corner-sm);
38+
}
39+
40+
@mixin filled {
41+
--container-color: var(--md-sys-color-primary);
42+
--icon-color: var(--md-sys-color-on-primary);
43+
--label-text-color: var(--md-sys-color-on-primary);
44+
}
45+
46+
@mixin filled-unselected {
47+
--container-color: var(--md-sys-color-surface-container);
48+
--icon-color: var(--md-sys-color-on-surface-variant);
49+
--label-text-color: var(--md-sys-color-on-surface-variant);
50+
}
51+
52+
@mixin filled-selected {
53+
--container-color: var(--md-sys-color-primary);
54+
--icon-color: var(--md-sys-color-on-primary);
55+
--label-text-color: var(--md-sys-color-on-primary);
56+
}
57+
58+
@mixin elevated {
59+
--container-color: var(--md-sys-color-surface-container-low);
60+
--container-elevation: var(--md-sys-elevation-shadow-1);
61+
--icon-color: var(--md-sys-color-primary);
62+
--label-text-color: var(--md-sys-color-primary);
63+
}
64+
65+
@mixin elevated-selected {
66+
--container-color: var(--md-sys-color-primary);
67+
--icon-color: var(--md-sys-color-on-primary);
68+
--label-text-color: var(--md-sys-color-on-primary);
69+
}
70+
71+
@mixin tonal {
72+
--container-color: var(--md-sys-color-secondary-container);
73+
--icon-color: var(--md-sys-color-on-secondary-container);
74+
--label-text-color: var(--md-sys-color-on-secondary-container);
75+
}
76+
77+
@mixin tonal-selected {
78+
--container-color: var(--md-sys-color-secondary);
79+
--icon-color: var(--md-sys-color-on-secondary);
80+
--label-text-color: var(--md-sys-color-on-secondary);
81+
}
82+
83+
@mixin outlined {
84+
--icon-color: var(--md-sys-color-on-surface-variant);
85+
--label-text-color: var(--md-sys-color-on-surface-variant);
86+
--outline-color: var(--md-sys-color-outline-variant);
87+
--outline-width: 1px;
88+
}
89+
90+
@mixin outlined-lg {
91+
--outline-width: 2px;
92+
}
93+
94+
@mixin outlined-xl {
95+
--outline-width: 3px;
96+
}
97+
98+
@mixin outlined-selected {
99+
--container-color: var(--md-sys-color-inverse-surface);
100+
--icon-color: var(--md-sys-color-inverse-on-surface);
101+
--label-text-color: var(--md-sys-color-inverse-on-surface);
102+
}
103+
104+
@mixin text {
105+
--label-text-color: var(--md-sys-color-primary);
106+
--icon-color: var(--md-sys-color-primary);
107+
--state-layer-color: var(--md-sys-color-primary);
108+
}
109+
110+
@mixin xs {
111+
--container-height: 32px;
112+
--leading-space: 12px;
113+
--trailing-space: 12px;
114+
}
115+
116+
@mixin sm {
117+
--container-height: 40px;
118+
--leading-space: 16px;
119+
--trailing-space: 16px;
120+
}
121+
122+
@mixin md {
123+
--container-height: 56px;
124+
--leading-space: 24px;
125+
--trailing-space: 24px;
126+
--icon-size: 24px;
127+
--label-text: var(--md-sys-typescale-title-md);
128+
--label-text-tracking: var(--md-sys-typescale-title-md-tracking);
129+
}
130+
131+
@mixin md-selected {
132+
--container-shape: var(--md-sys-shape-corner-lg);
133+
}
134+
135+
@mixin md-square {
136+
--container-shape: var(--md-sys-shape-corner-lg);
137+
}
138+
139+
@mixin md-pressed {
140+
--container-shape: var(--md-sys-shape-corner-md);
141+
}
142+
143+
@mixin lg {
144+
--container-height: 96px;
145+
--icon-label-space: 12px;
146+
--icon-size: 32px;
147+
--leading-space: 48px;
148+
--trailing-space: 48px;
149+
--label-text: var(--md-sys-typescale-headline-sm);
150+
--label-text-tracking: var(--md-sys-typescale-headline-sm-tracking);
151+
}
152+
153+
@mixin lg-selected {
154+
--container-shape: var(--md-sys-shape-corner-xl);
155+
}
156+
157+
@mixin lg-square {
158+
--container-shape: var(--md-sys-shape-corner-xl);
159+
}
160+
161+
@mixin lg-pressed {
162+
--container-shape: var(--md-sys-shape-corner-lg);
163+
}
164+
165+
@mixin xl {
166+
--container-height: 136px;
167+
--icon-label-space: 16px;
168+
--icon-size: 40px;
169+
--leading-space: 64px;
170+
--trailing-space: 64px;
171+
--label-text: var(--md-sys-typescale-headline-lg);
172+
--label-text-tracking: var(--md-sys-typescale-headline-lg-tracking);
173+
}
174+
175+
@mixin xl-selected {
176+
--container-shape: var(--md-sys-shape-corner-xl);
177+
}
178+
179+
@mixin xl-square {
180+
--container-shape: var(--md-sys-shape-corner-xl);
181+
}
182+
183+
@mixin xl-pressed {
184+
--container-shape: var(--md-sys-shape-corner-lg);
185+
}
186+
187+
@mixin disabled {
188+
--container-color: hsl(from var(--md-sys-color-on-surface) h s l / 10%);
189+
--container-elevation: var(--md-sys-elevation-shadow-0);
190+
--icon-color: hsl(from var(--md-sys-color-on-surface) h s l / 38%);
191+
--label-text-color: hsl(from var(--md-sys-color-on-surface) h s l / 38%);
192+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*!
2+
* Copyright 2026 Google LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
// go/keep-sorted start by_regex='(.+) prefix_order=sass:
7+
@use 'button-tokens';
8+
// go/keep-sorted end
9+
10+
@layer md.sys, md.comp.ripple, md.comp.focus-ring;
11+
@layer md.comp.button {
12+
.btn {
13+
@include button-tokens.root;
14+
&:is(.btn-selected, [aria-pressed='true']) {
15+
@include button-tokens.selected;
16+
}
17+
&.btn-square {
18+
@include button-tokens.square;
19+
}
20+
&:is(.btn-selected, [aria-pressed='true']):where(.btn-square) {
21+
@include button-tokens.selected-square;
22+
}
23+
&:is(:active, .active):where(:not(:disabled, .disabled)) {
24+
@include button-tokens.pressed;
25+
}
26+
&.btn-filled {
27+
@include button-tokens.filled;
28+
&:where(.btn-unselected, [aria-pressed='false']) {
29+
@include button-tokens.filled-unselected;
30+
}
31+
&:where(.btn-selected, [aria-pressed='true']) {
32+
@include button-tokens.filled-selected;
33+
}
34+
}
35+
&.btn-elevated {
36+
@include button-tokens.elevated;
37+
&:where(.btn-selected, [aria-pressed='true']) {
38+
@include button-tokens.elevated-selected;
39+
}
40+
}
41+
&.btn-tonal {
42+
@include button-tokens.tonal;
43+
&:where(.btn-selected, [aria-pressed='true']) {
44+
@include button-tokens.tonal-selected;
45+
}
46+
}
47+
&.btn-outlined {
48+
@include button-tokens.outlined;
49+
&:where(.btn-lg) {
50+
@include button-tokens.outlined-lg;
51+
}
52+
&:where(.btn-xl) {
53+
@include button-tokens.outlined-xl;
54+
}
55+
&:where(.btn-selected, [aria-pressed='true']) {
56+
@include button-tokens.outlined-selected;
57+
}
58+
}
59+
&.btn-text {
60+
@include button-tokens.text;
61+
}
62+
&.btn-xs {
63+
@include button-tokens.xs;
64+
}
65+
&.btn-sm {
66+
@include button-tokens.sm;
67+
}
68+
&.btn-md {
69+
@include button-tokens.md;
70+
&:where(.btn-selected, [aria-pressed='true']) {
71+
@include button-tokens.md-selected;
72+
}
73+
&:where(.btn-square) {
74+
@include button-tokens.md-square;
75+
}
76+
&:where(:is(:active, .active):not(:disabled, .disabled)) {
77+
@include button-tokens.md-pressed;
78+
}
79+
}
80+
&.btn-lg {
81+
@include button-tokens.lg;
82+
&:where(.btn-selected, [aria-pressed='true']) {
83+
@include button-tokens.lg-selected;
84+
}
85+
&:where(.btn-square) {
86+
@include button-tokens.lg-square;
87+
}
88+
&:where(:is(:active, .active):not(:disabled, .disabled)) {
89+
@include button-tokens.lg-pressed;
90+
}
91+
}
92+
&.btn-xl {
93+
@include button-tokens.xl;
94+
&:where(.btn-selected, [aria-pressed='true']) {
95+
@include button-tokens.xl-selected;
96+
}
97+
&:where(.btn-square) {
98+
@include button-tokens.xl-square;
99+
}
100+
&:where(:is(:active, .active):not(:disabled, .disabled)) {
101+
@include button-tokens.xl-pressed;
102+
}
103+
}
104+
&:is(:disabled, .disabled) {
105+
@include button-tokens.disabled;
106+
}
107+
108+
& {
109+
display: inline-flex;
110+
align-items: center;
111+
justify-content: center;
112+
min-height: var(--container-height);
113+
gap: var(--icon-label-space);
114+
padding-inline: var(--leading-space) var(--trailing-space);
115+
border: var(--outline-width) solid var(--outline-color);
116+
border-radius: var(--container-shape);
117+
box-shadow: var(--container-elevation);
118+
background-color: var(--container-color);
119+
color: var(--label-text-color);
120+
font: var(--label-text);
121+
letter-spacing: var(--label-text-tracking);
122+
transition:
123+
border-radius 350ms cubic-bezier(0.42, 1.67, 0.21, 0.9),
124+
var(--ripple-transition);
125+
--md-icon-color: var(--icon-color);
126+
--md-icon-size: var(--icon-size);
127+
}
128+
&:not(:disabled, .disabled) {
129+
cursor: pointer;
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)