@celke/kole-react 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -22
- package/README.md +968 -525
- package/dist/components/Avaliacao/Avaliacao.d.ts +1 -1
- package/dist/components/Avatar/GrupoAvatares.d.ts +16 -0
- package/dist/components/BlocoCodigo/BlocoCodigo.d.ts +15 -0
- package/dist/components/BotaoFechar/BotaoFechar.d.ts +1 -1
- package/dist/components/Busca/Busca.d.ts +1 -1
- package/dist/components/Calendario/Calendario.d.ts +100 -0
- package/dist/components/Calendario/MiniCalendario.d.ts +27 -0
- package/dist/components/Calendario/datasCalendario.d.ts +27 -0
- package/dist/components/Calendario/repetirEvento.d.ts +26 -0
- package/dist/components/Campo/Campo.d.ts +1 -0
- package/dist/components/CampoBusca/CampoBusca.d.ts +1 -1
- package/dist/components/CampoBusca/CampoCidade.d.ts +1 -1
- package/dist/components/CampoMensagem/CampoMensagem.d.ts +20 -1
- package/dist/components/Carregando/Carregando.d.ts +1 -1
- package/dist/components/ChatFlutuante/ChatFlutuante.d.ts +31 -0
- package/dist/components/Conversa/Conversa.d.ts +1 -1
- package/dist/components/Estrutura/Estrutura.d.ts +13 -2
- package/dist/components/Form/Form.d.ts +1 -1
- package/dist/components/Girador/Girador.d.ts +1 -1
- package/dist/components/Grafico/GraficoRosca.d.ts +31 -0
- package/dist/components/Grafico/MolduraGrafico.d.ts +36 -1
- package/dist/components/Grafico/escalas.d.ts +3 -0
- package/dist/components/InicioConversa/InicioConversa.d.ts +29 -0
- package/dist/components/Kanban/CartaoKanban.d.ts +35 -0
- package/dist/components/Kanban/Kanban.d.ts +62 -0
- package/dist/components/Kanban/Pecas.d.ts +30 -0
- package/dist/components/Kanban/tipos.d.ts +35 -0
- package/dist/components/ListaCards/ListaCards.d.ts +1 -1
- package/dist/components/ListaConversas/ListaConversas.d.ts +40 -0
- package/dist/components/ListaTarefas/ListaTarefas.d.ts +52 -0
- package/dist/components/Mensagem/Mensagem.d.ts +1 -1
- package/dist/components/NavegadorPeriodo/NavegadorPeriodo.d.ts +28 -0
- package/dist/components/Picklist/Picklist.d.ts +1 -1
- package/dist/components/Preferencias/Preferencias.d.ts +25 -2
- package/dist/components/QuadroArrastavel/QuadroArrastavel.d.ts +59 -6
- package/dist/components/Referencias/Referencias.d.ts +1 -1
- package/dist/components/SeletorContexto/SeletorContexto.d.ts +32 -0
- package/dist/components/SeletorIdioma/Bandeira.d.ts +10 -0
- package/dist/components/SeletorIdioma/SeletorIdioma.d.ts +23 -0
- package/dist/components/Site/CabecalhoSecao.d.ts +18 -0
- package/dist/components/Site/CampoPesquisa.d.ts +15 -0
- package/dist/components/Site/CardConteudo.d.ts +28 -0
- package/dist/components/Site/CardDepoimento.d.ts +14 -0
- package/dist/components/Site/CardPlano.d.ts +22 -0
- package/dist/components/Site/CardRecurso.d.ts +22 -0
- package/dist/components/Site/ConteudoArtigo.d.ts +12 -0
- package/dist/components/Site/DestaqueSite.d.ts +27 -0
- package/dist/components/Site/EstruturaSite.d.ts +60 -0
- package/dist/components/Site/FaixaChamada.d.ts +14 -0
- package/dist/components/Site/FaixaNumeros.d.ts +20 -0
- package/dist/components/Site/FiltroPilulas.d.ts +17 -0
- package/dist/components/Site/ListaNumerada.d.ts +21 -0
- package/dist/components/Site/Newsletter.d.ts +16 -0
- package/dist/components/Site/SecaoSite.d.ts +15 -0
- package/dist/components/Site/SumarioPagina.d.ts +17 -0
- package/dist/components/Site/linkSite.d.ts +18 -0
- package/dist/components/Tabela/Tabela.d.ts +1 -1
- package/dist/components/TelaLogin/TelaLogin.d.ts +1 -1
- package/dist/components/Valor/Valor.d.ts +53 -0
- package/dist/estilos.css +1 -1
- package/dist/i18n/contexto.d.ts +15 -0
- package/dist/i18n/en.d.ts +3 -0
- package/dist/i18n/es.d.ts +3 -0
- package/dist/i18n/idiomas.d.ts +41 -0
- package/dist/i18n/pt-BR.d.ts +545 -0
- package/dist/i18n/textos.d.ts +17 -0
- package/dist/index.d.ts +46 -2
- package/dist/index.js +9325 -3670
- package/dist/index.js.map +1 -1
- package/dist/tailwind.css +96 -82
- package/dist/tokens.css +489 -461
- package/dist/utils/dinheiro.d.ts +49 -0
- package/dist/utils/entidade.d.ts +3 -2
- package/dist/utils/periodo.d.ts +36 -0
- package/dist/utils/senha.d.ts +2 -1
- package/dist/utils/tempo.d.ts +32 -0
- package/dist/utils/texto.d.ts +12 -5
- package/dist/utils/tiposCampo.d.ts +3 -1
- package/dist/utils/validadores.d.ts +4 -2
- package/package.json +93 -91
package/README.md
CHANGED
|
@@ -1,525 +1,968 @@
|
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
A
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
`
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
+
### Idioma
|
|
38
|
+
|
|
39
|
+
Os textos da própria biblioteca saem em português do Brasil (padrão), inglês ou
|
|
40
|
+
espanhol. Ligue o seletor na topbar com `idiomas` e sincronize o i18n do seu
|
|
41
|
+
sistema em `aoMudarIdioma` (chamado ao montar e a cada troca). A biblioteca não
|
|
42
|
+
traduz os textos do sistema: ela só informa o idioma escolhido.
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<ProvedorUi idiomaInicial={usuario.locale} aoMudarIdioma={(idioma) => i18n.changeLanguage(idioma)}>
|
|
46
|
+
<Estrutura idiomas={['pt-BR', 'en', 'es']} {...props}>…</Estrutura>
|
|
47
|
+
</ProvedorUi>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Prioridade dos textos: prop passada > dicionário do idioma > pt-BR. Para trocar
|
|
51
|
+
um texto ou acrescentar um idioma, passe `textos` ao provedor
|
|
52
|
+
(`{ 'pt-BR': { tabela: { rotulo: 'Lista' } } }`). Regras brasileiras (máscaras e
|
|
53
|
+
validação de CPF, CNPJ, CEP e telefone) e a moeda não mudam com o idioma; só a
|
|
54
|
+
formatação e as mensagens. A demo traz a página Idioma com os exemplos de
|
|
55
|
+
react-i18next e Laravel.
|
|
56
|
+
|
|
57
|
+
### Tema sem piscar ao abrir
|
|
58
|
+
|
|
59
|
+
O `ProvedorUi` só aplica o tema depois que o React monta, e quem salvou o tema
|
|
60
|
+
escuro vê a página clara piscar por um instante. Para evitar, aplique o tema no
|
|
61
|
+
`<head>`, antes do CSS da página pintar:
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<!-- index.html -->
|
|
65
|
+
<head>
|
|
66
|
+
<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 l=p.idioma||"";if(l)h.lang=l;var d={"compacta":"density-compact","confortavel":"density-comfortable","espacosa":"density-cozy"}[p.densidade||"confortavel"];if(d)h.classList.add(d)})();</script>
|
|
67
|
+
</head>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Com Content-Security-Policy sem `'unsafe-inline'` em `script-src`, o script
|
|
71
|
+
inline é bloqueado: salve o mesmo conteúdo num arquivo (por exemplo,
|
|
72
|
+
`public/tema.js`) e carregue com `<script src="/tema.js"></script>` no
|
|
73
|
+
`<head>`, sem `type="module"` nem `defer`, para rodar antes da pintura.
|
|
74
|
+
|
|
75
|
+
Esse é o resultado de `scriptTemaInicial()`. Com SSR (Next.js, React Router em
|
|
76
|
+
modo framework), gere pela função, com o mesmo `temaInicial`/`densidadeInicial`
|
|
77
|
+
passado ao `ProvedorUi`:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { scriptTemaInicial } from '@celke/kole-react';
|
|
81
|
+
|
|
82
|
+
<head>
|
|
83
|
+
<script dangerouslySetInnerHTML={{ __html: scriptTemaInicial({ temaInicial: 'sistema' }) }} />
|
|
84
|
+
</head>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Layout com React Router
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
|
91
|
+
import { Estrutura } from '@celke/kole-react';
|
|
92
|
+
|
|
93
|
+
export function Layout({ sair }: { sair: () => void }) {
|
|
94
|
+
const { pathname } = useLocation();
|
|
95
|
+
const navigate = useNavigate();
|
|
96
|
+
return (
|
|
97
|
+
<Estrutura
|
|
98
|
+
marca={{ nome: 'Gestor', subtitulo: 'Gestão comercial', logo: '/logo.svg' }}
|
|
99
|
+
nav={[
|
|
100
|
+
{ itens: [{ rotulo: 'Painel', icone: 'house', rota: '/' }] },
|
|
101
|
+
{
|
|
102
|
+
rotulo: 'Cadastros',
|
|
103
|
+
itens: [
|
|
104
|
+
{ rotulo: 'Pedidos', icone: 'shopping-cart', rota: '/pedidos' },
|
|
105
|
+
{ rotulo: 'Tabelas', icone: 'table', filhos: [{ rotulo: 'Categorias', rota: '/categorias' }] },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
]}
|
|
109
|
+
rotaAtual={pathname}
|
|
110
|
+
aoNavegar={navigate}
|
|
111
|
+
usuario={{ nome: 'Gabrielly Ribeiro', cargo: 'Gerente comercial' }}
|
|
112
|
+
menuUsuario={[{ id: 'sair', rotulo: 'Sair', icone: 'log-out', destrutivo: true }]}
|
|
113
|
+
aoEscolherMenuUsuario={(id) => id === 'sair' && sair()}
|
|
114
|
+
>
|
|
115
|
+
<Outlet />
|
|
116
|
+
</Estrutura>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Para links de verdade do roteador (ctrl+clique abre em nova aba), passe
|
|
122
|
+
`renderLink={(p) => <Link to={p.href} className={p.className} onClick={p.onClick} aria-current={p['aria-current']}>{p.children}</Link>}`.
|
|
123
|
+
|
|
124
|
+
### Páginas fora do React no mesmo app (Inertia + Blade)
|
|
125
|
+
|
|
126
|
+
Num app Laravel com páginas Inertia e páginas Blade, o `<Link>` do Inertia só
|
|
127
|
+
serve para as páginas Inertia. Marque as outras com `recarregar: true`: o link
|
|
128
|
+
vira um `<a>` comum (sem `renderLink` nem `aoNavegar`) e abre com recarga
|
|
129
|
+
completa. Vale para o menu, a busca do menu e a trilha. Se a marca aponta para
|
|
130
|
+
uma página Blade, use `marca={{ …, recarregar: true }}`.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { Link, router, usePage } from '@inertiajs/react';
|
|
134
|
+
|
|
135
|
+
<Estrutura
|
|
136
|
+
nav={[
|
|
137
|
+
{
|
|
138
|
+
itens: [
|
|
139
|
+
{ rotulo: 'Painel', icone: 'house', rota: '/painel' }, // Inertia
|
|
140
|
+
{ rotulo: 'Relatórios', icone: 'file-text', rota: '/relatorios', recarregar: true }, // Blade
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
]}
|
|
144
|
+
rotaAtual={usePage().url}
|
|
145
|
+
aoNavegar={(rota) => router.visit(rota)}
|
|
146
|
+
renderLink={(p) => <Link href={p.href} className={p.className} onClick={p.onClick} aria-current={p['aria-current']}>{p.children}</Link>}
|
|
147
|
+
…
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Do Blade para o Inertia não precisa de nada: um link comum abre a página
|
|
152
|
+
Inertia. Para as telas Blade com o mesmo layout, veja o pacote Composer
|
|
153
|
+
[`celke/kole-laravel`](https://github.com/celkecursos/kole-laravel).
|
|
154
|
+
|
|
155
|
+
## Componentes
|
|
156
|
+
|
|
157
|
+
| Grupo | Componentes |
|
|
158
|
+
|---|---|
|
|
159
|
+
| Layout | `Estrutura`, `Busca`, `CabecalhoPagina`, `BoasVindas`, `TelaLogin`, `Abas`/`Aba`, `Card` |
|
|
160
|
+
| Ações | `Botao`, `AcaoLinha`, `Dica` |
|
|
161
|
+
| Formulário | `Form` (página ou modal, `entidade`/`modo`), `FormSecao`, `Campo`, `CampoTexto`, `CampoTextarea`, `CampoSenha`, `CampoSelect`, `CampoSelectMultiplo`, `CampoMarcacao`, `Escolha`, `Picklist`, `Filtros` |
|
|
162
|
+
| Campos prontos | `CampoCpf`, `CampoCnpj`, `CampoCpfCnpj`, `CampoCep`, `CampoTelefone`, `CampoPlaca`, `CampoRenavam`, `CampoChassi`, `CampoCnh`, `CampoImei`, `CampoEmail`, `CampoData`, `CampoHora`, `CampoDataHora`, `CampoMoeda`, `CampoInteiro`, `CampoUf`, `CampoUfMultiplo`, `CampoTipoPessoa` |
|
|
163
|
+
| Busca remota | `CampoBusca`, `CampoCidade`, `useBuscaAssincrona` |
|
|
164
|
+
| Arquivos | `CampoAnexo`, `VisualizadorArquivo`, `DialogoArquivo`, `Foto` |
|
|
165
|
+
| Dados | `Tabela`, `ListaCards`, `Paginacao`, `Indicador`/`Indicadores`, `Detalhes`, `Status`, `Chip`, `Avatar`, `GrupoAvatares`, `Placa` |
|
|
166
|
+
| Feedback | `Alerta`, `Dialogo` (central ou `lateral`), `useConfirmacao` (+ `.remover()`, `.avisar()`), `useToast`, `Vazio`, `Skeleton`, `Carregando` |
|
|
167
|
+
| Conversa | `Conversa`, `Mensagem`, `CampoMensagem`, `Referencias`, `ListaConversas`, `InicioConversa`, `BlocoCodigo`, `ChatFlutuante` |
|
|
168
|
+
| Site público | `EstruturaSite`, `RodapeSite`, `SecaoSite`, `DestaqueSite`, `CabecalhoSecao`, `CampoPesquisa`, `CardConteudo`, `CardRecurso`, `CardDepoimento`, `CardPlano`, `FaixaNumeros`, `FaixaChamada`, `ListaNumerada`, `FiltroPilulas`, `ConteudoArtigo`, `SumarioPagina`, `Newsletter` |
|
|
169
|
+
| Calendário | `Calendario` (visões mês, semana, dia e lista, com arrastar), `MiniCalendario`, `repetirEvento` |
|
|
170
|
+
| Kanban e tarefas | `Kanban`, `CartaoKanban`, `ListaTarefas`, `QuadroArrastavel`, `SeloPrioridade`, `SeloPrazo`, `Etiqueta`, `tempoRelativo`, `situacaoPrazo` |
|
|
171
|
+
| Gráficos | `GraficoLinha`, `GraficoBarras`, `GraficoCombinado`, `GraficoDispersao`, `GraficoRosca`, `MapaCalor`, `Minigrafico` (e `tendencia` no `Indicador`), `regressaoLinear` |
|
|
172
|
+
| Idioma | `SeletorIdioma`, `Bandeira`, `useTextos`, `useIdioma`, `textosPtBR`, `textosEn`, `textosEs`, `interpolar`, `pluralizar` (e `idiomas` na `Estrutura`) |
|
|
173
|
+
| Finanças | `Valor`, `ProvedorValores`, `BotaoOcultarValores`, `NavegadorPeriodo`, `SeletorContexto`, `formatarMoeda`, `formatarPercentual`, `dividirEmParcelas`, `periodoDe`, `noPeriodo` |
|
|
174
|
+
|
|
175
|
+
### Conversa (chat)
|
|
176
|
+
|
|
177
|
+
`Conversa` é a área rolável (acompanha o fim durante o streaming e para de
|
|
178
|
+
puxar se a pessoa rolar para cima); `Mensagem` é a bolha; `CampoMensagem` é o
|
|
179
|
+
campo de envio (Enter envia, Shift+Enter quebra a linha); `Referencias` lista
|
|
180
|
+
as fontes de uma resposta. A conversa ocupa a altura do contêiner pai, e a
|
|
181
|
+
busca da resposta (rede, streaming, Markdown) fica com o app.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { useState } from 'react';
|
|
185
|
+
import {
|
|
186
|
+
CampoMensagem,
|
|
187
|
+
Conversa,
|
|
188
|
+
Mensagem,
|
|
189
|
+
Referencias,
|
|
190
|
+
type AutorMensagem,
|
|
191
|
+
type EstadoMensagem,
|
|
192
|
+
type ItemReferencia,
|
|
193
|
+
} from '@celke/kole-react';
|
|
194
|
+
|
|
195
|
+
interface ItemChat {
|
|
196
|
+
id: string;
|
|
197
|
+
autor: AutorMensagem;
|
|
198
|
+
estado: EstadoMensagem;
|
|
199
|
+
texto: string;
|
|
200
|
+
hora: Date;
|
|
201
|
+
fontes?: ItemReferencia[]; // { id, titulo, detalhe?, trecho?, href? }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface ChatProps {
|
|
205
|
+
mensagens: ItemChat[];
|
|
206
|
+
gerando: boolean;
|
|
207
|
+
enviar: (texto: string) => void;
|
|
208
|
+
parar: () => void;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function Chat({ mensagens, gerando, enviar, parar }: ChatProps) {
|
|
212
|
+
const [texto, setTexto] = useState('');
|
|
213
|
+
return (
|
|
214
|
+
<div style={{ height: '32rem' }}>
|
|
215
|
+
<Conversa
|
|
216
|
+
campo={
|
|
217
|
+
<CampoMensagem
|
|
218
|
+
valor={texto}
|
|
219
|
+
aoMudar={setTexto}
|
|
220
|
+
aoEnviar={(t) => {
|
|
221
|
+
enviar(t);
|
|
222
|
+
setTexto('');
|
|
223
|
+
}}
|
|
224
|
+
gerando={gerando}
|
|
225
|
+
aoParar={parar}
|
|
226
|
+
/>
|
|
227
|
+
}
|
|
228
|
+
>
|
|
229
|
+
{mensagens.map((m) => (
|
|
230
|
+
<Mensagem
|
|
231
|
+
key={m.id}
|
|
232
|
+
autor={m.autor} // 'usuario' | 'assistente' | 'sistema'
|
|
233
|
+
estado={m.estado} // 'enviando' | 'gerando' | 'concluida' | 'erro'
|
|
234
|
+
hora={m.hora}
|
|
235
|
+
copiavel={m.autor === 'assistente'}
|
|
236
|
+
rodape={m.fontes && <Referencias itens={m.fontes} />}
|
|
237
|
+
>
|
|
238
|
+
{m.texto}
|
|
239
|
+
</Mensagem>
|
|
240
|
+
))}
|
|
241
|
+
</Conversa>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### Assistente de IA
|
|
248
|
+
|
|
249
|
+
A demo tem a página modelo **Assistente** (Advanced UI): histórico ao lado,
|
|
250
|
+
tela inicial com sugestões, anexos, ferramentas, seletor de modo e respostas com
|
|
251
|
+
tabela e código. As peças:
|
|
252
|
+
|
|
253
|
+
- `ListaConversas`: histórico agrupado por data (Fixadas, Hoje, Ontem, Últimos 7
|
|
254
|
+
dias, Últimos 30 dias e os meses), busca sem acento, renomear no próprio item
|
|
255
|
+
(Enter salva, Esc cancela), fixar e excluir. Ocupa a altura do pai.
|
|
256
|
+
- `InicioConversa`: saudação e sugestões, no `vazio` da `Conversa`. Em painel
|
|
257
|
+
estreito, `aparencia="pilulas"`.
|
|
258
|
+
- `CampoMensagem` com `anexos` (chips com `progresso` e `erro`), `aoRemoverAnexo`
|
|
259
|
+
e `aoAdicionarArquivos` (arrastar para o campo ou colar). Com anexos, dá para
|
|
260
|
+
enviar sem texto; enquanto um anexo sobe, o Enviar espera.
|
|
261
|
+
- `BlocoCodigo`: código com a linguagem e o botão de copiar. A `Mensagem` já
|
|
262
|
+
estiliza o Markdown renderizado pelo app (listas, tabelas, citações).
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import ReactMarkdown from 'react-markdown';
|
|
266
|
+
import { BlocoCodigo, InicioConversa, ListaConversas, type AnexoMensagem } from '@celke/kole-react';
|
|
267
|
+
|
|
268
|
+
<ListaConversas
|
|
269
|
+
itens={conversas} // { id, titulo, data, fixada? }
|
|
270
|
+
ativa={ativa}
|
|
271
|
+
aoEscolher={setAtiva}
|
|
272
|
+
aoNova={() => setAtiva(null)}
|
|
273
|
+
aoRenomear={(id, titulo) => api.renomear(id, titulo)}
|
|
274
|
+
aoExcluir={(id) => api.excluir(id)}
|
|
275
|
+
/>;
|
|
276
|
+
|
|
277
|
+
<Conversa
|
|
278
|
+
vazio={<InicioConversa titulo="Como posso ajudar?" sugestoes={[{ titulo: 'Resumir as vendas do mês', icone: 'chart-line' }]} aoEscolher={(s) => enviar(s.titulo)} />}
|
|
279
|
+
campo={<CampoMensagem valor={texto} aoMudar={setTexto} aoEnviar={enviar} anexos={anexos} aoRemoverAnexo={remover} aoAdicionarArquivos={subir} />}
|
|
280
|
+
>
|
|
281
|
+
{mensagens.map((m) => (
|
|
282
|
+
<Mensagem key={m.id} autor={m.autor} estado={m.estado}>
|
|
283
|
+
<ReactMarkdown
|
|
284
|
+
components={{
|
|
285
|
+
code: ({ className, children }) =>
|
|
286
|
+
className ? <BlocoCodigo linguagem={className.replace('language-', '')} codigo={String(children).trimEnd()} /> : <code>{children}</code>,
|
|
287
|
+
}}
|
|
288
|
+
>
|
|
289
|
+
{m.texto}
|
|
290
|
+
</ReactMarkdown>
|
|
291
|
+
</Mensagem>
|
|
292
|
+
))}
|
|
293
|
+
</Conversa>;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
A chamada à API (streaming), o histórico salvo, o envio dos anexos e o
|
|
297
|
+
Markdown ficam com o app.
|
|
298
|
+
|
|
299
|
+
#### Chat flutuante (site)
|
|
300
|
+
|
|
301
|
+
`ChatFlutuante` é o atendimento de site: botão fixo no canto ("Fale com a
|
|
302
|
+
gente", com `naoLidas`) que abre um painel com a conversa. Esc fecha e o foco
|
|
303
|
+
volta ao botão; no celular, o painel ocupa a tela. As páginas modelo do site da
|
|
304
|
+
demo já trazem um, com as perguntas frequentes como sugestões.
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<ChatFlutuante titulo="Fale com a gente" subtitulo="Respondemos em poucos minutos" naoLidas={1}>
|
|
308
|
+
<Conversa
|
|
309
|
+
vazio={<InicioConversa aparencia="pilulas" titulo="Olá! Como podemos ajudar?" sugestoes={faq} aoEscolher={(s) => enviar(s.titulo)} />}
|
|
310
|
+
campo={<CampoMensagem valor={texto} aoMudar={setTexto} aoEnviar={enviar} />}
|
|
311
|
+
>
|
|
312
|
+
{mensagens}
|
|
313
|
+
</Conversa>
|
|
314
|
+
</ChatFlutuante>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Site público
|
|
318
|
+
|
|
319
|
+
Para a parte aberta do sistema (site institucional, loja, portal) há uma moldura
|
|
320
|
+
própria, sem o menu lateral: `EstruturaSite`, com barra de aviso opcional,
|
|
321
|
+
cabeçalho fixo (marca, menu horizontal, botão de tema e as `acoes`), conteúdo e
|
|
322
|
+
`RodapeSite`. No celular, o menu vai para um painel aberto pelo botão Menu. Os
|
|
323
|
+
links são `<a href>` de verdade: dentro da `EstruturaSite`, os cartões, as listas e
|
|
324
|
+
a `Trilha` navegam pelo `aoNavegar`/`renderLink` do app, e Ctrl+clique ou o botão
|
|
325
|
+
do meio abrem em nova aba. A demo tem as páginas modelo de um site genérico (menu
|
|
326
|
+
**Site**): Home (faixa escura ou clara), Sobre, Serviços e planos, Blog, Post (sem
|
|
327
|
+
e com coluna lateral), Contato e as páginas legais (Termos de uso, Política de Privacidade e Política de
|
|
328
|
+
Cookies, com texto de exemplo a ser revisado por um advogado).
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
import { Link, Outlet, useLocation } from 'react-router-dom';
|
|
332
|
+
import {
|
|
333
|
+
Botao,
|
|
334
|
+
CabecalhoSecao,
|
|
335
|
+
CardConteudo,
|
|
336
|
+
CardDepoimento,
|
|
337
|
+
CardPlano,
|
|
338
|
+
CardRecurso,
|
|
339
|
+
DestaqueSite,
|
|
340
|
+
EstruturaSite,
|
|
341
|
+
FaixaChamada,
|
|
342
|
+
FaixaNumeros,
|
|
343
|
+
ListaNumerada,
|
|
344
|
+
Newsletter,
|
|
345
|
+
RodapeSite,
|
|
346
|
+
SecaoSite,
|
|
347
|
+
} from '@celke/kole-react';
|
|
348
|
+
|
|
349
|
+
export function LayoutSite() {
|
|
350
|
+
const { pathname } = useLocation();
|
|
351
|
+
return (
|
|
352
|
+
<EstruturaSite
|
|
353
|
+
marca={{ nome: 'Sua Marca', href: '/', logo: '/logo.svg' }}
|
|
354
|
+
nav={[
|
|
355
|
+
{ rotulo: 'Home', rota: '/' },
|
|
356
|
+
{ rotulo: 'Serviços', rota: '/servicos' },
|
|
357
|
+
{ rotulo: 'Contato', rota: '/contato' },
|
|
358
|
+
]}
|
|
359
|
+
rotaAtual={pathname}
|
|
360
|
+
renderLink={(p) => (
|
|
361
|
+
<Link to={p.href} className={p.className} onClick={p.onClick} aria-current={p['aria-current']}>
|
|
362
|
+
{p.children}
|
|
363
|
+
</Link>
|
|
364
|
+
)}
|
|
365
|
+
aviso={<><b>Novidade:</b> conheça a nova versão.</>}
|
|
366
|
+
acoes={<Botao rotulo="Fale conosco" />}
|
|
367
|
+
rodape={
|
|
368
|
+
<RodapeSite
|
|
369
|
+
nome="Sua Marca"
|
|
370
|
+
descricao="O que a empresa faz e para quem."
|
|
371
|
+
extra={<Newsletter aparencia="linha" rotuloBotao="Assinar" aoInscrever={async (email) => { /* envie ao servidor */ }} />}
|
|
372
|
+
colunas={[{ titulo: 'Navegação', links: [{ rotulo: 'Serviços', rota: '/servicos' }] }]}
|
|
373
|
+
copyright="© 2026 Sua Marca"
|
|
374
|
+
redes={[{ rotulo: 'Instagram', rota: 'https://instagram.com/suamarca' }]}
|
|
375
|
+
/>
|
|
376
|
+
}
|
|
377
|
+
>
|
|
378
|
+
<Outlet />
|
|
379
|
+
</EstruturaSite>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function Home() {
|
|
384
|
+
return (
|
|
385
|
+
<>
|
|
386
|
+
{/* aparencia="claro" centraliza o texto e põe a `midia` embaixo */}
|
|
387
|
+
<DestaqueSite selo="Seu slogan" titulo="Uma frase forte sobre o seu negócio." realce="negócio" lateral={<img src="/produto.png" alt="" />}>
|
|
388
|
+
<Botao rotulo="Começar agora" severidade="destaque" tamanho="grande" />
|
|
389
|
+
<FaixaNumeros aparencia="simples" itens={[{ valor: '+1.200', rotulo: 'clientes' }]} />
|
|
390
|
+
</DestaqueSite>
|
|
391
|
+
<SecaoSite>
|
|
392
|
+
<CabecalhoSecao sobretitulo="Diferenciais" titulo="Por que escolher a Sua Marca" />
|
|
393
|
+
<CardRecurso icone="shield" titulo="Seguro e confiável">Garantias e cuidados com o cliente.</CardRecurso>
|
|
394
|
+
<ListaNumerada itens={[{ titulo: 'Contato inicial', descricao: 'O cliente conta o que precisa.' }]} />
|
|
395
|
+
<FaixaNumeros itens={[{ valor: '98', sufixo: '%', rotulo: 'de satisfação' }]} />
|
|
396
|
+
<CardDepoimento texto="O atendimento foi rápido e o resultado veio." nome="Nome do Cliente" cargo="Cargo · Empresa" />
|
|
397
|
+
<CardConteudo href="/blog/post" midia={<img src="/post.jpg" alt="" />} titulo="Título do post" />
|
|
398
|
+
<CardPlano nome="Profissional" preco="99" periodo="/mês" itens={['Suporte prioritário']} destaque acao={<Botao rotulo="Assinar" bloco />} />
|
|
399
|
+
<FaixaChamada titulo="Pronto para começar?" acoes={<Botao rotulo="Solicitar contato" severidade="destaque" />} />
|
|
400
|
+
</SecaoSite>
|
|
401
|
+
</>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`CardConteudo` tem cinco formatos: `vertical` (grade de posts), `destaque`,
|
|
407
|
+
`horizontal` (post em destaque), `compacto` e `linha` (listagem com quadro à
|
|
408
|
+
direita). `CardRecurso` serve para diferenciais, serviços (`formato="horizontal"`),
|
|
409
|
+
valores (`numero`) e canais de contato (`sobretitulo`). `ConteudoArtigo` estiliza o
|
|
410
|
+
texto de posts e páginas como termos de uso; HTML vindo de Markdown deve chegar
|
|
411
|
+
sanitizado. `SumarioPagina` lista as seções de uma página longa (pelo `id` dos
|
|
412
|
+
títulos), marca a seção em leitura e rola até ela sem trocar o endereço, então
|
|
413
|
+
funciona também em app com rotas no `#hash`. `Newsletter` confere o e-mail antes de chamar `aoInscrever`. Para
|
|
414
|
+
perguntas frequentes, use o `Acordeao` (`icone="mais"`).
|
|
415
|
+
|
|
416
|
+
Tokens próprios do site: `--site-largura`, `--r-site`, `--r-site-2`,
|
|
417
|
+
`--sombra-site`, `--fs-site-hero`, `--fs-site-titulo`, `--fs-site-secao` e os tons
|
|
418
|
+
sobre o marinho (`--site-escuro-*`). A cor de ação é o `--pri` de sempre: para a cor
|
|
419
|
+
da marca do cliente, redefina `--pri`, `--pri-ink`, `--pri-bg` e `--pri-bd` (veja
|
|
420
|
+
"Tema e tokens").
|
|
421
|
+
|
|
422
|
+
### Calendário
|
|
423
|
+
|
|
424
|
+
`Calendario` mostra eventos na visão **mês** (grade de 6 semanas, hoje em
|
|
425
|
+
destaque, até `maxPorDia` eventos por dia e "+N mais", que abre a lista do dia),
|
|
426
|
+
nas visões **semana** e **dia** (faixa de horários de `horaInicio` a `horaFim`,
|
|
427
|
+
eventos posicionados pela hora, os que se sobrepõem lado a lado, faixa de dia
|
|
428
|
+
inteiro no topo e a linha vermelha do "agora") e na visão **lista** (eventos
|
|
429
|
+
agrupados por dia, a melhor no celular). O app guarda
|
|
430
|
+
e busca os eventos: o componente avisa o período visível em `aoMudarPeriodo` e os
|
|
431
|
+
cliques em `aoClicarDia` (ex.: criar evento) e `aoClicarEvento`.
|
|
432
|
+
|
|
433
|
+
```tsx
|
|
434
|
+
import { Calendario, type EventoCalendario } from '@celke/kole-react';
|
|
435
|
+
|
|
436
|
+
const eventos: EventoCalendario[] = [
|
|
437
|
+
{ id: 1, titulo: 'Reunião de vendas', inicio: new Date(2026, 10, 9, 8, 30), fim: new Date(2026, 10, 9, 9, 30), cor: 'pri', categoria: 'Reunião' },
|
|
438
|
+
{ id: 2, titulo: 'Promoção de Pix', inicio: new Date(2026, 10, 12), fim: new Date(2026, 10, 18), diaInteiro: true, cor: 'graf-2' },
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
<Calendario
|
|
442
|
+
eventos={eventos}
|
|
443
|
+
inicioSemana={1} // 0 = domingo (padrão), 1 = segunda
|
|
444
|
+
aoMudarPeriodo={({ inicio, fim }) => {
|
|
445
|
+
/* buscar os eventos de [inicio, fim) */
|
|
446
|
+
}}
|
|
447
|
+
aoClicarDia={(dia) => {
|
|
448
|
+
/* abrir o formulário de novo evento */
|
|
449
|
+
}}
|
|
450
|
+
aoClicarEvento={(evento) => {
|
|
451
|
+
/* abrir o evento */
|
|
452
|
+
}}
|
|
453
|
+
horaInicio={7} // semana e dia: faixa de horários (padrão 7h às 21h)
|
|
454
|
+
horaFim={21}
|
|
455
|
+
aoClicarHorario={(dataHora) => {
|
|
456
|
+
/* semana e dia: clique num horário vazio, arredondado para a meia hora */
|
|
457
|
+
}}
|
|
458
|
+
/>;
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Cores do evento pelos tokens: `pri`, `ok`, `warn`, `err`, `info`, `destaque` e
|
|
462
|
+
`graf-1` a `graf-6`. Teclado na visão mês: setas andam pelos dias, Home/End vão
|
|
463
|
+
ao começo e ao fim da semana, Page Up/Down trocam de mês e Enter equivale ao
|
|
464
|
+
clique no dia. Na semana e no dia, um cursor de 30 minutos: ↑/↓ mudam o horário,
|
|
465
|
+
←/→ o dia (passando para a semana ou o dia vizinho), Home/End vão ao primeiro e
|
|
466
|
+
ao último horário e Enter chama `aoClicarHorario` (sem ele, abre o evento daquele
|
|
467
|
+
horário). O leitor de tela ouve o dia, a hora e os eventos do horário. `visoes` escolhe quais visões aparecem na barra (padrão: as quatro).
|
|
468
|
+
|
|
469
|
+
#### Arrastar e esticar
|
|
470
|
+
|
|
471
|
+
Com `aoAlterarEvento`, o evento é arrastado para outro dia (mês e faixa de dia
|
|
472
|
+
inteiro) ou horário (semana e dia, de 15 em 15 minutos), e a borda de baixo do
|
|
473
|
+
bloco estica a duração. No toque, segure o evento antes de arrastar; Esc cancela.
|
|
474
|
+
Pelo teclado, Alt+setas movem (←/→ um dia; ↑/↓ uma semana no mês ou 15 minutos na
|
|
475
|
+
semana e no dia) e Alt+Shift+↑/↓ esticam. O calendário só avisa: o app grava e
|
|
476
|
+
devolve a lista nova em `eventos`. `fixo: true` trava um evento.
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
<Calendario
|
|
480
|
+
eventos={eventos}
|
|
481
|
+
aoAlterarEvento={(evento, { inicio, fim, tipo }) => {
|
|
482
|
+
// tipo: 'mover' (mesma duração) ou 'esticar' (só o fim mudou)
|
|
483
|
+
setEventos((l) => l.map((e) => (e.id === evento.id ? { ...e, inicio, fim } : e)));
|
|
484
|
+
}}
|
|
485
|
+
/>
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Cadastrar: intervalo e rascunho
|
|
489
|
+
|
|
490
|
+
Na semana e no dia, `aoSelecionarHorario` recebe `{ inicio, fim }`: arrastar sobre
|
|
491
|
+
horários vazios escolhe o intervalo (de 15 em 15 minutos) e um clique escolhe a
|
|
492
|
+
meia hora. No cursor do teclado, Shift+↑/↓ aumentam o intervalo e Enter
|
|
493
|
+
confirma. Com ele, o clique na faixa de horários não chama o `aoClicarHorario`.
|
|
494
|
+
Abra o formulário do app e passe os dados em `rascunho`: o evento aparece
|
|
495
|
+
tracejado, em qualquer visão, enquanto o formulário está aberto.
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
const [novo, setNovo] = useState<{ inicio: Date; fim: Date; titulo: string } | null>(null);
|
|
499
|
+
|
|
500
|
+
<Calendario
|
|
501
|
+
eventos={eventos}
|
|
502
|
+
aoSelecionarHorario={({ inicio, fim }) => setNovo({ inicio, fim, titulo: '' })}
|
|
503
|
+
rascunho={novo} // { inicio, fim?, titulo?, cor?, diaInteiro? }
|
|
504
|
+
/>;
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
O formulário fica no app (cada sistema tem seus campos). A documentação da demo
|
|
508
|
+
traz um pronto para copiar, com título, categoria, dia inteiro, data, horários,
|
|
509
|
+
"Repete" e excluir só um dia ou a série toda.
|
|
510
|
+
|
|
511
|
+
#### Eventos que se repetem
|
|
512
|
+
|
|
513
|
+
A série fica no app (o evento base e a regra), e `repetirEvento` calcula as
|
|
514
|
+
ocorrências do período visível. Cada ocorrência ganha `id` `"<id>@aaaa-mm-dd"`,
|
|
515
|
+
`repete: true` (ícone de repetição) e `serie` com o id da base. Mensal no dia 31
|
|
516
|
+
pula os meses sem esse dia.
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
import { repetirEvento, type PeriodoCalendario } from '@celke/kole-react';
|
|
520
|
+
|
|
521
|
+
const [periodo, setPeriodo] = useState<PeriodoCalendario | null>(null);
|
|
522
|
+
const ocorrencias = periodo
|
|
523
|
+
? repetirEvento(
|
|
524
|
+
{ id: 'vendas', titulo: 'Reunião de vendas', inicio: new Date(2026, 10, 2, 9), fim: new Date(2026, 10, 2, 10) },
|
|
525
|
+
{ frequencia: 'semanal', diasSemana: [1, 4], ate: new Date(2026, 11, 31), excecoes: [new Date(2026, 10, 19)] },
|
|
526
|
+
periodo,
|
|
527
|
+
)
|
|
528
|
+
: [];
|
|
529
|
+
|
|
530
|
+
<Calendario eventos={[...ocorrencias, ...avulsos]} aoMudarPeriodo={setPeriodo} />;
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Regra: `frequencia` (`diaria`, `semanal`, `mensal`, `anual`), `intervalo` (a cada
|
|
534
|
+
N), `diasSemana` (semanal, `0` = domingo), `ate`, `vezes` e `excecoes` (dias que a
|
|
535
|
+
série pula). Para mudar só uma ocorrência arrastada, ponha o dia dela em
|
|
536
|
+
`excecoes` e crie um evento avulso com o horário novo.
|
|
537
|
+
|
|
538
|
+
#### MiniCalendario
|
|
539
|
+
|
|
540
|
+
Mês pequeno para a coluna lateral: escolhe o dia, mostra bolinhas nos dias com
|
|
541
|
+
evento e destaca a semana visível. Ligue o dia escolhido à prop `data` do
|
|
542
|
+
`Calendario`, que leva a agenda até ele.
|
|
543
|
+
|
|
544
|
+
```tsx
|
|
545
|
+
<MiniCalendario
|
|
546
|
+
valor={dia}
|
|
547
|
+
aoEscolher={setDia}
|
|
548
|
+
marcados={diasComEvento} // Date[]
|
|
549
|
+
destaque={periodo} // { inicio, fim } com fim exclusivo, ex.: a semana do Calendario
|
|
550
|
+
aoMudarMes={(mes) => {
|
|
551
|
+
/* buscar os marcados do mês */
|
|
552
|
+
}}
|
|
553
|
+
/>
|
|
554
|
+
<Calendario eventos={eventos} data={dia} aoMudarPeriodo={setPeriodo} />
|
|
555
|
+
```
|
|
556
|
+
No celular, a semana mostra os eventos como faixas de cor; use dia ou lista para
|
|
557
|
+
ler os títulos.
|
|
558
|
+
|
|
559
|
+
### Kanban e tarefas
|
|
560
|
+
|
|
561
|
+
`Kanban` é o quadro pronto: colunas com cor, contagem, `limite` de cartões e
|
|
562
|
+
menu (renomear, recolher, excluir), cartões que se arrastam entre colunas (mouse,
|
|
563
|
+
toque e teclado), "Adicionar cartão", "Adicionar coluna", rolagem horizontal e
|
|
564
|
+
tela vazia. Cada ação aparece quando o callback dela é passado. Durante o
|
|
565
|
+
arraste o quadro mostra uma prévia; ao soltar, `aoMudar` recebe as colunas e
|
|
566
|
+
`aoSoltar` o movimento, que é o que o servidor precisa para gravar.
|
|
567
|
+
|
|
568
|
+
```tsx
|
|
569
|
+
import { CartaoKanban, Kanban, type ColunaKanban } from '@celke/kole-react';
|
|
570
|
+
|
|
571
|
+
const [colunas, setColunas] = useState<ColunaKanban<Cartao>[]>(inicial);
|
|
572
|
+
|
|
573
|
+
<Kanban
|
|
574
|
+
colunas={colunas}
|
|
575
|
+
aoMudar={setColunas}
|
|
576
|
+
aoSoltar={({ item, de, para }) => api.moverCartao(item.id, para.coluna, para.indice)}
|
|
577
|
+
aoSoltarColuna={({ colunas }) => api.ordenarColunas(colunas.map((c) => c.id))} // liga o arraste das colunas
|
|
578
|
+
filtro={busca ? (c) => c.titulo.includes(busca) : undefined} // esconde sem tirar do lugar
|
|
579
|
+
nomeItem={(c) => c.titulo}
|
|
580
|
+
renderCartao={(c, alca) => (
|
|
581
|
+
<CartaoKanban
|
|
582
|
+
titulo={c.titulo}
|
|
583
|
+
etiquetas={c.etiquetas}
|
|
584
|
+
prioridade={c.prioridade} // 'baixa' | 'media' | 'alta' | 'critica'
|
|
585
|
+
vencimento={c.vencimento} // 'AAAA-MM-DD': atrasado, hoje, em breve
|
|
586
|
+
subtarefas={{ feitas: 2, total: 5 }}
|
|
587
|
+
responsaveis={c.pessoas}
|
|
588
|
+
alca={alca}
|
|
589
|
+
aoAbrir={() => abrirDetalhe(c)}
|
|
590
|
+
/>
|
|
591
|
+
)}
|
|
592
|
+
aoAdicionarCartao={(colunaId, titulo) => criarCartao(colunaId, titulo)}
|
|
593
|
+
aoAdicionarColuna={(titulo) => criarColuna(titulo)}
|
|
594
|
+
aoRenomearColuna={renomear}
|
|
595
|
+
aoRecolherColuna={recolher}
|
|
596
|
+
aoExcluirColuna={async (id) => (await confirmar.remover()) && excluir(id)}
|
|
597
|
+
/>
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
`ListaTarefas` mostra tarefas em linhas: marcar como feita (`aoConcluir`),
|
|
601
|
+
situação, prioridade, prazo, etiquetas e responsáveis; `aoReordenar` liga o
|
|
602
|
+
arraste e recebe a lista na ordem nova; `filtro` faz as visões (hoje,
|
|
603
|
+
atrasadas, uma lista) sem perder a ordem das escondidas.
|
|
604
|
+
|
|
605
|
+
Os dois são montados sobre o `QuadroArrastavel`, que também serve sozinho:
|
|
606
|
+
`layout="faixa"`, `podeSoltar` (regra de destino), `visivel`,
|
|
607
|
+
`colunasArrastaveis` e rolagem automática perto das bordas. A demo traz as telas
|
|
608
|
+
completas em Aplicativos › Kanban (listagem de quadros, quadro com filtros e
|
|
609
|
+
detalhe do cartão) e Aplicativos › Tarefas.
|
|
610
|
+
|
|
611
|
+
### Finanças
|
|
612
|
+
|
|
613
|
+
`Valor` mostra dinheiro com o sentido certo: guarde o valor sempre positivo e
|
|
614
|
+
diga o `tipo` (`receita` em verde com `+`, `despesa` em vermelho com `−`,
|
|
615
|
+
`neutro` para transferência). Dentro de um `ProvedorValores`, o
|
|
616
|
+
`BotaoOcultarValores` esconde todos de uma vez, como nos apps de banco.
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
import { BotaoOcultarValores, NavegadorPeriodo, ProvedorValores, Valor, periodoDe } from '@celke/kole-react';
|
|
620
|
+
|
|
621
|
+
const [periodo, setPeriodo] = useState(() => periodoDe('mes')); // { tipo, de: 'AAAA-MM-DD', ate }
|
|
622
|
+
|
|
623
|
+
<ProvedorValores chave="financas-ocultos">
|
|
624
|
+
<NavegadorPeriodo valor={periodo} aoMudar={setPeriodo} /> {/* ‹ Novembro de 2026 › */}
|
|
625
|
+
<BotaoOcultarValores />
|
|
626
|
+
<Valor valor={2200} tipo="despesa" forte /> {/* −R$ 2.200,00 */}
|
|
627
|
+
</ProvedorValores>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
`GraficoRosca` faz o "despesas por categoria" (com `maxFatias` e `aoEscolher`
|
|
631
|
+
para abrir os lançamentos da fatia) e o `SeletorContexto`, na prop `contexto`
|
|
632
|
+
da `Estrutura`, troca o sistema inteiro entre pessoal e empresa (ou filiais).
|
|
633
|
+
`formatarMoeda`, `dividirEmParcelas` e `somarMoeda` cuidam do sinal de menos,
|
|
634
|
+
dos centavos das parcelas e do erro de ponto flutuante. A demo traz a solução
|
|
635
|
+
completa em Soluções › Finanças.
|
|
636
|
+
|
|
637
|
+
### Gráficos
|
|
638
|
+
|
|
639
|
+
Desenhados em SVG próprio, sem biblioteca de gráficos. A largura
|
|
640
|
+
acompanha o container (o desenho é refeito no tamanho novo, sem esticar o texto),
|
|
641
|
+
os números e as datas saem em pt-BR (`1.234,5`, `jan/21`) e as cores vêm dos
|
|
642
|
+
tokens `--graf-*`, então funcionam nos temas claro, escuro e sistema.
|
|
643
|
+
|
|
644
|
+
Comum a todos: `titulo` (obrigatório; vira o nome acessível), `descricao` (resumo
|
|
645
|
+
para leitor de tela), `altura` (padrão `280` px), `legenda` (padrão `true`; clicar
|
|
646
|
+
esconde ou mostra a série), `carregando`, `textoVazio` e `verDados` (padrão
|
|
647
|
+
`true`; alterna entre o gráfico e uma `Tabela` com os mesmos valores, o caminho
|
|
648
|
+
completo para quem não enxerga o gráfico). Passe o mouse, toque ou foque o
|
|
649
|
+
gráfico e use as setas do teclado: uma caixa mostra o valor de cada série. `null`
|
|
650
|
+
é "sem dado": falha na linha, barra ausente, célula hachurada, nunca zero.
|
|
651
|
+
|
|
652
|
+
**Animação** (`animacao`, padrão `false`): na entrada, as linhas se desenham da
|
|
653
|
+
esquerda para a direita, as barras crescem a partir do zero e os pontos e as
|
|
654
|
+
células surgem; rótulos e referências chegam depois. É só CSS e roda uma vez,
|
|
655
|
+
quando o gráfico aparece: dados que chegam depois (atualização periódica, por
|
|
656
|
+
exemplo) não reiniciam a animação. Com "reduzir movimento" ligado no sistema
|
|
657
|
+
(`prefers-reduced-motion`), nada se mexe. O `Indicador` também aceita `animacao`
|
|
658
|
+
para o minigráfico da `tendencia`. Prefira deixar desligado em painéis abertos
|
|
659
|
+
todo dia: a animação ajuda em apresentações, mas atrasa a leitura.
|
|
660
|
+
|
|
661
|
+
```tsx
|
|
662
|
+
import {
|
|
663
|
+
GraficoBarras,
|
|
664
|
+
GraficoCombinado,
|
|
665
|
+
GraficoDispersao,
|
|
666
|
+
GraficoLinha,
|
|
667
|
+
Indicador,
|
|
668
|
+
MapaCalor,
|
|
669
|
+
regressaoLinear,
|
|
670
|
+
} from '@celke/kole-react';
|
|
671
|
+
|
|
672
|
+
const reais = (v: number) => v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', maximumFractionDigits: 0 });
|
|
673
|
+
|
|
674
|
+
// séries no tempo: faixa sombreada no eixo X e linha de referência
|
|
675
|
+
<GraficoLinha
|
|
676
|
+
titulo="Faturamento mensal"
|
|
677
|
+
dados={[{ mes: '2025-10', valor: 248000 }, { mes: '2025-11', valor: null }, { mes: '2025-12', valor: 342000 }]}
|
|
678
|
+
eixoX={{ chave: 'mes', tipo: 'data' }}
|
|
679
|
+
eixoY={{ rotulo: 'Faturamento', formatar: reais }}
|
|
680
|
+
series={[{ chave: 'valor', rotulo: 'Faturamento' }]}
|
|
681
|
+
area
|
|
682
|
+
faixas={[{ de: '2025-11', ate: '2025-12', rotulo: 'Fim de ano' }]}
|
|
683
|
+
referencias={[{ valor: 260000, rotulo: 'Média mensal' }]}
|
|
684
|
+
/>;
|
|
685
|
+
|
|
686
|
+
// barras: várias séries ficam agrupadas; `divergente` pinta acima e abaixo de zero;
|
|
687
|
+
// `orientacao="horizontal"` serve para rankings (rótulo longo quebra em duas linhas)
|
|
688
|
+
<GraficoBarras
|
|
689
|
+
titulo="Faturamento acima ou abaixo da meta"
|
|
690
|
+
dados={[{ mes: '2026-05', diferenca: -12 }, { mes: '2026-06', diferenca: 8.5 }]}
|
|
691
|
+
eixoX={{ chave: 'mes', tipo: 'data' }}
|
|
692
|
+
eixoY={{ unidade: 'mil R$' }}
|
|
693
|
+
series={[{ chave: 'diferenca', rotulo: 'Diferença para a meta' }]}
|
|
694
|
+
divergente
|
|
695
|
+
/>;
|
|
696
|
+
|
|
697
|
+
// dois eixos: a legenda diz o eixo de cada série ("Ticket médio (R$, eixo direito)")
|
|
698
|
+
<GraficoCombinado
|
|
699
|
+
titulo="Pedidos e ticket médio"
|
|
700
|
+
dados={[{ mes: '2026-05', pedidos: 1420, ticket: 198.4 }, { mes: '2026-06', pedidos: 1510, ticket: 203.1 }]}
|
|
701
|
+
eixoX={{ chave: 'mes', tipo: 'data' }}
|
|
702
|
+
eixoY={{ rotulo: 'Pedidos' }}
|
|
703
|
+
eixoYDireito={{ rotulo: 'Ticket médio', unidade: 'R$' }}
|
|
704
|
+
barras={[{ chave: 'pedidos', rotulo: 'Pedidos' }]}
|
|
705
|
+
linhas={[{ chave: 'ticket', rotulo: 'Ticket médio' }]}
|
|
706
|
+
/>;
|
|
707
|
+
|
|
708
|
+
// um ponto por item; `regressao` desenha a reta e mostra r, r² e n
|
|
709
|
+
<GraficoDispersao
|
|
710
|
+
titulo="Preço × unidades vendidas"
|
|
711
|
+
dados={produtos} // [{ produto: 'Café torrado — 500 g', preco: 18.9, unidades: 610, promocao: false }, …]
|
|
712
|
+
eixoX={{ rotulo: 'Preço', unidade: 'R$' }}
|
|
713
|
+
eixoY={{ rotulo: 'Unidades vendidas' }}
|
|
714
|
+
chaveX="preco"
|
|
715
|
+
chaveY="unidades"
|
|
716
|
+
chaveRotulo="produto"
|
|
717
|
+
chaveDestaque="promocao"
|
|
718
|
+
regressao
|
|
719
|
+
nota="Estatísticas a mais (como o p-valor) são calculadas pelo app."
|
|
720
|
+
/>;
|
|
721
|
+
|
|
722
|
+
// grade de células (ex.: hora × dia da semana); com muitas linhas, rola com os rótulos das colunas presos no topo
|
|
723
|
+
<MapaCalor
|
|
724
|
+
titulo="Pedidos por hora e dia da semana"
|
|
725
|
+
linhas={['08h', '09h']}
|
|
726
|
+
colunas={['seg', 'ter']}
|
|
727
|
+
valores={[{ linha: '08h', coluna: 'seg', valor: 72 }, { linha: '08h', coluna: 'ter', valor: null }]}
|
|
728
|
+
unidade="pedidos"
|
|
729
|
+
/>;
|
|
730
|
+
|
|
731
|
+
// minigráfico abaixo do valor, na cor do indicador
|
|
732
|
+
<Indicador rotulo="Pedidos em junho" valor={1510} cor="var(--graf-3)" tendencia={[1180, 1240, 1320, 1290, 1420, 1510]} />;
|
|
733
|
+
|
|
734
|
+
// a mesma conta da dispersão, para usar no app: { inclinacao, intercepto, r, r2, n } ou null
|
|
735
|
+
const reta = regressaoLinear([{ x: 4.99, y: 880 }, { x: 18.9, y: 610 }, { x: 39.9, y: 190 }]);
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
Tipos do eixo X: `categoria` (texto, uma faixa por item), `data` (`Date`,
|
|
739
|
+
timestamp ou texto `aaaa-mm-dd`/`aaaa-mm`; o rótulo se ajusta a dados diários,
|
|
740
|
+
mensais ou anuais) e `numero`. Os rótulos do eixo X nunca se sobrepõem: se não
|
|
741
|
+
couberem, aparece um a cada N. `eixo.formatar` troca o formato dos números
|
|
742
|
+
(eixo, caixa de detalhes e tabela).
|
|
743
|
+
|
|
744
|
+
Tokens (definidos nos dois temas; redefina no CSS do app para mudar a paleta):
|
|
745
|
+
|
|
746
|
+
| Token | Uso |
|
|
747
|
+
|---|---|
|
|
748
|
+
| `--graf-1` a `--graf-6` | Cores das séries, nessa ordem (contraste de 3:1 ou mais contra `--surface`) |
|
|
749
|
+
| `--graf-pos` / `--graf-neg` | Barras divergentes, acima e abaixo de zero |
|
|
750
|
+
| `--graf-seq-1` a `--graf-seq-5` | Escala do mapa de calor, do valor mais baixo ao mais alto |
|
|
751
|
+
| `--graf-grade`, `--graf-eixo`, `--graf-texto` | Linhas de grade, eixos e rótulos |
|
|
752
|
+
| `--graf-faixa` | Faixa sombreada de destaque |
|
|
753
|
+
|
|
754
|
+
**Volume de pontos:** cada gráfico é fluido até cerca de 2.000 pontos. Séries
|
|
755
|
+
maiores devem chegar agregadas (por exemplo, os pedidos de anos somados por mês
|
|
756
|
+
em vez de um ponto por pedido, ou o mapa de calor em vez de uma linha só).
|
|
757
|
+
|
|
758
|
+
Utilitários (lógica pura, também exportados): validadores BR (`isCpf`, `isCnpj`,
|
|
759
|
+
`isCnh`, `isImei`, `validar(tipo, valor)`…), `TIPOS_CAMPO` (máscara +
|
|
760
|
+
placeholder + regra por tipo), `ESTADOS`/`TIPOS_PESSOA`, por extenso
|
|
761
|
+
(`numeroPorExtenso`, `valorMonetarioPorExtenso`, `listaPorExtenso`), texto
|
|
762
|
+
(`normalizar`, `nomeProprio`, `nomeCurto`, `iniciais`, `formatCpfCnpj`), datas
|
|
763
|
+
pt-BR (`parseDataBr`, `formatDataBr`…), `tituloDeEntidade`/`nomeDeArquivo`,
|
|
764
|
+
anexos (`formatBytes`, `tipoDeArquivo`, `iconeArquivo`), senha
|
|
765
|
+
(`validarSenha`, `forcaSenha`), picklist, JWT (`decodificarJwt`, `jwtExpirado`…)
|
|
766
|
+
e `severidadeCor`. A demo do repositório tem uma página interativa de cada um
|
|
767
|
+
(`#/utilitarios`).
|
|
768
|
+
|
|
769
|
+
Convenções:
|
|
770
|
+
|
|
771
|
+
- Campos são controlados por `valor` + `aoMudar`; aceitam `rotulo`, `dica`,
|
|
772
|
+
`erro`, `aviso`, `obrigatorio` e `variante="inline"` (pílula com rótulo dentro,
|
|
773
|
+
para barras de filtro).
|
|
774
|
+
- Severidades: `ok`, `info`, `warn`, `err`, `neutral` (botão: `pri`, `secundario`,
|
|
775
|
+
`ok`, `warn`, `err`).
|
|
776
|
+
- Ícones: [Lucide](https://lucide.dev/icons) (SVG, já incluso no pacote). Passe o
|
|
777
|
+
nome (`icone="save"`, `icone="trash-2"`) ou o componente
|
|
778
|
+
(`import { Rocket } from 'lucide-react'` → `icone={<Rocket />}`). Só os ícones
|
|
779
|
+
registrados entram no bundle; para usar outro pelo nome, registre uma vez:
|
|
780
|
+
`registrarIcones({ rocket: Rocket })`. Um nome não registrado avisa no console
|
|
781
|
+
em desenvolvimento.
|
|
782
|
+
- Pendência de campo: o erro aparece ao **sair** do campo depois de digitar, ou
|
|
783
|
+
quando o `Form` tenta salvar (todos os campos acusam). Um valor carregado de
|
|
784
|
+
fora que não atende ao formato não é recusado: vira aviso âmbar.
|
|
785
|
+
- Grade do `FormSecao`: 4 colunas na página, 3 no modal; `className="lp-col-2"`,
|
|
786
|
+
`lp-col-3` ou `lp-col-full` alarga o campo.
|
|
787
|
+
- Cadastro com vários formulários: passe `menu` (itens com `id`, `rotulo`,
|
|
788
|
+
`icone`, `href`) e `menuAtivo` ao `Form` de cada página; o menu lateral liga
|
|
789
|
+
os formulários entre si, cada um com o próprio Salvar.
|
|
790
|
+
|
|
791
|
+
## Tema e tokens
|
|
792
|
+
|
|
793
|
+
Toda cor/medida vem de variáveis CSS (os tokens). Para mudar a marca, redefina
|
|
794
|
+
os tokens no CSS do app (a ordem dos imports não importa, veja [CSS](#css)) — por exemplo
|
|
795
|
+
`--navy` (marinho da sidebar), `--pri` (azul de ação) e `--acento-claro` (laranja
|
|
796
|
+
do item ativo). O tema escuro tem valores próprios, então redefina lá também:
|
|
797
|
+
|
|
798
|
+
```css
|
|
799
|
+
:root {
|
|
800
|
+
--pri: #0f766e;
|
|
801
|
+
--pri-ink: #115e59;
|
|
802
|
+
}
|
|
803
|
+
[data-theme='dark'] {
|
|
804
|
+
--pri: #2dd4bf;
|
|
805
|
+
--pri-ink: #5eead4;
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
O tema vem do atributo `data-theme` no `<html>`: `light`, `dark` ou `system`
|
|
810
|
+
(segue o modo do sistema operacional, inclusive se ele mudar com a página
|
|
811
|
+
aberta). No `ProvedorUi`, são os temas `claro`, `escuro` e `sistema`
|
|
812
|
+
(`temaInicial`, `definirTema`). Enquanto a pessoa não escolhe, vale o
|
|
813
|
+
`temaInicial`, e sem ele o `sistema`. `escuro` no `usePreferencias()` diz o que
|
|
814
|
+
está na tela. A densidade usa as classes `density-compact`, `density-comfortable` e
|
|
815
|
+
`density-cozy` no `<body>`. O `ProvedorUi` cuida de tudo isso. A lista
|
|
816
|
+
completa de tokens, com os valores dos dois temas, está em
|
|
817
|
+
`@celke/kole-react/tokens.css`.
|
|
818
|
+
|
|
819
|
+
Para usar só os tokens fora do React: `@import '@celke/kole-react/tokens.css';`
|
|
820
|
+
e `<html data-theme="system">` (ou `light`/`dark`) para escolher o tema sem JS.
|
|
821
|
+
|
|
822
|
+
## CSS
|
|
823
|
+
|
|
824
|
+
Classes CSS usam o prefixo `lp-`, e todo o CSS da biblioteca fica na camada
|
|
825
|
+
`@layer kole`. CSS fora de camada sempre vence CSS em camada, então o CSS do
|
|
826
|
+
app personaliza o kole sem disputar especificidade nem depender da ordem dos
|
|
827
|
+
imports. O lado oposto: uma regra global do app (`a { color: … }`,
|
|
828
|
+
`h1 { font-size: … }`) também atinge os componentes, por isso escope as regras
|
|
829
|
+
de elemento do app (por exemplo, `.conteudo-artigo h1`).
|
|
830
|
+
|
|
831
|
+
### Com Tailwind 4
|
|
832
|
+
|
|
833
|
+
A ordem das camadas precisa ser declarada **antes** de qualquer CSS, então o
|
|
834
|
+
CSS do app (com o Tailwind) é importado antes do `estilos.css`:
|
|
835
|
+
|
|
836
|
+
```css
|
|
837
|
+
/* app.css — esta linha vem antes do @import do Tailwind */
|
|
838
|
+
@layer theme, base, kole, components, utilities;
|
|
839
|
+
@import 'tailwindcss';
|
|
840
|
+
@import '@celke/kole-react/tailwind.css'; /* opcional: a ponte abaixo */
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
```tsx
|
|
844
|
+
// main.tsx
|
|
845
|
+
import './app.css';
|
|
846
|
+
import '@celke/kole-react/estilos.css';
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
Assim o kole vence o preflight do Tailwind (botões e campos mantêm o estilo) e
|
|
850
|
+
os utilitários vencem o kole (`<Botao className="mt-4" />` funciona). Se o
|
|
851
|
+
`estilos.css` vier primeiro, a camada `kole` fica abaixo do preflight e os
|
|
852
|
+
componentes perdem o estilo.
|
|
853
|
+
|
|
854
|
+
A ponte `@celke/kole-react/tailwind.css` faz duas coisas:
|
|
855
|
+
|
|
856
|
+
- o `dark:` do Tailwind segue o tema do kole (`data-theme`), e não o do sistema
|
|
857
|
+
operacional, então uma tela com os dois não fica metade clara e metade escura;
|
|
858
|
+
- os tokens viram utilitários com o mesmo nome: `bg-surface`, `text-ink`,
|
|
859
|
+
`text-t3`, `border-border`, `bg-pri`, `text-ok-fg`, `bg-err-bg`,
|
|
860
|
+
`rounded-card`, `rounded-pill`… Como apontam para os tokens, trocam de tema
|
|
861
|
+
junto com o kole. `font-sans` e `font-mono` passam a ser as fontes do kole.
|
|
862
|
+
Para o site público: `max-w-site`, `rounded-site`, `rounded-site-2`,
|
|
863
|
+
`shadow-site`, `text-site-hero`, `text-site-titulo`, `text-site-secao` e, sobre
|
|
864
|
+
o marinho, `bg-navy text-site-escuro-t3`.
|
|
865
|
+
|
|
866
|
+
A densidade não chega aos utilitários: `p-4` e `h-10` são fixos, só os
|
|
867
|
+
componentes `lp-*` mudam com ela.
|
|
868
|
+
|
|
869
|
+
Vale igual para o site e para o sistema: os componentes de site (`EstruturaSite`,
|
|
870
|
+
`DestaqueSite`…) estão no mesmo `estilos.css`, na mesma camada `kole`, então
|
|
871
|
+
`<SecaoSite className="mt-8" />` funciona e o preflight não desfaz o estilo deles.
|
|
872
|
+
|
|
873
|
+
## Desenvolvimento
|
|
874
|
+
|
|
875
|
+
```bash
|
|
876
|
+
npm install
|
|
877
|
+
npm run dev # http://localhost:5173 — demo com todos os componentes
|
|
878
|
+
npm run typecheck
|
|
879
|
+
npm run build # dist/index.js + tipos (.d.ts) + dist/estilos.css + dist/tokens.css + dist/tailwind.css
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
A demo usa o mesmo CSS do pacote (na camada `kole`, com o tema `sistema`), e o
|
|
883
|
+
`demo/demo.css` fica fora da camada, como o CSS de um app.
|
|
884
|
+
|
|
885
|
+
### Documentação
|
|
886
|
+
|
|
887
|
+
A demo tem a seção **Documentação** (`/#/docs/...`): Instalação, Tema e tokens,
|
|
888
|
+
Tailwind, Carregamento, Changelog e 60 páginas de componente, com exemplos ao vivo, o código
|
|
889
|
+
de cada exemplo e a tabela de props. Fica em `demo/docs/`:
|
|
890
|
+
|
|
891
|
+
- `nav.ts`: o catálogo (categoria → componente). O menu, o índice
|
|
892
|
+
(`/#/docs/componentes`) e as rotas saem dele.
|
|
893
|
+
- `paginas/componentes/<slug>.tsx`: a página de cada componente (`export
|
|
894
|
+
default`), montada com `PaginaComponente`. Componente novo = uma linha no
|
|
895
|
+
catálogo + este arquivo.
|
|
896
|
+
- `exemplos/<slug>/<Nome>.tsx`: cada arquivo é ao mesmo tempo o exemplo que roda
|
|
897
|
+
e o código que a página mostra (os imports de `src` aparecem como
|
|
898
|
+
`@celke/kole-react`). Na página: `<Exemplo id="botao/Cores" titulo="Cores" />`.
|
|
899
|
+
As classes `demo-*` dos exemplos são só de layout da demo.
|
|
900
|
+
- `props.gerado.json`: as props de todas as interfaces exportadas (`BotaoProps`
|
|
901
|
+
vira `Botao`; `OpcoesToast`, `Coluna`… ficam com o próprio nome), lidas dos
|
|
902
|
+
tipos TypeScript: nome, tipo, obrigatória, padrão e o comentário `/** … */` da
|
|
903
|
+
prop como descrição. É gerado por `npm run docs:props`, que já roda no `dev` e
|
|
904
|
+
no `build:demo`; `npm run docs:conferir` falha se estiver desatualizado.
|
|
905
|
+
|
|
906
|
+
### Teste visual
|
|
907
|
+
|
|
908
|
+
Captura cada página do menu da demo (`demo/nav.ts`) nos temas claro e escuro e
|
|
909
|
+
compara pixel a pixel com uma referência. Também confere que o tema `sistema`,
|
|
910
|
+
com o sistema operacional no escuro, sai igual ao tema escuro. Página nova no
|
|
911
|
+
menu entra no teste sozinha.
|
|
912
|
+
|
|
913
|
+
O print muda de um sistema operacional para outro, então a referência fica em
|
|
914
|
+
`tests/visual/__referencia__/<sistema>/`. Só a de Linux vai para o git: é a que
|
|
915
|
+
o CI usa (`.github/workflows/visual.yml`, na imagem Docker do Playwright), em
|
|
916
|
+
todo PR e na `main`.
|
|
917
|
+
|
|
918
|
+
**Mudança visual de propósito:** o CI do PR vai falhar apontando as páginas que
|
|
919
|
+
mudaram (o relatório com as diferenças fica nos artefatos do workflow). Confira
|
|
920
|
+
e grave a referência nova: Actions → Teste visual → Run workflow, na branch do
|
|
921
|
+
PR, com "atualizar" marcado. O workflow faz o commit da referência na branch.
|
|
922
|
+
|
|
923
|
+
No Windows ou no macOS, o teste usa uma referência local, fora do git:
|
|
924
|
+
|
|
925
|
+
```bash
|
|
926
|
+
npx playwright install chromium # uma vez
|
|
927
|
+
npm run visual:referencia # ANTES de mexer no CSS: grava a referência
|
|
928
|
+
npm run visual # depois: aponta cada página que mudou
|
|
929
|
+
npx playwright show-report # diferenças lado a lado
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
Para comparar com uma versão já publicada, sirva a demo dela noutra porta (por
|
|
933
|
+
exemplo, num `git worktree` da tag) e gere a referência com
|
|
934
|
+
`KOLE_URL=http://localhost:5181 npm run visual:referencia`.
|
|
935
|
+
|
|
936
|
+
Para testar uma versão que ainda não está no npm, instale direto do GitHub
|
|
937
|
+
(o `prepare` gera o `dist/` na instalação) ou de um pacote local:
|
|
938
|
+
|
|
939
|
+
```bash
|
|
940
|
+
npm i github:celkecursos/kole-react
|
|
941
|
+
# ou: gere o .tgz na pasta da biblioteca e instale no app
|
|
942
|
+
npm pack # → celke-kole-react-<versão>.tgz
|
|
943
|
+
npm i ../kole-react/celke-kole-react-<versão>.tgz
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
Evite `npm i ../kole-react` (pasta): o npm cria um link e o bundler
|
|
947
|
+
pega o React do `node_modules` da biblioteca — dois Reacts no app quebram os hooks
|
|
948
|
+
(_Invalid hook call_). Se precisar do link durante o desenvolvimento, use
|
|
949
|
+
`resolve: { dedupe: ['react', 'react-dom'] }` no `vite.config` do app.
|
|
950
|
+
|
|
951
|
+
Publicar uma versão nova: `npm version patch` (só correção) ou `npm version minor`
|
|
952
|
+
(muda a aparência ou a API — antes da 1.0, o `^0.x` dos apps não pega a minor
|
|
953
|
+
sozinho), registre no [CHANGELOG](CHANGELOG.md) e confira o conteúdo com
|
|
954
|
+
`npm pack --dry-run`. Depois do merge na `main`, publique pelo GitHub: Actions →
|
|
955
|
+
Publicar → Run workflow (`.github/workflows/publish.yml`). O npm confia nesse
|
|
956
|
+
workflow pela publicação confiável (Trusted Publisher, sem token) e o pacote
|
|
957
|
+
sai com o atestado de procedência. O workflow só deixa a versão em espera
|
|
958
|
+
(`npm stage publish`): ela vai ao ar depois que um mantenedor aprova com o
|
|
959
|
+
segundo fator, com `npm stage list @celke/kole-react` e `npm stage approve <id>`
|
|
960
|
+
(ou pelo site do npm). O workflow recusa versão que já está no npm.
|
|
961
|
+
|
|
962
|
+
## Autor
|
|
963
|
+
|
|
964
|
+
Criado por **Cesar Szpak**.
|
|
965
|
+
|
|
966
|
+
## Licença
|
|
967
|
+
|
|
968
|
+
MIT © 2026 Celke — veja o arquivo [LICENSE](https://github.com/celkecursos/kole-react/blob/main/LICENSE).
|