fokus-styles 2.0.0 → 2.3.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 (131) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -3
  3. package/dist/css/components.css +510 -2
  4. package/dist/css/components.css.map +1 -1
  5. package/dist/css/components.min.css +1 -1
  6. package/dist/css/components.min.css.map +1 -1
  7. package/dist/css/fokus-components.css +4050 -0
  8. package/dist/css/fokus-components.css.map +1 -0
  9. package/dist/css/fokus-components.min.css +2 -0
  10. package/dist/css/fokus-components.min.css.map +1 -0
  11. package/dist/css/fokus-core.css +1563 -0
  12. package/dist/css/fokus-core.css.map +1 -0
  13. package/dist/css/fokus-core.min.css +2 -0
  14. package/dist/css/fokus-core.min.css.map +1 -0
  15. package/dist/css/fokus-dark.css +304 -0
  16. package/dist/css/fokus-dark.css.map +1 -0
  17. package/dist/css/fokus-dark.min.css +2 -0
  18. package/dist/css/fokus-dark.min.css.map +1 -0
  19. package/dist/css/fokus-utilities.css +4176 -0
  20. package/dist/css/fokus-utilities.css.map +1 -0
  21. package/dist/css/fokus-utilities.min.css +2 -0
  22. package/dist/css/fokus-utilities.min.css.map +1 -0
  23. package/dist/css/fokus.css +1339 -53
  24. package/dist/css/fokus.css.map +1 -1
  25. package/dist/css/fokus.min.css +1 -1
  26. package/dist/css/fokus.min.css.map +1 -1
  27. package/dist/css/forms.css +115 -2
  28. package/dist/css/forms.css.map +1 -1
  29. package/dist/css/forms.min.css +1 -1
  30. package/dist/css/forms.min.css.map +1 -1
  31. package/dist/css/helpers.css +376 -2
  32. package/dist/css/helpers.css.map +1 -1
  33. package/dist/css/helpers.min.css +1 -1
  34. package/dist/css/helpers.min.css.map +1 -1
  35. package/dist/css/layout.css +560 -2
  36. package/dist/css/layout.css.map +1 -1
  37. package/dist/css/layout.min.css +1 -1
  38. package/dist/css/layout.min.css.map +1 -1
  39. package/dist/js/fokus.js +347 -12
  40. package/dist/js/fokus.js.map +4 -4
  41. package/dist/js/fokus.min.js +17 -17
  42. package/dist/js/fokus.min.js.map +4 -4
  43. package/docs/components/accordion.md +3 -0
  44. package/docs/components/avatar.md +13 -0
  45. package/docs/components/button-group.md +13 -0
  46. package/docs/components/close-button.md +13 -0
  47. package/docs/components/code.md +13 -0
  48. package/docs/components/icon-link.md +13 -0
  49. package/docs/components/list-group.md +13 -0
  50. package/docs/components/modal.md +3 -0
  51. package/docs/components/navbar.md +3 -0
  52. package/docs/components/pagination.md +3 -0
  53. package/docs/components/placeholder.md +13 -0
  54. package/docs/components/ratio.md +13 -0
  55. package/docs/components/scrollspy.md +13 -0
  56. package/docs/components/skeleton.md +3 -0
  57. package/docs/components/table.md +3 -0
  58. package/docs/guides/javascript-api.md +9 -0
  59. package/docs/guides/utility-api.md +9 -0
  60. package/docs/reference/size-baseline.json +13 -13
  61. package/mockup/assets/example-theme.css +3 -1
  62. package/mockup/assets/example-theme.js +2 -0
  63. package/mockup/assets/showcase-contracts.js +2 -2
  64. package/mockup/assets/showcase.css +4 -2
  65. package/mockup/assets/showcase.js +2 -0
  66. package/mockup/examples/js-foundation.html +25 -0
  67. package/mockup/examples/tables-navbar.html +12 -8
  68. package/mockup/examples/theming.html +2 -1
  69. package/mockup/foundations.html +2 -2
  70. package/package.json +14 -1
  71. package/packages/fokus-cli/bin/fokus.mjs +18 -1
  72. package/packages/fokus-cli/lib/build.mjs +12 -2
  73. package/packages/fokus-components/scss/components/_accordion.scss +19 -0
  74. package/packages/fokus-components/scss/components/_avatar.scss +22 -0
  75. package/packages/fokus-components/scss/components/_button-group.scss +14 -0
  76. package/packages/fokus-components/scss/components/_close-button.scss +22 -0
  77. package/packages/fokus-components/scss/components/_code.scss +19 -0
  78. package/packages/fokus-components/scss/components/_icon-link.scss +8 -0
  79. package/packages/fokus-components/scss/components/_index.scss +10 -0
  80. package/packages/fokus-components/scss/components/_list-group.scss +26 -0
  81. package/packages/fokus-components/scss/components/_modal.scss +36 -0
  82. package/packages/fokus-components/scss/components/_navbar.scss +48 -0
  83. package/packages/fokus-components/scss/components/_offcanvas.scss +19 -0
  84. package/packages/fokus-components/scss/components/_pagination.scss +10 -0
  85. package/packages/fokus-components/scss/components/_placeholder.scss +13 -0
  86. package/packages/fokus-components/scss/components/_progress.scss +8 -0
  87. package/packages/fokus-components/scss/components/_ratio.scss +10 -0
  88. package/packages/fokus-components/scss/components/_scrollspy.scss +5 -0
  89. package/packages/fokus-components/scss/components/_skeleton.scss +19 -0
  90. package/packages/fokus-components/scss/components/_spinner.scss +9 -53
  91. package/packages/fokus-components/scss/components/_tables.scss +20 -0
  92. package/packages/fokus-components/scss/forms/_forms.scss +29 -0
  93. package/packages/fokus-core/scss/base/_accessibility.scss +45 -0
  94. package/packages/fokus-core/scss/base/_index.scss +1 -0
  95. package/packages/fokus-core/scss/layout/_grid.scss +18 -0
  96. package/packages/fokus-core/scss/settings/_config.scss +43 -0
  97. package/packages/fokus-core/scss/settings/_index.scss +1 -0
  98. package/packages/fokus-core/scss/themes/_dark.scss +38 -2
  99. package/packages/fokus-core/scss/tokens/_root.scss +14 -0
  100. package/packages/fokus-core/scss/tools/_mixins.scss +8 -0
  101. package/packages/fokus-js/js/accordion.js +7 -1
  102. package/packages/fokus-js/js/core/focus.js +20 -0
  103. package/packages/fokus-js/js/core/overlay.d.ts +2 -0
  104. package/packages/fokus-js/js/core/overlay.js +10 -0
  105. package/packages/fokus-js/js/core/register.js +4 -2
  106. package/packages/fokus-js/js/focus-trap.d.ts +3 -0
  107. package/packages/fokus-js/js/focus-trap.js +1 -0
  108. package/packages/fokus-js/js/fokus.d.ts +7 -0
  109. package/packages/fokus-js/js/fokus.js +7 -0
  110. package/packages/fokus-js/js/form-validation.d.ts +7 -0
  111. package/packages/fokus-js/js/form-validation.js +20 -0
  112. package/packages/fokus-js/js/modal.js +16 -2
  113. package/packages/fokus-js/js/navbar.d.ts +10 -0
  114. package/packages/fokus-js/js/navbar.js +27 -0
  115. package/packages/fokus-js/js/offcanvas.d.ts +4 -0
  116. package/packages/fokus-js/js/offcanvas.js +16 -2
  117. package/packages/fokus-js/js/pagination.d.ts +7 -0
  118. package/packages/fokus-js/js/pagination.js +38 -0
  119. package/packages/fokus-js/js/scrollspy.d.ts +7 -0
  120. package/packages/fokus-js/js/scrollspy.js +26 -0
  121. package/packages/fokus-js/js/spinner.d.ts +9 -0
  122. package/packages/fokus-js/js/spinner.js +18 -0
  123. package/packages/fokus-js/js/theme.d.ts +9 -0
  124. package/packages/fokus-js/js/theme.js +19 -0
  125. package/packages/fokus-js/js/toast.js +4 -0
  126. package/packages/fokus-utilities/scss/utilities/_api.scss +48 -0
  127. package/packages/fokus-utilities/scss/utilities/_index.scss +2 -0
  128. package/packages/fokus-utilities/scss/utilities/_states.scss +19 -0
  129. package/packages/fokus-utilities/scss/utilities/_visibility.scss +24 -0
  130. package/scss/entries/core-entry.scss +5 -0
  131. package/scss/entries/dark-entry.scss +3 -0
