fokus-styles 2.3.0 → 2.5.0

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 (101) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/css/components.css +583 -83
  3. package/dist/css/components.css.map +1 -1
  4. package/dist/css/components.min.css +1 -1
  5. package/dist/css/components.min.css.map +1 -1
  6. package/dist/css/fokus-components.css +583 -83
  7. package/dist/css/fokus-components.css.map +1 -1
  8. package/dist/css/fokus-components.min.css +1 -1
  9. package/dist/css/fokus-components.min.css.map +1 -1
  10. package/dist/css/fokus-core.css +22 -0
  11. package/dist/css/fokus-core.css.map +1 -1
  12. package/dist/css/fokus-core.min.css +1 -1
  13. package/dist/css/fokus-core.min.css.map +1 -1
  14. package/dist/css/fokus-dark.css +10 -0
  15. package/dist/css/fokus-dark.css.map +1 -1
  16. package/dist/css/fokus-dark.min.css +1 -1
  17. package/dist/css/fokus-dark.min.css.map +1 -1
  18. package/dist/css/fokus-rtl.css +9166 -0
  19. package/dist/css/fokus-rtl.css.map +1 -0
  20. package/dist/css/fokus-rtl.min.css +2 -0
  21. package/dist/css/fokus-rtl.min.css.map +1 -0
  22. package/dist/css/fokus-utilities.css +158 -0
  23. package/dist/css/fokus-utilities.css.map +1 -1
  24. package/dist/css/fokus-utilities.min.css +1 -1
  25. package/dist/css/fokus-utilities.min.css.map +1 -1
  26. package/dist/css/fokus.css +1018 -258
  27. package/dist/css/fokus.css.map +1 -1
  28. package/dist/css/fokus.min.css +1 -1
  29. package/dist/css/fokus.min.css.map +1 -1
  30. package/dist/css/forms.css +322 -176
  31. package/dist/css/forms.css.map +1 -1
  32. package/dist/css/forms.min.css +1 -1
  33. package/dist/css/forms.min.css.map +1 -1
  34. package/dist/css/helpers.css +158 -0
  35. package/dist/css/helpers.css.map +1 -1
  36. package/dist/css/helpers.min.css +1 -1
  37. package/dist/css/helpers.min.css.map +1 -1
  38. package/dist/css/layout.css +22 -0
  39. package/dist/css/layout.css.map +1 -1
  40. package/dist/css/layout.min.css +1 -1
  41. package/dist/css/layout.min.css.map +1 -1
  42. package/dist/js/fokus.js +270 -25
  43. package/dist/js/fokus.js.map +2 -2
  44. package/dist/js/fokus.min.js +18 -18
  45. package/dist/js/fokus.min.js.map +3 -3
  46. package/docs/components/alert.md +69 -24
  47. package/docs/components/badge.md +23 -4
  48. package/docs/components/checkbox.md +16 -3
  49. package/docs/components/popover.md +20 -7
  50. package/docs/components/radio.md +11 -1
  51. package/docs/components/responsive.md +19 -0
  52. package/docs/components/switch.md +30 -8
  53. package/docs/components/tag.md +19 -4
  54. package/docs/components/toast.md +72 -59
  55. package/docs/components/tooltip.md +38 -2
  56. package/docs/guides/accessibility.md +7 -0
  57. package/docs/guides/rtl-and-system-preferences.md +28 -0
  58. package/docs/prompts/prompt-plan.md +4 -4
  59. package/docs/reference/accessibility-matrix.md +7 -7
  60. package/docs/reference/contrast-report.md +4 -0
  61. package/docs/reference/definitions.md +5 -1
  62. package/docs/reference/design-tokens.md +14 -0
  63. package/docs/reference/size-baseline.json +13 -13
  64. package/mockup/assets/example-theme.css +11 -0
  65. package/mockup/assets/showcase-contracts.js +6 -6
  66. package/mockup/examples/alerts.html +105 -31
  67. package/mockup/examples/badges-alerts.html +91 -59
  68. package/mockup/examples/check-radio-switch.html +575 -99
  69. package/mockup/examples/popover.html +81 -0
  70. package/mockup/examples/tag.html +97 -87
  71. package/mockup/examples/toast.html +113 -0
  72. package/mockup/examples/tooltip.html +68 -0
  73. package/mockup/forms.html +1 -1
  74. package/mockup/overlays-commands.html +2 -2
  75. package/package.json +2 -1
  76. package/packages/fokus-components/scss/components/_alerts.scss +128 -6
  77. package/packages/fokus-components/scss/components/_badges.scss +26 -10
  78. package/packages/fokus-components/scss/components/_index.scss +1 -0
  79. package/packages/fokus-components/scss/components/_popover.scss +131 -21
  80. package/packages/fokus-components/scss/components/_responsive.scss +23 -0
  81. package/packages/fokus-components/scss/components/_tag.scss +37 -14
  82. package/packages/fokus-components/scss/components/_toasts.scss +156 -11
  83. package/packages/fokus-components/scss/components/_tooltips.scss +70 -18
  84. package/packages/fokus-components/scss/forms/_check-radio-switch.scss +313 -215
  85. package/packages/fokus-components/scss/forms/_forms.scss +16 -0
  86. package/packages/fokus-core/scss/base/_accessibility.scss +14 -0
  87. package/packages/fokus-core/scss/tokens/_root.scss +11 -0
  88. package/packages/fokus-js/js/carousel.d.ts +4 -0
  89. package/packages/fokus-js/js/carousel.js +8 -0
  90. package/packages/fokus-js/js/core/positioning.js +18 -9
  91. package/packages/fokus-js/js/dropdown.d.ts +4 -0
  92. package/packages/fokus-js/js/dropdown.js +7 -0
  93. package/packages/fokus-js/js/popover.js +26 -1
  94. package/packages/fokus-js/js/tabs.d.ts +3 -0
  95. package/packages/fokus-js/js/tabs.js +5 -0
  96. package/packages/fokus-js/js/tag.js +17 -0
  97. package/packages/fokus-js/js/toast.js +77 -3
  98. package/packages/fokus-js/js/tooltip.d.ts +8 -1
  99. package/packages/fokus-js/js/tooltip.js +106 -13
  100. package/packages/fokus-utilities/scss/utilities/_api.scss +48 -0
  101. package/scss/entries/rtl-entry.scss +11 -0
