fokus-styles 2.4.0 → 2.6.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 (212) hide show
  1. package/dist/css/components.css +561 -83
  2. package/dist/css/components.min.css +1 -1
  3. package/dist/css/fokus-components.css +561 -83
  4. package/dist/css/fokus-components.min.css +1 -1
  5. package/dist/css/fokus-rtl.css +697 -83
  6. package/dist/css/fokus-rtl.min.css +1 -1
  7. package/dist/css/fokus-utilities.css +136 -0
  8. package/dist/css/fokus-utilities.min.css +1 -1
  9. package/dist/css/fokus.css +996 -258
  10. package/dist/css/fokus.min.css +1 -1
  11. package/dist/css/forms.css +300 -176
  12. package/dist/css/forms.min.css +1 -1
  13. package/dist/css/helpers.css +136 -0
  14. package/dist/css/helpers.min.css +1 -1
  15. package/dist/js/fokus.js +270 -25
  16. package/dist/js/fokus.min.js +18 -18
  17. package/package.json +17 -15
  18. package/packages/fokus-components/scss/components/_alerts.scss +128 -6
  19. package/packages/fokus-components/scss/components/_badges.scss +26 -10
  20. package/packages/fokus-components/scss/components/_index.scss +1 -0
  21. package/packages/fokus-components/scss/components/_popover.scss +131 -21
  22. package/packages/fokus-components/scss/components/_responsive.scss +23 -0
  23. package/packages/fokus-components/scss/components/_tag.scss +37 -14
  24. package/packages/fokus-components/scss/components/_toasts.scss +156 -11
  25. package/packages/fokus-components/scss/components/_tooltips.scss +70 -18
  26. package/packages/fokus-components/scss/forms/_check-radio-switch.scss +313 -215
  27. package/packages/fokus-components/scss/forms/_forms.scss +16 -0
  28. package/packages/fokus-js/js/carousel.d.ts +4 -0
  29. package/packages/fokus-js/js/carousel.js +8 -0
  30. package/packages/fokus-js/js/core/positioning.js +18 -9
  31. package/packages/fokus-js/js/dropdown.d.ts +4 -0
  32. package/packages/fokus-js/js/dropdown.js +7 -0
  33. package/packages/fokus-js/js/popover.js +26 -1
  34. package/packages/fokus-js/js/tabs.d.ts +3 -0
  35. package/packages/fokus-js/js/tabs.js +5 -0
  36. package/packages/fokus-js/js/tag.js +17 -0
  37. package/packages/fokus-js/js/toast.js +77 -3
  38. package/packages/fokus-js/js/tooltip.d.ts +8 -1
  39. package/packages/fokus-js/js/tooltip.js +106 -13
  40. package/packages/fokus-utilities/scss/utilities/_api.scss +48 -0
  41. package/CHANGELOG.md +0 -910
  42. package/dist/css/components.css.map +0 -1
  43. package/dist/css/components.min.css.map +0 -1
  44. package/dist/css/fokus-components.css.map +0 -1
  45. package/dist/css/fokus-components.min.css.map +0 -1
  46. package/dist/css/fokus-core.css.map +0 -1
  47. package/dist/css/fokus-core.min.css.map +0 -1
  48. package/dist/css/fokus-dark.css.map +0 -1
  49. package/dist/css/fokus-dark.min.css.map +0 -1
  50. package/dist/css/fokus-rtl.css.map +0 -1
  51. package/dist/css/fokus-rtl.min.css.map +0 -1
  52. package/dist/css/fokus-utilities.css.map +0 -1
  53. package/dist/css/fokus-utilities.min.css.map +0 -1
  54. package/dist/css/fokus.css.map +0 -1
  55. package/dist/css/fokus.min.css.map +0 -1
  56. package/dist/css/fonts.css.map +0 -1
  57. package/dist/css/fonts.min.css.map +0 -1
  58. package/dist/css/forms.css.map +0 -1
  59. package/dist/css/forms.min.css.map +0 -1
  60. package/dist/css/helpers.css.map +0 -1
  61. package/dist/css/helpers.min.css.map +0 -1
  62. package/dist/css/layout.css.map +0 -1
  63. package/dist/css/layout.min.css.map +0 -1
  64. package/dist/js/fokus.js.map +0 -7
  65. package/dist/js/fokus.min.js.map +0 -7
  66. package/docs/README.md +0 -86
  67. package/docs/comparison.md +0 -124
  68. package/docs/components/accordion.md +0 -104
  69. package/docs/components/alert-dialog.md +0 -99
  70. package/docs/components/alert.md +0 -59
  71. package/docs/components/avatar.md +0 -13
  72. package/docs/components/badge.md +0 -82
  73. package/docs/components/breadcrumb.md +0 -93
  74. package/docs/components/button-group.md +0 -13
  75. package/docs/components/button.md +0 -98
  76. package/docs/components/card.md +0 -111
  77. package/docs/components/carousel.md +0 -167
  78. package/docs/components/checkbox.md +0 -97
  79. package/docs/components/close-button.md +0 -13
  80. package/docs/components/code.md +0 -13
  81. package/docs/components/collapse.md +0 -78
  82. package/docs/components/combobox.md +0 -123
  83. package/docs/components/command-palette.md +0 -131
  84. package/docs/components/datatable.md +0 -173
  85. package/docs/components/datepicker.md +0 -137
  86. package/docs/components/divider.md +0 -57
  87. package/docs/components/dropdown.md +0 -103
  88. package/docs/components/empty-state.md +0 -65
  89. package/docs/components/file-upload-advanced.md +0 -116
  90. package/docs/components/file-upload.md +0 -93
  91. package/docs/components/icon-link.md +0 -13
  92. package/docs/components/input-group.md +0 -73
  93. package/docs/components/input.md +0 -90
  94. package/docs/components/list-group.md +0 -13
  95. package/docs/components/modal.md +0 -120
  96. package/docs/components/navbar.md +0 -74
  97. package/docs/components/nested-menu.md +0 -90
  98. package/docs/components/notification-center.md +0 -116
  99. package/docs/components/offcanvas.md +0 -102
  100. package/docs/components/pagination.md +0 -145
  101. package/docs/components/placeholder.md +0 -13
  102. package/docs/components/popover.md +0 -105
  103. package/docs/components/progress.md +0 -126
  104. package/docs/components/radio.md +0 -70
  105. package/docs/components/range.md +0 -83
  106. package/docs/components/rating.md +0 -91
  107. package/docs/components/ratio.md +0 -13
  108. package/docs/components/scrollspy.md +0 -13
  109. package/docs/components/segmented-control.md +0 -82
  110. package/docs/components/select.md +0 -89
  111. package/docs/components/skeleton.md +0 -70
  112. package/docs/components/stepper.md +0 -107
  113. package/docs/components/switch.md +0 -75
  114. package/docs/components/table.md +0 -90
  115. package/docs/components/tabs.md +0 -104
  116. package/docs/components/tag.md +0 -129
  117. package/docs/components/tile.md +0 -112
  118. package/docs/components/timeline.md +0 -78
  119. package/docs/components/toast.md +0 -95
  120. package/docs/components/tooltip.md +0 -77
  121. package/docs/components/tree-view.md +0 -118
  122. package/docs/contributing/contributing.md +0 -18
  123. package/docs/getting-started/installation.md +0 -70
  124. package/docs/getting-started/usage.md +0 -105
  125. package/docs/guides/accessibility.md +0 -102
  126. package/docs/guides/charts.md +0 -73
  127. package/docs/guides/dark-mode.md +0 -90
  128. package/docs/guides/icons.md +0 -99
  129. package/docs/guides/javascript-api.md +0 -9
  130. package/docs/guides/layout-advanced.md +0 -118
  131. package/docs/guides/migration-clarus-to-fokus.md +0 -48
  132. package/docs/guides/migration-external.md +0 -123
  133. package/docs/guides/migration-v1.md +0 -63
  134. package/docs/guides/print.md +0 -30
  135. package/docs/guides/rtl-and-system-preferences.md +0 -28
  136. package/docs/guides/theming.md +0 -140
  137. package/docs/guides/utility-api.md +0 -9
  138. package/docs/prompts/prompt-plan.md +0 -45
  139. package/docs/reference/accessibility-matrix.md +0 -77
  140. package/docs/reference/browser-support.md +0 -75
  141. package/docs/reference/contrast-report.md +0 -50
  142. package/docs/reference/definitions.md +0 -864
  143. package/docs/reference/design-tokens.md +0 -194
  144. package/docs/reference/scss-architecture.md +0 -227
  145. package/docs/reference/size-baseline.json +0 -34
  146. package/docs/reference/stability.md +0 -89
  147. package/docs/showcase.md +0 -25
  148. package/mockup/README.md +0 -41
  149. package/mockup/assets/carousel-planning.png +0 -0
  150. package/mockup/assets/carousel-workspace.png +0 -0
  151. package/mockup/assets/example-theme.css +0 -8
  152. package/mockup/assets/example-theme.js +0 -11
  153. package/mockup/assets/showcase-contracts.js +0 -84
  154. package/mockup/assets/showcase.css +0 -91
  155. package/mockup/assets/showcase.js +0 -724
  156. package/mockup/content-data.html +0 -9
  157. package/mockup/examples/accordion-tabs-toast.html +0 -149
  158. package/mockup/examples/alert-dialog.html +0 -84
  159. package/mockup/examples/alerts.html +0 -41
  160. package/mockup/examples/badges-alerts.html +0 -77
  161. package/mockup/examples/buttons.html +0 -58
  162. package/mockup/examples/cards.html +0 -132
  163. package/mockup/examples/carousel.html +0 -126
  164. package/mockup/examples/charts.html +0 -102
  165. package/mockup/examples/check-radio-switch.html +0 -144
  166. package/mockup/examples/collapse.html +0 -69
  167. package/mockup/examples/combobox.html +0 -60
  168. package/mockup/examples/command-palette.html +0 -64
  169. package/mockup/examples/datatable.html +0 -113
  170. package/mockup/examples/datepicker.html +0 -62
  171. package/mockup/examples/divider.html +0 -45
  172. package/mockup/examples/dropdown-tooltip.html +0 -77
  173. package/mockup/examples/empty-state.html +0 -56
  174. package/mockup/examples/file-drop.html +0 -74
  175. package/mockup/examples/file-upload-advanced.html +0 -68
  176. package/mockup/examples/forms-advanced.html +0 -77
  177. package/mockup/examples/hover-card.html +0 -86
  178. package/mockup/examples/icons.html +0 -157
  179. package/mockup/examples/input-group.html +0 -74
  180. package/mockup/examples/js-foundation.html +0 -215
  181. package/mockup/examples/layout.html +0 -134
  182. package/mockup/examples/modal-select.html +0 -127
  183. package/mockup/examples/nested-menu.html +0 -73
  184. package/mockup/examples/notification-center.html +0 -86
  185. package/mockup/examples/offcanvas-popover.html +0 -133
  186. package/mockup/examples/pagination-breadcrumbs.html +0 -92
  187. package/mockup/examples/range.html +0 -69
  188. package/mockup/examples/rating.html +0 -103
  189. package/mockup/examples/segmented-control.html +0 -96
  190. package/mockup/examples/skeletons.html +0 -82
  191. package/mockup/examples/spinner-progress.html +0 -130
  192. package/mockup/examples/stepper.html +0 -119
  193. package/mockup/examples/tables-navbar.html +0 -89
  194. package/mockup/examples/tag.html +0 -105
  195. package/mockup/examples/theming.html +0 -68
  196. package/mockup/examples/tile.html +0 -107
  197. package/mockup/examples/timeline.html +0 -108
  198. package/mockup/examples/tree-view.html +0 -68
  199. package/mockup/feedback-actions.html +0 -9
  200. package/mockup/forms.html +0 -13
  201. package/mockup/foundations.html +0 -7
  202. package/mockup/kitchen-sink.html +0 -696
  203. package/mockup/navigation-disclosure.html +0 -11
  204. package/mockup/overlays-commands.html +0 -12
  205. package/mockup/templates/README.md +0 -20
  206. package/mockup/templates/admin.html +0 -334
  207. package/mockup/templates/auth.html +0 -234
  208. package/mockup/templates/dashboard.html +0 -442
  209. package/mockup/templates/landing.html +0 -397
  210. package/packages/fokus-icons/package.json +0 -46
  211. package/scripts/migrate-fokus-map.json +0 -1366
  212. package/scripts/migrate-fokus.mjs +0 -97
@@ -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
  }