@@ -55,6 +55,9 @@ força todos fechados).
55
55
 
56
56
  ## A11y
57
57
 
58
+ Use `.fs-accordion-flush` para remover bordas externas. Com `data-fs-always-open="true"`,
59
+ um painel aberto não pode ser fechado isoladamente.
60
+
58
61
  `aria-controls`/`aria-expanded` (botão) e `role="region"`/
59
62
  `aria-labelledby` (painel) aplicados automaticamente. Cada botão é um
60
63
  `<button>` nativo dentro de um heading (`<h3>`) — navegável por `Tab` e
@@ -0,0 +1,13 @@
1
+ # Avatar
2
+
3
+ ## Visão geral
4
+
5
+ Use `.fs-avatar` para representar uma pessoa, equipe ou entidade com imagem ou iniciais.
6
+
7
+ ## Estados
8
+
9
+ Combine `.fs-avatar-sm`, `.fs-avatar-lg` e `.fs-avatar-square` conforme o contexto.
10
+
11
+ ## A11y
12
+
13
+ Imagens devem ter `alt` descritivo; iniciais devem ter um nome acessível com `aria-label`.
@@ -0,0 +1,13 @@
1
+ # Button group
2
+
3
+ ## Visão geral
4
+
5
+ `.fs-button-group` agrupa ações relacionadas em uma unidade visual.
6
+
7
+ ## Estados
8
+
9
+ Use `.fs-button-group-vertical` em barras verticais e mantenha apenas uma ação primária.
10
+
11
+ ## A11y
12
+
13
+ Use `role="group"` e `aria-label` quando o agrupamento não tiver um título visível.
@@ -0,0 +1,13 @@
1
+ # Close button
2
+
3
+ ## Visão geral
4
+
5
+ `.fs-close` fornece um controle compacto para fechar painéis, diálogos e toasts.
6
+
7
+ ## Estados
8
+
9
+ O estado hover e o focus-visible são fornecidos pelo componente.
10
+
11
+ ## A11y
12
+
13
+ Sempre informe `aria-label="Fechar"` e associe o controle ao conteúdo que ele fecha.
@@ -0,0 +1,13 @@
1
+ # Code
2
+
3
+ ## Visão geral
4
+
5
+ `.fs-code` estiliza código inline e `.fs-pre` apresenta blocos pré-formatados.
6
+
7
+ ## Estados
8
+
9
+ O bloco mantém rolagem horizontal para preservar linhas longas.
10
+
11
+ ## A11y
12
+
13
+ Use `code` para trechos curtos e `pre` para blocos; não transmita informação somente por cor.
@@ -0,0 +1,13 @@
1
+ # Icon link
2
+
3
+ ## Visão geral
4
+
5
+ `.fs-icon-link` alinha um ícone a um link textual.
6
+
7
+ ## Estados
8
+
9
+ O ícone acompanha o hover do link e respeita redução de movimento.
10
+
11
+ ## A11y
12
+
13
+ O texto do link deve continuar presente; o ícone decorativo deve usar `aria-hidden="true"`.
@@ -0,0 +1,13 @@
1
+ # List group
2
+
3
+ ## Visão geral
4
+
5
+ `.fs-list-group` e `.fs-list-group-item` compõem listas de navegação ou seleção.
6
+
7
+ ## Estados
8
+
9
+ Use `.is-active` para o item atual e elementos `a` ou `button` para itens interativos.
10
+
11
+ ## A11y
12
+
13
+ Escolha uma lista semântica e forneça nome ao grupo quando ele representar uma região.
@@ -61,6 +61,9 @@ explícito fecha. Use para fluxos que exigem uma decisão explícita.
61
61
 