@@ -1,25 +1,65 @@
1
1
  @use "../../../fokus-core/scss/settings" as *;
2
2
  @use "../../../fokus-core/scss/tools" as *;
3
3
 
4
- // Checkbox/radio/switch 100% CSS, reusando a técnica de input oculto +
5
- // label irmã do Segmented Control (_segmented-control.scss) e do Rating
6
- // (_rating.scss): `<input>` clipado visualmente (não `display: none` — teclado
7
- // e leitor de tela continuam enxergando o controle) + `<label for="id">`
8
- // como irmã, que desenha a caixa/trilho via `::before` e a marca/knob via
9
- // `::after`. O `<label>` é o alvo de clique/hover/estado, mas o input
10
- // continua sendo o elemento focável e semântico (checkbox/radio nativos).
11
- // Wrapper (`.fs-check`/`.fs-radio`/`.fs-switch`) carrega o tamanho
12
- // (`-sm`/`-lg`) e agrupa input+label, no mesmo papel de `.fs-segmented-item`.
4
+ // Controles nativos: o input preserva semântica e teclado; o label fornece
5
+ // somente a camada visual. Todas as dimensões públicas são tokens locais para
6
+ // permitir customização por instância sem !important.
13
7
  @layer components {
14
8
  .fs-check,
15
9
  .fs-radio,
16
10
  .fs-switch {
17
11
  display: inline-flex;
18
12
  align-items: center;
13
+ min-inline-size: 0;
19
14
  }
20
15
 
21
16
  .fs-check {
22
- --fs-check-mark: var(--fs-color-on-primary);
17
+ --fs-check-size: 18px;
18
+ --fs-check-border-width: 2px;
19
+ --fs-check-radius: var(--fs-radius-sm);
20
+ --fs-check-gap: var(--fs-space-2);
21
+ --fs-check-mark-color: var(--fs-color-on-primary);
22
+ --fs-control-hover-bg: var(--fs-color-subtle);
23
+ --fs-control-active-bg: var(--fs-alert-primary-bg);
24
+ --fs-control-error-color: var(--fs-color-danger);
25
+ --fs-control-success-color: var(--fs-color-success);
26
+ }
27
+
28
+ .fs-radio {
29
+ --fs-radio-size: 18px;
30
+ --fs-radio-dot-size: 8px;
31
+ --fs-radio-radius: 50%;
32
+ --fs-radio-gap: var(--fs-space-2);
33
+ --fs-control-hover-bg: var(--fs-color-subtle);
34
+ --fs-control-active-bg: var(--fs-alert-primary-bg);
35
+ --fs-control-error-color: var(--fs-color-danger);
36
+ --fs-control-success-color: var(--fs-color-success);
37
+ }
38
+
39
+ .fs-switch {
40
+ --fs-switch-track-width: 40px;
41
+ --fs-switch-track-height: 24px;
42
+ --fs-switch-thumb-width: 18px;
43
+ --fs-switch-thumb-height: 18px;
44
+ --fs-switch-thumb-offset: 3px;
45
+ --fs-switch-on-offset: 19px;
46
+ --fs-switch-radius: var(--fs-radius-md);
47
+ --fs-switch-thumb-radius: var(--fs-radius-sm);
48
+ --fs-switch-gap: var(--fs-space-2);
49
+ --fs-switch-track-bg: var(--fs-color-subtle);
50
+ --fs-switch-track-border: var(--fs-color-border-default);
51
+ --fs-switch-track-checked-bg: var(--fs-color-primary);
52
+ --fs-switch-thumb-bg: var(--fs-color-surface);
53
+ --fs-switch-thumb-border: var(--fs-color-border-default);
54
+ --fs-switch-thumb-shadow: var(--fs-shadow-sm);
55
+ --fs-switch-track-shadow: inset 0 1px 1px rgb(15 23 42 / 12%);
56
+ --fs-switch-hover-ring: color-mix(in srgb, var(--fs-color-primary) 18%, transparent);
57
+ --fs-switch-hover-shadow: 0 0 0 4px var(--fs-switch-hover-ring);
58
+ --fs-switch-active-scale: 0.92;
59
+ --fs-control-hover-bg: var(--fs-color-subtle);
60
+ --fs-control-active-bg: var(--fs-alert-primary-bg);
61
+ --fs-control-error-color: var(--fs-color-danger);
62
+ --fs-control-success-color: var(--fs-color-success);
23
63
  }
24
64
 
25
65
  .fs-check-input,
@@ -42,75 +82,95 @@
42
82
  position: relative;
43
83
  display: inline-flex;
44
84
  align-items: center;
45
- min-height: 20px;
46
- padding-left: 28px;
85
+ min-block-size: 24px;
86
+ min-inline-size: 24px;
47
87
  color: var(--fs-color-text);
48
88
  font-size: $font-size-md;
89
+ line-height: 1.35;
49
90
  cursor: pointer;
50
91
  user-select: none;
51
92
  }
