@celke/kole-react 0.0.0-stage → 0.2.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 (103) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +370 -3
  3. package/dist/components/Abas/Abas.d.ts +55 -0
  4. package/dist/components/AcaoLinha/AcaoLinha.d.ts +12 -0
  5. package/dist/components/Acordeao/Acordeao.d.ts +42 -0
  6. package/dist/components/Alerta/Alerta.d.ts +39 -0
  7. package/dist/components/Arquivo/CampoAnexo.d.ts +27 -0
  8. package/dist/components/Arquivo/DialogoArquivo.d.ts +11 -0
  9. package/dist/components/Arquivo/Foto.d.ts +29 -0
  10. package/dist/components/Arquivo/VisualizadorArquivo.d.ts +12 -0
  11. package/dist/components/Avaliacao/Avaliacao.d.ts +30 -0
  12. package/dist/components/Avatar/Avatar.d.ts +12 -0
  13. package/dist/components/Badge/Badge.d.ts +43 -0
  14. package/dist/components/Balao/Balao.d.ts +30 -0
  15. package/dist/components/BoasVindas/BoasVindas.d.ts +19 -0
  16. package/dist/components/Botao/Botao.d.ts +40 -0
  17. package/dist/components/BotaoFechar/BotaoFechar.d.ts +10 -0
  18. package/dist/components/Busca/Busca.d.ts +27 -0
  19. package/dist/components/CabecalhoPagina/CabecalhoPagina.d.ts +21 -0
  20. package/dist/components/Campo/Campo.d.ts +63 -0
  21. package/dist/components/CampoBusca/CampoBusca.d.ts +28 -0
  22. package/dist/components/CampoBusca/CampoCidade.d.ts +25 -0
  23. package/dist/components/CampoDominio/CampoDominio.d.ts +17 -0
  24. package/dist/components/CampoMarcacao/CampoMarcacao.d.ts +13 -0
  25. package/dist/components/CampoMensagem/CampoMensagem.d.ts +31 -0
  26. package/dist/components/CampoSelect/CampoSelect.d.ts +25 -0
  27. package/dist/components/CampoSenha/CampoSenha.d.ts +16 -0
  28. package/dist/components/CampoTextarea/CampoTextarea.d.ts +11 -0
  29. package/dist/components/CampoTexto/CampoTexto.d.ts +63 -0
  30. package/dist/components/Card/Card.d.ts +51 -0
  31. package/dist/components/Carregando/Carregando.d.ts +10 -0
  32. package/dist/components/Carrossel/Carrossel.d.ts +33 -0
  33. package/dist/components/Chip/Chip.d.ts +11 -0
  34. package/dist/components/Citacao/Citacao.d.ts +20 -0
  35. package/dist/components/Confirmacao/Confirmacao.d.ts +35 -0
  36. package/dist/components/Conversa/Conversa.d.ts +21 -0
  37. package/dist/components/Detalhes/Detalhes.d.ts +12 -0
  38. package/dist/components/Dialogo/Dialogo.d.ts +37 -0
  39. package/dist/components/Dialogo/Sobreposicao.d.ts +19 -0
  40. package/dist/components/Dica/Dica.d.ts +15 -0
  41. package/dist/components/Escolha/Escolha.d.ts +14 -0
  42. package/dist/components/Estrutura/Estrutura.d.ts +106 -0
  43. package/dist/components/Filtros/Filtros.d.ts +22 -0
  44. package/dist/components/Form/Form.d.ts +85 -0
  45. package/dist/components/Girador/Girador.d.ts +15 -0
  46. package/dist/components/GrupoBotoes/GrupoBotoes.d.ts +56 -0
  47. package/dist/components/Imagem/Imagem.d.ts +40 -0
  48. package/dist/components/Indicador/Indicador.d.ts +29 -0
  49. package/dist/components/Lista/Lista.d.ts +47 -0
  50. package/dist/components/ListaCards/ListaCards.d.ts +28 -0
  51. package/dist/components/Mensagem/Mensagem.d.ts +40 -0
  52. package/dist/components/MenuSuspenso/MenuSuspenso.d.ts +79 -0
  53. package/dist/components/Paginacao/Paginacao.d.ts +13 -0
  54. package/dist/components/Picklist/Picklist.d.ts +28 -0
  55. package/dist/components/Placa/Placa.d.ts +9 -0
  56. package/dist/components/Preferencias/Preferencias.d.ts +43 -0
  57. package/dist/components/Progresso/Progresso.d.ts +42 -0
  58. package/dist/components/QuadroArrastavel/QuadroArrastavel.d.ts +27 -0
  59. package/dist/components/Recolhivel/Recolhivel.d.ts +14 -0
  60. package/dist/components/Referencias/Referencias.d.ts +24 -0
  61. package/dist/components/Skeleton/Skeleton.d.ts +8 -0
  62. package/dist/components/Status/Status.d.ts +10 -0
  63. package/dist/components/Tabela/Tabela.d.ts +57 -0
  64. package/dist/components/TelaErro/TelaErro.d.ts +25 -0
  65. package/dist/components/TelaLogin/TelaLogin.d.ts +66 -0
  66. package/dist/components/Toast/Toast.d.ts +62 -0
  67. package/dist/components/Trilha/Trilha.d.ts +60 -0
  68. package/dist/components/Vazio/Vazio.d.ts +12 -0
  69. package/dist/css.d.ts +1 -0
  70. package/dist/estilos.css +72 -0
  71. package/dist/fonts/LICENSE +93 -0
  72. package/dist/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  73. package/dist/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  74. package/dist/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  75. package/dist/fonts/inter-greek-wght-normal.woff2 +0 -0
  76. package/dist/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  77. package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
  78. package/dist/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  79. package/dist/index.d.ts +81 -0
  80. package/dist/index.js +6938 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/tailwind.css +82 -0
  83. package/dist/tokens.css +407 -0
  84. package/dist/utils/Icone.d.ts +22 -0
  85. package/dist/utils/anexos.d.ts +20 -0
  86. package/dist/utils/constantes.d.ts +14 -0
  87. package/dist/utils/cx.d.ts +2 -0
  88. package/dist/utils/datas.d.ts +17 -0
  89. package/dist/utils/entidade.d.ts +19 -0
  90. package/dist/utils/jwt.d.ts +11 -0
  91. package/dist/utils/mascara.d.ts +19 -0
  92. package/dist/utils/picklist.d.ts +15 -0
  93. package/dist/utils/portal.d.ts +8 -0
  94. package/dist/utils/senha.d.ts +21 -0
  95. package/dist/utils/severidade.d.ts +7 -0
  96. package/dist/utils/texto.d.ts +28 -0
  97. package/dist/utils/tipos.d.ts +8 -0
  98. package/dist/utils/tiposCampo.d.ts +12 -0
  99. package/dist/utils/useBuscaAssincrona.d.ts +22 -0
  100. package/dist/utils/useCliqueFora.d.ts +3 -0
  101. package/dist/utils/usePosicaoFlutuante.d.ts +13 -0
  102. package/dist/utils/validadores.d.ts +42 -0
  103. package/package.json +91 -6
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Celke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,370 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @celke/kole-react
2
+
3
+ Biblioteca de componentes **React** para sistemas administrativos. Design tokens
4
+ próprios (cores, raios, densidade, tema claro/escuro), CSS puro e sem dependência
5
+ de Tailwind.
6
+
7
+ ## Instalação
8
+
9
+ ```bash
10
+ npm i @celke/kole-react
11
+ ```
12
+
13
+ Requisitos: React 18.2+ ou 19. A fonte Inter já vem dentro do pacote (o
14
+ `estilos.css` carrega só os alfabetos que a página usa). O pacote é só ESM, traz
15
+ os tipos TypeScript e já vem marcado com `'use client'`, então funciona no App
16
+ Router do Next.js.
17
+
18
+ ## Uso
19
+
20
+ ```tsx
21
+ // main.tsx
22
+ import { createRoot } from 'react-dom/client';
23
+ import '@celke/kole-react/estilos.css';
24
+ import { ProvedorUi } from '@celke/kole-react';
25
+ import { App } from './App';
26
+
27
+ createRoot(document.getElementById('root')!).render(
28
+ <ProvedorUi>
29
+ <App />
30
+ </ProvedorUi>,
31
+ );
32
+ ```
33
+
34
+ `ProvedorUi` liga tema/densidade (lembrados no navegador), toasts (`useToast`) e
35
+ confirmação (`useConfirmacao`).
36
+
37
+ ### Tema sem piscar ao abrir
38
+
39
+ O `ProvedorUi` só aplica o tema depois que o React monta, e quem salvou o tema
40
+ escuro vê a página clara piscar por um instante. Para evitar, aplique o tema no
41
+ `<head>`, antes do CSS da página pintar:
42
+
43
+ ```html
44
+ <!-- index.html -->
45
+ <head>
46
+ <script>(function(){var p={};try{p=JSON.parse(localStorage.getItem("lp-preferencias")||"{}")||{}}catch(e){}var t=p.tema||"sistema";var h=document.documentElement;h.setAttribute("data-theme",{"claro":"light","escuro":"dark","sistema":"system"}[t]||"light");var d={"compacta":"density-compact","confortavel":"density-comfortable","espacosa":"density-cozy"}[p.densidade||"confortavel"];if(d)h.classList.add(d)})();</script>
47
+ </head>
48
+ ```
49
+
50
+ Com Content-Security-Policy sem `'unsafe-inline'` em `script-src`, o script
51
+ inline é bloqueado: salve o mesmo conteúdo num arquivo (por exemplo,
52
+ `public/tema.js`) e carregue com `<script src="/tema.js"></script>` no
53
+ `<head>`, sem `type="module"` nem `defer`, para rodar antes da pintura.
54
+
55
+ Esse é o resultado de `scriptTemaInicial()`. Com SSR (Next.js, React Router em
56
+ modo framework), gere pela função, com o mesmo `temaInicial`/`densidadeInicial`
57
+ passado ao `ProvedorUi`:
58
+
59
+ ```tsx
60
+ import { scriptTemaInicial } from '@celke/kole-react';
61
+
62
+ <head>
63
+ <script dangerouslySetInnerHTML={{ __html: scriptTemaInicial({ temaInicial: 'sistema' }) }} />
64
+ </head>
65
+ ```
66
+
67
+ ### Layout com React Router
68
+
69
+ ```tsx
70
+ import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom';
71
+ import { Estrutura } from '@celke/kole-react';
72
+
73
+ export function Layout({ sair }: { sair: () => void }) {
74
+ const { pathname } = useLocation();
75
+ const navigate = useNavigate();
76
+ return (
77
+ <Estrutura
78
+ marca={{ nome: 'Gestor', subtitulo: 'Gestão comercial', logo: '/logo.svg' }}
79
+ nav={[
80
+ { itens: [{ rotulo: 'Painel', icone: 'house', rota: '/' }] },
81
+ {
82
+ rotulo: 'Cadastros',
83
+ itens: [
84
+ { rotulo: 'Pedidos', icone: 'shopping-cart', rota: '/pedidos' },
85
+ { rotulo: 'Tabelas', icone: 'table', filhos: [{ rotulo: 'Categorias', rota: '/categorias' }] },
86
+ ],
87
+ },
88
+ ]}
89
+ rotaAtual={pathname}
90
+ aoNavegar={navigate}
91
+ usuario={{ nome: 'Gabrielly Ribeiro', cargo: 'Gerente comercial' }}
92
+ menuUsuario={[{ id: 'sair', rotulo: 'Sair', icone: 'log-out', destrutivo: true }]}
93
+ aoEscolherMenuUsuario={(id) => id === 'sair' && sair()}
94
+ >
95
+ <Outlet />
96
+ </Estrutura>
97
+ );
98
+ }
99
+ ```
100
+
101
+ Para links de verdade do roteador (ctrl+clique abre em nova aba), passe
102
+ `renderLink={(p) => <Link to={p.href} className={p.className} onClick={p.onClick} aria-current={p['aria-current']}>{p.children}</Link>}`.
103
+
104
+ ## Componentes
105
+
106
+ | Grupo | Componentes |
107
+ |---|---|
108
+ | Layout | `Estrutura`, `Busca`, `CabecalhoPagina`, `BoasVindas`, `TelaLogin`, `Abas`/`Aba`, `Card` |
109
+ | Ações | `Botao`, `AcaoLinha`, `Dica` |
110
+ | Formulário | `Form` (página ou modal, `entidade`/`modo`), `FormSecao`, `Campo`, `CampoTexto`, `CampoTextarea`, `CampoSenha`, `CampoSelect`, `CampoSelectMultiplo`, `CampoMarcacao`, `Escolha`, `Picklist`, `Filtros` |
111
+ | Campos prontos | `CampoCpf`, `CampoCnpj`, `CampoCpfCnpj`, `CampoCep`, `CampoTelefone`, `CampoPlaca`, `CampoRenavam`, `CampoChassi`, `CampoCnh`, `CampoImei`, `CampoEmail`, `CampoData`, `CampoHora`, `CampoDataHora`, `CampoMoeda`, `CampoInteiro`, `CampoUf`, `CampoUfMultiplo`, `CampoTipoPessoa` |
112
+ | Busca remota | `CampoBusca`, `CampoCidade`, `useBuscaAssincrona` |
113
+ | Arquivos | `CampoAnexo`, `VisualizadorArquivo`, `DialogoArquivo`, `Foto` |
114
+ | Dados | `Tabela`, `ListaCards`, `Paginacao`, `Indicador`/`Indicadores`, `Detalhes`, `Status`, `Chip`, `Avatar`, `Placa` |
115
+ | Feedback | `Alerta`, `Dialogo` (central ou `lateral`), `useConfirmacao` (+ `.remover()`, `.avisar()`), `useToast`, `Vazio`, `Skeleton`, `Carregando` |
116
+ | Conversa | `Conversa`, `Mensagem`, `CampoMensagem`, `Referencias` |
117
+
118
+ ### Conversa (chat)
119
+
120
+ `Conversa` é a área rolável (acompanha o fim durante o streaming e para de
121
+ puxar se a pessoa rolar para cima); `Mensagem` é a bolha; `CampoMensagem` é o
122
+ campo de envio (Enter envia, Shift+Enter quebra a linha); `Referencias` lista
123
+ as fontes de uma resposta. A conversa ocupa a altura do contêiner pai, e a
124
+ busca da resposta (rede, streaming, Markdown) fica com o app.
125
+
126
+ ```tsx
127
+ import { useState } from 'react';
128
+ import {
129
+ CampoMensagem,
130
+ Conversa,
131
+ Mensagem,
132
+ Referencias,
133
+ type AutorMensagem,
134
+ type EstadoMensagem,
135
+ type ItemReferencia,
136
+ } from '@celke/kole-react';
137
+
138
+ interface ItemChat {
139
+ id: string;
140
+ autor: AutorMensagem;
141
+ estado: EstadoMensagem;
142
+ texto: string;
143
+ hora: Date;
144
+ fontes?: ItemReferencia[]; // { id, titulo, detalhe?, trecho?, href? }
145
+ }
146
+
147
+ interface ChatProps {
148
+ mensagens: ItemChat[];
149
+ gerando: boolean;
150
+ enviar: (texto: string) => void;
151
+ parar: () => void;
152
+ }
153
+
154
+ export function Chat({ mensagens, gerando, enviar, parar }: ChatProps) {
155
+ const [texto, setTexto] = useState('');
156
+ return (
157
+ <div style={{ height: '32rem' }}>
158
+ <Conversa
159
+ campo={
160
+ <CampoMensagem
161
+ valor={texto}
162
+ aoMudar={setTexto}
163
+ aoEnviar={(t) => {
164
+ enviar(t);
165
+ setTexto('');
166
+ }}
167
+ gerando={gerando}
168
+ aoParar={parar}
169
+ />
170
+ }
171
+ >
172
+ {mensagens.map((m) => (
173
+ <Mensagem
174
+ key={m.id}
175
+ autor={m.autor} // 'usuario' | 'assistente' | 'sistema'
176
+ estado={m.estado} // 'enviando' | 'gerando' | 'concluida' | 'erro'
177
+ hora={m.hora}
178
+ copiavel={m.autor === 'assistente'}
179
+ rodape={m.fontes && <Referencias itens={m.fontes} />}
180
+ >
181
+ {m.texto}
182
+ </Mensagem>
183
+ ))}
184
+ </Conversa>
185
+ </div>
186
+ );
187
+ }
188
+ ```
189
+
190
+ Utilitários (lógica pura, também exportados): validadores BR (`isCpf`, `isCnpj`,
191
+ `isCnh`, `isImei`, `validar(tipo, valor)`…), `TIPOS_CAMPO` (máscara +
192
+ placeholder + regra por tipo), `ESTADOS`/`TIPOS_PESSOA`, por extenso
193
+ (`numeroPorExtenso`, `valorMonetarioPorExtenso`, `listaPorExtenso`), texto
194
+ (`normalizar`, `nomeProprio`, `nomeCurto`, `iniciais`, `formatCpfCnpj`), datas
195
+ pt-BR (`parseDataBr`, `formatDataBr`…), `tituloDeEntidade`/`nomeDeArquivo`,
196
+ anexos (`formatBytes`, `tipoDeArquivo`, `iconeArquivo`), senha
197
+ (`validarSenha`, `forcaSenha`), picklist, JWT (`decodificarJwt`, `jwtExpirado`…)
198
+ e `severidadeCor`. A demo do repositório tem uma página interativa de cada um
199
+ (`#/utilitarios`).
200
+
201
+ Convenções:
202
+
203
+ - Campos são controlados por `valor` + `aoMudar`; aceitam `rotulo`, `dica`,
204
+ `erro`, `aviso`, `obrigatorio` e `variante="inline"` (pílula com rótulo dentro,
205
+ para barras de filtro).
206
+ - Severidades: `ok`, `info`, `warn`, `err`, `neutral` (botão: `pri`, `secundario`,
207
+ `ok`, `warn`, `err`).
208
+ - Ícones: [Lucide](https://lucide.dev/icons) (SVG, já incluso no pacote). Passe o
209
+ nome (`icone="save"`, `icone="trash-2"`) ou o componente
210
+ (`import { Rocket } from 'lucide-react'` → `icone={<Rocket />}`). Só os ícones
211
+ registrados entram no bundle; para usar outro pelo nome, registre uma vez:
212
+ `registrarIcones({ rocket: Rocket })`. Um nome não registrado avisa no console
213
+ em desenvolvimento.
214
+ - Pendência de campo: o erro aparece ao **sair** do campo depois de digitar, ou
215
+ quando o `Form` tenta salvar (todos os campos acusam). Um valor carregado de
216
+ fora que não atende ao formato não é recusado: vira aviso âmbar.
217
+ - Grade do `FormSecao`: 4 colunas na página, 3 no modal; `className="lp-col-2"`,
218
+ `lp-col-3` ou `lp-col-full` alarga o campo.
219
+ - Cadastro com vários formulários: passe `menu` (itens com `id`, `rotulo`,
220
+ `icone`, `href`) e `menuAtivo` ao `Form` de cada página; o menu lateral liga
221
+ os formulários entre si, cada um com o próprio Salvar.
222
+
223
+ ## Tema e tokens
224
+
225
+ Toda cor/medida vem de variáveis CSS (os tokens). Para mudar a marca, redefina
226
+ os tokens no CSS do app (a ordem dos imports não importa, veja [CSS](#css)) — por exemplo
227
+ `--navy` (marinho da sidebar), `--pri` (azul de ação) e `--acento-claro` (laranja
228
+ do item ativo). O tema escuro tem valores próprios, então redefina lá também:
229
+
230
+ ```css
231
+ :root {
232
+ --pri: #0f766e;
233
+ --pri-ink: #115e59;
234
+ }
235
+ [data-theme='dark'] {
236
+ --pri: #2dd4bf;
237
+ --pri-ink: #5eead4;
238
+ }
239
+ ```
240
+
241
+ O tema vem do atributo `data-theme` no `<html>`: `light`, `dark` ou `system`
242
+ (segue o modo do sistema operacional, inclusive se ele mudar com a página
243
+ aberta). No `ProvedorUi`, são os temas `claro`, `escuro` e `sistema`
244
+ (`temaInicial`, `definirTema`). Enquanto a pessoa não escolhe, vale o
245
+ `temaInicial`, e sem ele o `sistema`. `escuro` no `usePreferencias()` diz o que
246
+ está na tela. A densidade usa as classes `density-compact`, `density-comfortable` e
247
+ `density-cozy` no `<body>`. O `ProvedorUi` cuida de tudo isso. A lista
248
+ completa de tokens, com os valores dos dois temas, está em
249
+ `@celke/kole-react/tokens.css`.
250
+
251
+ Para usar só os tokens fora do React: `@import '@celke/kole-react/tokens.css';`
252
+ e `<html data-theme="system">` (ou `light`/`dark`) para escolher o tema sem JS.
253
+
254
+ ## CSS
255
+
256
+ Classes CSS usam o prefixo `lp-`, e todo o CSS da biblioteca fica na camada
257
+ `@layer kole`. CSS fora de camada sempre vence CSS em camada, então o CSS do
258
+ app personaliza o kole sem disputar especificidade nem depender da ordem dos
259
+ imports. O lado oposto: uma regra global do app (`a { color: … }`,
260
+ `h1 { font-size: … }`) também atinge os componentes, por isso escope as regras
261
+ de elemento do app (por exemplo, `.conteudo-artigo h1`).
262
+
263
+ ### Com Tailwind 4
264
+
265
+ A ordem das camadas precisa ser declarada **antes** de qualquer CSS, então o
266
+ CSS do app (com o Tailwind) é importado antes do `estilos.css`:
267
+
268
+ ```css
269
+ /* app.css — esta linha vem antes do @import do Tailwind */
270
+ @layer theme, base, kole, components, utilities;
271
+ @import 'tailwindcss';
272
+ @import '@celke/kole-react/tailwind.css'; /* opcional: a ponte abaixo */
273
+ ```
274
+
275
+ ```tsx
276
+ // main.tsx
277
+ import './app.css';
278
+ import '@celke/kole-react/estilos.css';
279
+ ```
280
+
281
+ Assim o kole vence o preflight do Tailwind (botões e campos mantêm o estilo) e
282
+ os utilitários vencem o kole (`<Botao className="mt-4" />` funciona). Se o
283
+ `estilos.css` vier primeiro, a camada `kole` fica abaixo do preflight e os
284
+ componentes perdem o estilo.
285
+
286
+ A ponte `@celke/kole-react/tailwind.css` faz duas coisas:
287
+
288
+ - o `dark:` do Tailwind segue o tema do kole (`data-theme`), e não o do sistema
289
+ operacional, então uma tela com os dois não fica metade clara e metade escura;
290
+ - os tokens viram utilitários com o mesmo nome: `bg-surface`, `text-ink`,
291
+ `text-t3`, `border-border`, `bg-pri`, `text-ok-fg`, `bg-err-bg`,
292
+ `rounded-card`, `rounded-pill`… Como apontam para os tokens, trocam de tema
293
+ junto com o kole. `font-sans` e `font-mono` passam a ser as fontes do kole.
294
+
295
+ A densidade não chega aos utilitários: `p-4` e `h-10` são fixos, só os
296
+ componentes `lp-*` mudam com ela.
297
+
298
+ ## Desenvolvimento
299
+
300
+ ```bash
301
+ npm install
302
+ npm run dev # http://localhost:5173 — demo com todos os componentes
303
+ npm run typecheck
304
+ npm run build # dist/index.js + tipos (.d.ts) + dist/estilos.css + dist/tokens.css + dist/tailwind.css
305
+ ```
306
+
307
+ A demo usa o mesmo CSS do pacote (na camada `kole`, com o tema `sistema`), e o
308
+ `demo/demo.css` fica fora da camada, como o CSS de um app.
309
+
310
+ ### Teste visual
311
+
312
+ Captura cada página do menu da demo (`demo/nav.ts`) nos temas claro e escuro e
313
+ compara pixel a pixel com uma referência. Também confere que o tema `sistema`,
314
+ com o sistema operacional no escuro, sai igual ao tema escuro. Página nova no
315
+ menu entra no teste sozinha.
316
+
317
+ O print muda de um sistema operacional para outro, então a referência fica em
318
+ `tests/visual/__referencia__/<sistema>/`. Só a de Linux vai para o git: é a que
319
+ o CI usa (`.github/workflows/visual.yml`, na imagem Docker do Playwright), em
320
+ todo PR e na `main`.
321
+
322
+ **Mudança visual de propósito:** o CI do PR vai falhar apontando as páginas que
323
+ mudaram (o relatório com as diferenças fica nos artefatos do workflow). Confira
324
+ e grave a referência nova: Actions → Teste visual → Run workflow, na branch do
325
+ PR, com "atualizar" marcado. O workflow faz o commit da referência na branch.
326
+
327
+ No Windows ou no macOS, o teste usa uma referência local, fora do git:
328
+
329
+ ```bash
330
+ npx playwright install chromium # uma vez
331
+ npm run visual:referencia # ANTES de mexer no CSS: grava a referência
332
+ npm run visual # depois: aponta cada página que mudou
333
+ npx playwright show-report # diferenças lado a lado
334
+ ```
335
+
336
+ Para comparar com uma versão já publicada, sirva a demo dela noutra porta (por
337
+ exemplo, num `git worktree` da tag) e gere a referência com
338
+ `KOLE_URL=http://localhost:5181 npm run visual:referencia`.
339
+
340
+ Para testar uma versão que ainda não está no npm, instale direto do GitHub
341
+ (o `prepare` gera o `dist/` na instalação) ou de um pacote local:
342
+
343
+ ```bash
344
+ npm i github:celkecursos/kole-react
345
+ # ou: gere o .tgz na pasta da biblioteca e instale no app
346
+ npm pack # → celke-kole-react-<versão>.tgz
347
+ npm i ../kole-react/celke-kole-react-<versão>.tgz
348
+ ```
349
+
350
+ Evite `npm i ../kole-react` (pasta): o npm cria um link e o bundler
351
+ pega o React do `node_modules` da biblioteca — dois Reacts no app quebram os hooks
352
+ (_Invalid hook call_). Se precisar do link durante o desenvolvimento, use
353
+ `resolve: { dedupe: ['react', 'react-dom'] }` no `vite.config` do app.
354
+
355
+ Publicar uma versão nova: `npm version patch` (só correção) ou `npm version minor`
356
+ (muda a aparência ou a API — antes da 1.0, o `^0.x` dos apps não pega a minor
357
+ sozinho), registre no [CHANGELOG](CHANGELOG.md) e confira o conteúdo com
358
+ `npm pack --dry-run`. Depois do merge na `main`, publique pelo GitHub: Actions →
359
+ Publicar → Run workflow (`.github/workflows/publish.yml`). O npm confia nesse
360
+ workflow pela publicação confiável (Trusted Publisher, sem token nem segundo
361
+ fator) e o pacote sai com o atestado de procedência. O workflow recusa versão
362
+ que já está no npm.
363
+
364
+ ## Autor
365
+
366
+ Criado por **Cesar Szpak**.
367
+
368
+ ## Licença
369
+
370
+ MIT © 2026 Celke — veja o arquivo [LICENSE](https://github.com/celkecursos/kole-react/blob/main/LICENSE).
@@ -0,0 +1,55 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type IconeProp } from '../../utils/Icone.js';
3
+ export interface AbaProps {
4
+ valor: string;
5
+ rotulo: string;
6
+ icone?: IconeProp;
7
+ desabilitada?: boolean;
8
+ /** Número de pendências (chip âmbar na aba). */
9
+ pendentes?: number;
10
+ /** Selo livre depois do rótulo (ex.: `<Badge>`). */
11
+ badge?: ReactNode;
12
+ /** Abas vizinhas com o mesmo grupo viram uma aba com menu suspenso. */
13
+ grupo?: string;
14
+ children?: ReactNode;
15
+ }
16
+ /** Painel de uma aba. Só funciona dentro de `<Abas>`. */
17
+ export declare function Aba(_props: AbaProps): null;
18
+ /**
19
+ * `sublinhado` (padrão) · `classica` = aba com borda colada no painel ·
20
+ * `pilulas` = ativa sólida · `suave` = ativa em fundo claro · `contorno` =
21
+ * todas com borda, ativa sólida · `segmentado` = trilho cinza com a ativa
22
+ * destacada · `passos` = círculos numerados · `cartao` = ativa e painel na
23
+ * mesma cor · `blocos` = caixas com ícone em cima e seta para o painel.
24
+ */
25
+ export type VarianteAbas = 'sublinhado' | 'classica' | 'pilulas' | 'suave' | 'contorno' | 'segmentado' | 'passos' | 'cartao' | 'blocos';
26
+ /** `preencher` = abas crescem conforme o rótulo · `justificado` = todas da mesma largura. */
27
+ export type AlinhamentoAbas = 'inicio' | 'centro' | 'fim' | 'preencher' | 'justificado';
28
+ export interface AbasProps {
29
+ /** Aba ativa (controlado). Sem ele, começa na primeira. */
30
+ ativa?: string;
31
+ aoMudar?: (valor: string) => void;
32
+ /** Nome acessível da lista de abas. */
33
+ rotulo?: string;
34
+ variante?: VarianteAbas;
35
+ /** Padrão: `preencher` no sublinhado, `inicio` nas demais. */
36
+ alinhamento?: AlinhamentoAbas;
37
+ /** `responsiva` = empilha as abas abaixo de 36rem. */
38
+ orientacao?: 'horizontal' | 'vertical' | 'responsiva';
39
+ /** Ícone acima do rótulo. */
40
+ iconeAcima?: boolean;
41
+ /** `caixa` = painel com borda e respiro. */
42
+ painel?: 'livre' | 'caixa';
43
+ /** `base` = abas abaixo do painel (rodapé). */
44
+ posicaoBarra?: 'topo' | 'base';
45
+ /** Título à esquerda, na mesma linha das abas. */
46
+ cabecalho?: ReactNode;
47
+ /** Modo página: cabeçalho com Voltar + título acima das abas. */
48
+ titulo?: string;
49
+ identidade?: string;
50
+ aoVoltar?: () => void;
51
+ acoes?: ReactNode;
52
+ children: ReactNode;
53
+ className?: string;
54
+ }
55
+ export declare function Abas({ ativa, aoMudar, rotulo, variante, alinhamento, orientacao, iconeAcima, painel, posicaoBarra, cabecalho, titulo, identidade, aoVoltar, acoes, children, className, }: AbasProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import type { ButtonHTMLAttributes } from 'react';
2
+ import { type IconeProp } from '../../utils/Icone.js';
3
+ export interface AcaoLinhaProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
4
+ icone: IconeProp;
5
+ /** Nome da ação: vira dica e aria-label. */
6
+ rotulo: string;
7
+ /** `pri` = azul · `destaque` = laranja · `err` = vermelho. */
8
+ severidade?: 'neutra' | 'pri' | 'destaque' | 'err';
9
+ carregando?: boolean;
10
+ }
11
+ /** Botão-ícone compacto para ações de linha de tabela/card. */
12
+ export declare function AcaoLinha({ icone, rotulo, severidade, carregando, disabled, className, type, ...resto }: AcaoLinhaProps): import("react").JSX.Element;
@@ -0,0 +1,42 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type IconeProp } from '../../utils/Icone.js';
3
+ export type CorAcordeao = 'pri' | 'secundario' | 'ok' | 'warn' | 'err' | 'info' | 'destaque' | 'escuro';
4
+ /**
5
+ * `padrao` = caixa com borda · `sem-borda` = encostado no container ·
6
+ * `solido` = cabeçalho aberto na cor · `suave` = cabeçalho aberto em tom
7
+ * claro · `contorno` = borda na cor.
8
+ */
9
+ export type VarianteAcordeao = 'padrao' | 'sem-borda' | 'solido' | 'suave' | 'contorno';
10
+ export interface AcordeaoProps {
11
+ children: ReactNode;
12
+ /** Vários itens abertos ao mesmo tempo. */
13
+ multiplo?: boolean;
14
+ /** Itens abertos (controlado). */
15
+ abertos?: string[];
16
+ /** Itens abertos de início (não controlado). */
17
+ padraoAbertos?: string[];
18
+ aoMudar?: (abertos: string[]) => void;
19
+ variante?: VarianteAcordeao;
20
+ cor?: CorAcordeao;
21
+ /** Itens soltos, com espaço entre eles. */
22
+ separado?: boolean;
23
+ /** `seta` (padrão) · `mais` = + / − · `nenhum`. */
24
+ icone?: 'seta' | 'mais' | 'nenhum';
25
+ /** Lado do ícone. */
26
+ posicaoIcone?: 'fim' | 'inicio';
27
+ className?: string;
28
+ }
29
+ /** Lista de seções que abrem e fecham (acordeão). */
30
+ export declare function Acordeao({ children, multiplo, abertos: controlado, padraoAbertos, aoMudar, variante, cor, separado, icone, posicaoIcone, className, }: AcordeaoProps): import("react").JSX.Element;
31
+ export interface ItemAcordeaoProps {
32
+ valor: string;
33
+ titulo: ReactNode;
34
+ /** Ícone antes do título. */
35
+ icone?: IconeProp;
36
+ /** Cor só deste item (sobrepõe a do acordeão). */
37
+ cor?: CorAcordeao;
38
+ desabilitado?: boolean;
39
+ children: ReactNode;
40
+ }
41
+ /** Seção de um `Acordeao`. */
42
+ export declare function ItemAcordeao({ valor, titulo, icone, cor, desabilitado, children }: ItemAcordeaoProps): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type IconeProp } from '../../utils/Icone.js';
3
+ import type { Severidade } from '../../utils/tipos.js';
4
+ /** Cores do alerta: as severidades + `pri` (azul de ação) e `destaque` (laranja da marca). */
5
+ export type CorAlerta = Severidade | 'pri' | 'destaque';
6
+ export type AparenciaAlerta = 'suave' | 'solido' | 'contorno' | 'destaque' | 'faixa' | 'cartao';
7
+ export interface AlertaProps {
8
+ severidade?: CorAlerta;
9
+ /**
10
+ * `suave` (padrão) = fundo claro · `solido` = fundo cheio · `contorno` =
11
+ * só a borda · `destaque` = fundo neutro com filete lateral colorido ·
12
+ * `faixa` = título numa faixa sólida sobre o texto · `cartao` = bloco
13
+ * centralizado com ícone grande (avisos de página, confirmações).
14
+ */
15
+ aparencia?: AparenciaAlerta;
16
+ /** `pilula` = cantos totalmente arredondados. */
17
+ formato?: 'padrao' | 'pilula';
18
+ sombra?: 'pequena' | 'media' | 'grande';
19
+ /** Linha em negrito acima da mensagem. */
20
+ titulo?: ReactNode;
21
+ mensagem?: ReactNode;
22
+ children?: ReactNode;
23
+ /** Ícone da severidade por padrão; outro ícone, ou `false` para nenhum. */
24
+ icone?: IconeProp | false;
25
+ /** Imagem/avatar no lugar do ícone. */
26
+ midia?: ReactNode;
27
+ /** Botões ou links. */
28
+ acoes?: ReactNode;
29
+ /** `lado` = à direita da mensagem (padrão) · `abaixo` = sob o texto. */
30
+ posicaoAcoes?: 'lado' | 'abaixo';
31
+ /** Mostra o "×" e some ao clicar (sem controle externo). */
32
+ dispensavel?: boolean;
33
+ /** Mostra o "×" e avisa o app (que decide se o alerta sai da tela). */
34
+ aoFechar?: () => void;
35
+ /** `circulo` = "×" num círculo sólido da cor do alerta. */
36
+ botaoFechar?: 'simples' | 'circulo';
37
+ className?: string;
38
+ }
39
+ export declare function Alerta({ severidade, aparencia, formato, sombra, titulo, mensagem, children, icone, midia, acoes, posicaoAcoes, dispensavel, aoFechar, botaoFechar, className, }: AlertaProps): import("react").JSX.Element | null;
@@ -0,0 +1,27 @@
1
+ import { type Anexo } from '../../utils/anexos.js';
2
+ import { type CampoBaseProps } from '../Campo/Campo.js';
3
+ export interface CampoAnexoProps extends Omit<CampoBaseProps, 'variante' | 'obrigatorio'> {
4
+ /** Lista serializável de anexos (sem o `File`). */
5
+ valor?: Anexo[];
6
+ aoMudar?: (valor: Anexo[]) => void;
7
+ /** Filtro do seletor nativo (ex.: `image/*,.pdf`). */
8
+ accept?: string;
9
+ /** Vários arquivos (padrão: sim). Com `false`, a nova escolha substitui. */
10
+ multiple?: boolean;
11
+ /** Tamanho máximo por arquivo, em bytes (0 = sem limite). */
12
+ maxTamanho?: number;
13
+ /** Mínimo de anexos; acima de zero o campo é obrigatório. */
14
+ minimo?: number;
15
+ disabled?: boolean;
16
+ id?: string;
17
+ }
18
+ /** O que a `ref` do `CampoAnexo` expõe. */
19
+ export interface CampoAnexoRef {
20
+ /** O `File` da sessão por trás do anexo, ou `null` (anexo já salvo no backend). */
21
+ arquivoDe: (anexo: Pick<Anexo, 'nome' | 'tamanho'>) => File | null;
22
+ }
23
+ /**
24
+ * Zona de anexos (clicar ou arrastar). O valor é `Anexo[]`; o `File` real fica
25
+ * na sessão e sai por `ref.current.arquivoDe(anexo)` na hora do upload.
26
+ */
27
+ export declare const CampoAnexo: import("react").ForwardRefExoticComponent<CampoAnexoProps & import("react").RefAttributes<CampoAnexoRef>>;
@@ -0,0 +1,11 @@
1
+ import { type Anexo } from '../../utils/anexos.js';
2
+ export interface DialogoArquivoProps {
3
+ aberto: boolean;
4
+ aoFechar: () => void;
5
+ /** A lista INTEIRA; aponte o item clicado com `indice`. */
6
+ arquivos: readonly Anexo[];
7
+ indice: number;
8
+ aoMudarIndice: (indice: number) => void;
9
+ }
10
+ /** Visualizador em tela cheia que folheia uma lista de anexos (botões ou ← →). */
11
+ export declare function DialogoArquivo({ aberto, aoFechar, arquivos, indice, aoMudarIndice }: DialogoArquivoProps): import("react").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { Anexo } from '../../utils/anexos.js';
3
+ export type EntradaFoto = string | Anexo | readonly (string | Anexo)[] | null | undefined;
4
+ export interface FotoProps {
5
+ /** A foto ou a coleção: URL, `Anexo` ou lista misturando os dois. */
6
+ fotos?: EntradaFoto;
7
+ /** De quem é a foto: vira o `alt` e as iniciais do avatar sem foto. */
8
+ nome: string;
9
+ tamanho?: 'sm' | 'md' | 'lg';
10
+ /** Válvulas de escape da escala (ex.: `13rem`). */
11
+ altura?: string;
12
+ largura?: string;
13
+ /** Clique abre a galeria em tela cheia. Não use dentro de outro alvo clicável. */
14
+ ampliavel?: boolean;
15
+ /** Qual foto da coleção (padrão: 0). */
16
+ indice?: number;
17
+ aoMudarIndice?: (indice: number) => void;
18
+ aoAmpliar?: (indice: number) => void;
19
+ /** Substitui o avatar padrão quando não há foto (ex.: "Foto indisponível"). */
20
+ semFoto?: ReactNode;
21
+ /** Conteúdo sobre a imagem (selo, marca d'água). */
22
+ sobreposto?: ReactNode;
23
+ className?: string;
24
+ }
25
+ /**
26
+ * Moldura de foto em tamanho de leitura: fundo desfocado da própria foto,
27
+ * sombra e anel. Entre o `Avatar` (miniatura) e o `DialogoArquivo` (tela cheia).
28
+ */
29
+ export declare function Foto({ fotos, nome, tamanho, altura, largura, ampliavel, indice: indiceExterno, aoMudarIndice, aoAmpliar, semFoto, sobreposto, className, }: FotoProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { type Anexo } from '../../utils/anexos.js';
2
+ export interface VisualizadorArquivoProps {
3
+ arquivo: Anexo | null;
4
+ /** A imagem/vídeo/áudio falhou ao carregar (URL expirada, 404, codec). */
5
+ aoFalhar?: (arquivo: Anexo) => void;
6
+ className?: string;
7
+ }
8
+ /**
9
+ * Superfície de exibição de UM anexo: imagem (zoom, giro, arrasto), PDF,
10
+ * vídeo e áudio nativos; o resto cai num estado com botão "Baixar".
11
+ */
12
+ export declare function VisualizadorArquivo({ arquivo, aoFalhar, className }: VisualizadorArquivoProps): import("react").JSX.Element;
@@ -0,0 +1,30 @@
1
+ export interface AvaliacaoProps {
2
+ /** Nota atual (0 = sem nota). */
3
+ valor: number;
4
+ /**
5
+ * Muda a nota. Se devolver uma Promise (ex.: salvar no servidor), o
6
+ * componente fica ocupado até ela terminar.
7
+ */
8
+ aoMudar?: (valor: number) => void | Promise<unknown>;
9
+ /** Quantidade de estrelas. */
10
+ max?: number;
11
+ /** `1` = estrelas inteiras · `0.5` = meias estrelas. */
12
+ passo?: 1 | 0.5;
13
+ /** Só mostra a nota (aceita fração, ex.: 3.7). */
14
+ somenteLeitura?: boolean;
15
+ /** Nome para leitor de tela (ex.: "Avaliação do produto"). */
16
+ rotulo: string;
17
+ icone?: 'star' | 'heart';
18
+ tamanho?: 'pequeno' | 'medio' | 'grande';
19
+ /** Texto para cada nota inteira (índice 0 = 1 estrela), mostrado ao lado. */
20
+ mensagens?: string[];
21
+ /** Chamado ao passar o mouse (com a nota sob o ponteiro, ou `null` ao sair). */
22
+ aoPassar?: (valor: number | null) => void;
23
+ /** Botão para limpar a nota. */
24
+ limpavel?: boolean;
25
+ /** Texto enquanto `aoMudar` não termina. */
26
+ textoOcupado?: string;
27
+ className?: string;
28
+ }
29
+ /** Avaliação por estrelas (ou corações): editável, com meias estrelas, ou só leitura. */
30
+ export declare function Avaliacao({ valor, aoMudar, max, passo, somenteLeitura, rotulo, icone, tamanho, mensagens, aoPassar, limpavel, textoOcupado, className, }: AvaliacaoProps): import("react").JSX.Element;