62
62
  ## A11y
63
63
 
64
+ Use `.fs-modal-sm`, `.fs-modal-lg`, `.fs-modal-xl`, `.fs-modal-fullscreen` e
65
+ `.fs-modal-scrollable` conforme o fluxo. Em telas menores o diálogo respeita safe areas.
66
+
64
67
  Ao inicializar, o componente controla `aria-hidden` e, quando encontra
65
68
  `.fs-modal-title`, cria automaticamente o vínculo `aria-labelledby`. Um
66
69
  `id` existente no título é preservado.
@@ -40,6 +40,9 @@ composição (o que vai dentro) que muda.
40
40
 
41
41
  ## A11y
42
42
 
43
+ O padrão responsivo usa `.fs-navbar-toggler`, `.fs-navbar-collapse` e `data-fs-target`.
44
+ Mantenha `aria-expanded` e um nome acessível no botão.
45
+
43
46
  - Use `<nav>` como elemento raiz (já no exemplo) — landmark de navegação
44
47
  pra leitores de tela.
45
48
  - Se houver mais de uma navbar na página, diferencie com
@@ -98,6 +98,9 @@ de `aria-label` (`"Anterior"`/`"Próxima"`) já que o SVG é `aria-hidden`.
98
98
 
99
99
  ## A11y
100
100
 
101
+ Envolva a paginação em `.fs-pagination-scroll` quando houver muitos itens. Use `nav`
102
+ nomeado, `aria-current="page"` e desabilite controles sem destino real.
103
+
101
104
  - Envolva a lista num `<nav aria-label="Paginação">` — o elemento `<nav>`
102
105
  não é gerado pelo framework, adicione no seu HTML.
103
106
  - Marque a página atual com `aria-current="page"` no link ativo, além de
@@ -0,0 +1,13 @@
1
+ # Placeholder
2
+
3
+ ## Visão geral
4
+
5
+ `.fs-placeholder` representa conteúdo ainda carregando e é visualmente distinto de Skeleton.
6
+
7
+ ## Estados
8
+
9
+ Use `.fs-placeholder-glow`, `.fs-placeholder-wave` ou `.fs-placeholder-btn` conforme o formato.
10
+
11
+ ## A11y
12
+
13
+ Anuncie carregamento com `aria-busy="true"` no contêiner e uma região live quando necessário.
@@ -0,0 +1,13 @@
1
+ # Ratio
2
+
3
+ ## Visão geral
4
+
5
+ `.fs-ratio` cria frames de mídia responsivos sem depender da altura do viewport.
6
+
7
+ ## Estados
8
+
9
+ Escolha `.fs-ratio-1x1`, `.fs-ratio-4x3`, `.fs-ratio-16x9` ou `.fs-ratio-21x9`.
10
+
11
+ ## A11y
12
+
13
+ Mantenha texto alternativo em imagens e legendas ou título acessível em mídia incorporada.
@@ -0,0 +1,13 @@
1
+ # Scrollspy
2
+
3
+ ## Visão geral
4
+
5
+ `data-fs-scrollspy` destaca o link da seção atualmente visível e pode ser usado em navegação lateral.
6
+
7
+ ## Estados
8
+
9
+ O módulo JS aplica `.is-active` e emite `fs:scrollspy:activate`.
10
+
11
+ ## A11y
12
+
13
+ Use uma navegação nomeada, mantenha foco visível e não remova a indicação textual do destino.
@@ -38,6 +38,9 @@ dados reais chegarem.
38
38
 
39
39
  ## A11y
40
40
 
41
+ As variantes `.fs-skeleton-card`, `.fs-skeleton-table` e `.fs-skeleton-avatar` são visuais;
42
+ anuncie o carregamento no contêiner com `aria-busy="true"`.
43
+
41
44
  A animação para automaticamente sob `prefers-reduced-motion: reduce`
42
45
  (fica estática, sem pulso/varredura). Se o carregamento for anunciado a
43
46
  leitores de tela, envolva o conjunto de skeletons num contêiner com
@@ -53,6 +53,9 @@ opcional que reusa `.fs-table`.
53
53
 
54
54
  ## A11y
55
55
 
56
+ Use `.fs-table-responsive` para rolagem horizontal, `.fs-table-caption` para legenda e
57
+ `.fs-table-empty` para estados sem dados. `.fs-table-priority` preserva colunas essenciais.
58
+
56
59
  - Use `<th scope="col">`/`<th scope="row">` conforme o cabeçalho for de
57
60
  coluna ou linha — não é aplicado automaticamente.
58
61
  - Para tabelas grandes/complexas, considere `<caption>` descrevendo o