52
93
 
53
- // Caixa (checkbox/radio) ou trilho (switch), desenhados no `::before` da
54
- // label. Radio é a exceção redonda da regra anti-circular do projeto
55
- // (junto do spinner) — ver docs/internal/plans.
94
+ .fs-check-label {
95
+ padding-inline-start: calc(var(--fs-check-size) + var(--fs-check-gap));
96
+ }
97
+
98
+ .fs-radio-label {
99
+ padding-inline-start: calc(var(--fs-radio-size) + var(--fs-radio-gap));
100
+ }
101
+
102
+ .fs-switch-label {
103
+ padding-inline-start: calc(var(--fs-switch-track-width) + var(--fs-switch-gap));
104
+ }
105
+
56
106
  .fs-check-label::before,
57
107
  .fs-radio-label::before,
58
108
  .fs-switch-label::before {
59
109
  position: absolute;
60
- left: 0;
110
+ inset-block-start: 50%;
61
111
  box-sizing: border-box;
62
- border: 1px solid var(--fs-color-border);
112
+ border: var(--fs-border-width) solid var(--fs-color-border);
63
113
  background-color: var(--fs-color-surface);
64
114
  content: "";
65
- transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
66
- }
67
-
68
- .fs-check-label::before,
69
- .fs-radio-label::before {
70
- width: 18px;
71
- height: 18px;
115
+ transform: translateY(-50%);
116
+ transition:
117
+ background-color var(--fs-transition-fast) var(--fs-ease-standard),
118
+ border-color var(--fs-transition-fast) var(--fs-ease-standard),
119
+ box-shadow var(--fs-transition-fast) var(--fs-ease-standard);
72
120
  }
73
121
 
74
122
  .fs-check-label::before {
75
- border: 2px solid var(--fs-color-primary);
76
- border-radius: 0;
123
+ inset-inline-start: 0;
124
+ width: var(--fs-check-size);
125
+ height: var(--fs-check-size);
126
+ border-width: var(--fs-check-border-width);
127
+ border-radius: var(--fs-check-radius);
77
128
  }
78
129
 
79
130
  .fs-radio-label::before {
80
- border-radius: 50%;
131
+ inset-inline-start: 0;
132
+ width: var(--fs-radio-size);
133
+ height: var(--fs-radio-size);
134
+ border-radius: var(--fs-radio-radius);
81
135
  }
82
136
 
