@inchurch/matcha-theme 22.40.0 → 22.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/abstracts/_colors.scss +291 -291
  2. package/abstracts/_elevation.scss +108 -108
  3. package/abstracts/_functions.scss +321 -321
  4. package/abstracts/_media-breakpoint.scss +26 -26
  5. package/abstracts/_theme-api.scss +224 -224
  6. package/abstracts/_typography.scss +128 -128
  7. package/base/_reset.scss +487 -487
  8. package/components/app-page-header.scss +260 -260
  9. package/components/matcha-accordion.scss +297 -297
  10. package/components/matcha-audio-player.scss +31 -31
  11. package/components/matcha-autocomplete.scss +145 -145
  12. package/components/matcha-avatar.scss +426 -426
  13. package/components/matcha-badge.scss +120 -120
  14. package/components/matcha-breadcrumb.scss +231 -231
  15. package/components/matcha-button-toggle.scss +484 -484
  16. package/components/matcha-buttons.scss +973 -973
  17. package/components/matcha-calendar.scss +253 -253
  18. package/components/matcha-cards.scss +212 -212
  19. package/components/matcha-chart-card.scss +53 -53
  20. package/components/matcha-checkbox.scss +266 -266
  21. package/components/matcha-chip.scss +311 -311
  22. package/components/matcha-color-pick.scss +34 -34
  23. package/components/matcha-command-palette.scss +316 -316
  24. package/components/matcha-crop.scss +419 -419
  25. package/components/matcha-date.scss +81 -81
  26. package/components/matcha-divider.scss +100 -100
  27. package/components/matcha-draggable.scss +23 -23
  28. package/components/matcha-drawer.scss +376 -376
  29. package/components/matcha-drop-image.scss +14 -14
  30. package/components/matcha-drop-list.scss +430 -430
  31. package/components/matcha-emoji.scss +69 -69
  32. package/components/matcha-empty-state.scss +230 -230
  33. package/components/matcha-file-uploader.scss +650 -650
  34. package/components/matcha-flag.scss +103 -103
  35. package/components/matcha-footer.scss +91 -91
  36. package/components/matcha-form-field.scss +883 -883
  37. package/components/matcha-form-section.scss +100 -100
  38. package/components/matcha-header.scss +135 -135
  39. package/components/matcha-highlight.scss +164 -164
  40. package/components/matcha-hint-text.scss +90 -90
  41. package/components/matcha-horizontal-tree.scss +247 -247
  42. package/components/matcha-icon.scss +111 -111
  43. package/components/matcha-image.scss +60 -60
  44. package/components/matcha-list.scss +211 -211
  45. package/components/matcha-menu.scss +99 -99
  46. package/components/matcha-modal.scss +210 -210
  47. package/components/matcha-nav.scss +770 -770
  48. package/components/matcha-notification.scss +417 -417
  49. package/components/matcha-option.scss +170 -170
  50. package/components/matcha-page-builder.scss +4196 -4196
  51. package/components/matcha-paginator.scss +699 -699
  52. package/components/matcha-panel.scss +216 -216
  53. package/components/matcha-period.scss +174 -174
  54. package/components/matcha-profile-card.scss +50 -50
  55. package/components/matcha-progress-bar.scss +355 -355
  56. package/components/matcha-radio.scss +325 -325
  57. package/components/matcha-ripple.scss +7 -7
  58. package/components/matcha-scrollbar.scss +33 -33
  59. package/components/matcha-scrollbox-shadow.scss +120 -120
  60. package/components/matcha-section.scss +102 -102
  61. package/components/matcha-select-multiple.scss +56 -56
  62. package/components/matcha-select.scss +643 -629
  63. package/components/matcha-skeleton.scss +182 -182
  64. package/components/matcha-slide-toggle.scss +369 -369
  65. package/components/matcha-slider.scss +285 -285
  66. package/components/matcha-snack-bar.scss +259 -259
  67. package/components/matcha-spin.scss +164 -164
  68. package/components/matcha-spinner.scss +97 -97
  69. package/components/matcha-stepper.scss +376 -376
  70. package/components/matcha-table-cell.scss +107 -107
  71. package/components/matcha-table.scss +722 -722
  72. package/components/matcha-tabs.scss +642 -642
  73. package/components/matcha-text-editor.scss +111 -111
  74. package/components/matcha-time.scss +33 -33
  75. package/components/matcha-timepicker.scss +13 -13
  76. package/components/matcha-toolbar.scss +366 -366
  77. package/components/matcha-tooltip.scss +235 -235
  78. package/components/matcha-tree-view.scss +658 -658
  79. package/layout/matcha-page-layout.scss +778 -778
  80. package/main.scss +334 -334
  81. package/package.json +1 -1
  82. package/tokens/_animations-tokens.scss +37 -37
  83. package/tokens/_breakpoints-tokens.scss +35 -35
  84. package/tokens/_color-tokens.scss +288 -288
  85. package/tokens/_elevation-tokens.scss +33 -33
  86. package/tokens/_emit-tokens.scss +329 -329
  87. package/tokens/_material-palette.scss +1149 -1149
  88. package/tokens/_primitives.scss +98 -98
  89. package/tokens/_semantic-aliases.scss +120 -120
  90. package/tokens/_spacing-tokens.scss +94 -94
  91. package/tokens/_static-tokens.scss +137 -137
  92. package/tokens/_typography-tokens.scss +65 -65
  93. package/vendors/charts.scss +90 -90