@@ -0,0 +1,9 @@
1
+ # API JavaScript
2
+
3
+ Os módulos vanilla são SSR-safe quando importados e expõem instâncias idempotentes por
4
+ `getOrCreateInstance`. Modal, Theme, Navbar, Scrollspy e FormValidation também oferecem
5
+ destruição explícita com `dispose()`.
6
+
7
+ Os eventos comuns são `fs:show`, `fs:shown`, `fs:hide` e `fs:hidden`. Atributos declarativos
8
+ usam `data-fs-toggle`, `data-fs-target`, `data-fs-dismiss`, `data-fs-placement` e
9
+ `data-fs-theme`.
@@ -0,0 +1,9 @@
1
+ # Utility API e configuração Sass
2
+
3
+ O Fokus Styles oferece presets públicos em `fokus-styles/core`, `fokus-styles/components`,
4
+ `fokus-styles/utilities` e `fokus-styles/themes`. O arquivo `fokus-styles/config` expõe o
5
+ mapa `$fs-config` para entradas Sass customizadas.
6
+
7
+ O mixin `fs-fluid-type()` usa `clamp()` para escalar valores entre dois limites sem exigir
8
+ JavaScript. Classes utilitárias novas seguem o namespace `fs-u-*` e as classes existentes
9
+ continuam válidas dentro da linha 2.x.
@@ -1,34 +1,34 @@
1
1
  {
2
- "measuredAt": "2026-09-07T21:04:20.442Z",
2
+ "measuredAt": "2026-09-07T22:40:15.289Z",
3
3
  "unit": "bytes",
4
4
  "files": {
5
5
  "css/layout.min.css": {
6
- "bytes": 21560,
7
- "gzipBytes": 4809
6
+ "bytes": 28539,
7
+ "gzipBytes": 5951
8
8
  },
9
9
  "css/forms.min.css": {
10
- "bytes": 30769,
11
- "gzipBytes": 6476
10
+ "bytes": 33746,
11
+ "gzipBytes": 6986
12
12
  },
13
13
  "css/components.min.css": {
14
- "bytes": 83375,
15
- "gzipBytes": 14496
14
+ "bytes": 92904,
15
+ "gzipBytes": 16191
16
16
  },
17
17
  "css/helpers.min.css": {
18
- "bytes": 53574,
19
- "gzipBytes": 9104
18
+ "bytes": 61032,
19
+ "gzipBytes": 10609
20
20
  },
21
21
  "css/fonts.min.css": {
22
22
  "bytes": 1505,
23
23
  "gzipBytes": 409
24
24
  },
25
25
  "css/fokus.min.css": {
26
- "bytes": 144930,
27
- "gzipBytes": 22544
26
+ "bytes": 164376,
27
+ "gzipBytes": 25875
28
28
  },
29
29
  "js/fokus.min.js": {
30
- "bytes": 98921,
31
- "gzipBytes": 21196
30
+ "bytes": 107627,
31
+ "gzipBytes": 23063
32
32
  }
33
33
  }
34
34
  }
@@ -1,5 +1,7 @@
1
1
  html[data-theme="dark"],