83
- // Marca de check (dois traços em "V", via borda rotacionada). Os estados
84
- // indeterminado e inválido substituem essa geometria mais abaixo.
85
- .fs-check-label::after {
137
+ .fs-check-label::after,
138
+ .fs-radio-label::after,
139
+ .fs-switch-label::after {
86
140
  position: absolute;
87
- left: 4px;
88
- width: 9px;
89
- height: 5px;
90
141
  content: "";
91
142
  opacity: 0;
92
- border-bottom: 2px solid var(--fs-check-mark);
93
- border-left: 2px solid var(--fs-check-mark);
94
- transform: translateY(-1px) rotate(-45deg);
95
- transition: opacity 0.1s ease-in-out;
143
+ transition:
144
+ opacity var(--fs-transition-fast) var(--fs-ease-standard),
145
+ background-color var(--fs-transition-fast) var(--fs-ease-standard),
146
+ inset-inline-start var(--fs-transition-fast) var(--fs-ease-standard),
147
+ transform var(--fs-transition-fast) var(--fs-ease-standard);
148
+ }
149
+
150
+ .fs-check-label::after {
151
+ inset-inline-start: calc((var(--fs-check-size) - 9px) / 2);
152
+ inset-block-start: 50%;
153
+ width: 9px;
154
+ height: 5px;
155
+ border-block-end: 2px solid var(--fs-check-mark-color);
156
+ border-inline-start: 2px solid var(--fs-check-mark-color);
157
+ transform: translateY(-65%) rotate(-45deg);
96
158
  }
97
159
 
98
160
  .fs-radio-label::after {
99
- position: absolute;
100
- left: 5px;
101
- width: 8px;
102
- height: 8px;
161
+ inset-inline-start: calc((var(--fs-radio-size) - var(--fs-radio-dot-size)) / 2);
162
+ inset-block-start: 50%;
163
+ width: var(--fs-radio-dot-size);
164
+ height: var(--fs-radio-dot-size);
103
165
  border-radius: 50%;
104
- content: "";
105
- opacity: 0;
106
166
  background-color: var(--fs-color-on-primary);
107
- transition: opacity 0.1s ease-in-out;
167
+ transform: translateY(-50%);
108
168
  }
109
169
 
110
170
  .fs-check-input:checked + .fs-check-label::before,
111
171
  .fs-radio-input:checked + .fs-radio-label::before {
112
- background-color: var(--fs-color-primary);
113
172
  border-color: var(--fs-color-primary);
173
+ background-color: var(--fs-color-primary);
114
174
  }
115
175
 
116
176
  .fs-check-input:checked + .fs-check-label::after,
@@ -118,246 +178,284 @@
118
178
  opacity: 1;
119
179
  }
120
180
 