@@ -1,973 +1,973 @@
1
- @use "../abstracts/media-breakpoint" as *;
2
- @use "../abstracts/functions" as *;
3
- @use "../tokens/breakpoints-tokens" as *;
4
- // =============================================================================
5
- // matcha-buttons.scss — Button & Icon Button (modernized)
6
- // Aligned with Figma: matcha/button + matcha/icon-button
7
- // Sizes: SM(32) MD(40) LG(48) XL(56) + legacy aliases (tiny/small/medium/large/huge)
8
- // Variants: Filled(default) Outline Ghost(basic) Alpha Link
9
- // State layer: ::before opacity pattern (consistent with all DS components)
10
- // =============================================================================
11
-
12
- // ── Disabled mixins ─────────────────────────────────────────────────────────
13
-
14
- @mixin disabled-solid-button($theme) {
15
- background: var(--matcha-color-disabled-bg);
16
- color: var(--matcha-color-disabled-fg);
17
- box-shadow: 0 0 0 0px inset;
18
- pointer-events: none;
19
- }
20
-
21
- @mixin disabled-basic-button($theme) {
22
- background: var(--matcha-color-surface);
23
- color: var(--matcha-color-disabled-bg);
24
- box-shadow: 0 0 0 0px inset;
25
- pointer-events: none;
26
- }
27
-
28
- @mixin disabled-outline-button($theme) {
29
- background: transparent;
30
- color: var(--matcha-color-disabled-bg);
31
- box-shadow: 0 0 0 var(--matcha-border-thin, 2px) inset;
32
- pointer-events: none;
33
- }
34
-
35
- @mixin disabled-alpha-button($theme) {
36
- background: var(--matcha-color-disabled-bg-alpha, var(--matcha-color-interaction-disabled-bg-alpha));
37
- color: var(--matcha-color-disabled-bg);
38
- box-shadow: 0 0 0 0px inset;
39
- pointer-events: none;
40
- }
41
-
42
- @mixin disabled-link-button($theme) {
43
- background: transparent;
44
- color: var(--matcha-color-disabled-bg);
45
- box-shadow: 0 0 0 0px inset;
46
- pointer-events: none;
47
- }
48
-
49
- // ── Size classes (responsive, aligned with Figma XS/SM/MD/LG/XL) ────────────
50
- //
51
- // Cada size define `min-width` igual à sua `height`: o botão nunca fica mais
52
- // estreito que alto. Sem esse piso a largura é só `padding + conteúdo`, e um
53
- // label curto ("OK", "X") vira um retângulo VERTICAL — no XS, 23×28. Com o
54
- // piso o pior caso é um quadrado, com o conteúdo centrado pelo
55
- // `justify-content: center` do bloco estrutural. Label longo nunca atinge o
56
- // piso, então nada muda onde já estava certo.
57
- //
58
- // Efeito colateral desejado em flex container: o botão tem `overflow: hidden`,
59
- // o que já anula o `min-width: auto` (a automatic minimum size do flexbox só
60
- // vale para item com `overflow: visible`) — ou seja, hoje ele encolhe até ~0 e
61
- // corta o label. O piso troca esse colapso por um mínimo quadrado.
62
-
63
- @mixin generate-matcha-button-size-classes($helper-breakpoints) {
64
- @each $breakpoint, $materialBreakpoint in $helper-breakpoints {
65
- @include media-breakpoint($materialBreakpoint) {
66
- $infix: if($materialBreakpoint == null, "", "-#{$breakpoint}");
67
-
68
- // XS/Tiny — 28px. Figma matcha/button XS: pad 8 · gap 2 · text/caption (11/400/14, letter 0.3)
69
- &[size#{$infix}="xs"],
70
- &[size#{$infix}="tiny"] {
71
- padding: 0 var(--matcha-space-100, 8px);
72
- font-size: var(--matcha-text-p-tiny, 11px);
73
- font-weight: var(--matcha-weight-regular, 400);
74
- line-height: var(--matcha-leading-p-tiny, 14px);
75
- letter-spacing: var(--matcha-letter-spacing-caption, 0.3px);
76
- height: 28px;
77
- min-width: 28px;
78
- gap: var(--matcha-space-025, 2px);
79
- }
80
-
81
- // SM/Small — 32px. Figma: pad 8 · gap 4 · text/button-sm (13/500/19 letter 0.2)
82
- &[size#{$infix}="sm"],
83
- &[size#{$infix}="small"] {
84
- padding: 0 var(--matcha-space-100, 8px);
85
- font-size: var(--matcha-text-label-sm, 13px);
86
- font-weight: var(--matcha-weight-medium, 500);
87
- line-height: var(--matcha-leading-label-sm, 19px);
88
- height: 32px;
89
- min-width: 32px;
90
- gap: var(--matcha-space-050, 4px);
91
- }
92
-
93
- // MD/Medium — 40px. Figma: pad 16 · gap 8 · text/button-md (14/500/20)
94
- &[size#{$infix}="md"],
95
- &[size#{$infix}="medium"] {
96
- padding: 0 var(--matcha-space-200, 16px);
97
- font-size: var(--matcha-text-md, 14px);
98
- font-weight: var(--matcha-weight-medium, 500);
99
- line-height: var(--matcha-leading-md, 20px);
100
- height: 40px;
101
- min-width: 40px;
102
- gap: var(--matcha-space-100, 8px);
103
- }
104
-
105
- // LG/Large — 48px. Figma: pad 24 · gap 8
106
- &[size#{$infix}="lg"],
107
- &[size#{$infix}="large"] {
108
- padding: 0 var(--matcha-space-300, 24px);
109
- font-size: var(--matcha-text-lg, 16px);
110
- font-weight: var(--matcha-weight-semibold, 600);
111
- line-height: var(--matcha-leading-label-lg, 22px);
112
- height: 48px;
113
- min-width: 48px;
114
- gap: var(--matcha-space-100, 8px);
115
- }
116
-
117
- // XL/Huge — 56px. Figma: pad 32 · gap 8
118
- &[size#{$infix}="xl"],
119
- &[size#{$infix}="huge"] {
120
- padding: 0 var(--matcha-space-400, 32px);
121
- font-size: var(--matcha-text-lg, 16px);
122
- font-weight: var(--matcha-weight-semibold, 600);
123
- line-height: var(--matcha-leading-label-lg, 22px);
124
- height: 56px;
125
- min-width: 56px;
126
- gap: var(--matcha-space-100, 8px);
127
- }
128
- }
129
- }
130
- }
131
-
132
- // ── Structural ──────────────────────────────────────────────────────────────
133
-
134
- button[matcha-button],
135
- a[matcha-button],
136
- button[matcha-icon-button],
137
- a[matcha-icon-button],
138
- button[matcha-link],
139
- a[matcha-link] {
140
- overflow: hidden;
141
- position: relative;
142
- font-size: var(--matcha-text-md, 14px);
143
- font-weight: var(--matcha-weight-medium, 500);
144
- font-family: inherit;
145
- height: px-to-rem(40px); // default MD; overridden by size attribute selectors
146
- min-width: px-to-rem(40px); // piso quadrado do default MD; ver mixin de sizes
147
- appearance: auto;
148
- text-rendering: auto;
149
- letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
150
- word-spacing: normal;
151
- line-height: normal;
152
- text-transform: none;
153
- text-indent: 0px;
154
- text-shadow: none;
155
- text-align: center;
156
- text-wrap: nowrap;
157
- text-decoration: none; // reset do underline default quando o host é <a> (variant != link)
158
- display: flex;
159
- justify-content: center;
160
- align-items: center;
161
- cursor: pointer;
162
- box-sizing: border-box;
163
- border: none;
164
- border-radius: var(--matcha-radius-lg, 8px);
165
-
166
- // Figma matcha/button usa DOIS feedbacks combinados:
167
- // 1. Cor do bg muda (accent-default → accent-hover → accent-pressed)
168
- // 2. State-layer overlay por cima (matcha/color/state-layer/fg)
169
- // - Filled: opacity 0.15 hover / 0.30 active
170
- // - Outline/Ghost: opacity 0.10 hover / 0.20 active
171
- // ::before aplica o state-layer; :hover/:active ou .is-state-* ativam.
172
- // NOTA: declarada ANTES das regras aninhadas (evita a deprecação mixed-decls);
173
- // é onde o hoisting do Sass já a colocava — output byte-idêntico.
174
- transition:
175
- background-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
176
- box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1),
177
- color 150ms cubic-bezier(0.4, 0, 0.2, 1);
178
-
179
- * { pointer-events: none; }
180
-
181
- // Figma spec: ícones dentro de button herdam a foreground color do botão
182
- // (matcha-icon tem color-default próprio via tema — forçamos inherit com !important
183
- // pra vencer o tema que aplica em [class^="i-matcha-"] globalmente)
184
- matcha-icon,
185
- [class^="i-matcha-"],
186
- [class*=" i-matcha-"] {
187
- color: inherit !important;
188
- }
189
-
190
- @include generate-matcha-button-size-classes($helper-breakpoints);
191
-
192
- &::before {
193
- content: '';
194
- position: absolute;
195
- inset: 0;
196
- background: var(--matcha-color-state-layer-fg, currentColor);
197
- opacity: 0;
198
- border-radius: inherit;
199
- pointer-events: none;
200
- transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
201
- }
202
-
203
- // Filled (variant default) — state-layer forte
204
- &:hover:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link])::before,
205
- &.is-state-hover:not([outline]):not([basic]):not([ghost]):not([alpha])::before {
206
- opacity: 0.15;
207
- }
208
- &:active:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link])::before,
209
- &.is-state-active:not([outline]):not([basic]):not([ghost]):not([alpha])::before {
210
- opacity: 0.30;
211
- }
212
-
213
- // Outline / Ghost — state-layer sutil
214
- &[outline]:hover:not([disabled]):not([disabled="true"])::before,
215
- &[basic]:hover:not([disabled]):not([disabled="true"])::before,
216
- &[ghost]:hover:not([disabled]):not([disabled="true"])::before,
217
- &[outline].is-state-hover::before,
218
- &[basic].is-state-hover::before,
219
- &[ghost].is-state-hover::before {
220
- opacity: 0.10;
221
- }
222
- &[outline]:active:not([disabled]):not([disabled="true"])::before,
223
- &[basic]:active:not([disabled]):not([disabled="true"])::before,
224
- &[ghost]:active:not([disabled]):not([disabled="true"])::before,
225
- &[outline].is-state-active::before,
226
- &[basic].is-state-active::before,
227
- &[ghost].is-state-active::before {
228
- opacity: 0.20;
229
- }
230
-
231
- &[disabled] {
232
- pointer-events: none;
233
- cursor: default;
234
- }
235
-
236
- // ── Variant modifiers (estrutural — cores vêm do mixin de tema) ──
237
- &[basic]:not([basic="false"]),
238
- &[ghost]:not([ghost="false"]) {
239
- background: transparent;
240
- }
241
-
242
- &[outline]:not([outline="false"]) {
243
- background: transparent;
244
-
245
- &[link]:not([link="false"]) {
246
- &[size="xs"], &[size="tiny"] { padding: 0 var(--matcha-space-100, 8px); gap: var(--matcha-space-050, 4px); }
247
- &[size="sm"], &[size="small"] { padding: 0 var(--matcha-space-100, 8px); gap: var(--matcha-space-050, 4px); }
248
- &[size="md"], &[size="medium"] { padding: 0 var(--matcha-space-150, 12px); gap: var(--matcha-space-100, 8px); }
249
- &[size="lg"], &[size="large"] { padding: 0 var(--matcha-space-200, 16px); gap: var(--matcha-space-150, 12px); }
250
- &[size="xl"], &[size="huge"] { padding: 0 var(--matcha-space-200, 16px); gap: var(--matcha-space-150, 12px); }
251
- }
252
- }
253
-
254
- &[pill]:not([pill="false"]) {
255
- border-radius: var(--matcha-radius-pill, 9999px);
256
- }
257
-
258
- &[link]:not([link="false"]) {
259
- text-transform: initial;
260
- letter-spacing: 0;
261
- text-decoration: none;
262
- padding: 0;
263
- background: transparent;
264
- }
265
-
266
- // Link legado é texto inline (padding 0) — o piso quadrado do size viraria
267
- // espaço morto clicável em volta do label, então cai aqui.
268
- // O `:not([outline])` é deliberado: o combo [outline][link] é um botão com
269
- // borda e padding real (bloco acima), esse MANTÉM o piso.
270
- &[link]:not([link="false"]):not([outline]) {
271
- min-width: 0;
272
- }
273
-
274
- // ── matcha-link specific (Figma spec 16914:500) ──
275
- // SM=11/14 Regular letter 0.3 · MD=14/20 Medium · LG=16/22 SemiBold
276
- // Hover/Active → underline automático. Underline=true → permanente.
277
- // Disabled → opacity via disabled-fg color (no underline).
278
- &[matcha-link] {
279
- height: auto;
280
- min-height: 0;
281
- max-height: none;
282
- min-width: 0; // texto inline não tem piso quadrado (ver mixin de sizes)
283
- padding: 0;
284
- background: transparent;
285
- border-radius: 0;
286
- text-decoration: none; // reset do underline default do <a>; hover/class re-aplica
287
-
288
- &[size="sm"], &[size="tiny"] {
289
- font-size: var(--matcha-text-p-tiny, 11px);
290
- line-height: var(--matcha-leading-p-tiny, 14px);
291
- font-weight: var(--matcha-weight-regular, 400);
292
- letter-spacing: var(--matcha-letter-spacing-caption, 0.3px);
293
- height: auto;
294
- max-height: none;
295
- }
296
- &[size="md"], &[size="small"] {
297
- font-size: var(--matcha-text-md, 14px);
298
- line-height: var(--matcha-leading-md, 20px);
299
- font-weight: var(--matcha-weight-medium, 500);
300
- letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
301
- height: auto;
302
- max-height: none;
303
- }
304
- &[size="lg"], &[size="medium"] {
305
- font-size: var(--matcha-text-lg, 16px);
306
- line-height: var(--matcha-leading-label-lg, 22px);
307
- font-weight: var(--matcha-weight-semibold, 600);
308
- letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
309
- height: auto;
310
- max-height: none;
311
- }
312
-
313
- // State layer fica off (link não tem overlay/bg)
314
- &::before { display: none; }
315
- // Press scale também off — link é texto
316
- transition: none;
317
- &:active:not([disabled]):not([disabled="true"]) { transform: none; }
318
-
319
- // Hover e active: underline (Figma spec)
320
- &:hover:not([disabled]):not([disabled="true"]),
321
- &:active:not([disabled]):not([disabled="true"]) {
322
- text-decoration: underline;
323
- }
324
-
325
- // Underline permanente via property (classe do HostBinding no component)
326
- &.matcha-link--underline {
327
- text-decoration: underline;
328
- }
329
-
330
- // ── Canonical extended size names (tiny|small|medium|large|huge) ────────
331
- // matcha-link expõe a escala CANÔNICA por extenso, 1:1 com sm/md/lg
332
- // (sm→small · md→medium · lg→large). O bloco acima pareia esses nomes com
333
- // a escala LEGADA do matcha-button (small=md, medium=lg), então
334
- // re-asseguramos aqui — scoped APENAS ao matcha-link — os mesmos pixels do
335
- // tier abreviado correspondente (sem mudança visual). tiny→sm tier (menor
336
- // disponível), huge→lg tier (maior disponível no link).
337
- &[size="tiny"],
338
- &[size="small"] {
339
- font-size: var(--matcha-text-p-tiny, 11px);
340
- line-height: var(--matcha-leading-p-tiny, 14px);
341
- font-weight: var(--matcha-weight-regular, 400);
342
- letter-spacing: var(--matcha-letter-spacing-caption, 0.3px);
343
- height: auto;
344
- max-height: none;
345
- }
346
- &[size="medium"] {
347
- font-size: var(--matcha-text-md, 14px);
348
- line-height: var(--matcha-leading-md, 20px);
349
- font-weight: var(--matcha-weight-medium, 500);
350
- letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
351
- height: auto;
352
- max-height: none;
353
- }
354
- &[size="large"],
355
- &[size="huge"] {
356
- font-size: var(--matcha-text-lg, 16px);
357
- line-height: var(--matcha-leading-label-lg, 22px);
358
- font-weight: var(--matcha-weight-semibold, 600);
359
- letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
360
- height: auto;
361
- max-height: none;
362
- }
363
- }
364
-
365
- &[icon]:not([icon="false"]) {
366
- aspect-ratio: 1;
367
- display: flex;
368
- align-items: center;
369
- justify-content: center;
370
-
371
- // Icon-button padding per Figma spec matcha/icon-button (all-direction):
372
- // XS=6 · SM=6 · MD=8 · LG=12 · XL=16 (padding igual em todos os lados)
373
- &[size="xs"], &[size="tiny"] {
374
- padding: var(--matcha-space-075, 6px) !important;
375
- }
376
- &[size="sm"], &[size="small"] {
377
- padding: var(--matcha-space-075, 6px) !important;
378
- }
379
- &[size="md"], &[size="medium"] {
380
- padding: var(--matcha-space-100, 8px) !important;
381
- }
382
- &[size="lg"], &[size="large"] {
383
- padding: var(--matcha-space-150, 12px) !important;
384
- }
385
- &[size="xl"], &[size="huge"] {
386
- padding: var(--matcha-space-200, 16px) !important;
387
- }
388
-
389
- // Auto-size inner matcha-icon conforme Figma matcha/icon-button spec:
390
- // XS→16 · SM→20 · MD→24 · LG→24 · XL→24
391
- &[size="xs"] [class^="i-matcha-"],
392
- &[size="xs"] [class*=" i-matcha-"],
393
- &[size="tiny"] [class^="i-matcha-"],
394
- &[size="tiny"] [class*=" i-matcha-"] {
395
- font-size: var(--matcha-icon-size-sm, 16px);
396
- width: var(--matcha-icon-size-sm, 16px);
397
- height: var(--matcha-icon-size-sm, 16px);
398
- line-height: var(--matcha-icon-size-sm, 16px);
399
- }
400
- &[size="sm"] [class^="i-matcha-"],
401
- &[size="sm"] [class*=" i-matcha-"],
402
- &[size="small"] [class^="i-matcha-"],
403
- &[size="small"] [class*=" i-matcha-"] {
404
- font-size: 20px;
405
- width: 20px;
406
- height: 20px;
407
- line-height: 20px;
408
- }
409
- &[size="md"] [class^="i-matcha-"],
410
- &[size="md"] [class*=" i-matcha-"],
411
- &[size="medium"] [class^="i-matcha-"],
412
- &[size="medium"] [class*=" i-matcha-"],
413
- &[size="lg"] [class^="i-matcha-"],
414
- &[size="lg"] [class*=" i-matcha-"],
415
- &[size="large"] [class^="i-matcha-"],
416
- &[size="large"] [class*=" i-matcha-"],
417
- &[size="xl"] [class^="i-matcha-"],
418
- &[size="xl"] [class*=" i-matcha-"],
419
- &[size="huge"] [class^="i-matcha-"],
420
- &[size="huge"] [class*=" i-matcha-"] {
421
- font-size: var(--matcha-icon-size-md, 24px);
422
- width: var(--matcha-icon-size-md, 24px);
423
- height: var(--matcha-icon-size-md, 24px);
424
- line-height: var(--matcha-icon-size-md, 24px);
425
- }
426
- }
427
-
428
- // ── Badge ──
429
- &[badge]:not([badge="false"]) {
430
- overflow: visible;
431
- position: relative;
432
-
433
- &::after {
434
- content: "";
435
- position: absolute;
436
- top: -0.25rem;
437
- right: -0.25rem;
438
- width: 1.32rem;
439
- height: 1.32rem;
440
- border-radius: var(--matcha-radius-pill, 9999px);
441
- z-index: 2;
442
- }
443
-
444
- &[size="sm"]::after, &[size="small"]::after { width: 0.75rem; height: 0.75rem; top: -0.15rem; right: -0.15rem; }
445
- &[size="md"]::after, &[size="medium"]::after { width: 1rem; height: 1rem; top: -0.2rem; right: -0.2rem; }
446
- &[size="xl"]::after, &[size="huge"]::after { width: 1.5rem; height: 1.5rem; top: -0.3rem; right: -0.3rem; }
447
- }
448
-
449
- // ── Focus ring ──
450
- &:focus-visible:not([disabled]) {
451
- outline: var(--matcha-state-focus-ring-width, 2px) solid var(--matcha-color-accent, #6200ee);
452
- outline-offset: var(--matcha-state-focus-ring-offset, 2px);
453
- }
454
-
455
- @media (prefers-reduced-motion: reduce) {
456
- &::before { transition: none; }
457
- &, &:active:not([disabled]):not([disabled="true"]) { transition: none; transform: none; }
458
- }
459
-
460
- // ── Loading state ──
461
- // Spinner inline do lado direito do conteúdo + content dims pra 60%
462
- // Preserva width (não shift) e bloqueia cliques com pointer-events.
463
- &.matcha-button--loading {
464
- pointer-events: none;
465
- cursor: progress;
466
-
467
- // Dim no content interior (tudo exceto o spinner)
468
- > :not(.matcha-button__spinner) {
469
- opacity: 0.6;
470
- }
471
- }
472
-
473
- .matcha-button__spinner {
474
- display: inline-flex;
475
- align-items: center;
476
- justify-content: center;
477
- margin-left: var(--matcha-space-100, 8px);
478
- flex: none;
479
- line-height: 0;
480
-
481
- svg {
482
- display: block;
483
- }
484
-
485
- @media (prefers-reduced-motion: reduce) {
486
- svg circle animateTransform {
487
- // Retarda mas não remove (ainda sinaliza loading)
488
- dur: 2s !important;
489
- }
490
- }
491
- }
492
-
493
- // Visually hidden span pra screen readers (loading announcement)
494
- .matcha-button__sr-only {
495
- position: absolute !important;
496
- width: 1px !important;
497
- height: 1px !important;
498
- padding: 0 !important;
499
- margin: -1px !important;
500
- overflow: hidden !important;
501
- clip: rect(0, 0, 0, 0) !important;
502
- white-space: nowrap !important;
503
- border: 0 !important;
504
- }
505
-
506
- // ── Touch targets (WCAG 2.5.5: 44x44 mínimo em coarse pointers) ──
507
- // XS (28), SM (32), MD (40) ficam abaixo de 44px — expande hit area invisível.
508
- // NÃO re-declarar `position` aqui: o bloco estrutural acima já garante botão
509
- // positioned (relative), e o ::after ancora em qualquer positioned. Re-assertar
510
- // com [size] sobe a especificidade pra (0,2,1) e quebra consumidores que
511
- // reposicionam o botão com absolute (close mobile do notification/banner).
512
- @media (pointer: coarse) {
513
- &[size="xs"],
514
- &[size="sm"], &[size="tiny"],
515
- &[size="md"], &[size="small"] {
516
- &::after {
517
- content: '';
518
- position: absolute;
519
- inset: -8px; // expande ~16px em cada eixo → 28+16=44, 32+16=48, 40+16=56 ✓
520
- z-index: 0;
521
- pointer-events: auto; // captura cliques aqui
522
- }
523
-
524
- // ::before já é usado pro state layer, então nosso overlay vai no ::after
525
- // State layer (::before) fica colado ao botão visual, overlay (::after) expande hit.
526
- }
527
- }
528
- }
529
-
530
- // ── Theme mixin ─────────────────────────────────────────────────────────────
531
-
532
- @mixin matcha-button-theme($theme) {
533
- // ═════════════════════════════════════════════════════════════════════════
534
- // CSS var-based theming (regra DS-ARCHITECTURE #7: Tokens over hardcode)
535
- //
536
- // Todo botão usa --matcha-button-color como token interno.
537
- // `[color="X"]` apenas mapeia pra esses tokens — as variantes
538
- // (filled/outline/ghost/alpha/link) interpretam um único set de vars.
539
- //
540
- // Consumidor pode override QUALQUER cor via style:
541
- // <button matcha-button style="--matcha-button-color: #ff00ff">Custom</button>
542
- //
543
- // Adicionar cor nova = adicionar 1 mapping (3 linhas).
544
- // ═════════════════════════════════════════════════════════════════════════
545
-
546
- button[matcha-button],
547
- a[matcha-button],
548
- button[matcha-icon-button],
549
- a[matcha-icon-button],
550
- button[matcha-link],
551
- a[matcha-link] {
552
- // Tokens internos (Figma matcha/button 13913:1088):
553
- // --matcha-button-color = bg do filled (Default)
554
- // --matcha-button-color-hover = bg filled / stroke outline (Hover)
555
- // --matcha-button-color-pressed = bg filled / stroke+text (Active)
556
- // --matcha-button-color-text-on = texto do filled (accent/text-on etc.)
557
- // --matcha-button-color-alpha = alpha modifier / ripple
558
- // --matcha-button-color-edge = stroke do outline (Default)
559
- --matcha-button-color: var(--matcha-color-neutral);
560
- --matcha-button-color-hover: var(--matcha-color-neutral-hover);
561
- --matcha-button-color-pressed: var(--matcha-color-neutral-hover);
562
- --matcha-button-color-text-on: var(--matcha-color-neutral-contrast);
563
- --matcha-button-color-alpha: var(--matcha-color-neutral-alpha);
564
- --matcha-button-color-edge: var(--matcha-color-fg);
565
-
566
- // Badge ring herda o bg do botão (filled) — outline/ghost override pra surface,
567
- // pois o bg deles é transparente e o ring deve casar com a página atrás.
568
- --matcha-color-badge-border: var(--matcha-button-color);
569
-
570
- // ── Color mapping ────────────────────────────────────────────────────────────────
571
- &[color="basic"] {
572
- --matcha-button-color: var(--matcha-color-neutral);
573
- --matcha-button-color-hover: var(--matcha-color-neutral-hover);
574
- --matcha-button-color-pressed: var(--matcha-color-neutral-hover);
575
- --matcha-button-color-text-on: var(--matcha-color-neutral-contrast);
576
- --matcha-button-color-alpha: var(--matcha-color-neutral-alpha);
577
- --matcha-button-color-edge: var(--matcha-color-fg);
578
- }
579
- &[color="primary"] {
580
- --matcha-button-color: var(--matcha-color-primary);
581
- --matcha-button-color-hover: var(--matcha-color-primary-hover);
582
- --matcha-button-color-pressed: var(--matcha-color-primary-pressed);
583
- --matcha-button-color-text-on: var(--matcha-color-primary-contrast);
584
- --matcha-button-color-alpha: var(--matcha-color-primary-alpha);
585
- --matcha-button-color-edge: var(--matcha-color-primary);
586
- }
587
- &[color="accent"] {
588
- --matcha-button-color: var(--matcha-color-accent);
589
- --matcha-button-color-hover: var(--matcha-color-accent-hover);
590
- --matcha-button-color-pressed: var(--matcha-color-accent-pressed);
591
- --matcha-button-color-text-on: var(--matcha-color-accent-text-on);
592
- --matcha-button-color-alpha: var(--matcha-color-accent-alpha);
593
- --matcha-button-color-edge: var(--matcha-color-accent);
594
- }
595
- &[color="success"] {
596
- --matcha-button-color: var(--matcha-color-success);
597
- --matcha-button-color-hover: var(--matcha-color-success-hover);
598
- --matcha-button-color-pressed: var(--matcha-color-success-pressed);
599
- --matcha-button-color-text-on: var(--matcha-color-success-contrast);
600
- --matcha-button-color-alpha: var(--matcha-color-success-alpha);
601
- --matcha-button-color-edge: var(--matcha-color-success);
602
- }
603
- &[color="warn"] {
604
- --matcha-button-color: var(--matcha-color-warn);
605
- --matcha-button-color-hover: var(--matcha-color-warn-hover);
606
- --matcha-button-color-pressed: var(--matcha-color-warn-pressed);
607
- --matcha-button-color-text-on: var(--matcha-color-warn-contrast);
608
- --matcha-button-color-alpha: var(--matcha-color-warn-alpha);
609
- --matcha-button-color-edge: var(--matcha-color-warn);
610
- }
611
- &[color="danger"] {
612
- --matcha-button-color: var(--matcha-color-danger);
613
- --matcha-button-color-hover: var(--matcha-color-danger-hover);
614
- --matcha-button-color-pressed: var(--matcha-color-danger-pressed);
615
- --matcha-button-color-text-on: var(--matcha-color-danger-contrast);
616
- --matcha-button-color-alpha: var(--matcha-color-danger-alpha);
617
- --matcha-button-color-edge: var(--matcha-color-danger);
618
- }
619
- &[color="info"] {
620
- --matcha-button-color: var(--matcha-color-info);
621
- --matcha-button-color-hover: var(--matcha-color-info-hover);
622
- --matcha-button-color-pressed: var(--matcha-color-info-pressed);
623
- --matcha-button-color-text-on: var(--matcha-color-info-contrast);
624
- --matcha-button-color-alpha: var(--matcha-color-info-alpha);
625
- --matcha-button-color-edge: var(--matcha-color-info);
626
- }
627
-
628
- // ── Filled (default variant) ──────────────────────────────────────────────────────
629
- // Figma: Default=accent · Hover=accent-hover · Active=accent-pressed
630
- background: var(--matcha-button-color);
631
- color: var(--matcha-button-color-text-on);
632
-
633
- &:hover:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link]) {
634
- background: var(--matcha-button-color-hover);
635
- }
636
- &:active:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link]) {
637
- background: var(--matcha-button-color-pressed);
638
- }
639
-
640
- // ── Variant: outline ────────────────────────────────────────────────────────────────
641
- // Figma matcha/button (13913:1088) vs matcha/icon-button (13914:482):
642
- // BUTTON outline: texto=text-primary · ícones=edge (accent)
643
- // ICON-BUTTON outline: host color=edge (ícone herda via inherit !important)
644
- // Hover: icons/host = accent-hover (texto button mantém primary)
645
- // Active: tudo = accent-pressed
646
- &[outline]:not([outline="false"]),
647
- &[variant="outline"] {
648
- background: transparent;
649
- box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-button-color-edge);
650
- // Color slot: text segue --matcha-button-color-edge (border+text matching),
651
- // com fallback pra text-primary quando consumer não setar a var.
652
- // Antes era hardcoded text-primary → consumer tinha que duelar specificity.
653
- color: var(--matcha-button-color-edge);
654
- // Badge ring transparente — bg é transparente, qualquer cor fixa
655
- // colide com o bg-do-pai-da-página (impossível detectar em CSS).
656
- --matcha-color-badge-border: transparent;
657
-
658
- &:hover:not([disabled]):not([disabled="true"]) {
659
- box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-button-color-hover);
660
- }
661
- &:active:not([disabled]):not([disabled="true"]) {
662
- box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-button-color-pressed);
663
- color: var(--matcha-button-color-pressed);
664
- }
665
- }
666
-
667
- // Icon-button outline: host color = edge (ícone é o único conteúdo semântico)
668
- &[matcha-icon-button][outline]:not([outline="false"]),
669
- &[matcha-icon-button][variant="outline"] {
670
- color: var(--matcha-button-color-edge);
671
-
672
- &:hover:not([disabled]):not([disabled="true"]) {
673
- color: var(--matcha-button-color-hover);
674
- }
675
- &:active:not([disabled]):not([disabled="true"]) {
676
- color: var(--matcha-button-color-pressed);
677
- }
678
- }
679
-
680
- // Button outline: ícones (Left/Right) acompanham edge — texto mantém primary
681
- &[matcha-button][outline]:not([outline="false"]) matcha-icon,
682
- &[matcha-button][outline]:not([outline="false"]) [class^="i-matcha-"],
683
- &[matcha-button][outline]:not([outline="false"]) [class*=" i-matcha-"],
684
- &[matcha-button][variant="outline"] matcha-icon,
685
- &[matcha-button][variant="outline"] [class^="i-matcha-"],
686
- &[matcha-button][variant="outline"] [class*=" i-matcha-"] {
687
- color: var(--matcha-button-color-edge) !important;
688
- }
689
- &[matcha-button][outline]:hover:not([disabled]):not([disabled="true"]) matcha-icon,
690
- &[matcha-button][outline]:hover:not([disabled]):not([disabled="true"]) [class^="i-matcha-"],
691
- &[matcha-button][outline].is-state-hover matcha-icon {
692
- color: var(--matcha-button-color-hover) !important;
693
- }
694
- &[matcha-button][outline]:active:not([disabled]):not([disabled="true"]) matcha-icon,
695
- &[matcha-button][outline]:active:not([disabled]):not([disabled="true"]) [class^="i-matcha-"],
696
- &[matcha-button][outline].is-state-active matcha-icon {
697
- color: var(--matcha-button-color-pressed) !important;
698
- }
699
-
700
- // ── Variant: ghost (basic = legacy alias) ────────────────────────────────────────
701
- // Figma: Default/Hover=text-primary · Active=accent-pressed (texto + ícones)
702
- // Slot --matcha-button-ghost-text com fallback text-primary (Figma intent
703
- // preservado). Consumer override via parent: --matcha-button-ghost-text: X
704
- &[basic]:not([basic="false"]),
705
- &[ghost]:not([ghost="false"]),
706
- &[variant="ghost"] {
707
- background: transparent;
708
- color: var(--matcha-button-ghost-text, var(--matcha-color-text-primary));
709
- // Badge ring transparente — bg é transparente, qualquer cor fixa
710
- // colide com o bg-do-pai-da-página (impossível detectar em CSS).
711
- --matcha-color-badge-border: transparent;
712
-
713
- &:active:not([disabled]):not([disabled="true"]) {
714
- color: var(--matcha-button-color-pressed);
715
- }
716
- }
717
-
718
- // Button ghost: ícones acompanham edge (accent) — texto mantém primary
719
- &[matcha-button][basic]:not([basic="false"]) matcha-icon,
720
- &[matcha-button][basic]:not([basic="false"]) [class^="i-matcha-"],
721
- &[matcha-button][ghost]:not([ghost="false"]) matcha-icon,
722
- &[matcha-button][ghost]:not([ghost="false"]) [class^="i-matcha-"],
723
- &[matcha-button][variant="ghost"] matcha-icon,
724
- &[matcha-button][variant="ghost"] [class^="i-matcha-"] {
725
- color: var(--matcha-button-color-edge) !important;
726
- }
727
- // Hover: ícone mantém a cor de repouso (edge), igual ao texto (Figma:
728
- // Default/Hover=text-primary). Antes usava --matcha-button-color-hover,
729
- // um token pensado como cor de FUNDO de hover (ver comentário da linha
730
- // ~526) sendo aplicado como cor de ÍCONE — funcionava por coincidência em
731
- // botões soltos, mas junto de um --matcha-color-neutral-hover claro
732
- // (#d8e1da) o contraste do ícone contra fundos escuros (ex.: matcha-panel)
733
- // cai pra ~1.34:1, bem abaixo do mínimo WCAG de 3:1 pra ícones.
734
- &[matcha-button][ghost]:active:not([disabled]):not([disabled="true"]) matcha-icon,
735
- &[matcha-button][basic]:active:not([disabled]):not([disabled="true"]) matcha-icon,
736
- &[matcha-button][ghost].is-state-active matcha-icon,
737
- &[matcha-button][basic].is-state-active matcha-icon {
738
- color: var(--matcha-button-color-pressed) !important;
739
- }
740
-
741
- // ── Icon-button neutral (basic/default) — Figma matcha/icon-button ──
742
- // Outline/Ghost Default (13914:179 / 13914:332) vinculam o glyph — e a
743
- // borda do Outline — a matcha/color/icon/default (#75767a light / #9aa0a6
744
- // dark), o MESMO token do matcha-icon solto. O texto do botão usa fg/
745
- // text-primary; o ícone do icon-button NÃO. Redireciona edge + ghost-text
746
- // só pro icon-button neutro. Cores override (accent/danger/...) seguem a
747
- // própria cor via os blocos &[color="..."] acima.
748
- &[matcha-icon-button]:not([color]),
749
- &[matcha-icon-button][color="basic"] {
750
- --matcha-button-color-edge: var(--matcha-color-icon-default);
751
- --matcha-button-ghost-text: var(--matcha-color-icon-default);
752
- }
753
-
754
- // ── Modifier: alpha ──────────────────────────────────────────────
755
- &[alpha]:not([alpha="false"]) {
756
- background: var(--matcha-button-color-alpha);
757
- color: var(--matcha-button-color);
758
- }
759
-
760
- // ── Modifier: link (legacy, afeta button[link="true"]) ───────────
761
- // matcha-link tem seu próprio bloco (&[matcha-link]) com regras
762
- // próprias de underline (hover + .matcha-link--underline)
763
- &[link]:not([link="false"]):not([matcha-link]) {
764
- background: transparent;
765
- color: var(--matcha-button-color);
766
- text-decoration: underline;
767
- }
768
- // matcha-link: apenas cor do texto (o bloco estrutural [matcha-link]
769
- // define text-decoration: none + hover/active + class underline)
770
- &[matcha-link] {
771
- color: var(--matcha-button-color);
772
- }
773
-
774
- // ── State: disabled ──────────────────────────────────────────────
775
- // Figma (13913:1088) Disabled state:
776
- // Filled: bg=interaction/disabled-bg · label+icons=accent/text-on (mesma cor do filled default)
777
- // Outline: stroke=interaction/disabled-bg (NÃO disabled-border) · label+icons=disabled-fg
778
- // Ghost: sem bg/stroke · label+icons=disabled-fg
779
- // State-layer overlay DESLIGADO em todos disabled.
780
- &[disabled]:not([disabled="false"]),
781
- &[disabled="true"] {
782
- pointer-events: none;
783
- cursor: not-allowed;
784
-
785
- // state-layer overlay desligado
786
- &::before { opacity: 0 !important; }
787
-
788
- // Filled — bg disabled + texto/ícones accent-text-on (Figma spec)
789
- background: var(--matcha-color-interaction-disabled-bg);
790
- color: var(--matcha-color-accent-text-on);
791
- box-shadow: none;
792
-
793
- // Outline — stroke usa mesma cor do filled-bg (Figma usa interaction/disabled-bg)
794
- &[outline]:not([outline="false"]),
795
- &[variant="outline"] {
796
- background: transparent;
797
- color: var(--matcha-color-interaction-disabled-fg);
798
- box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-color-interaction-disabled-bg);
799
- }
800
-
801
- // Ghost / basic / link — só texto dedicado
802
- &[basic]:not([basic="false"]),
803
- &[ghost]:not([ghost="false"]),
804
- &[variant="ghost"],
805
- &[link]:not([link="false"]) {
806
- background: transparent;
807
- color: var(--matcha-color-interaction-disabled-fg);
808
- box-shadow: none;
809
- }
810
-
811
- // Alpha — bg alpha dedicado
812
- &[alpha]:not([alpha="false"]) {
813
- background: var(--matcha-color-interaction-disabled-bg-alpha);
814
- color: var(--matcha-color-interaction-disabled-fg);
815
- }
816
-
817
- // Ícones do button (Left/Right) no disabled:
818
- // Filled: herdam color do host (accent-text-on) ✓
819
- // Outline/Ghost: forçam disabled-fg pra sobrescrever regra !important do edge
820
- &[matcha-button][outline] matcha-icon,
821
- &[matcha-button][outline] [class^="i-matcha-"],
822
- &[matcha-button][outline] [class*=" i-matcha-"],
823
- &[matcha-button][basic] matcha-icon,
824
- &[matcha-button][basic] [class^="i-matcha-"],
825
- &[matcha-button][ghost] matcha-icon,
826
- &[matcha-button][ghost] [class^="i-matcha-"],
827
- &[matcha-button][variant="outline"] matcha-icon,
828
- &[matcha-button][variant="outline"] [class^="i-matcha-"],
829
- &[matcha-button][variant="ghost"] matcha-icon,
830
- &[matcha-button][variant="ghost"] [class^="i-matcha-"] {
831
- color: var(--matcha-color-interaction-disabled-fg) !important;
832
- }
833
- }
834
-
835
- // ── Badge indicator (always accent, pattern fixo) ────────────────
836
- &[badge]:not([badge="false"])::after {
837
- background: var(--matcha-color-accent);
838
- }
839
-
840
- // ── Ripple overlay ───────────────────────────────────────────────
841
- .ripple {
842
- background: var(--matcha-button-color-alpha);
843
- }
844
- }
845
- }
846
-
847
- // ── matcha-icon-button: auto-size inner icons per Figma spec ────────────────
848
- // XS button (28) → icon 16 · SM (32) → 20 · MD (40)/LG (48)/XL (56) → 24
849
- button[matcha-icon-button],
850
- a[matcha-icon-button] {
851
- aspect-ratio: 1;
852
-
853
- matcha-icon {
854
- flex: none; // NÃO expandir com o button
855
- }
856
-
857
- &[size="xs"] matcha-icon,
858
- &[size="tiny"] matcha-icon {
859
- font-size: 16px !important;
860
- width: 16px !important;
861
- height: 16px !important;
862
- line-height: 16px !important;
863
- }
864
- &[size="sm"] matcha-icon,
865
- &[size="small"] matcha-icon {
866
- font-size: 20px !important;
867
- width: 20px !important;
868
- height: 20px !important;
869
- line-height: 20px !important;
870
- }
871
- &[size="md"] matcha-icon,
872
- &[size="medium"] matcha-icon,
873
- &[size="lg"] matcha-icon,
874
- &[size="large"] matcha-icon,
875
- &[size="xl"] matcha-icon,
876
- &[size="huge"] matcha-icon {
877
- font-size: 24px !important;
878
- width: 24px !important;
879
- height: 24px !important;
880
- line-height: 24px !important;
881
- }
882
-
883
- // Tamanho (height) e padding por size já vêm corretos do mixin compartilhado
884
- // (generate-matcha-button-size-classes, aplicado via o seletor combinado no
885
- // topo do arquivo) + do bloco &[icon] acima — ambos pareiam cada abreviação
886
- // com seu extenso correto (sm↔small, md↔medium, lg↔large, xl↔huge). Este
887
- // componente não precisa reafirmar nada aqui.
888
- }
889
-
890
- // ── matcha-button COM TEXTO: auto-size do matcha-icon projetado ──────────────
891
- // O icon-button (acima) e o matcha-button[icon] (icon-only, no bloco &[icon]) já escalavam o
892
- // ícone; o botão com texto não escalava nada, então o matcha-icon caía no próprio default
893
- // (--matcha-icon-size-md, 24px) — proporcional só de MD (40px) pra cima. Num tiny (28px, texto
894
- // 11/14) sobrava um ícone de 24px.
895
- //
896
- // Escala igual à do icon-button: XS/tiny → 16 · SM/small → 20 · MD+ → 24 (o default do
897
- // matcha-icon já é 24, então MD/LG/XL não precisam de regra).
898
- //
899
- // Duas diferenças deliberadas em relação ao icon-button:
900
- // 1. `matcha-icon:not([size])` — aqui o ícone divide o botão com o texto, e um `size`
901
- // explícito do consumidor é escolha legítima que deve continuar valendo. O auto-size só
902
- // age quando ninguém decidiu.
903
- // 2. sem `!important`, consequência do item 1: como a regra nem casa quando há `size`, não há
904
- // disputa de especificidade contra a classe `.i-size-*` que o componente emite.
905
- //
906
- // `:not([icon])` e `:not([matcha-icon-button])` preservam os dois casos que já tinham
907
- // tratamento próprio. O bloco icon-only usa `[icon]:not([icon="false"])`, então em teoria
908
- // `icon="false"` é botão com texto e deveria entrar aqui; não há um único uso dessa forma no
909
- // showcase nem no painel, e um seletor extra só para ela seria ruído.
910
- button[matcha-button]:not([matcha-icon-button]):not([icon]),
911
- a[matcha-button]:not([matcha-icon-button]):not([icon]) {
912
- &[size="xs"] matcha-icon:not([size]),
913
- &[size="tiny"] matcha-icon:not([size]) {
914
- font-size: var(--matcha-icon-size-sm, 16px);
915
- width: var(--matcha-icon-size-sm, 16px);
916
- height: var(--matcha-icon-size-sm, 16px);
917
- line-height: var(--matcha-icon-size-sm, 16px);
918
- }
919
-
920
- // 20px não tem token (a escala de ícone vai 16/24/32) — mesmo literal que o icon-button usa.
921
- &[size="sm"] matcha-icon:not([size]),
922
- &[size="small"] matcha-icon:not([size]) {
923
- font-size: 20px;
924
- width: 20px;
925
- height: 20px;
926
- line-height: 20px;
927
- }
928
- }
929
-
930
- // ── Forced colors (Windows High Contrast) ──
931
- // Buttons precisam ser claramente acionáveis em high-contrast.
932
- @media (forced-colors: active) {
933
- button[matcha-button],
934
- a[matcha-button],
935
- button[matcha-icon-button],
936
- a[matcha-icon-button] {
937
- // Filled: usa botão de sistema (ButtonFace/ButtonText) — afford clicável.
938
- background: ButtonFace !important;
939
- color: ButtonText !important;
940
- border: 1px solid ButtonText !important;
941
- box-shadow: none !important;
942
-
943
- // Outline/ghost/alpha/link — bg transparente, border CanvasText.
944
- &[outline], &[basic], &[alpha], &[link] {
945
- background: transparent !important;
946
- border-color: CanvasText !important;
947
- color: CanvasText !important;
948
- }
949
-
950
- // Disabled — GrayText system token.
951
- &[disabled],
952
- &.is-disabled {
953
- color: GrayText !important;
954
- border-color: GrayText !important;
955
- }
956
-
957
- &:focus-visible {
958
- outline: 2px solid CanvasText !important;
959
- outline-offset: 2px;
960
- }
961
- }
962
- }
963
-
964
- // ── Print ──
965
- // Buttons hide (interativos, não fazem sentido em papel).
966
- @media print {
967
- button[matcha-button],
968
- a[matcha-button],
969
- button[matcha-icon-button],
970
- a[matcha-icon-button] {
971
- display: none !important;
972
- }
973
- }
1
+ @use "../abstracts/media-breakpoint" as *;
2
+ @use "../abstracts/functions" as *;
3
+ @use "../tokens/breakpoints-tokens" as *;
4
+ // =============================================================================
5
+ // matcha-buttons.scss — Button & Icon Button (modernized)
6
+ // Aligned with Figma: matcha/button + matcha/icon-button
7
+ // Sizes: SM(32) MD(40) LG(48) XL(56) + legacy aliases (tiny/small/medium/large/huge)
8
+ // Variants: Filled(default) Outline Ghost(basic) Alpha Link
9
+ // State layer: ::before opacity pattern (consistent with all DS components)
10
+ // =============================================================================
11
+
12
+ // ── Disabled mixins ─────────────────────────────────────────────────────────
13
+
14
+ @mixin disabled-solid-button($theme) {
15
+ background: var(--matcha-color-disabled-bg);
16
+ color: var(--matcha-color-disabled-fg);
17
+ box-shadow: 0 0 0 0px inset;
18
+ pointer-events: none;
19
+ }
20
+
21
+ @mixin disabled-basic-button($theme) {
22
+ background: var(--matcha-color-surface);
23
+ color: var(--matcha-color-disabled-bg);
24
+ box-shadow: 0 0 0 0px inset;
25
+ pointer-events: none;
26
+ }
27
+
28
+ @mixin disabled-outline-button($theme) {
29
+ background: transparent;
30
+ color: var(--matcha-color-disabled-bg);
31
+ box-shadow: 0 0 0 var(--matcha-border-thin, 2px) inset;
32
+ pointer-events: none;
33
+ }
34
+
35
+ @mixin disabled-alpha-button($theme) {
36
+ background: var(--matcha-color-disabled-bg-alpha, var(--matcha-color-interaction-disabled-bg-alpha));
37
+ color: var(--matcha-color-disabled-bg);
38
+ box-shadow: 0 0 0 0px inset;
39
+ pointer-events: none;
40
+ }
41
+
42
+ @mixin disabled-link-button($theme) {
43
+ background: transparent;
44
+ color: var(--matcha-color-disabled-bg);
45
+ box-shadow: 0 0 0 0px inset;
46
+ pointer-events: none;
47
+ }
48
+
49
+ // ── Size classes (responsive, aligned with Figma XS/SM/MD/LG/XL) ────────────
50
+ //
51
+ // Cada size define `min-width` igual à sua `height`: o botão nunca fica mais
52
+ // estreito que alto. Sem esse piso a largura é só `padding + conteúdo`, e um
53
+ // label curto ("OK", "X") vira um retângulo VERTICAL — no XS, 23×28. Com o
54
+ // piso o pior caso é um quadrado, com o conteúdo centrado pelo
55
+ // `justify-content: center` do bloco estrutural. Label longo nunca atinge o
56
+ // piso, então nada muda onde já estava certo.
57
+ //
58
+ // Efeito colateral desejado em flex container: o botão tem `overflow: hidden`,
59
+ // o que já anula o `min-width: auto` (a automatic minimum size do flexbox só
60
+ // vale para item com `overflow: visible`) — ou seja, hoje ele encolhe até ~0 e
61
+ // corta o label. O piso troca esse colapso por um mínimo quadrado.
62
+
63
+ @mixin generate-matcha-button-size-classes($helper-breakpoints) {
64
+ @each $breakpoint, $materialBreakpoint in $helper-breakpoints {
65
+ @include media-breakpoint($materialBreakpoint) {
66
+ $infix: if($materialBreakpoint == null, "", "-#{$breakpoint}");
67
+
68
+ // XS/Tiny — 28px. Figma matcha/button XS: pad 8 · gap 2 · text/caption (11/400/14, letter 0.3)
69
+ &[size#{$infix}="xs"],
70
+ &[size#{$infix}="tiny"] {
71
+ padding: 0 var(--matcha-space-100, 8px);
72
+ font-size: var(--matcha-text-p-tiny, 11px);
73
+ font-weight: var(--matcha-weight-regular, 400);
74
+ line-height: var(--matcha-leading-p-tiny, 14px);
75
+ letter-spacing: var(--matcha-letter-spacing-caption, 0.3px);
76
+ height: 28px;
77
+ min-width: 28px;
78
+ gap: var(--matcha-space-025, 2px);
79
+ }
80
+
81
+ // SM/Small — 32px. Figma: pad 8 · gap 4 · text/button-sm (13/500/19 letter 0.2)
82
+ &[size#{$infix}="sm"],
83
+ &[size#{$infix}="small"] {
84
+ padding: 0 var(--matcha-space-100, 8px);
85
+ font-size: var(--matcha-text-label-sm, 13px);
86
+ font-weight: var(--matcha-weight-medium, 500);
87
+ line-height: var(--matcha-leading-label-sm, 19px);
88
+ height: 32px;
89
+ min-width: 32px;
90
+ gap: var(--matcha-space-050, 4px);
91
+ }
92
+
93
+ // MD/Medium — 40px. Figma: pad 16 · gap 8 · text/button-md (14/500/20)
94
+ &[size#{$infix}="md"],
95
+ &[size#{$infix}="medium"] {
96
+ padding: 0 var(--matcha-space-200, 16px);
97
+ font-size: var(--matcha-text-md, 14px);
98
+ font-weight: var(--matcha-weight-medium, 500);
99
+ line-height: var(--matcha-leading-md, 20px);
100
+ height: 40px;
101
+ min-width: 40px;
102
+ gap: var(--matcha-space-100, 8px);
103
+ }
104
+
105
+ // LG/Large — 48px. Figma: pad 24 · gap 8
106
+ &[size#{$infix}="lg"],
107
+ &[size#{$infix}="large"] {
108
+ padding: 0 var(--matcha-space-300, 24px);
109
+ font-size: var(--matcha-text-lg, 16px);
110
+ font-weight: var(--matcha-weight-semibold, 600);
111
+ line-height: var(--matcha-leading-label-lg, 22px);
112
+ height: 48px;
113
+ min-width: 48px;
114
+ gap: var(--matcha-space-100, 8px);
115
+ }
116
+
117
+ // XL/Huge — 56px. Figma: pad 32 · gap 8
118
+ &[size#{$infix}="xl"],
119
+ &[size#{$infix}="huge"] {
120
+ padding: 0 var(--matcha-space-400, 32px);
121
+ font-size: var(--matcha-text-lg, 16px);
122
+ font-weight: var(--matcha-weight-semibold, 600);
123
+ line-height: var(--matcha-leading-label-lg, 22px);
124
+ height: 56px;
125
+ min-width: 56px;
126
+ gap: var(--matcha-space-100, 8px);
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ // ── Structural ──────────────────────────────────────────────────────────────
133
+
134
+ button[matcha-button],
135
+ a[matcha-button],
136
+ button[matcha-icon-button],
137
+ a[matcha-icon-button],
138
+ button[matcha-link],
139
+ a[matcha-link] {
140
+ overflow: hidden;
141
+ position: relative;
142
+ font-size: var(--matcha-text-md, 14px);
143
+ font-weight: var(--matcha-weight-medium, 500);
144
+ font-family: inherit;
145
+ height: px-to-rem(40px); // default MD; overridden by size attribute selectors
146
+ min-width: px-to-rem(40px); // piso quadrado do default MD; ver mixin de sizes
147
+ appearance: auto;
148
+ text-rendering: auto;
149
+ letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
150
+ word-spacing: normal;
151
+ line-height: normal;
152
+ text-transform: none;
153
+ text-indent: 0px;
154
+ text-shadow: none;
155
+ text-align: center;
156
+ text-wrap: nowrap;
157
+ text-decoration: none; // reset do underline default quando o host é <a> (variant != link)
158
+ display: flex;
159
+ justify-content: center;
160
+ align-items: center;
161
+ cursor: pointer;
162
+ box-sizing: border-box;
163
+ border: none;
164
+ border-radius: var(--matcha-radius-lg, 8px);
165
+
166
+ // Figma matcha/button usa DOIS feedbacks combinados:
167
+ // 1. Cor do bg muda (accent-default → accent-hover → accent-pressed)
168
+ // 2. State-layer overlay por cima (matcha/color/state-layer/fg)
169
+ // - Filled: opacity 0.15 hover / 0.30 active
170
+ // - Outline/Ghost: opacity 0.10 hover / 0.20 active
171
+ // ::before aplica o state-layer; :hover/:active ou .is-state-* ativam.
172
+ // NOTA: declarada ANTES das regras aninhadas (evita a deprecação mixed-decls);
173
+ // é onde o hoisting do Sass já a colocava — output byte-idêntico.
174
+ transition:
175
+ background-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
176
+ box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1),
177
+ color 150ms cubic-bezier(0.4, 0, 0.2, 1);
178
+
179
+ * { pointer-events: none; }
180
+
181
+ // Figma spec: ícones dentro de button herdam a foreground color do botão
182
+ // (matcha-icon tem color-default próprio via tema — forçamos inherit com !important
183
+ // pra vencer o tema que aplica em [class^="i-matcha-"] globalmente)
184
+ matcha-icon,
185
+ [class^="i-matcha-"],
186
+ [class*=" i-matcha-"] {
187
+ color: inherit !important;
188
+ }
189
+
190
+ @include generate-matcha-button-size-classes($helper-breakpoints);
191
+
192
+ &::before {
193
+ content: '';
194
+ position: absolute;
195
+ inset: 0;
196
+ background: var(--matcha-color-state-layer-fg, currentColor);
197
+ opacity: 0;
198
+ border-radius: inherit;
199
+ pointer-events: none;
200
+ transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
201
+ }
202
+
203
+ // Filled (variant default) — state-layer forte
204
+ &:hover:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link])::before,
205
+ &.is-state-hover:not([outline]):not([basic]):not([ghost]):not([alpha])::before {
206
+ opacity: 0.15;
207
+ }
208
+ &:active:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link])::before,
209
+ &.is-state-active:not([outline]):not([basic]):not([ghost]):not([alpha])::before {
210
+ opacity: 0.30;
211
+ }
212
+
213
+ // Outline / Ghost — state-layer sutil
214
+ &[outline]:hover:not([disabled]):not([disabled="true"])::before,
215
+ &[basic]:hover:not([disabled]):not([disabled="true"])::before,
216
+ &[ghost]:hover:not([disabled]):not([disabled="true"])::before,
217
+ &[outline].is-state-hover::before,
218
+ &[basic].is-state-hover::before,
219
+ &[ghost].is-state-hover::before {
220
+ opacity: 0.10;
221
+ }
222
+ &[outline]:active:not([disabled]):not([disabled="true"])::before,
223
+ &[basic]:active:not([disabled]):not([disabled="true"])::before,
224
+ &[ghost]:active:not([disabled]):not([disabled="true"])::before,
225
+ &[outline].is-state-active::before,
226
+ &[basic].is-state-active::before,
227
+ &[ghost].is-state-active::before {
228
+ opacity: 0.20;
229
+ }
230
+
231
+ &[disabled] {
232
+ pointer-events: none;
233
+ cursor: default;
234
+ }
235
+
236
+ // ── Variant modifiers (estrutural — cores vêm do mixin de tema) ──
237
+ &[basic]:not([basic="false"]),
238
+ &[ghost]:not([ghost="false"]) {
239
+ background: transparent;
240
+ }
241
+
242
+ &[outline]:not([outline="false"]) {
243
+ background: transparent;
244
+
245
+ &[link]:not([link="false"]) {
246
+ &[size="xs"], &[size="tiny"] { padding: 0 var(--matcha-space-100, 8px); gap: var(--matcha-space-050, 4px); }
247
+ &[size="sm"], &[size="small"] { padding: 0 var(--matcha-space-100, 8px); gap: var(--matcha-space-050, 4px); }
248
+ &[size="md"], &[size="medium"] { padding: 0 var(--matcha-space-150, 12px); gap: var(--matcha-space-100, 8px); }
249
+ &[size="lg"], &[size="large"] { padding: 0 var(--matcha-space-200, 16px); gap: var(--matcha-space-150, 12px); }
250
+ &[size="xl"], &[size="huge"] { padding: 0 var(--matcha-space-200, 16px); gap: var(--matcha-space-150, 12px); }
251
+ }
252
+ }
253
+
254
+ &[pill]:not([pill="false"]) {
255
+ border-radius: var(--matcha-radius-pill, 9999px);
256
+ }
257
+
258
+ &[link]:not([link="false"]) {
259
+ text-transform: initial;
260
+ letter-spacing: 0;
261
+ text-decoration: none;
262
+ padding: 0;
263
+ background: transparent;
264
+ }
265
+
266
+ // Link legado é texto inline (padding 0) — o piso quadrado do size viraria
267
+ // espaço morto clicável em volta do label, então cai aqui.
268
+ // O `:not([outline])` é deliberado: o combo [outline][link] é um botão com
269
+ // borda e padding real (bloco acima), esse MANTÉM o piso.
270
+ &[link]:not([link="false"]):not([outline]) {
271
+ min-width: 0;
272
+ }
273
+
274
+ // ── matcha-link specific (Figma spec 16914:500) ──
275
+ // SM=11/14 Regular letter 0.3 · MD=14/20 Medium · LG=16/22 SemiBold
276
+ // Hover/Active → underline automático. Underline=true → permanente.
277
+ // Disabled → opacity via disabled-fg color (no underline).
278
+ &[matcha-link] {
279
+ height: auto;
280
+ min-height: 0;
281
+ max-height: none;
282
+ min-width: 0; // texto inline não tem piso quadrado (ver mixin de sizes)
283
+ padding: 0;
284
+ background: transparent;
285
+ border-radius: 0;
286
+ text-decoration: none; // reset do underline default do <a>; hover/class re-aplica
287
+
288
+ &[size="sm"], &[size="tiny"] {
289
+ font-size: var(--matcha-text-p-tiny, 11px);
290
+ line-height: var(--matcha-leading-p-tiny, 14px);
291
+ font-weight: var(--matcha-weight-regular, 400);
292
+ letter-spacing: var(--matcha-letter-spacing-caption, 0.3px);
293
+ height: auto;
294
+ max-height: none;
295
+ }
296
+ &[size="md"], &[size="small"] {
297
+ font-size: var(--matcha-text-md, 14px);
298
+ line-height: var(--matcha-leading-md, 20px);
299
+ font-weight: var(--matcha-weight-medium, 500);
300
+ letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
301
+ height: auto;
302
+ max-height: none;
303
+ }
304
+ &[size="lg"], &[size="medium"] {
305
+ font-size: var(--matcha-text-lg, 16px);
306
+ line-height: var(--matcha-leading-label-lg, 22px);
307
+ font-weight: var(--matcha-weight-semibold, 600);
308
+ letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
309
+ height: auto;
310
+ max-height: none;
311
+ }
312
+
313
+ // State layer fica off (link não tem overlay/bg)
314
+ &::before { display: none; }
315
+ // Press scale também off — link é texto
316
+ transition: none;
317
+ &:active:not([disabled]):not([disabled="true"]) { transform: none; }
318
+
319
+ // Hover e active: underline (Figma spec)
320
+ &:hover:not([disabled]):not([disabled="true"]),
321
+ &:active:not([disabled]):not([disabled="true"]) {
322
+ text-decoration: underline;
323
+ }
324
+
325
+ // Underline permanente via property (classe do HostBinding no component)
326
+ &.matcha-link--underline {
327
+ text-decoration: underline;
328
+ }
329
+
330
+ // ── Canonical extended size names (tiny|small|medium|large|huge) ────────
331
+ // matcha-link expõe a escala CANÔNICA por extenso, 1:1 com sm/md/lg
332
+ // (sm→small · md→medium · lg→large). O bloco acima pareia esses nomes com
333
+ // a escala LEGADA do matcha-button (small=md, medium=lg), então
334
+ // re-asseguramos aqui — scoped APENAS ao matcha-link — os mesmos pixels do
335
+ // tier abreviado correspondente (sem mudança visual). tiny→sm tier (menor
336
+ // disponível), huge→lg tier (maior disponível no link).
337
+ &[size="tiny"],
338
+ &[size="small"] {
339
+ font-size: var(--matcha-text-p-tiny, 11px);
340
+ line-height: var(--matcha-leading-p-tiny, 14px);
341
+ font-weight: var(--matcha-weight-regular, 400);
342
+ letter-spacing: var(--matcha-letter-spacing-caption, 0.3px);
343
+ height: auto;
344
+ max-height: none;
345
+ }
346
+ &[size="medium"] {
347
+ font-size: var(--matcha-text-md, 14px);
348
+ line-height: var(--matcha-leading-md, 20px);
349
+ font-weight: var(--matcha-weight-medium, 500);
350
+ letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
351
+ height: auto;
352
+ max-height: none;
353
+ }
354
+ &[size="large"],
355
+ &[size="huge"] {
356
+ font-size: var(--matcha-text-lg, 16px);
357
+ line-height: var(--matcha-leading-label-lg, 22px);
358
+ font-weight: var(--matcha-weight-semibold, 600);
359
+ letter-spacing: var(--matcha-letter-spacing-button, 0.2px);
360
+ height: auto;
361
+ max-height: none;
362
+ }
363
+ }
364
+
365
+ &[icon]:not([icon="false"]) {
366
+ aspect-ratio: 1;
367
+ display: flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+
371
+ // Icon-button padding per Figma spec matcha/icon-button (all-direction):
372
+ // XS=6 · SM=6 · MD=8 · LG=12 · XL=16 (padding igual em todos os lados)
373
+ &[size="xs"], &[size="tiny"] {
374
+ padding: var(--matcha-space-075, 6px) !important;
375
+ }
376
+ &[size="sm"], &[size="small"] {
377
+ padding: var(--matcha-space-075, 6px) !important;
378
+ }
379
+ &[size="md"], &[size="medium"] {
380
+ padding: var(--matcha-space-100, 8px) !important;
381
+ }
382
+ &[size="lg"], &[size="large"] {
383
+ padding: var(--matcha-space-150, 12px) !important;
384
+ }
385
+ &[size="xl"], &[size="huge"] {
386
+ padding: var(--matcha-space-200, 16px) !important;
387
+ }
388
+
389
+ // Auto-size inner matcha-icon conforme Figma matcha/icon-button spec:
390
+ // XS→16 · SM→20 · MD→24 · LG→24 · XL→24
391
+ &[size="xs"] [class^="i-matcha-"],
392
+ &[size="xs"] [class*=" i-matcha-"],
393
+ &[size="tiny"] [class^="i-matcha-"],
394
+ &[size="tiny"] [class*=" i-matcha-"] {
395
+ font-size: var(--matcha-icon-size-sm, 16px);
396
+ width: var(--matcha-icon-size-sm, 16px);
397
+ height: var(--matcha-icon-size-sm, 16px);
398
+ line-height: var(--matcha-icon-size-sm, 16px);
399
+ }
400
+ &[size="sm"] [class^="i-matcha-"],
401
+ &[size="sm"] [class*=" i-matcha-"],
402
+ &[size="small"] [class^="i-matcha-"],
403
+ &[size="small"] [class*=" i-matcha-"] {
404
+ font-size: 20px;
405
+ width: 20px;
406
+ height: 20px;
407
+ line-height: 20px;
408
+ }
409
+ &[size="md"] [class^="i-matcha-"],
410
+ &[size="md"] [class*=" i-matcha-"],
411
+ &[size="medium"] [class^="i-matcha-"],
412
+ &[size="medium"] [class*=" i-matcha-"],
413
+ &[size="lg"] [class^="i-matcha-"],
414
+ &[size="lg"] [class*=" i-matcha-"],
415
+ &[size="large"] [class^="i-matcha-"],
416
+ &[size="large"] [class*=" i-matcha-"],
417
+ &[size="xl"] [class^="i-matcha-"],
418
+ &[size="xl"] [class*=" i-matcha-"],
419
+ &[size="huge"] [class^="i-matcha-"],
420
+ &[size="huge"] [class*=" i-matcha-"] {
421
+ font-size: var(--matcha-icon-size-md, 24px);
422
+ width: var(--matcha-icon-size-md, 24px);
423
+ height: var(--matcha-icon-size-md, 24px);
424
+ line-height: var(--matcha-icon-size-md, 24px);
425
+ }
426
+ }
427
+
428
+ // ── Badge ──
429
+ &[badge]:not([badge="false"]) {
430
+ overflow: visible;
431
+ position: relative;
432
+
433
+ &::after {
434
+ content: "";
435
+ position: absolute;
436
+ top: -0.25rem;
437
+ right: -0.25rem;
438
+ width: 1.32rem;
439
+ height: 1.32rem;
440
+ border-radius: var(--matcha-radius-pill, 9999px);
441
+ z-index: 2;
442
+ }
443
+
444
+ &[size="sm"]::after, &[size="small"]::after { width: 0.75rem; height: 0.75rem; top: -0.15rem; right: -0.15rem; }
445
+ &[size="md"]::after, &[size="medium"]::after { width: 1rem; height: 1rem; top: -0.2rem; right: -0.2rem; }
446
+ &[size="xl"]::after, &[size="huge"]::after { width: 1.5rem; height: 1.5rem; top: -0.3rem; right: -0.3rem; }
447
+ }
448
+
449
+ // ── Focus ring ──
450
+ &:focus-visible:not([disabled]) {
451
+ outline: var(--matcha-state-focus-ring-width, 2px) solid var(--matcha-color-accent, #6200ee);
452
+ outline-offset: var(--matcha-state-focus-ring-offset, 2px);
453
+ }
454
+
455
+ @media (prefers-reduced-motion: reduce) {
456
+ &::before { transition: none; }
457
+ &, &:active:not([disabled]):not([disabled="true"]) { transition: none; transform: none; }
458
+ }
459
+
460
+ // ── Loading state ──
461
+ // Spinner inline do lado direito do conteúdo + content dims pra 60%
462
+ // Preserva width (não shift) e bloqueia cliques com pointer-events.
463
+ &.matcha-button--loading {
464
+ pointer-events: none;
465
+ cursor: progress;
466
+
467
+ // Dim no content interior (tudo exceto o spinner)
468
+ > :not(.matcha-button__spinner) {
469
+ opacity: 0.6;
470
+ }
471
+ }
472
+
473
+ .matcha-button__spinner {
474
+ display: inline-flex;
475
+ align-items: center;
476
+ justify-content: center;
477
+ margin-left: var(--matcha-space-100, 8px);
478
+ flex: none;
479
+ line-height: 0;
480
+
481
+ svg {
482
+ display: block;
483
+ }
484
+
485
+ @media (prefers-reduced-motion: reduce) {
486
+ svg circle animateTransform {
487
+ // Retarda mas não remove (ainda sinaliza loading)
488
+ dur: 2s !important;
489
+ }
490
+ }
491
+ }
492
+
493
+ // Visually hidden span pra screen readers (loading announcement)
494
+ .matcha-button__sr-only {
495
+ position: absolute !important;
496
+ width: 1px !important;
497
+ height: 1px !important;
498
+ padding: 0 !important;
499
+ margin: -1px !important;
500
+ overflow: hidden !important;
501
+ clip: rect(0, 0, 0, 0) !important;
502
+ white-space: nowrap !important;
503
+ border: 0 !important;
504
+ }
505
+
506
+ // ── Touch targets (WCAG 2.5.5: 44x44 mínimo em coarse pointers) ──
507
+ // XS (28), SM (32), MD (40) ficam abaixo de 44px — expande hit area invisível.
508
+ // NÃO re-declarar `position` aqui: o bloco estrutural acima já garante botão
509
+ // positioned (relative), e o ::after ancora em qualquer positioned. Re-assertar
510
+ // com [size] sobe a especificidade pra (0,2,1) e quebra consumidores que
511
+ // reposicionam o botão com absolute (close mobile do notification/banner).
512
+ @media (pointer: coarse) {
513
+ &[size="xs"],
514
+ &[size="sm"], &[size="tiny"],
515
+ &[size="md"], &[size="small"] {
516
+ &::after {
517
+ content: '';
518
+ position: absolute;
519
+ inset: -8px; // expande ~16px em cada eixo → 28+16=44, 32+16=48, 40+16=56 ✓
520
+ z-index: 0;
521
+ pointer-events: auto; // captura cliques aqui
522
+ }
523
+
524
+ // ::before já é usado pro state layer, então nosso overlay vai no ::after
525
+ // State layer (::before) fica colado ao botão visual, overlay (::after) expande hit.
526
+ }
527
+ }
528
+ }
529
+
530
+ // ── Theme mixin ─────────────────────────────────────────────────────────────
531
+
532
+ @mixin matcha-button-theme($theme) {
533
+ // ═════════════════════════════════════════════════════════════════════════
534
+ // CSS var-based theming (regra DS-ARCHITECTURE #7: Tokens over hardcode)
535
+ //
536
+ // Todo botão usa --matcha-button-color como token interno.
537
+ // `[color="X"]` apenas mapeia pra esses tokens — as variantes
538
+ // (filled/outline/ghost/alpha/link) interpretam um único set de vars.
539
+ //
540
+ // Consumidor pode override QUALQUER cor via style:
541
+ // <button matcha-button style="--matcha-button-color: #ff00ff">Custom</button>
542
+ //
543
+ // Adicionar cor nova = adicionar 1 mapping (3 linhas).
544
+ // ═════════════════════════════════════════════════════════════════════════
545
+
546
+ button[matcha-button],
547
+ a[matcha-button],
548
+ button[matcha-icon-button],
549
+ a[matcha-icon-button],
550
+ button[matcha-link],
551
+ a[matcha-link] {
552
+ // Tokens internos (Figma matcha/button 13913:1088):
553
+ // --matcha-button-color = bg do filled (Default)
554
+ // --matcha-button-color-hover = bg filled / stroke outline (Hover)
555
+ // --matcha-button-color-pressed = bg filled / stroke+text (Active)
556
+ // --matcha-button-color-text-on = texto do filled (accent/text-on etc.)
557
+ // --matcha-button-color-alpha = alpha modifier / ripple
558
+ // --matcha-button-color-edge = stroke do outline (Default)
559
+ --matcha-button-color: var(--matcha-color-neutral);
560
+ --matcha-button-color-hover: var(--matcha-color-neutral-hover);
561
+ --matcha-button-color-pressed: var(--matcha-color-neutral-hover);
562
+ --matcha-button-color-text-on: var(--matcha-color-neutral-contrast);
563
+ --matcha-button-color-alpha: var(--matcha-color-neutral-alpha);
564
+ --matcha-button-color-edge: var(--matcha-color-fg);
565
+
566
+ // Badge ring herda o bg do botão (filled) — outline/ghost override pra surface,
567
+ // pois o bg deles é transparente e o ring deve casar com a página atrás.
568
+ --matcha-color-badge-border: var(--matcha-button-color);
569
+
570
+ // ── Color mapping ────────────────────────────────────────────────────────────────
571
+ &[color="basic"] {
572
+ --matcha-button-color: var(--matcha-color-neutral);
573
+ --matcha-button-color-hover: var(--matcha-color-neutral-hover);
574
+ --matcha-button-color-pressed: var(--matcha-color-neutral-hover);
575
+ --matcha-button-color-text-on: var(--matcha-color-neutral-contrast);
576
+ --matcha-button-color-alpha: var(--matcha-color-neutral-alpha);
577
+ --matcha-button-color-edge: var(--matcha-color-fg);
578
+ }
579
+ &[color="primary"] {
580
+ --matcha-button-color: var(--matcha-color-primary);
581
+ --matcha-button-color-hover: var(--matcha-color-primary-hover);
582
+ --matcha-button-color-pressed: var(--matcha-color-primary-pressed);
583
+ --matcha-button-color-text-on: var(--matcha-color-primary-contrast);
584
+ --matcha-button-color-alpha: var(--matcha-color-primary-alpha);
585
+ --matcha-button-color-edge: var(--matcha-color-primary);
586
+ }
587
+ &[color="accent"] {
588
+ --matcha-button-color: var(--matcha-color-accent);
589
+ --matcha-button-color-hover: var(--matcha-color-accent-hover);
590
+ --matcha-button-color-pressed: var(--matcha-color-accent-pressed);
591
+ --matcha-button-color-text-on: var(--matcha-color-accent-text-on);
592
+ --matcha-button-color-alpha: var(--matcha-color-accent-alpha);
593
+ --matcha-button-color-edge: var(--matcha-color-accent);
594
+ }
595
+ &[color="success"] {
596
+ --matcha-button-color: var(--matcha-color-success);
597
+ --matcha-button-color-hover: var(--matcha-color-success-hover);
598
+ --matcha-button-color-pressed: var(--matcha-color-success-pressed);
599
+ --matcha-button-color-text-on: var(--matcha-color-success-contrast);
600
+ --matcha-button-color-alpha: var(--matcha-color-success-alpha);
601
+ --matcha-button-color-edge: var(--matcha-color-success);
602
+ }
603
+ &[color="warn"] {
604
+ --matcha-button-color: var(--matcha-color-warn);
605
+ --matcha-button-color-hover: var(--matcha-color-warn-hover);
606
+ --matcha-button-color-pressed: var(--matcha-color-warn-pressed);
607
+ --matcha-button-color-text-on: var(--matcha-color-warn-contrast);
608
+ --matcha-button-color-alpha: var(--matcha-color-warn-alpha);
609
+ --matcha-button-color-edge: var(--matcha-color-warn);
610
+ }
611
+ &[color="danger"] {
612
+ --matcha-button-color: var(--matcha-color-danger);
613
+ --matcha-button-color-hover: var(--matcha-color-danger-hover);
614
+ --matcha-button-color-pressed: var(--matcha-color-danger-pressed);
615
+ --matcha-button-color-text-on: var(--matcha-color-danger-contrast);
616
+ --matcha-button-color-alpha: var(--matcha-color-danger-alpha);
617
+ --matcha-button-color-edge: var(--matcha-color-danger);
618
+ }
619
+ &[color="info"] {
620
+ --matcha-button-color: var(--matcha-color-info);
621
+ --matcha-button-color-hover: var(--matcha-color-info-hover);
622
+ --matcha-button-color-pressed: var(--matcha-color-info-pressed);
623
+ --matcha-button-color-text-on: var(--matcha-color-info-contrast);
624
+ --matcha-button-color-alpha: var(--matcha-color-info-alpha);
625
+ --matcha-button-color-edge: var(--matcha-color-info);
626
+ }
627
+
628
+ // ── Filled (default variant) ──────────────────────────────────────────────────────
629
+ // Figma: Default=accent · Hover=accent-hover · Active=accent-pressed
630
+ background: var(--matcha-button-color);
631
+ color: var(--matcha-button-color-text-on);
632
+
633
+ &:hover:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link]) {
634
+ background: var(--matcha-button-color-hover);
635
+ }
636
+ &:active:not([disabled]):not([disabled="true"]):not([outline]):not([basic]):not([ghost]):not([link]):not([alpha]):not([matcha-link]) {
637
+ background: var(--matcha-button-color-pressed);
638
+ }
639
+
640
+ // ── Variant: outline ────────────────────────────────────────────────────────────────
641
+ // Figma matcha/button (13913:1088) vs matcha/icon-button (13914:482):
642
+ // BUTTON outline: texto=text-primary · ícones=edge (accent)
643
+ // ICON-BUTTON outline: host color=edge (ícone herda via inherit !important)
644
+ // Hover: icons/host = accent-hover (texto button mantém primary)
645
+ // Active: tudo = accent-pressed
646
+ &[outline]:not([outline="false"]),
647
+ &[variant="outline"] {
648
+ background: transparent;
649
+ box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-button-color-edge);
650
+ // Color slot: text segue --matcha-button-color-edge (border+text matching),
651
+ // com fallback pra text-primary quando consumer não setar a var.
652
+ // Antes era hardcoded text-primary → consumer tinha que duelar specificity.
653
+ color: var(--matcha-button-color-edge);
654
+ // Badge ring transparente — bg é transparente, qualquer cor fixa
655
+ // colide com o bg-do-pai-da-página (impossível detectar em CSS).
656
+ --matcha-color-badge-border: transparent;
657
+
658
+ &:hover:not([disabled]):not([disabled="true"]) {
659
+ box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-button-color-hover);
660
+ }
661
+ &:active:not([disabled]):not([disabled="true"]) {
662
+ box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-button-color-pressed);
663
+ color: var(--matcha-button-color-pressed);
664
+ }
665
+ }
666
+
667
+ // Icon-button outline: host color = edge (ícone é o único conteúdo semântico)
668
+ &[matcha-icon-button][outline]:not([outline="false"]),
669
+ &[matcha-icon-button][variant="outline"] {
670
+ color: var(--matcha-button-color-edge);
671
+
672
+ &:hover:not([disabled]):not([disabled="true"]) {
673
+ color: var(--matcha-button-color-hover);
674
+ }
675
+ &:active:not([disabled]):not([disabled="true"]) {
676
+ color: var(--matcha-button-color-pressed);
677
+ }
678
+ }
679
+
680
+ // Button outline: ícones (Left/Right) acompanham edge — texto mantém primary
681
+ &[matcha-button][outline]:not([outline="false"]) matcha-icon,
682
+ &[matcha-button][outline]:not([outline="false"]) [class^="i-matcha-"],
683
+ &[matcha-button][outline]:not([outline="false"]) [class*=" i-matcha-"],
684
+ &[matcha-button][variant="outline"] matcha-icon,
685
+ &[matcha-button][variant="outline"] [class^="i-matcha-"],
686
+ &[matcha-button][variant="outline"] [class*=" i-matcha-"] {
687
+ color: var(--matcha-button-color-edge) !important;
688
+ }
689
+ &[matcha-button][outline]:hover:not([disabled]):not([disabled="true"]) matcha-icon,
690
+ &[matcha-button][outline]:hover:not([disabled]):not([disabled="true"]) [class^="i-matcha-"],
691
+ &[matcha-button][outline].is-state-hover matcha-icon {
692
+ color: var(--matcha-button-color-hover) !important;
693
+ }
694
+ &[matcha-button][outline]:active:not([disabled]):not([disabled="true"]) matcha-icon,
695
+ &[matcha-button][outline]:active:not([disabled]):not([disabled="true"]) [class^="i-matcha-"],
696
+ &[matcha-button][outline].is-state-active matcha-icon {
697
+ color: var(--matcha-button-color-pressed) !important;
698
+ }
699
+
700
+ // ── Variant: ghost (basic = legacy alias) ────────────────────────────────────────
701
+ // Figma: Default/Hover=text-primary · Active=accent-pressed (texto + ícones)
702
+ // Slot --matcha-button-ghost-text com fallback text-primary (Figma intent
703
+ // preservado). Consumer override via parent: --matcha-button-ghost-text: X
704
+ &[basic]:not([basic="false"]),
705
+ &[ghost]:not([ghost="false"]),
706
+ &[variant="ghost"] {
707
+ background: transparent;
708
+ color: var(--matcha-button-ghost-text, var(--matcha-color-text-primary));
709
+ // Badge ring transparente — bg é transparente, qualquer cor fixa
710
+ // colide com o bg-do-pai-da-página (impossível detectar em CSS).
711
+ --matcha-color-badge-border: transparent;
712
+
713
+ &:active:not([disabled]):not([disabled="true"]) {
714
+ color: var(--matcha-button-color-pressed);
715
+ }
716
+ }
717
+
718
+ // Button ghost: ícones acompanham edge (accent) — texto mantém primary
719
+ &[matcha-button][basic]:not([basic="false"]) matcha-icon,
720
+ &[matcha-button][basic]:not([basic="false"]) [class^="i-matcha-"],
721
+ &[matcha-button][ghost]:not([ghost="false"]) matcha-icon,
722
+ &[matcha-button][ghost]:not([ghost="false"]) [class^="i-matcha-"],
723
+ &[matcha-button][variant="ghost"] matcha-icon,
724
+ &[matcha-button][variant="ghost"] [class^="i-matcha-"] {
725
+ color: var(--matcha-button-color-edge) !important;
726
+ }
727
+ // Hover: ícone mantém a cor de repouso (edge), igual ao texto (Figma:
728
+ // Default/Hover=text-primary). Antes usava --matcha-button-color-hover,
729
+ // um token pensado como cor de FUNDO de hover (ver comentário da linha
730
+ // ~526) sendo aplicado como cor de ÍCONE — funcionava por coincidência em
731
+ // botões soltos, mas junto de um --matcha-color-neutral-hover claro
732
+ // (#d8e1da) o contraste do ícone contra fundos escuros (ex.: matcha-panel)
733
+ // cai pra ~1.34:1, bem abaixo do mínimo WCAG de 3:1 pra ícones.
734
+ &[matcha-button][ghost]:active:not([disabled]):not([disabled="true"]) matcha-icon,
735
+ &[matcha-button][basic]:active:not([disabled]):not([disabled="true"]) matcha-icon,
736
+ &[matcha-button][ghost].is-state-active matcha-icon,
737
+ &[matcha-button][basic].is-state-active matcha-icon {
738
+ color: var(--matcha-button-color-pressed) !important;
739
+ }
740
+
741
+ // ── Icon-button neutral (basic/default) — Figma matcha/icon-button ──
742
+ // Outline/Ghost Default (13914:179 / 13914:332) vinculam o glyph — e a
743
+ // borda do Outline — a matcha/color/icon/default (#75767a light / #9aa0a6
744
+ // dark), o MESMO token do matcha-icon solto. O texto do botão usa fg/
745
+ // text-primary; o ícone do icon-button NÃO. Redireciona edge + ghost-text
746
+ // só pro icon-button neutro. Cores override (accent/danger/...) seguem a
747
+ // própria cor via os blocos &[color="..."] acima.
748
+ &[matcha-icon-button]:not([color]),
749
+ &[matcha-icon-button][color="basic"] {
750
+ --matcha-button-color-edge: var(--matcha-color-icon-default);
751
+ --matcha-button-ghost-text: var(--matcha-color-icon-default);
752
+ }
753
+
754
+ // ── Modifier: alpha ──────────────────────────────────────────────
755
+ &[alpha]:not([alpha="false"]) {
756
+ background: var(--matcha-button-color-alpha);
757
+ color: var(--matcha-button-color);
758
+ }
759
+
760
+ // ── Modifier: link (legacy, afeta button[link="true"]) ───────────
761
+ // matcha-link tem seu próprio bloco (&[matcha-link]) com regras
762
+ // próprias de underline (hover + .matcha-link--underline)
763
+ &[link]:not([link="false"]):not([matcha-link]) {
764
+ background: transparent;
765
+ color: var(--matcha-button-color);
766
+ text-decoration: underline;
767
+ }
768
+ // matcha-link: apenas cor do texto (o bloco estrutural [matcha-link]
769
+ // define text-decoration: none + hover/active + class underline)
770
+ &[matcha-link] {
771
+ color: var(--matcha-button-color);
772
+ }
773
+
774
+ // ── State: disabled ──────────────────────────────────────────────
775
+ // Figma (13913:1088) Disabled state:
776
+ // Filled: bg=interaction/disabled-bg · label+icons=accent/text-on (mesma cor do filled default)
777
+ // Outline: stroke=interaction/disabled-bg (NÃO disabled-border) · label+icons=disabled-fg
778
+ // Ghost: sem bg/stroke · label+icons=disabled-fg
779
+ // State-layer overlay DESLIGADO em todos disabled.
780
+ &[disabled]:not([disabled="false"]),
781
+ &[disabled="true"] {
782
+ pointer-events: none;
783
+ cursor: not-allowed;
784
+
785
+ // state-layer overlay desligado
786
+ &::before { opacity: 0 !important; }
787
+
788
+ // Filled — bg disabled + texto/ícones accent-text-on (Figma spec)
789
+ background: var(--matcha-color-interaction-disabled-bg);
790
+ color: var(--matcha-color-accent-text-on);
791
+ box-shadow: none;
792
+
793
+ // Outline — stroke usa mesma cor do filled-bg (Figma usa interaction/disabled-bg)
794
+ &[outline]:not([outline="false"]),
795
+ &[variant="outline"] {
796
+ background: transparent;
797
+ color: var(--matcha-color-interaction-disabled-fg);
798
+ box-shadow: inset 0 0 0 var(--matcha-border-thin, 2px) var(--matcha-color-interaction-disabled-bg);
799
+ }
800
+
801
+ // Ghost / basic / link — só texto dedicado
802
+ &[basic]:not([basic="false"]),
803
+ &[ghost]:not([ghost="false"]),
804
+ &[variant="ghost"],
805
+ &[link]:not([link="false"]) {
806
+ background: transparent;
807
+ color: var(--matcha-color-interaction-disabled-fg);
808
+ box-shadow: none;
809
+ }
810
+
811
+ // Alpha — bg alpha dedicado
812
+ &[alpha]:not([alpha="false"]) {
813
+ background: var(--matcha-color-interaction-disabled-bg-alpha);
814
+ color: var(--matcha-color-interaction-disabled-fg);
815
+ }
816
+
817
+ // Ícones do button (Left/Right) no disabled:
818
+ // Filled: herdam color do host (accent-text-on) ✓
819
+ // Outline/Ghost: forçam disabled-fg pra sobrescrever regra !important do edge
820
+ &[matcha-button][outline] matcha-icon,
821
+ &[matcha-button][outline] [class^="i-matcha-"],
822
+ &[matcha-button][outline] [class*=" i-matcha-"],
823
+ &[matcha-button][basic] matcha-icon,
824
+ &[matcha-button][basic] [class^="i-matcha-"],
825
+ &[matcha-button][ghost] matcha-icon,
826
+ &[matcha-button][ghost] [class^="i-matcha-"],
827
+ &[matcha-button][variant="outline"] matcha-icon,
828
+ &[matcha-button][variant="outline"] [class^="i-matcha-"],
829
+ &[matcha-button][variant="ghost"] matcha-icon,
830
+ &[matcha-button][variant="ghost"] [class^="i-matcha-"] {
831
+ color: var(--matcha-color-interaction-disabled-fg) !important;
832
+ }
833
+ }
834
+
835
+ // ── Badge indicator (always accent, pattern fixo) ────────────────
836
+ &[badge]:not([badge="false"])::after {
837
+ background: var(--matcha-color-accent);
838
+ }
839
+
840
+ // ── Ripple overlay ───────────────────────────────────────────────
841
+ .ripple {
842
+ background: var(--matcha-button-color-alpha);
843
+ }
844
+ }
845
+ }
846
+
847
+ // ── matcha-icon-button: auto-size inner icons per Figma spec ────────────────
848
+ // XS button (28) → icon 16 · SM (32) → 20 · MD (40)/LG (48)/XL (56) → 24
849
+ button[matcha-icon-button],
850
+ a[matcha-icon-button] {
851
+ aspect-ratio: 1;
852
+
853
+ matcha-icon {
854
+ flex: none; // NÃO expandir com o button
855
+ }
856
+
857
+ &[size="xs"] matcha-icon,
858
+ &[size="tiny"] matcha-icon {
859
+ font-size: 16px !important;
860
+ width: 16px !important;
861
+ height: 16px !important;
862
+ line-height: 16px !important;
863
+ }
864
+ &[size="sm"] matcha-icon,
865
+ &[size="small"] matcha-icon {
866
+ font-size: 20px !important;
867
+ width: 20px !important;
868
+ height: 20px !important;
869
+ line-height: 20px !important;
870
+ }
871
+ &[size="md"] matcha-icon,
872
+ &[size="medium"] matcha-icon,
873
+ &[size="lg"] matcha-icon,
874
+ &[size="large"] matcha-icon,
875
+ &[size="xl"] matcha-icon,
876
+ &[size="huge"] matcha-icon {
877
+ font-size: 24px !important;
878
+ width: 24px !important;
879
+ height: 24px !important;
880
+ line-height: 24px !important;
881
+ }
882
+
883
+ // Tamanho (height) e padding por size já vêm corretos do mixin compartilhado
884
+ // (generate-matcha-button-size-classes, aplicado via o seletor combinado no
885
+ // topo do arquivo) + do bloco &[icon] acima — ambos pareiam cada abreviação
886
+ // com seu extenso correto (sm↔small, md↔medium, lg↔large, xl↔huge). Este
887
+ // componente não precisa reafirmar nada aqui.
888
+ }
889
+
890
+ // ── matcha-button COM TEXTO: auto-size do matcha-icon projetado ──────────────
891
+ // O icon-button (acima) e o matcha-button[icon] (icon-only, no bloco &[icon]) já escalavam o
892
+ // ícone; o botão com texto não escalava nada, então o matcha-icon caía no próprio default
893
+ // (--matcha-icon-size-md, 24px) — proporcional só de MD (40px) pra cima. Num tiny (28px, texto
894
+ // 11/14) sobrava um ícone de 24px.
895
+ //
896
+ // Escala igual à do icon-button: XS/tiny → 16 · SM/small → 20 · MD+ → 24 (o default do
897
+ // matcha-icon já é 24, então MD/LG/XL não precisam de regra).
898
+ //
899
+ // Duas diferenças deliberadas em relação ao icon-button:
900
+ // 1. `matcha-icon:not([size])` — aqui o ícone divide o botão com o texto, e um `size`
901
+ // explícito do consumidor é escolha legítima que deve continuar valendo. O auto-size só
902
+ // age quando ninguém decidiu.
903
+ // 2. sem `!important`, consequência do item 1: como a regra nem casa quando há `size`, não há
904
+ // disputa de especificidade contra a classe `.i-size-*` que o componente emite.
905
+ //
906
+ // `:not([icon])` e `:not([matcha-icon-button])` preservam os dois casos que já tinham
907
+ // tratamento próprio. O bloco icon-only usa `[icon]:not([icon="false"])`, então em teoria
908
+ // `icon="false"` é botão com texto e deveria entrar aqui; não há um único uso dessa forma no
909
+ // showcase nem no painel, e um seletor extra só para ela seria ruído.
910
+ button[matcha-button]:not([matcha-icon-button]):not([icon]),
911
+ a[matcha-button]:not([matcha-icon-button]):not([icon]) {
912
+ &[size="xs"] matcha-icon:not([size]),
913
+ &[size="tiny"] matcha-icon:not([size]) {
914
+ font-size: var(--matcha-icon-size-sm, 16px);
915
+ width: var(--matcha-icon-size-sm, 16px);
916
+ height: var(--matcha-icon-size-sm, 16px);
917
+ line-height: var(--matcha-icon-size-sm, 16px);
918
+ }
919
+
920
+ // 20px não tem token (a escala de ícone vai 16/24/32) — mesmo literal que o icon-button usa.
921
+ &[size="sm"] matcha-icon:not([size]),
922
+ &[size="small"] matcha-icon:not([size]) {
923
+ font-size: 20px;
924
+ width: 20px;
925
+ height: 20px;
926
+ line-height: 20px;
927
+ }
928
+ }
929
+
930
+ // ── Forced colors (Windows High Contrast) ──
931
+ // Buttons precisam ser claramente acionáveis em high-contrast.
932
+ @media (forced-colors: active) {
933
+ button[matcha-button],
934
+ a[matcha-button],
935
+ button[matcha-icon-button],
936
+ a[matcha-icon-button] {
937
+ // Filled: usa botão de sistema (ButtonFace/ButtonText) — afford clicável.
938
+ background: ButtonFace !important;
939
+ color: ButtonText !important;
940
+ border: 1px solid ButtonText !important;
941
+ box-shadow: none !important;
942
+
943
+ // Outline/ghost/alpha/link — bg transparente, border CanvasText.
944
+ &[outline], &[basic], &[alpha], &[link] {
945
+ background: transparent !important;
946
+ border-color: CanvasText !important;
947
+ color: CanvasText !important;
948
+ }
949
+
950
+ // Disabled — GrayText system token.
951
+ &[disabled],
952
+ &.is-disabled {
953
+ color: GrayText !important;
954
+ border-color: GrayText !important;
955
+ }
956
+
957
+ &:focus-visible {
958
+ outline: 2px solid CanvasText !important;
959
+ outline-offset: 2px;
960
+ }
961
+ }
962
+ }
963
+
964
+ // ── Print ──
965
+ // Buttons hide (interativos, não fazem sentido em papel).
966
+ @media print {
967
+ button[matcha-button],
968
+ a[matcha-button],
969
+ button[matcha-icon-button],
970
+ a[matcha-icon-button] {
971
+ display: none !important;
972
+ }
973
+ }