2
- html[data-theme="dark"] body {
2
+ html[data-fs-theme="dark"],
3
+ html[data-theme="dark"] body,
4
+ html[data-fs-theme="dark"] body {
3
5
  min-height: 100%;
4
6
  color: var(--fs-color-text);
5
7
  background-color: var(--fs-color-surface);
@@ -3,7 +3,9 @@
3
3
  const exampleTheme = new URLSearchParams(window.location.search).get("theme");
4
4
 
5
5
  if (exampleTheme === "dark") {
6
+ document.documentElement.setAttribute("data-fs-theme", "dark");
6
7
  document.documentElement.setAttribute("data-theme", "dark");
7
8
  } else {
9
+ document.documentElement.setAttribute("data-fs-theme", "light");
8
10
  document.documentElement.removeAttribute("data-theme");
9
11
  }
@@ -7,7 +7,7 @@ window.showcaseContracts = {
7
7
  },
8
8
  theming: {
9
9
  use: [{ label: "Tema de produto", text: "Use para alternar claro/escuro ou aplicar uma marca sem sobrescrever cores de componentes isoladamente." }],
10
- api: [{ label: "data-theme=\"dark\"", code: true, text: "aplicado em html ou em um contêiner, ativa aliases escuros dos tokens semânticos." }, { label: "data-fs-brand=\"violet|corporate|vibrant\"", code: true, text: "seleciona uma marca publicada; pode ser combinado com data-theme." }, { label: "--fs-color-*", code: true, text: "tokens semânticos consumidos pelos componentes; evite trocar cores literais em cada componente." }],
10
+ api: [{ label: "data-fs-theme=\"light|dark|auto\"", code: true, text: "controla o color mode canônico em html ou em um contêiner." }, { label: "data-theme=\"dark\"", code: true, text: "permanece como compatibilidade para consumidores existentes." }, { label: "data-fs-brand=\"violet|corporate|vibrant\"", code: true, text: "seleciona uma marca publicada; pode ser combinado com data-fs-theme." }, { label: "--fs-color-*", code: true, text: "tokens semânticos consumidos pelos componentes; evite trocar cores literais em cada componente." }],
11
11
  a11y: [{ label: "Contraste", text: "revalide contraste após criar marca ou sobrescrever token." }, { label: "Preferência", text: "não alterne tema sem controle explícito e persistência coerente para a pessoa usuária." }],
12
12
  states: [{ label: "Claro e escuro", text: "teste superfície, texto, borda, foco e componentes sobrepostos em ambos." }],
13
13
  },
@@ -62,7 +62,7 @@ window.showcaseContracts = {
62
62
  "empty-state": { use: [{ label: "Sem conteúdo", text: "Use para primeira utilização, busca sem resultado ou região sem dados." }], api: [{ label: ".fs-empty-state", code: true, text: "contêiner centralizado." }, { label: ".fs-empty-state-icon / -title / -text / -action", code: true, text: "organizam ilustração, mensagem e CTA." }], a11y: [{ label: "Próximo passo", text: "título e ação devem explicar a situação fora do contexto visual." }], states: [{ label: "Cenário", text: "valide sem dados, sem resultados e carregamento concluído." }] },
63
63
  skeleton: { use: [{ label: "Carregamento previsível", text: "Use para reservar espaço de conteúdo temporário e reduzir salto de layout." }], api: [{ label: ".fs-skeleton", code: true, text: "bloco-base." }, { label: ".fs-skeleton-text / -avatar / -button / -card", code: true, text: "formas prontas para estruturas comuns." }], a11y: [{ label: "Decoração", text: "oculte o skeleton de leitores de tela e anuncie carregamento na região adequada." }], states: [{ label: "Movimento", text: "teste animação reduzida, conteúdo lento e substituição pelo resultado." }] },
64
64
  progress: { use: [{ label: "Operação em curso", text: "Use spinner para espera indeterminada e progress para avanço mensurável." }], api: [{ label: ".fs-spinner / .fs-spinner-sm / .fs-spinner-lg", code: true, text: "indicador indeterminado." }, { label: ".fs-progress > .fs-progress-bar", code: true, text: "trilha e preenchimento." }, { label: "role=\"progressbar\" + aria-valuenow/min/max", code: true, text: "expõe valor de progresso." }], a11y: [{ label: "Status", text: "informe o que está acontecendo e valor atual quando houver." }], states: [{ label: "Operação", text: "teste indeterminado, 0%, intermediário, concluído e erro." }] },
65
- navbar: { use: [{ label: "Navegação global", text: "Use para destinos principais do produto, não para ações locais." }], api: [{ label: ".fs-navbar / .fs-navbar-brand / .fs-navbar-nav", code: true, text: "estrutura de marca e links." }, { label: ".fs-navbar-toggler + data-fs=\"navbar\"", code: true, text: "ativa colapso no viewport compacto." }], a11y: [{ label: "Destino atual", text: "use aria-current=\"page\" no link ativo e nomeie o nav." }], states: [{ label: "Compacto", text: "teste aberto, fechado, teclado e foco no menu móvel." }] },
65
+ navbar: { use: [{ label: "Navegação global", text: "Use para destinos principais do produto, não para ações locais." }], api: [{ label: ".fs-navbar / .fs-navbar-brand / .fs-navbar-nav", code: true, text: "estrutura de marca e links." }, { label: ".fs-navbar-toggler + data-fs-toggle=\"navbar\" + data-fs-target", code: true, text: "ativa colapso no viewport compacto e mantém aria-expanded." }], a11y: [{ label: "Destino atual", text: "use aria-current=\"page\" no link ativo e nomeie o nav." }], states: [{ label: "Compacto", text: "teste aberto, fechado, teclado e foco no menu móvel." }] },
66
66
  breadcrumb: { use: [{ label: "Hierarquia", text: "Use para mostrar o caminho até a página atual em estruturas profundas." }], api: [{ label: ".fs-breadcrumb > .fs-breadcrumb-item", code: true, text: "lista de níveis; o último representa a página atual." }, { label: "aria-current=\"page\"", code: true, text: "marca o item atual sem link." }], a11y: [{ label: "Navegação", text: "envolva em nav com aria-label=\"Breadcrumb\"." }], states: [{ label: "Comprimento", text: "teste rótulos longos e reflow em telas pequenas." }] },
67
67
  pagination: { use: [{ label: "Coleção paginada", text: "Use quando resultados são divididos em páginas e a pessoa precisa mudar de intervalo." }], api: [{ label: ".fs-pagination / .fs-page-item / .fs-page-link", code: true, text: "estrutura da lista de páginas." }, { label: ".is-active / .is-disabled", code: true, text: "página atual e limite indisponível." }], a11y: [{ label: "Nomes", text: "use aria-label no nav e aria-current=\"page\" na página atual." }], states: [{ label: "Limites", text: "teste primeira, última, ativa, disabled e foco." }] },
68
68
  accordion: { use: [{ label: "Detalhe sob demanda", text: "Use para agrupar seções independentes e reduzir densidade inicial da página." }], api: [{ label: ".fs-accordion + data-fs=\"accordion\"", code: true, text: "contêiner que inicializa os painéis." }, { label: ".fs-accordion-button / .fs-accordion-collapse", code: true, text: "gatilho e região expansível de cada item." }, { label: "data-multiple=\"true\"", code: true, text: "permite vários painéis abertos; sem ele, abrir um fecha os demais." }], a11y: [{ label: "Semântica", text: "o componente sincroniza aria-expanded, aria-controls e região nomeada." }], states: [{ label: "Painel", text: "teste aberto inicial, exclusivo, múltiplo, teclado e movimento reduzido." }] },
@@ -4,7 +4,7 @@
4
4
  color-scheme: light;
5
5
  }
6
6
 
7
- html[data-theme="dark"] { color-scheme: dark; }
7
+ html[data-theme="dark"], html[data-fs-theme="dark"] { color-scheme: dark; }
8
8
 
9
9
  * { box-sizing: border-box; }
10
10
 
@@ -65,7 +65,9 @@ html[data-theme="dark"] .showcase-detail-body a,
65
65
  html[data-theme="dark"] .showcase-token-html { color: var(--fs-alert-primary-text); }
66
66
  html[data-theme="dark"] .showcase-token-css { color: var(--fs-alert-danger-text); }
67
67
  html[data-theme="dark"] .showcase-token-example,
68
- html[data-theme="dark"] .showcase-detail-example { color: var(--fs-alert-info-text); }
68
+ html[data-theme="dark"] .showcase-detail-example,
69
+ html[data-fs-theme="dark"] .showcase-token-example,
70
+ html[data-fs-theme="dark"] .showcase-detail-example { color: var(--fs-alert-info-text); }
69
71
 
70
72
  .showcase-preview { width: 100%; min-height: 560px; border: 1px solid var(--fs-color-border); border-radius: var(--fs-radius-md); background: var(--fs-color-subtle); }
71
73
  .showcase-code-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
@@ -482,6 +482,7 @@ const apiExtensions = {
482
482
  };
483
483
 
484
484
  if (new URLSearchParams(window.location.search).get("theme") === "dark") {
485
+ document.documentElement.setAttribute("data-fs-theme", "dark");
485
486
  document.documentElement.setAttribute("data-theme", "dark");
486
487
  if (themeButton) themeButton.textContent = "Usar tema claro";
487
488
  }
@@ -713,6 +714,7 @@ for (const item of catalog) {
713
714
 
714
715
  themeButton?.addEventListener("click", () => {
715
716
  const dark = document.documentElement.getAttribute("data-theme") !== "dark";
717
+ document.documentElement.setAttribute("data-fs-theme", dark ? "dark" : "light");
716
718
  document.documentElement.toggleAttribute("data-theme", dark);
717
719
  if (dark) document.documentElement.setAttribute("data-theme", "dark");
718
720
  themeButton.textContent = dark ? "Usar tema claro" : "Usar tema escuro";
@@ -82,6 +82,21 @@
82
82
  </div>
83
83
  </section>
84
84
 
85
+ <section>
86
+ <h2 class="fs-u-fs-md fs-u-fw-medium fs-u-mb-2">4. API declarativa 2.3</h2>
87
+ <p class="fs-u-fs-sm fs-u-mb-3">Theme, validação e eventos comuns usam os exports vanilla sem dependências externas.</p>
88
+ <div class="fs-form-row">
89
+ <button type="button" class="fs-btn" id="theme-toggle">Alternar tema com FokusStyles.Theme</button>
90
+ <form class="fs-form-col" data-fs="form-validation" id="api-form" novalidate>
91
+ <label class="fs-form-label" for="api-required">Campo obrigatório</label>
92
+ <input class="fs-form-control" id="api-required" required aria-describedby="api-feedback">
93
+ <div class="fs-invalid-feedback" id="api-feedback">Informe um valor para continuar.</div>
94
+ <button class="fs-btn fs-btn-primary" type="submit">Validar formulário</button>
95
+ </form>
96
+ </div>
97
+ <p class="fs-u-fs-xs fs-u-mt-2" id="api-status" role="status" aria-live="polite"></p>
98
+ </section>
99
+
85
100
  <section>
86
101
  <h2 class="fs-u-fs-md fs-u-fw-medium fs-u-mb-2">2. Foco, teclado e overlay (FokusStyles.core.createFocusTrap / onEscapeKey / lockScroll / onClickOutside)</h2>
87
102
  <p class="fs-u-fs-sm fs-u-mb-3">Abre um painel com scroll da página bloqueado, foco preso via Tab/Shift+Tab, fecha com Escape ou clique fora.</p>
@@ -184,6 +199,16 @@
184
199
  core.collapse(collapsePanel);
185
200
  }
186
201
  });
202
+
203
+ const apiStatus = document.getElementById("api-status");
204
+ document.getElementById("theme-toggle").addEventListener("click", () => {
205
+ const next = FokusStyles.Theme.getOrCreateInstance().get() === "dark" ? "light" : "dark";
206
+ FokusStyles.Theme.set(next);
207
+ apiStatus.textContent = `Tema ${next} aplicado.`;
208
+ });
209
+ document.getElementById("api-form").addEventListener("fs:form:validated", (event) => {
210
+ apiStatus.textContent = event.detail.valid ? "Formulário válido." : "Formulário precisa de atenção.";
211
+ });
187
212
  </script>
188
213
 
189
214
  </body>
@@ -15,7 +15,8 @@
15
15
  background-color: var(--fs-color-subtle);
16
16
  }
17
17
 
18
- html[data-theme="dark"] .theme-panel {
18
+ html[data-theme="dark"] .theme-panel,
19
+ html[data-fs-theme="dark"] .theme-panel {
19
20
  color: var(--fs-color-text);
20
21
  background-color: var(--fs-color-surface);
21
22
  }
@@ -25,14 +26,17 @@
25
26
  </head>
26
27
  <body>
27
28
  <div class="theme-panel">
28
- <nav class="fs-navbar fs-u-mb-3">
29
+ <nav class="fs-navbar fs-u-mb-3" data-fs="navbar" aria-label="Navegação principal">
29
30
  <a href="#" class="fs-navbar-brand">FokusStyles</a>
30
- <ul class="fs-navbar-nav">
31
- <li><a href="#" class="fs-nav-link is-active">Início</a></li>
32
- <li><a href="#" class="fs-nav-link">Projetos <span class="fs-badge fs-badge-primary fs-badge-sm">3</span></a></li>
33
- <li><a href="#" class="fs-nav-link is-disabled">Configurações</a></li>
34
- </ul>
35
- <button type="button" class="fs-btn fs-btn-primary fs-btn-sm">Entrar</button>
31
+ <button type="button" class="fs-navbar-toggler" data-fs-toggle="navbar" data-fs-target="#demo-navbar-menu" aria-controls="demo-navbar-menu" aria-expanded="false">Menu</button>
32
+ <div class="fs-navbar-collapse" id="demo-navbar-menu" hidden>
33
+ <ul class="fs-navbar-nav">
34
+ <li><a href="#" class="fs-nav-link is-active" aria-current="page">Início</a></li>
35
+ <li><a href="#" class="fs-nav-link">Projetos <span class="fs-badge fs-badge-primary fs-badge-sm">3</span></a></li>
36
+ <li><a href="#" class="fs-nav-link is-disabled">Configurações</a></li>
37
+ </ul>
38
+ <button type="button" class="fs-btn fs-btn-primary fs-btn-sm">Entrar</button>
39
+ </div>
36
40
  </nav>
37
41
 
38
42
  <div class="fs-table-responsive">
@@ -17,7 +17,8 @@
17
17
  padding: 24px;
18
18
  }
19
19
 
20
- html[data-theme="dark"] .theme-panel {
20
+ html[data-theme="dark"] .theme-panel,
21
+ html[data-fs-theme="dark"] .theme-panel {
21
22
  color: var(--fs-color-text);
22
23
  background-color: var(--fs-color-surface);
23
24
  }
@@ -1,7 +1,7 @@
1
1
  <!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>FokusStyles — Fundamentos</title><link rel="stylesheet" href="../dist/css/fonts.css"><link rel="stylesheet" href="../dist/css/components.css"><link rel="stylesheet" href="../dist/css/forms.css"><link rel="stylesheet" href="../dist/css/helpers.css"><link rel="stylesheet" href="assets/showcase.css"></head><body><main class="showcase-shell"><header class="showcase-hero"><p class="showcase-eyebrow">Fokus Styles · laboratório</p><h1 class="showcase-title">Fundamentos e infraestrutura</h1><p class="showcase-lead">Tokens, layout, temas, ícones e utilitários JavaScript que sustentam os componentes. Cada preview é executável; o código mostrado é extraído do próprio exemplo.</p></header><div class="showcase-toolbar"><button class="fs-btn fs-btn-primary" type="button" data-showcase-theme>Usar tema escuro</button><a class="fs-btn" href="README.md">Índice dos laboratórios</a></div><nav class="showcase-nav" aria-label="Componentes desta página" data-showcase-navigation></nav><div data-showcase-sections></div></main><script id="showcase-catalog" type="application/json">[
2
2
  {"id":"layout","title":"Layout","description":"Estruturas de página, grid, containers e comportamento responsivo para compor interfaces previsíveis.","use":"Use como base de páginas e regiões; não use utilitários para substituir semântica HTML.","api":".fs-container, .fs-row, .fs-col-* e utilitários fs-u-*.","a11y":"Mantenha landmarks, ordem lógica de leitura e títulos hierárquicos.","states":"Breakpoints, densidade e regiões de conteúdo.","source":"examples/layout.html","height":900},
3
- {"id":"theming","title":"Temas e marcas","description":"Alternância de data-theme e data-fs-brand sobre os tokens semânticos do FokusStyles.","use":"Use para personalizar a aparência sem alterar markup de componentes.","api":"data-theme=dark e data-fs-brand; tokens --fs-*.","a11y":"Verifique contraste após qualquer marca personalizada.","states":"Claro, escuro e marcas alternativas.","source":"examples/theming.html","height":650},
3
+ {"id":"theming","title":"Temas e marcas","description":"Alternância de data-fs-theme, compatibilidade data-theme e data-fs-brand sobre os tokens semânticos do FokusStyles.","use":"Use para personalizar a aparência sem alterar markup de componentes.","api":"data-fs-theme=light|dark|auto, data-theme e data-fs-brand; tokens --fs-*.","a11y":"Verifique contraste após qualquer marca personalizada.","states":"Claro, escuro, automático e marcas alternativas.","source":"examples/theming.html","height":650},
4
4
  {"id":"icons","title":"Ícones","description":"Catálogo e uso de ícones com rótulos acessíveis e alinhamento consistente.","use":"Use ícones para reforçar ações conhecidas; mantenha texto quando houver ambiguidade.","api":"Classes de ícone e utilitários de tamanho publicados.","a11y":"Ícones decorativos são ocultos; botões somente com ícone recebem nome acessível.","states":"Tamanhos, cores e ícones em controles.","source":"examples/icons.html","height":1200},
5
5
  {"id":"divider","title":"Divider","description":"Separadores visuais para organizar grupos relacionados sem substituir hierarquia de conteúdo.","use":"Separe blocos próximos em listas, painéis e formulários.","api":".fs-divider e modificadores de orientação.","a11y":"Não use o separador como único indicador de mudança de assunto.","states":"Horizontal, vertical e espaçamentos adjacentes.","source":"examples/divider.html","height":520},
6
- {"id":"js-foundation","title":"Infraestrutura JavaScript","description":"Posicionamento, foco, overlay e transições reutilizados pelos componentes interativos.","use":"Use as APIs públicas apenas quando montar comportamento avançado fora dos componentes prontos.","api":"FokusStyles.core.computePosition(), createFocusTrap(), collapse() e expand().","a11y":"Preserve foco, Escape, bloqueio de scroll e preferência por movimento reduzido.","states":"Flip de posicionamento, focus trap, clique externo e transições.","source":"examples/js-foundation.html","height":1100}
6
+ {"id":"js-foundation","title":"Infraestrutura JavaScript","description":"Posicionamento, foco, overlays, temas, validação e transições reutilizados pelos componentes interativos.","use":"Use as APIs públicas apenas quando montar comportamento avançado fora dos componentes prontos.","api":"FokusStyles.core, Theme.set(), FormValidation, eventos fs:* e data-fs-toggle.","a11y":"Preserve foco, Escape, bloqueio de scroll, aria-invalid e preferência por movimento reduzido.","states":"Flip de posicionamento, focus trap, tema, validação, clique externo e transições.","source":"examples/js-foundation.html","height":1500}
7
7
  ]</script><script src="../dist/js/fokus.js"></script><script src="assets/showcase-contracts.js"></script><script src="assets/showcase.js"></script></body></html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fokus-styles",
3
- "version": "2.0.0",
3
+ "version": "2.3.0",
4
4
  "description": "Fokus Styles is a complete CSS framework with components, utilities, vanilla JavaScript, icons, React wrappers, and a CLI.",
5
5
  "keywords": [
6
6
  "css",
@@ -48,6 +48,19 @@
48
48
  "./forms.css": "./dist/css/forms.css",
49
49
  "./components.css": "./dist/css/components.css",
50
50
  "./helpers.css": "./dist/css/helpers.css",
51
+ "./core": "./dist/css/fokus-core.css",
52
+ "./components": "./dist/css/fokus-components.css",
53
+ "./utilities": "./dist/css/fokus-utilities.css",
54
+ "./themes": "./dist/css/fokus-dark.css",
55
+ "./config": "./packages/fokus-core/scss/settings/_config.scss",
56
+ "./js/theme": {
57
+ "types": "./packages/fokus-js/js/theme.d.ts",
58
+ "default": "./packages/fokus-js/js/theme.js"
59
+ },
60
+ "./js/focus-trap": {
61
+ "types": "./packages/fokus-js/js/focus-trap.d.ts",
62
+ "default": "./packages/fokus-js/js/focus-trap.js"
63
+ },
51
64
  "./fonts.css": "./dist/css/fonts.css",
52
65
  "./js": {
53
66
  "types": "./packages/fokus-js/js/fokus.d.ts",
@@ -10,6 +10,7 @@ const HELP = `fokus — CLI do Fokus Styles
10
10
 
11
11
  Uso:
12
12
  fokus build <entry.scss> -o <saida.css> [--minify]
13
+ fokus build <entry.scss> -o <saida.css> [--preset core|components|utilities|full]
13
14
  fokus theme <nome> --primary <#hex> [-o <saida.css>]
14
15
  fokus analyze <arquivo...>
15
16
 
@@ -40,6 +41,12 @@ function parseFlags(args) {
40
41
  } else if (arg === "--primary") {
41
42
  flags.primary = args[i + 1];
42
43
  i += 1;
44
+ } else if (arg === "--preset") {
45
+ flags.preset = args[i + 1];
46
+ i += 1;
47
+ } else if (arg === "--exclude") {
48
+ flags.exclude = args[i + 1]?.split(",").filter(Boolean) ?? [];
49
+ i += 1;
43
50
  } else {
44
51
  positional.push(arg);
45
52
  }
@@ -58,7 +65,17 @@ async function runBuild(args) {
58
65
  return;
59
66
  }
60
67
 
61
- const { outPath } = await buildCss({ entry, out: flags.out, minify: Boolean(flags.minify) });
68
+ const presetEntries = { core: ["scss", "entries", "core-entry.scss"], components: ["scss", "entries", "components-entry.scss"], utilities: ["scss", "entries", "utilities-entry.scss"], full: ["scss", "fokus.scss"] };
69
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
70
+ const resolvedEntry = flags.preset && presetEntries[flags.preset]
71
+ ? path.join(projectRoot, ...presetEntries[flags.preset])
72
+ : entry;
73
+ if (flags.preset && !presetEntries[flags.preset]) {
74
+ console.error(`Preset desconhecido: ${flags.preset}`);
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+ const { outPath } = await buildCss({ entry: resolvedEntry, out: flags.out, minify: Boolean(flags.minify), exclude: flags.exclude ?? [] });
62
79
  console.log(`CSS compilado em ${outPath}`);
63
80
  }
64
81
 
@@ -9,7 +9,7 @@ import cssnano from "cssnano";
9
9
  // `fokus-styles/scss` ou qualquer outro pacote Sass) usando o mesmo pipeline
10
10
  // do build interno do FokusStyles (sass -> autoprefixer -> cssnano opcional),
11
11
  // sem exigir que o consumidor monte a própria toolchain.
12
- export async function buildCss({ entry, out, minify = false, loadPaths = [] }) {
12
+ export async function buildCss({ entry, out, minify = false, loadPaths = [], exclude = [] }) {
13
13
  const entryPath = path.resolve(entry);
14
14
  const outPath = path.resolve(out);
15
15
 
@@ -20,7 +20,17 @@ export async function buildCss({ entry, out, minify = false, loadPaths = [] }) {
20
20
  style: "expanded",
21
21
  });
22
22
 
23
- const plugins = minify ? [autoprefixer, cssnano] : [autoprefixer];
23
+ const excluded = exclude.map((name) => String(name).replace(/^fs-/, "").trim()).filter(Boolean);
24
+ const excludePlugin = {
25
+ postcssPlugin: "fokus-exclude-components",
26
+ Once(root) {
27
+ if (excluded.length === 0) return;
28
+ root.walkRules((rule) => {
29
+ if (excluded.some((name) => rule.selector.includes(`.fs-${name}`) || rule.selector.includes(`[data-fs=\"${name}\"]`))) rule.remove();
30
+ });
31
+ },
32
+ };
33
+ const plugins = minify ? [excludePlugin, autoprefixer, cssnano] : [excludePlugin, autoprefixer];
24
34
  const result = await postcss(plugins).process(compiled.css, {
25
35
  from: entryPath,
26
36
  to: outPath,
@@ -27,6 +27,19 @@
27
27
  border-bottom-left-radius: var(--fs-radius-md);
28
28
  }
29
29
 
30
+ .fs-accordion-flush .fs-accordion-item {
31
+ border-inline: 0;
32
+ border-radius: 0;
33
+ }
34
+
35
+ .fs-accordion-flush .fs-accordion-item:first-child {
36
+ border-block-start: 0;
37
+ }
38
+
39
+ .fs-accordion-always-open .fs-accordion-button[aria-expanded="true"]::after {
40
+ transform: rotate(-135deg);
41
+ }
42
+
30
43
  .fs-accordion-header {
31
44
  margin: 0;
32
45
  }
@@ -79,4 +92,10 @@
79
92
  border-top: 1px solid var(--fs-color-border);
80
93
  background-color: var(--fs-color-surface-raised);
81
94
  }
95
+
96
+ @media (prefers-reduced-motion: reduce) {
97
+ .fs-accordion-button::after {
98
+ transition: none;
99
+ }
100
+ }
82
101
  }