121
- .fs-check-input:not(:checked, :indeterminate, :disabled, .is-invalid) + .fs-check-label:hover::before {
122
- background-color: color-mix(in srgb, var(--fs-color-primary) 10%, transparent);
123
- }
124
-
125
- .fs-check-input:not(:disabled, :indeterminate, .is-invalid) + .fs-check-label:active::before {
126
- background-color: color-mix(in srgb, var(--fs-color-primary) 86%, var(--fs-color-black));
127
- border-color: var(--fs-color-primary);
181
+ .fs-check-input:not(:disabled, :indeterminate, .is-invalid) + .fs-check-label:hover::before,
182
+ .fs-radio-input:not(:disabled, .is-invalid) + .fs-radio-label:hover::before,
183
+ .fs-switch-input:not(:disabled, .is-invalid) + .fs-switch-label:hover::before {
184
+ background-color: var(--fs-control-hover-bg);
128
185
  }
129
186
 
130
- .fs-check-input:checked:not(:disabled, .is-invalid) + .fs-check-label:active::after {
131
- transform: translateY(-1px) rotate(-45deg) scale(0.9);
187
+ .fs-check-input:checked:not(:disabled, .is-invalid) + .fs-check-label:hover::before,
188
+ .fs-radio-input:checked:not(:disabled, .is-invalid) + .fs-radio-label:hover::before,
189
+ .fs-switch-input:checked:not(:disabled, .is-invalid) + .fs-switch-label:hover::before {
190
+ background-color: color-mix(in srgb, var(--fs-color-primary) 88%, var(--fs-color-black));
132
191
  }
133
192
 
134
- // Switch: trilho retangular arredondado (não pílula) e knob também
135
- // retangular arredondado — decisão de forma do projeto (nada circular além
136
- // do radio/spinner). Desliza via `left` no `::after`.
137
- .fs-switch-label {
138
- padding-left: 44px;
193
+ .fs-check-input:not(:disabled, .is-invalid) + .fs-check-label:active::before,
194
+ .fs-radio-input:not(:disabled, .is-invalid) + .fs-radio-label:active::before,
195
+ .fs-switch-input:not(:disabled, .is-invalid) + .fs-switch-label:active::before {
196
+ background-color: var(--fs-control-active-bg);
197
+ border-color: var(--fs-color-primary);
139
198
  }
140
199
 
141
200
  .fs-switch-label::before {
142
- width: 36px;
143
- height: 20px;
144
- border-radius: var(--fs-radius-md);
201
+ inset-inline-start: 0;
202
+ width: var(--fs-switch-track-width);
203
+ height: var(--fs-switch-track-height);
204
+ border-color: var(--fs-switch-track-border);
205
+ border-radius: var(--fs-switch-radius);
206
+ background-color: var(--fs-switch-track-bg);
207
+ box-shadow: var(--fs-switch-track-shadow);
145
208
  }
146
209
 
147
210
  .fs-switch-label::after {
148
- position: absolute;
149
- left: 2px;
150
- width: 16px;
151
- height: 14px;
152
- border-radius: var(--fs-radius-sm);
153
- content: "";
154
- background-color: var(--fs-color-border);
155
- transition: left 0.15s ease-in-out, background-color 0.15s ease-in-out;
211
+ inset-inline-start: var(--fs-switch-thumb-offset);
212
+ inset-block-start: 50%;
213
+ width: var(--fs-switch-thumb-width);
214
+ height: var(--fs-switch-thumb-height);
215
+ border-radius: var(--fs-switch-thumb-radius);
216
+ border: var(--fs-border-width) solid var(--fs-switch-thumb-border);
217
+ background-color: var(--fs-switch-thumb-bg);
218
+ opacity: 1;
219
+ transform: translateY(-50%);
220
+ box-shadow: var(--fs-switch-thumb-shadow);
156
221
  }
157
222
 
158
223
  .fs-switch-input:checked + .fs-switch-label::before {
159
- background-color: var(--fs-color-primary);
160
- border-color: var(--fs-color-primary);
224
+ border-color: var(--fs-switch-track-checked-bg);
225
+ background-color: var(--fs-switch-track-checked-bg);
226
+ box-shadow: none;
161
227
  }
162
228
 
163
229
  .fs-switch-input:checked + .fs-switch-label::after {
164
- left: 18px;
230
+ inset-inline-start: var(--fs-switch-on-offset);
231
+ border-color: var(--fs-color-on-primary);
165
232
  background-color: var(--fs-color-on-primary);
166
233
  }
167
234
 
168
- // Foco: anel sobre a caixa/trilho (`::before`), não sobre o texto.
235
+ .fs-switch-input:not(:disabled, .is-invalid) + .fs-switch-label:hover::before {
236
+ box-shadow: var(--fs-switch-hover-shadow), var(--fs-switch-track-shadow);
237
+ }
238
+
239
+ .fs-switch-input:checked:not(:disabled, .is-invalid) + .fs-switch-label:hover::before {
240
+ box-shadow: var(--fs-switch-hover-shadow);
241
+ }
242
+
243
+ .fs-switch-input:not(:disabled) + .fs-switch-label:active::after {
244
+ transform: translateY(-50%) scale(var(--fs-switch-active-scale));
245
+ }
246
+
169
247
  .fs-check-input:focus-visible + .fs-check-label::before,
170
248
  .fs-radio-input:focus-visible + .fs-radio-label::before,
171
249
  .fs-switch-input:focus-visible + .fs-switch-label::before {
172
250
  @include focus-ring;
173
251
  }
174
252
 
253
+ .fs-check-input:disabled + .fs-check-label,
175
254
  .fs-radio-input:disabled + .fs-radio-label,
176
255
  .fs-switch-input:disabled + .fs-switch-label {
177
- opacity: 0.5;
256
+ color: var(--fs-color-muted);
257
+ cursor: default;
258
+ opacity: var(--fs-disabled-opacity);
178
259
  pointer-events: none;
179
260
  }
180
261
 
181
- .fs-check-input:disabled + .fs-check-label {
182
- color: var(--fs-color-muted);
183
- opacity: 1;
184
- pointer-events: none;
262
+ .fs-check-input:disabled + .fs-check-label::before,
263
+ .fs-radio-input:disabled + .fs-radio-label::before,
264
+ .fs-switch-input:disabled + .fs-switch-label::before {
265
+ border-color: var(--fs-switch-track-border);
266
+ background-color: var(--fs-switch-track-bg);
267
+ box-shadow: none;
185
268
  }
186
269
 
187
- .fs-check-input:disabled:not(:checked, :indeterminate) + .fs-check-label::before {
188
- border-color: var(--fs-color-border);
270
+ .fs-switch-input:disabled + .fs-switch-label::after {
189
271
  background-color: var(--fs-color-subtle);
272
+ box-shadow: none;
190
273
  }
191
274
 
192
- .fs-check-input:disabled:checked + .fs-check-label::before {
193
- border-color: color-mix(in srgb, var(--fs-color-primary) 55%, var(--fs-color-border));
194
- background-color: color-mix(in srgb, var(--fs-color-primary) 45%, var(--fs-color-subtle));
275
+ .fs-check-input:disabled:checked + .fs-check-label::before,
276
+ .fs-radio-input:disabled:checked + .fs-radio-label::before,
277
+ .fs-switch-input:disabled:checked + .fs-switch-label::before {
278
+ border-color: color-mix(in srgb, var(--fs-switch-track-checked-bg) 55%, var(--fs-color-border));
279
+ background-color: color-mix(in srgb, var(--fs-switch-track-checked-bg) 45%, var(--fs-switch-track-bg));
195
280
  }
196
281
 
197
- .fs-check-input:disabled:checked + .fs-check-label::after {
198
- opacity: 0.72;
282
+ .fs-switch-input:disabled:checked + .fs-switch-label::after {
283
+ background-color: var(--fs-color-on-primary);
199
284
  }
200
285
 
201
- // Validação (mesma convenção de `.fs-form-control.is-valid`/`.is-invalid`).
202
- .fs-radio-input.is-valid + .fs-radio-label::before {
203
- border-color: var(--fs-color-success);
286
+ .fs-check-input.is-valid + .fs-check-label::before,
287
+ .fs-radio-input.is-valid + .fs-radio-label::before,
288
+ .fs-switch-input.is-valid + .fs-switch-label::before,
289
+ .fs-check-input[aria-invalid="false"] + .fs-check-label::before,
290
+ .fs-radio-input[aria-invalid="false"] + .fs-radio-label::before,
291
+ .fs-switch-input[aria-invalid="false"] + .fs-switch-label::before {
292
+ border-color: var(--fs-control-success-color);
204
293
  }
205
294
 
206
295
  .fs-check-input.is-invalid + .fs-check-label::before,
207
- .fs-radio-input.is-invalid + .fs-radio-label::before {
208
- border-color: var(--fs-color-danger);
209
- }
210
-
211
- // Tamanhos: reescalam caixa/trilho/knob e o espaço reservado para eles
212
- // (`padding-left`) proporcionalmente.
213
- .fs-check-sm .fs-check-label,
214
- .fs-radio-sm .fs-radio-label {
215
- min-height: 16px;
216
- padding-left: 24px;
217
- font-size: $font-size-sm;
218
- }
219
-
220
- .fs-check-sm .fs-check-label::before,
221
- .fs-radio-sm .fs-radio-label::before {
222
- width: 15px;
223
- height: 15px;
224
- }
225
-
226
- .fs-check-sm .fs-check-label::after {
227
- left: 3px;
228
- width: 8px;
229
- height: 4px;
230
- }
231
-
232
- .fs-radio-sm .fs-radio-label::after {
233
- left: 4px;
234
- width: 7px;
235
- height: 7px;
236
- }
237
-
238
- .fs-check-lg .fs-check-label,
239
- .fs-radio-lg .fs-radio-label {
240
- min-height: 24px;
241
- padding-left: 34px;
242
- font-size: $font-size-lg;
243
- }
244
-
245
- .fs-check-lg .fs-check-label::before,
246
- .fs-radio-lg .fs-radio-label::before {
247
- width: 22px;
248
- height: 22px;
249
- }
250
-
251
- .fs-check-lg .fs-check-label::after {
252
- left: 5px;
253
- width: 11px;
254
- height: 6px;
255
- }
256
-
257
- .fs-radio-lg .fs-radio-label::after {
258
- left: 6px;
259
- width: 10px;
260
- height: 10px;
261
- }
262
-
263
- .fs-switch-sm .fs-switch-label {
264
- min-height: 16px;
265
- padding-left: 38px;
266
- font-size: $font-size-sm;
267
- }
268
-
269
- .fs-switch-sm .fs-switch-label::before {
270
- width: 30px;
271
- height: 16px;
272
- }
273
-
274
- .fs-switch-sm .fs-switch-label::after {
275
- width: 12px;
276
- height: 10px;
296
+ .fs-radio-input.is-invalid + .fs-radio-label::before,
297
+ .fs-switch-input.is-invalid + .fs-switch-label::before,
298
+ .fs-check-input[aria-invalid="true"] + .fs-check-label::before,
299
+ .fs-radio-input[aria-invalid="true"] + .fs-radio-label::before,
300
+ .fs-switch-input[aria-invalid="true"] + .fs-switch-label::before {
301
+ border-color: var(--fs-control-error-color);
277
302
  }
278
303
 
279
- .fs-switch-sm .fs-switch-input:checked + .fs-switch-label::after {
280
- left: 16px;
304
+ .fs-switch-input.is-invalid:checked + .fs-switch-label::before,
305
+ .fs-switch-input[aria-invalid="true"]:checked + .fs-switch-label::before {
306
+ background-color: var(--fs-control-error-color);
307
+ box-shadow: none;
281
308
  }
282
309
 
283
- .fs-switch-lg .fs-switch-label {
284
- min-height: 26px;
285
- padding-left: 52px;
286
- font-size: $font-size-lg;
287
- }
288
-
289
- .fs-switch-lg .fs-switch-label::before {
290
- width: 44px;
291
- height: 24px;
310
+ .fs-switch-input.is-valid:checked + .fs-switch-label::before,
311
+ .fs-switch-input[aria-invalid="false"]:checked + .fs-switch-label::before {
312
+ background-color: var(--fs-control-success-color);
313
+ border-color: var(--fs-control-success-color);
314
+ box-shadow: none;
292
315
  }
293
316
 
294
- .fs-switch-lg .fs-switch-label::after {
295
- width: 20px;
296
- height: 18px;
317
+ .fs-check-input.is-invalid + .fs-check-label::before,
318
+ .fs-check-input[aria-invalid="true"] + .fs-check-label::before {
319
+ background-color: var(--fs-control-error-color);
297
320
  }
298
321
 
299
- .fs-switch-lg .fs-switch-input:checked + .fs-switch-label::after {
300
- left: 22px;
322
+ .fs-check-input.is-invalid + .fs-check-label::after,
323
+ .fs-check-input[aria-invalid="true"] + .fs-check-label::after {
324
+ inset-inline-start: 50%;
325
+ inset-block-start: 50%;
326
+ width: 9px;
327
+ height: 9px;
328
+ border: 0;
329
+ background-image:
330
+ linear-gradient(45deg, transparent 42%, var(--fs-color-on-danger, var(--fs-color-white)) 42% 58%, transparent 58%),
331
+ linear-gradient(-45deg, transparent 42%, var(--fs-color-on-danger, var(--fs-color-white)) 42% 58%, transparent 58%);
332
+ opacity: 1;
333
+ transform: translate(-50%, -50%);
301
334
  }
302
335
 
303
- // Estado parcial: borda tracejada em primary e traço horizontal. Mantém o
304
- // fundo definido para não se confundir com a superfície ao redor.
305
336
  .fs-check-input:indeterminate + .fs-check-label::before {
306
- border: 2px dashed var(--fs-color-primary);
337
+ border-color: var(--fs-color-primary);
307
338
  background-color: var(--fs-color-surface);
308
339
  }
309
340
 
310
341
  .fs-check-input:indeterminate + .fs-check-label::after {
311
- left: 4px;
342
+ inset-inline-start: calc((var(--fs-check-size) - 10px) / 2);
343
+ inset-block-start: 50%;
312
344
  width: 10px;
313
- height: 0;
345
+ height: 2px;
346
+ border: 0;
347
+ border-radius: 1px;
348
+ background-color: var(--fs-color-primary);
314
349
  opacity: 1;
315
- border-bottom: 2px solid var(--fs-color-primary);
316
- border-left: 0;
317
- transform: translateY(3px) rotate(0deg);
350
+ transform: translateY(-50%);
318
351
  }
319
352
 
320
- .fs-check-sm .fs-check-input:indeterminate + .fs-check-label::after {
321
- left: 3px;
322
- width: 8px;
353
+ .fs-check-sm,
354
+ .fs-radio-sm,
355
+ .fs-switch-sm {
356
+ font-size: $font-size-sm;
323
357
  }
324
358
 
325
- .fs-check-lg .fs-check-input:indeterminate + .fs-check-label::after {
326
- left: 5px;
327
- width: 12px;
359
+ .fs-check-sm {
360
+ --fs-check-size: 15px;
361
+ --fs-check-gap: 9px;
328
362
  }
329
363
 
330
- // Erro: X branco sobre fundo danger, visível mesmo quando o checkbox não
331
- // está marcado. Os gradientes desenham o símbolo sem conteúdo decorativo no
332
- // HTML e preservam o input nativo como fonte de semântica.
333
- .fs-check-input.is-invalid + .fs-check-label::before {
334
- background-color: var(--fs-color-danger);
364
+ .fs-radio-sm {
365
+ --fs-radio-size: 15px;
366
+ --fs-radio-dot-size: 7px;
367
+ --fs-radio-gap: 9px;
335
368
  }
336
369
 
337
- .fs-check-input.is-invalid + .fs-check-label::after {
338
- top: 6px;
339
- left: 5px;
340
- width: 8px;
341
- height: 8px;
342
- opacity: 1;
343
- border: 0;
344
- background-image:
345
- linear-gradient(45deg, transparent 42%, #{color-contrast($fokus-color-danger)} 42% 58%, transparent 58%),
346
- linear-gradient(-45deg, transparent 42%, #{color-contrast($fokus-color-danger)} 42% 58%, transparent 58%);
347
- transform: none;
370
+ .fs-switch-sm {
371
+ --fs-switch-track-width: 32px;
372
+ --fs-switch-track-height: 18px;
373
+ --fs-switch-thumb-width: 14px;
374
+ --fs-switch-thumb-height: 14px;
375
+ --fs-switch-thumb-offset: 2px;
376
+ --fs-switch-on-offset: 16px;
377
+ --fs-switch-hover-shadow: 0 0 0 3px var(--fs-switch-hover-ring);
348
378
  }
349
379
 
350
- .fs-check-sm .fs-check-input.is-invalid + .fs-check-label::after {
351
- top: 5px;
352
- left: 4px;
353
- width: 7px;
354
- height: 7px;
380
+ .fs-check-lg,
381
+ .fs-radio-lg,
382
+ .fs-switch-lg {
383
+ font-size: $font-size-lg;
355
384
  }
356
385
 
357
- .fs-check-lg .fs-check-input.is-invalid + .fs-check-label::after {
358
- top: 6px;
359
- left: 6px;
360
- width: 10px;
361
- height: 10px;
386
+ .fs-check-lg {
387
+ --fs-check-size: 22px;
388
+ --fs-check-gap: 12px;
389
+ }
390
+
391
+ .fs-radio-lg {
392
+ --fs-radio-size: 22px;
393
+ --fs-radio-dot-size: 10px;
394
+ --fs-radio-gap: 12px;
395
+ }
396
+
397
+ .fs-switch-lg {
398
+ --fs-switch-track-width: 48px;
399
+ --fs-switch-track-height: 28px;
400
+ --fs-switch-thumb-width: 22px;
401
+ --fs-switch-thumb-height: 22px;
402
+ --fs-switch-thumb-offset: 3px;
403
+ --fs-switch-on-offset: 23px;
404
+ --fs-switch-hover-shadow: 0 0 0 5px var(--fs-switch-hover-ring);
405
+ }
406
+
407
+ @media (prefers-reduced-motion: reduce) {
408
+ .fs-check-label::before,
409
+ .fs-check-label::after,
410
+ .fs-radio-label::before,
411
+ .fs-radio-label::after,
412
+ .fs-switch-label::before,
413
+ .fs-switch-label::after {
414
+ transition-duration: 0ms;
415
+ }
416
+ }
417
+
418
+ @media (prefers-contrast: more) {
419
+ .fs-check,
420
+ .fs-radio,
421
+ .fs-switch {
422
+ --fs-border-width: 2px;
423
+ --fs-focus-width: 3px;
424
+ }
425
+ }
426
+
427
+ @media (forced-colors: active) {
428
+ .fs-check-label::before,
429
+ .fs-radio-label::before,
430
+ .fs-switch-label::before,
431
+ .fs-switch-label::after {
432
+ border-color: ButtonText;
433
+ forced-color-adjust: auto;
434
+ }
435
+
436
+ .fs-check-input:checked + .fs-check-label::before,
437
+ .fs-radio-input:checked + .fs-radio-label::before,
438
+ .fs-switch-input:checked + .fs-switch-label::before {
439
+ background-color: Highlight;
440
+ border-color: Highlight;
441
+ }
442
+
443
+ .fs-switch-label::after {
444
+ background-color: ButtonFace;
445
+ border-color: ButtonText;
446
+ box-shadow: none;
447
+ }
448
+
449
+ .fs-switch-input:checked + .fs-switch-label::after {
450
+ background-color: HighlightText;
451
+ border-color: HighlightText;
452
+ }
453
+
454
+ .fs-check-input:focus-visible + .fs-check-label::before,
455
+ .fs-radio-input:focus-visible + .fs-radio-label::before,
456
+ .fs-switch-input:focus-visible + .fs-switch-label::before {
457
+ outline: 3px solid Highlight;
458
+ outline-offset: 2px;
459
+ }
362
460
  }
363
461
  }