@celke/kole-react 0.1.0 → 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.
- package/LICENSE +22 -22
- package/README.md +370 -255
- package/dist/components/Preferencias/Preferencias.d.ts +20 -3
- package/dist/estilos.css +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +360 -337
- package/dist/index.js.map +1 -1
- package/dist/tailwind.css +82 -0
- package/dist/tokens.css +403 -321
- package/package.json +91 -83
package/README.md
CHANGED
|
@@ -1,255 +1,370 @@
|
|
|
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
|
-
###
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
(
|
|
164
|
-
(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
`
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
`
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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).
|