@@ -0,0 +1,22 @@
1
+ /* stylelint-disable declaration-block-single-line-max-declarations */
2
+ @layer components {
3
+ .fs-avatar {
4
+ display: inline-flex;
5
+ flex: 0 0 auto;
6
+ align-items: center;
7
+ justify-content: center;
8
+ width: 2.5rem;
9
+ height: 2.5rem;
10
+ overflow: hidden;
11
+ color: var(--fs-color-on-primary);
12
+ font-weight: 600;
13
+ background: var(--fs-color-primary);
14
+ border-radius: 50%;
15
+ object-fit: cover;
16
+ }
17
+
18
+ .fs-avatar-sm { width: 2rem; height: 2rem; font-size: .8125rem; }
19
+ .fs-avatar-lg { width: 3.5rem; height: 3.5rem; font-size: 1.25rem; }
20
+ .fs-avatar-square { border-radius: var(--fs-radius-md); }
21
+ .fs-avatar img { width: 100%; height: 100%; object-fit: cover; }
22
+ }
@@ -0,0 +1,14 @@
1
+ /* stylelint-disable declaration-block-single-line-max-declarations */
2
+ @layer components {
3
+ .fs-button-group {
4
+ display: inline-flex;
5
+ vertical-align: middle;
6
+ }
7
+
8
+ .fs-button-group > .fs-btn { border-radius: 0; }
9
+ .fs-button-group > .fs-btn:first-child { border-start-start-radius: var(--fs-radius-sm); border-end-start-radius: var(--fs-radius-sm); }
10
+ .fs-button-group > .fs-btn:last-child { border-start-end-radius: var(--fs-radius-sm); border-end-end-radius: var(--fs-radius-sm); }
11
+ .fs-button-group > .fs-btn + .fs-btn { margin-inline-start: -1px; }
12
+ .fs-button-group-vertical { flex-direction: column; align-items: flex-start; }
13
+ .fs-button-group-vertical > .fs-btn { width: 100%; }
14
+ }