@topjoao/top-design-system 0.1.0-beta.9 → 0.1.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/README.md CHANGED
@@ -1,12 +1,35 @@
1
1
  # TopSolutions Design System
2
2
 
3
3
  Biblioteca de componentes Vue 3, estilos e tokens visuais compartilhados pela
4
- TopSolutions.
4
+ TopSolutions.
5
+
6
+ ## Índice
7
+
8
+ - [Instalação e configuração](#instalação)
9
+ - [Tema e tokens](#tema-topsolutions)
10
+ - [Integração com Nuxt e Vue 3](#uso-com-nuxt)
11
+ - [Componentes](#componentes-publicos)
12
+ - [Tipos e exports públicos](#tipos-e-exports-públicos)
13
+ - [Desenvolvimento da biblioteca](#desenvolvimento-da-biblioteca)
14
+
15
+ ## Componentes públicos
16
+
17
+ | Componente | Finalidade | `v-model` |
18
+ |---|---|---|
19
+ | `TopButton` | Botão com estilos e estados da marca. | — |
20
+ | `TopConfirmDialog` | Confirmação modal controlada pela aplicação. | `boolean` |
21
+ | `TopDatePicker` | Campo de data, múltiplas datas ou intervalo. | `Date`/array de `Date` |
22
+ | `TopFileUpload` | Escolha, validação e prévia de arquivos; não faz upload. | `File`/`File[]`/`null` |
23
+ | `TopInputText` | Campo textual com label, erro e acessibilidade. | `string` |
24
+ | `TopNavBar` | Navegação responsiva, breadcrumbs, favoritos e Drawer. | `searchValue` e `mobileOpen` opcionais |
25
+ | `TopSelect` | Autocomplete pesquisável com paginação virtual. | opção/opções |
26
+ | `TopTabs` | Abas com painéis nomeados. | `string \| number` |
27
+ | `TopToast` | Renderizador de notificações do serviço Toast do PrimeVue. | — |
5
28
 
6
29
  ## Instalação
7
30
 
8
31
  ```bash
9
- npm install @topjoao/top-design-system@beta
32
+ npm install @topjoao/top-design-system
10
33
  ```
11
34
 
12
35
  A aplicação consumidora deve possuir Vue 3, PrimeVue, PrimeIcons e
@@ -29,6 +52,16 @@ app.use(PrimeVue, {
29
52
  })
30
53
  ```
31
54
 
55
+ Para ativar o modo escuro, adicione ou remova `.dark` uma única vez no elemento
56
+ `<html>` da aplicação. Os componentes usam tokens semânticos de superfície, texto e
57
+ borda fornecidos por `style.css`; não é necessário passar classes `dark:` em cada
58
+ uso. A aplicação pode substituir esses tokens após importar o CSS da biblioteca.
59
+
60
+ Os tokens públicos de campos são `--top-field-background`,
61
+ `--top-field-background-readonly`, `--top-field-background-disabled`,
62
+ `--top-field-text`, `--top-field-label`, `--top-field-placeholder`,
63
+ `--top-field-border`, `--top-field-border-hover` e `--top-field-icon`.
64
+
32
65
  O preset é independente dos ajustes de layout próprios do TopLicita; ele
33
66
  contém apenas tokens semânticos compartilháveis, como cores primárias, neutras,
34
67
  sucesso, alerta e erro.
@@ -99,6 +132,262 @@ createApp(App).use(TopSolutionsDesignSystem).mount('#app')
99
132
  Após o registro, os componentes podem ser utilizados sem importação manual. A
100
133
  entrada `/plugin` também fornece as declarações globais usadas pela IDE.
101
134
 
135
+ ## TopNavBar
136
+
137
+ Barra de navegação responsiva inspirada no AppSidebar do TopLicita. O
138
+ componente fornece apenas layout e interação: a aplicação consumidora continua
139
+ responsável por rotas, permissões, sessão, cliente/órgão, busca remota,
140
+ favoritos, Aia, suporte e integrações. Nenhuma dessas ações é executada pela
141
+ biblioteca; todas são comunicadas por eventos.
142
+
143
+ ```vue
144
+ <script setup lang="ts">
145
+ import { ref } from 'vue'
146
+ import {
147
+ TopNavBar,
148
+ type TopNavAction,
149
+ type TopNavItem,
150
+ type TopNavSection,
151
+ } from '@topjoao/top-design-system'
152
+
153
+ const clienteAtual = ref('prefeitura-a')
154
+ const favorito = ref(false)
155
+
156
+ const secoes: TopNavSection[] = [
157
+ {
158
+ id: 'planejamento',
159
+ label: 'Planejamento',
160
+ icon: 'pi pi-book',
161
+ children: [
162
+ {
163
+ id: 'programacao',
164
+ label: 'Programação',
165
+ children: [
166
+ { id: 'calendario', label: 'Calendário', to: '/programacao/calendario' },
167
+ ],
168
+ },
169
+ ],
170
+ },
171
+ ]
172
+
173
+ function navegar(item: TopNavItem) {
174
+ if (item.to) router.push(item.to)
175
+ }
176
+
177
+ function executarIntegracao(action: TopNavAction) {
178
+ // Abra a integração identificada por action.id.
179
+ }
180
+ </script>
181
+
182
+ <template>
183
+ <TopNavBar
184
+ :sections="secoes"
185
+ active-item-id="calendario"
186
+ searchable
187
+ show-aia
188
+ show-support
189
+ show-favorite-toggle
190
+ :favorite-active="favorito"
191
+ :actions="[
192
+ { id: 'integracoes', label: 'Integrações', icon: 'pi pi-th-large' },
193
+ ]"
194
+ :user="{ name: 'João Silva', subtitle: 'Administrador', initials: 'JS' }"
195
+ :user-menu-items="[
196
+ { id: 'perfil', label: 'Meu perfil', icon: 'pi pi-user' },
197
+ { id: 'sair', label: 'Sair', icon: 'pi pi-sign-out' },
198
+ ]"
199
+ @navigate="navegar"
200
+ @search="consultarMenusPermitidos"
201
+ @toggle-aia="alternarAia"
202
+ @open-support="abrirCentralSuporte"
203
+ @toggle-favorite="favorito = !favorito"
204
+ @action="executarIntegracao"
205
+ @user-action="executarAcaoDaSessao"
206
+ >
207
+ <template #brand="{ compact }">
208
+ <img src="/logo.svg" alt="Minha organização">
209
+ <span v-if="!compact">Sistema de Contratações</span>
210
+ </template>
211
+
212
+ <template #context="{ compact }">
213
+ <select v-model="clienteAtual" aria-label="Cliente atual">
214
+ <option value="prefeitura-a">Prefeitura A</option>
215
+ <option value="prefeitura-b">Prefeitura B</option>
216
+ </select>
217
+ <span v-if="!compact">Poder Executivo</span>
218
+ </template>
219
+ </TopNavBar>
220
+ </template>
221
+ ```
222
+
223
+ ### Menu e breadcrumbs
224
+
225
+ `sections` aceita uma árvore de profundidade arbitrária. Cada nó usa
226
+ `TopNavItem` (`id`, `label`, `to?`, `icon?`, `disabled?`, `children?`, `data?`).
227
+ Seções sem filhos são removidas e um `children: []` nunca cria um painel vazio.
228
+ No desktop, a primeira coluna do painel mestre–detalhe tem `16rem`; os itens de
229
+ cada nível são repartidos em colunas de `18rem`, no máximo dez por coluna.
230
+ Subníveis aparecem sempre à direita do nível de origem.
231
+
232
+ Breadcrumbs estão habilitados por padrão e começam por `Início / Navegação`.
233
+ Há duas formas de fornecer a trilha:
234
+
235
+ - informe `breadcrumbs` com itens `TopNavBreadcrumb`; `sectionId` liga o item a
236
+ uma seção e `menuId` liga a qualquer nó com filhos;
237
+ - omita `breadcrumbs` e informe `activeItemId`; a trilha é derivada da árvore.
238
+
239
+ Por exemplo, `calendario` dentro de `Programação` em `Planejamento` gera
240
+ `Início / Navegação / Planejamento / Programação / Calendário`. A rota ativa
241
+ não recebe fundo permanente nos menus desktop; somente hover e o ramo que está
242
+ sendo explorado recebem destaque.
243
+
244
+ ### Props
245
+
246
+ | Prop | Tipo / padrão | Finalidade |
247
+ |---|---|---|
248
+ | `sections` | `TopNavSection[]` / `[]` | Árvore de navegação já filtrada pela aplicação. |
249
+ | `breadcrumbs` | `TopNavBreadcrumb[]` / `[]` | Trilha explícita; vazia permite derivação por `activeItemId`. |
250
+ | `homeItem` | `TopNavItem` / `Início` | Item inicial emitido ao clicar em Início. |
251
+ | `navigationLabel` | `string` / `Navegação` | Rótulo do menu mestre. |
252
+ | `activeItemId` | `string` | Nó atual, usado no breadcrumb e no Drawer. |
253
+ | `searchable` | `boolean` / `false` | Habilita busca desktop e busca própria do Drawer. |
254
+ | `searchValue` | `string` | Valor opcionalmente controlado com `v-model:search-value`. |
255
+ | `searchResults` | `TopNavItem[]` | Resultados controlados; sem a prop, a árvore é filtrada localmente. |
256
+ | `searchPlaceholder` | `string` | Placeholder das duas buscas. |
257
+ | `showAia`, `showSupport` | `boolean` / `false` | Exibem as ações opcionais. |
258
+ | `aiaActive` | `boolean` / `false` | Estado visual do botão Aia. |
259
+ | `aiaLabel`, `supportLabel` | `string` | Textos acessíveis e rótulos do Drawer. |
260
+ | `actions` | `TopNavAction[]` / `[]` | Ações genéricas, como integrações, emitidas por `action`. |
261
+ | `client` | `TopNavClient` | Organização, cliente ou escopo ativo exibido no cabeçalho; não cria ação nem seletor. |
262
+ | `user` | `TopNavUser` | Dados exclusivamente visuais do usuário. |
263
+ | `userMenuItems` | `TopNavUserMenuItem[]` | Opções emitidas por `user-action`; suporta separadores. |
264
+ | `favoriteItems` | `TopNavItem[]` / `[]` | Favoritos fornecidos pelo pai para dropdown e Drawer. |
265
+ | `showFavoriteToggle` | `boolean` / `false` | Exibe a estrela da página atual. |
266
+ | `favoriteActive` | `boolean` / `false` | Estado visual da estrela atual. |
267
+ | `mobileOpen` | `boolean` | Controle opcional com `v-model:mobile-open`. |
268
+ | `appearance` | `TopNavBarAppearance` | Tokens visuais locais descritos abaixo. |
269
+
270
+ ### Eventos
271
+
272
+ | Evento | Payload | Quando ocorre |
273
+ |---|---|---|
274
+ | `navigate` | `TopNavItem` | Início, menu, resultado ou favorito é selecionado. |
275
+ | `search` | `string` | A consulta muda no desktop ou no Drawer. |
276
+ | `update:searchValue` | `string` | Atualização de `v-model:search-value`. |
277
+ | `update:mobileOpen` | `boolean` | Atualização de `v-model:mobile-open`. |
278
+ | `toggle-aia` | — | A ação Aia é acionada. |
279
+ | `open-support` | — | A ação de suporte é acionada. |
280
+ | `toggle-favorite` | — | A estrela da página atual é acionada. |
281
+ | `action` | `TopNavAction` | Uma ação genérica/integração é acionada. |
282
+ | `user-action` | `TopNavUserMenuItem` | Uma opção de usuário é selecionada. |
283
+ | `user-click` | `TopNavUser \| undefined` | A área de usuário sem menu é acionada. |
284
+
285
+ `Ctrl+K` e `Cmd+K` abrem/focam a busca. Em telas menores que `1024px`, o
286
+ atalho abre primeiro o Drawer e foca a busca móvel. `Escape` fecha os painéis.
287
+
288
+ ### Slots
289
+
290
+ | Slot | Uso |
291
+ |---|---|
292
+ | `brand` | Marca; recebe `{ compact }`. |
293
+ | `context`, `scope` ou `client` | Contexto operacional; aliases com prioridade nessa ordem, recebem `{ client, compact }`. O slot `client` substitui a apresentação padrão da prop `client`. |
294
+ | `context-compact` | Variante explícita usada no último estágio de overflow. |
295
+ | `actions` | Conteúdo adicional do cabeçalho; recebe `{ compact, close }`. |
296
+ | `aia-icon`, `support-icon` | Ícones customizados das ações quadradas. |
297
+ | `user` | Conteúdo do gatilho de usuário; recebe `{ user, compact }`. |
298
+ | `user-menu` | Painel de usuário; recebe `{ items, select }`. |
299
+ | `search-results` | Resultados customizados; recebe `{ items, select }`. |
300
+ | `breadcrumb-actions` | Ações adicionais no fim da segunda faixa. |
301
+ | `drawer-header` | Cabeçalho inteiro; recebe `{ close }`. |
302
+ | `drawer-brand` | Marca do Drawer; por padrão reutiliza `brand`. |
303
+ | `drawer-search` | Busca inteira; recebe `{ query, update, clear }`. |
304
+ | `drawer-actions` | Ações; recebe `{ actions, select, close }`. |
305
+ | `drawer-before-menu`, `drawer-after-menu` | Conteúdo antes/depois do trilho rolável. |
306
+ | `drawer-menu` | Substitui a árvore; recebe `{ sections, select, close }`. |
307
+ | `drawer-user` | Rodapé de usuário; recebe `{ user, open, toggle }`. |
308
+ | `drawer-footer` | Conteúdo final adicional; recebe `{ close }`. |
309
+
310
+ ### Faixa de favoritos
311
+
312
+ No desktop, `favoriteItems` aparece primeiro em uma faixa horizontal abaixo dos
313
+ breadcrumbs. A biblioteca mede uma cópia invisível da faixa com
314
+ `ResizeObserver`: se a largura real não couber, ela é substituída pelo dropdown
315
+ `Favoritos`. O mesmo dropdown é usado depois que a página passa de `120px` de
316
+ scroll e a faixa retorna apenas ao chegar a `24px` ou menos. Os valores evitam
317
+ oscilações perto do topo e reproduzem o comportamento do AppSidebar.
318
+
319
+ ### Aparência e responsividade
320
+
321
+ `appearance` é propositalmente pequeno e semântico. Use `colors` para
322
+ `navigation`, `text`, `mutedText`, `accent`, `border`, `menuText`,
323
+ `mobileActiveText` e `favoritesText`; `surfaces` para `breadcrumb`, `drawer`,
324
+ `drawerFooter`, `menu`, `menuHover`, `menuExplored`, `mobileActive`,
325
+ `favorites` e `currentPage`; `borders` para `menu` (a borda externa de 6px),
326
+ `menuOutline`, `mobileDivider`, `favorites` e `currentPage`; `shape` para
327
+ `radius`, `drawerRadius`, `shadow` e `favoritesShadow`; e `focus` para
328
+ `onDark` e `onLight`.
329
+
330
+ ```ts
331
+ const appearance = {
332
+ colors: { navigation: '#09090b', text: '#f4f4f5', border: 'rgb(255 255 255 / 14%)' },
333
+ surfaces: { drawer: '#09090b', menu: '#18181b', menuHover: '#27272a' },
334
+ shape: { radius: '8px', shadow: '0 12px 30px rgb(0 0 0 / 25%)' },
335
+ }
336
+ ```
337
+
338
+ A biblioteca não fixa tipografia inline. Por padrão, família, tamanho, peso e
339
+ altura de linha são herdados da aplicação consumidora. Além disso, `appearance`
340
+ só cria variáveis inline para propriedades que foram realmente informadas; os
341
+ valores fiéis ao AppSidebar existem apenas como *fallbacks* no CSS. Assim, um
342
+ tema global pode controlar o componente sem precisar usar `!important`:
343
+
344
+ ```css
345
+ :root {
346
+ --top-nav-font-family: var(--app-font-family);
347
+ --top-nav-font-size: var(--app-font-size);
348
+ --top-nav-strong-font-weight: 600;
349
+ --top-nav-action-size: 2.5rem;
350
+ --top-nav-header-padding: 0.625rem 1.5rem;
351
+ --top-nav-menu-item-padding: 0.5rem 0.75rem;
352
+ --top-nav-section-width: 16rem;
353
+ --top-nav-column-width: 18rem;
354
+ --top-nav-mobile-active-bg: #fff;
355
+ --top-nav-mobile-active-fg: #025a84;
356
+ --top-nav-mobile-divider: rgb(255 255 255 / 20%);
357
+ --top-nav-mobile-item-gap: 0.25rem;
358
+ --top-nav-mobile-submenu-padding: 0.5rem 0 0.375rem 0.35rem;
359
+ --top-nav-favorites-bg: #f8fafc;
360
+ --top-nav-favorites-fg: #0f172a;
361
+ --top-nav-favorites-border: #e2e8f0;
362
+ --top-nav-favorites-shadow: 0 1px 2px rgb(15 23 42 / 6%);
363
+ --top-nav-focus-ring: #dbeafe;
364
+ --top-nav-focus-ring-light: #1d4ed8;
365
+ --top-nav-current-bg: rgb(255 255 255 / 10%);
366
+ --top-nav-current-border: rgb(255 255 255 / 15%);
367
+ --top-nav-menu-scrollbar-thumb: #94a3b8;
368
+ }
369
+ ```
370
+
371
+ As variáveis globais também alcançam o Drawer teleportado. Use-as para ajustes
372
+ estruturais — tipografia, larguras, alturas, padding e espaçamentos do menu —
373
+ em vez de props no componente. Um valor passado por `appearance` tem precedência
374
+ local e não altera os demais tokens do tema.
375
+
376
+ O breakpoint móvel é `1024px`. No desktop, um `ResizeObserver` reaplica a mesma
377
+ sequência progressiva do AppSidebar: ações colapsam abaixo de `1440px`, busca
378
+ vira ícone abaixo de `1320px`, nome do usuário some abaixo de `1180px` e, se o
379
+ conteúdo ainda transbordar, o contexto recebe `compact: true`. O último estágio
380
+ também limita o contêiner do contexto a `3.25rem`; use `context-compact` quando
381
+ quiser controlar exatamente o que permanece visível.
382
+
383
+ Abaixo de `1024px`, a navegação desktop desaparece e o Drawer do PrimeVue assume.
384
+ Ele mantém cabeçalho, busca, ações, trilho translúcido rolável, árvore recursiva,
385
+ favoritos e usuário. A transição `menu-expand` existe somente dentro do Drawer;
386
+ os dropdowns desktop abrem sem animação e suas áreas de hover incluem o espaço
387
+ entre gatilho e painel. Nessa largura, os breadcrumbs deixam de rolar
388
+ horizontalmente: eles quebram em linhas e o bloco da página atual ocupa sua
389
+ própria linha para permanecer legível.
390
+
102
391
  ## TopButton
103
392
 
104
393
  Exemplo
@@ -107,6 +396,7 @@ Exemplo
107
396
  <template>
108
397
  <TopButton label="Salvar" icon="pi pi-save" @click="salvar" />
109
398
  <TopButton label="Cancelar" secondary />
399
+ <TopButton label="Excluir" severity="danger" />
110
400
 
111
401
  <TopButton label="Consultar" outlined>
112
402
  <template #icon>
@@ -125,12 +415,16 @@ Exemplo
125
415
  | `loading` | `boolean` | `false` |
126
416
  | `class` | `string` | `''` |
127
417
  | `outlined` | `boolean` | `false` |
418
+ | `severity` | `'primary' \| 'secondary' \| 'success' \| 'warn' \| 'danger'` | `'primary'` |
128
419
  | `secondary` | `boolean` | `false` |
129
420
  | `disabled` | `boolean` | `false` |
130
421
  | `unstyled` | `boolean` | `false` |
131
422
  | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` |
132
423
 
133
- O componente emite `click` sem payload e oferece o slot nomeado `icon`.
424
+ O componente emite `click` sem payload. O slot nomeado `icon` não recebe
425
+ parâmetros e substitui a prop `icon` — útil para SVGs, `HugeiconsIcon` ou um
426
+ ícone com estado próprio. `secondary` permanece como atalho compatível para
427
+ `severity="secondary"` e tem precedência sobre `severity`.
134
428
 
135
429
  ## TopConfirmDialog
136
430
 
@@ -179,48 +473,189 @@ não fecha automaticamente o diálogo, permitindo que a aplicação aguarde uma
179
473
  operação assíncrona. Os slots `message` e default permitem substituir a mensagem
180
474
  textual.
181
475
 
182
- ## TopInputText
183
-
184
- Campo textual baseado no `InputText` do PrimeVue. O `v-model` é sempre
185
- `string`, inclusive para códigos, documentos e identificadores compostos
186
- somente por dígitos; por exemplo, `"001234"` preserva os zeros à esquerda.
187
-
188
- ```vue
189
- <script setup lang="ts">
190
- import { ref } from 'vue'
191
- import { TopInputText } from '@topjoao/top-design-system'
192
-
193
- const codigo = ref('001234')
194
- </script>
476
+ ## TopDatePicker
477
+
478
+ Seletor de datas baseado no `DatePicker` do PrimeVue. O valor permanece como
479
+ `Date` (ou arrays de `Date` nos modos `multiple` e `range`); `dateFormat` altera
480
+ somente a apresentação no campo e não converte o `v-model` para texto.
481
+ No modo padrão (`single` com `dd/mm/yy`), a digitação recebe automaticamente a
482
+ máscara brasileira `dd/mm/aaaa`.
483
+
484
+ ```vue
485
+ <script setup lang="ts">
486
+ import { ref } from 'vue'
487
+ import { TopDatePicker } from '@topjoao/top-design-system'
488
+
489
+ const dataNascimento = ref<Date | null>(null)
490
+ </script>
491
+
492
+ <template>
493
+ <TopDatePicker
494
+ v-model="dataNascimento"
495
+ label="Data de nascimento"
496
+ placeholder="Selecione a data"
497
+ :max-date="new Date()"
498
+ required
499
+ error="Informe uma data válida."
500
+ @date-select="validarData"
501
+ />
502
+ </template>
503
+ ```
504
+
505
+ | Prop | Tipo | Padrão |
506
+ |---|---|---|
507
+ | `modelValue` | `Date \| Date[] \| (Date \| null)[] \| null` | `null` |
508
+ | `label` | `string` | `''` |
509
+ | `placeholder` | `string` | `'dd/mm/aaaa'` |
510
+ | `required` | `boolean` | `false` |
511
+ | `error` | `string` | `''` |
512
+ | `invalid` | `boolean` | `false` |
513
+ | `disabled` | `boolean` | `false` |
514
+ | `readonly` | `boolean` | `false` |
515
+ | `selectionMode` | `'single' \| 'multiple' \| 'range'` | `'single'` |
516
+ | `dateFormat` | `string` | `'dd/mm/yy'` |
517
+ | `minDate` / `maxDate` | `Date` | `undefined` |
518
+ | `showIcon` | `boolean` | `true` |
519
+ | `iconDisplay` | `'button' \| 'input'` | `'input'` |
520
+ | `manualInput` | `boolean` | `true` |
521
+ | `showButtonBar` | `boolean` | `false` |
522
+ | `appendTo` | `'body' \| 'self' \| HTMLElement` | `'body'` |
523
+
524
+ Eventos: `update:modelValue`, `input`, `change`, `date-select`, `show`, `hide`,
525
+ `today-click`, `clear-click`, `month-change`, `year-change`, `focus`, `blur` e
526
+ `keydown`. Os slots do `DatePicker` do PrimeVue são repassados pelo wrapper,
527
+ incluindo `date`, `header`, `footer`, `buttonbar`, `inputicon`, `dropdownicon`,
528
+ `previcon` e `nexticon`.
195
529
 
196
- <template>
197
- <TopInputText
198
- id="codigo"
199
- v-model="codigo"
200
- label="Código"
201
- placeholder="Digite o código"
202
- required
203
- maxlength="10"
204
- autocomplete="off"
205
- />
206
- </template>
207
- ```
530
+ O slot `date` recebe as props de dia disponibilizadas pelo `DatePicker`; os
531
+ slots de ícone recebem as props correspondentes do PrimeVue. Os slots `header`,
532
+ `footer` e `buttonbar` permitem substituir essas regiões. Todos são apenas
533
+ repassados: o wrapper preserva as props e o comportamento do componente-base.
534
+
535
+ `required` adiciona o atributo nativo e o asterisco visual; a validação continua
536
+ sob responsabilidade da aplicação. `error` ativa o estado inválido, associa a
537
+ mensagem ao input com atributos ARIA e a exibe abaixo do campo. `readonly`
538
+ impede edição e seleção sem desabilitar o controle, enquanto `disabled` remove a
539
+ interação. No modo escuro, o campo usa os tokens públicos `--top-field-*`.
540
+
541
+ ## TopFileUpload
542
+
543
+ Seletor de arquivos baseado no `FileUpload` do PrimeVue. O componente valida e
544
+ apresenta os arquivos, mas não os envia: a aplicação consumidora controla o
545
+ upload por `v-model` e pelos eventos.
546
+
547
+ ```vue
548
+ <script setup lang="ts">
549
+ import { ref } from 'vue'
550
+ import { TopFileUpload } from '@topjoao/top-design-system'
551
+
552
+ const anexos = ref<File[]>([])
553
+
554
+ function enviarArquivos(files: File[]) {
555
+ // Envie os arquivos usando o serviço da aplicação.
556
+ }
557
+ </script>
558
+
559
+ <template>
560
+ <TopFileUpload
561
+ v-model="anexos"
562
+ label="Anexos"
563
+ accept=".pdf,image/*"
564
+ multiple
565
+ :max-file-size="5 * 1024 * 1024"
566
+ :max-files="5"
567
+ required
568
+ @select="enviarArquivos"
569
+ />
570
+ </template>
571
+ ```
572
+
573
+ | Prop | Tipo | Padrão |
574
+ |---|---|---|
575
+ | `modelValue` | `File \| File[] \| null` | `null` |
576
+ | `label` | `string` | `''` |
577
+ | `placeholder` | `string` | `'Arraste e solte o arquivo aqui'` |
578
+ | `required` | `boolean` | `false` |
579
+ | `disabled` | `boolean` | `false` |
580
+ | `accept` | `string` | `''` |
581
+ | `multiple` | `boolean` | `false` |
582
+ | `maxFileSize` | `number \| null` (bytes) | `null` |
583
+ | `maxFiles` | `number \| null` | `null` |
584
+ | `error` | `string` | `''` |
585
+ | `selectLabel` | `string` | `'Selecionar arquivo'` |
586
+ | `removeLabel` | `string` | `'Remover'` |
587
+ | `loading` | `boolean` | `false` |
588
+
589
+ Eventos: `update:modelValue`, `select`, `change`, `remove`, `clear` e `error`.
590
+ Erros de tipo, tamanho e quantidade possuem `code`, `message` e o `file`
591
+ relacionado. Os slots `empty` e `preview` permitem customizar a área vazia e a
592
+ pré-visualização. Os métodos `choose()` e `clear()` ficam disponíveis pela ref
593
+ do componente.
208
594
 
209
- | Prop | Tipo | Padrão |
595
+ | Slot | Parâmetros recebidos | Uso |
210
596
  |---|---|---|
211
- | `modelValue` | `string` | `''` |
212
- | `label` | `string` | `''` |
213
- | `placeholder` | `string` | `''` |
214
- | `required` | `boolean` | `false` |
215
- | `error` | `string` | `''` |
216
- | `disabled` | `boolean` | `false` |
217
- | `readonly` | `boolean` | `false` |
597
+ | `empty` | `{ choose, disabled }` | Substitui a área vazia. Chame `choose()` para abrir o seletor nativo. |
598
+ | `preview` | `{ file, index, url }` | Substitui a miniatura. `url` é uma object URL apenas para imagens; nos demais casos, é `''`. |
218
599
 
219
- Atributos e eventos nativos adicionais, como `name`, `maxlength`,
220
- `autocomplete`, `inputmode`, `pattern`, `aria-*`, `data-*`, `focus` e `blur`,
221
- são repassados ao elemento `input` interno.
600
+ ```vue
601
+ <TopFileUpload ref="upload" v-model="anexos" multiple>
602
+ <template #empty="{ choose, disabled }">
603
+ <button type="button" :disabled="disabled" @click="choose()">Anexar documentos</button>
604
+ </template>
605
+ <template #preview="{ file, url }">
606
+ <img v-if="url" :src="url" :alt="file.name">
607
+ <span v-else>{{ file.name }}</span>
608
+ </template>
609
+ </TopFileUpload>
610
+ ```
222
611
 
223
- ## TopSelect
612
+ `accept` aceita extensões (`.pdf`), MIME types (`application/pdf`) e curingas
613
+ MIME (`image/*`), separados por vírgula. `maxFileSize` é contado em bytes. Em
614
+ modo simples, a última seleção válida substitui a anterior; com `multiple`,
615
+ arquivos válidos são acumulados sem duplicar nome, tipo e tamanho.
616
+
617
+ ## TopInputText
618
+
619
+ Campo textual baseado no `InputText` do PrimeVue. O `v-model` é sempre
620
+ `string`, inclusive para códigos, documentos e identificadores compostos
621
+ somente por dígitos; por exemplo, `"001234"` preserva os zeros à esquerda.
622
+
623
+ ```vue
624
+ <script setup lang="ts">
625
+ import { ref } from 'vue'
626
+ import { TopInputText } from '@topjoao/top-design-system'
627
+
628
+ const codigo = ref('001234')
629
+ </script>
630
+
631
+ <template>
632
+ <TopInputText
633
+ id="codigo"
634
+ v-model="codigo"
635
+ label="Código"
636
+ placeholder="Digite o código"
637
+ required
638
+ maxlength="10"
639
+ autocomplete="off"
640
+ />
641
+ </template>
642
+ ```
643
+
644
+ | Prop | Tipo | Padrão |
645
+ |---|---|---|
646
+ | `modelValue` | `string` | `''` |
647
+ | `label` | `string` | `''` |
648
+ | `placeholder` | `string` | `''` |
649
+ | `required` | `boolean` | `false` |
650
+ | `error` | `string` | `''` |
651
+ | `disabled` | `boolean` | `false` |
652
+ | `readonly` | `boolean` | `false` |
653
+
654
+ Atributos e eventos nativos adicionais, como `name`, `maxlength`,
655
+ `autocomplete`, `inputmode`, `pattern`, `aria-*`, `data-*`, `focus` e `blur`,
656
+ são repassados ao elemento `input` interno.
657
+
658
+ ## TopSelect
224
659
 
225
660
  Seletor pesquisável baseado no `AutoComplete` do PrimeVue. Ele é genérico: a
226
661
  aplicação fornece os itens, executa a busca e decide qualquer apresentação de
@@ -260,33 +695,193 @@ function searchCustomers({ query }: { query: string }) {
260
695
  </template>
261
696
  ```
262
697
 
263
- | Prop | Tipo | Padrão |
264
- |---|---|---|
265
- | `options` | `array` | `[]` |
698
+ | Prop | Tipo | Padrão |
699
+ |---|---|---|
700
+ | `modelValue` | `SelectOption \| SelectOption[] \| null` | `null` |
701
+ | `options` | `array` | `[]` |
266
702
  | `optionLabel` | `string \| function` | `'label'` |
267
703
  | `optionKey` | `string` | `'id'` |
268
704
  | `optionPrefix` | `string` | `''` |
269
705
  | `showOptionPrefix` | `boolean` | `false` |
270
706
  | `showSelectedPrefix` | `boolean` | `false` |
271
707
  | `loading` / `disabled` / `invalid` | `boolean` | `false` |
272
- | `placeholder` | `string` | `'Search...'` |
708
+ | `placeholder` | `string` | `'Pesquisar...'` |
273
709
  | `minQueryLength` | `number` | `1` |
274
710
  | `multiple` / `forceSelection` | `boolean` | `false` / `true` |
275
711
  | `panelWidth` / `scrollHeight` | `string` | `null` / `'250px'` |
276
- | `emptyMessage` / `loadingMessage` | `string` | mensagens padrão em inglês |
712
+ | `emptyMessage` / `loadingMessage` | `string` | `'Nenhum resultado encontrado.'` / `'Carregando...'` |
277
713
  | `closeOnSelect` | `boolean` | `false` |
278
714
 
279
715
  Eventos: `update:modelValue`, `search`, `loadMore`, `clear`, `select` e
280
716
  `change`.
281
717
 
282
- Slots: `icon`, `option`, `selected-item`, `chip`, `empty`, `option-group` e
283
- `footer`. O slot `icon` recebe `loading`; sem ele, o componente mostra uma lupa
284
- ou um indicador de carregamento. Quando um slot não é informado, o componente usa sua apresentação padrão.
285
- O texto das opções é limitado visualmente pela largura disponível do painel,
286
- sem corte por quantidade fixa de caracteres; o tooltip padrão exibe o valor
287
- completo quando `showTooltip` está ativo.
718
+ Slots: `icon`, `option`, `selected-item`, `chip`, `empty`, `option-group` e
719
+ `footer`. O slot `icon` recebe `loading`; sem ele, o componente mostra uma lupa
720
+ ou um indicador de carregamento. Quando um slot não é informado, o componente usa sua apresentação padrão.
721
+ O texto das opções é limitado visualmente pela largura disponível do painel,
722
+ sem corte por quantidade fixa de caracteres; o tooltip padrão exibe o valor
723
+ completo quando `showTooltip` está ativo.
724
+
725
+ | Slot | Parâmetros recebidos | Uso |
726
+ |---|---|---|
727
+ | `icon` | `{ loading }` | Ícone à esquerda do campo. |
728
+ | `option` | Props nativas, mais `{ option, label, prefix, query }` | Linha de uma opção. |
729
+ | `selected-item` | Props nativas, incluindo `item` | Valor único escolhido. |
730
+ | `chip` | Props nativas, incluindo `value` e `removeCallback` | Tag no modo múltiplo. |
731
+ | `empty` | — | Conteúdo quando não há opções; também substitui o loading padrão. |
732
+ | `option-group` | Props nativas, incluindo `option` | Cabeçalho de um grupo. |
733
+ | `footer` | Props nativas do AutoComplete | Rodapé do painel. |
734
+
735
+ `search` recebe `{ originalEvent, query }` a cada consulta; atualize `options`
736
+ com os resultados. `loadMore` repassa o evento do virtual scroller para busca
737
+ paginar. Pela ref, `hideDropdown()` fecha o painel.
738
+
739
+ ## TopTabs
288
740
 
289
- ## Desenvolvimento da biblioteca
741
+ Navegação em abas baseada em `Tabs`, `TabList`, `Tab`, `TabPanels` e `TabPanel`
742
+ do PrimeVue, com estrutura simplificada e estilos dos temas claro e escuro do
743
+ Design System.
744
+
745
+ ```vue
746
+ <script setup lang="ts">
747
+ import { ref } from 'vue'
748
+ import {
749
+ TopTabs,
750
+ type TopTabItem,
751
+ type TopTabValue,
752
+ } from '@topjoao/top-design-system'
753
+
754
+ const activeTab = ref<TopTabValue>('dados')
755
+ const tabs: TopTabItem[] = [
756
+ { value: 'dados', label: 'Dados' },
757
+ { value: 'documentos', label: 'Documentos' },
758
+ { value: 'auditoria', label: 'Auditoria', disabled: true },
759
+ ]
760
+ </script>
761
+
762
+ <template>
763
+ <TopTabs v-model="activeTab" :tabs="tabs">
764
+ <template #dados>
765
+ Dados gerais do processo
766
+ </template>
767
+
768
+ <template #documentos>
769
+ Documentos anexados
770
+ </template>
771
+
772
+ <template #auditoria>
773
+ Histórico de auditoria
774
+ </template>
775
+ </TopTabs>
776
+ </template>
777
+ ```
778
+
779
+ | Prop | Tipo | Padrão |
780
+ |---|---|---|
781
+ | `modelValue` | `string \| number` | obrigatório |
782
+ | `tabs` | `TopTabItem[]` | obrigatório |
783
+ | `lazy` | `boolean` | `false` |
784
+ | `scrollable` | `boolean` | `true` |
785
+
786
+ Cada `TopTabItem` possui `value`, `label` e `disabled?`. O `value` deve ser
787
+ único e identifica tanto a seleção quanto o slot do painel; por exemplo,
788
+ `value: 'documentos'` utiliza `#documentos`. Cada slot recebe `tab` e `active`.
789
+ O componente emite somente `update:modelValue`. Atributos adicionais, incluindo
790
+ as opções de passthrough do PrimeVue, são repassados ao componente `Tabs`.
791
+
792
+ ## TopToast
793
+
794
+ `TopToast` personaliza o renderizador do serviço `Toast` do PrimeVue. Registre
795
+ `ToastService` uma vez, renderize um único `TopToast` perto da raiz e dispare
796
+ mensagens com `useToast`. Ele não recebe uma lista de mensagens por prop.
797
+
798
+ ```ts
799
+ // main.ts
800
+ import PrimeVue from 'primevue/config'
801
+ import ToastService from 'primevue/toastservice'
802
+ import { createApp } from 'vue'
803
+ import App from './App.vue'
804
+
805
+ createApp(App).use(PrimeVue).use(ToastService).mount('#app')
806
+ ```
807
+
808
+ ```vue
809
+ <script setup lang="ts">
810
+ import { useToast } from 'primevue/usetoast'
811
+ import { TopButton, TopToast } from '@topjoao/top-design-system'
812
+
813
+ const toast = useToast()
814
+ const abrirCadastro = () => { /* navegue para o cadastro */ }
815
+
816
+ function salvar() {
817
+ toast.add({
818
+ severity: 'success', summary: 'Cadastro concluído', detail: 'As alterações foram salvas.', life: 5000,
819
+ data: {
820
+ footer: 'Protocolo: CAD-2026-0042',
821
+ action: { label: 'Ver cadastro', icon: 'pi pi-arrow-right', onClick: () => abrirCadastro() },
822
+ },
823
+ })
824
+ }
825
+ </script>
826
+
827
+ <template>
828
+ <TopToast :base-z-index="30000" />
829
+ <TopButton label="Salvar" @click="salvar" />
830
+ </template>
831
+ ```
832
+
833
+ | Prop | Tipo | Padrão | Finalidade |
834
+ |---|---|---|---|
835
+ | `baseZIndex` | `number` | `30000` | Camada base das notificações. |
836
+
837
+ Além das opções usuais de `ToastMessageOptions` do PrimeVue (`severity`,
838
+ `summary`, `detail`, `life`, `closable` etc.), `data` aceita `TopToastData`:
839
+
840
+ | Campo | Tipo | Efeito |
841
+ |---|---|---|
842
+ | `data.footer` | `string \| number` | Texto discreto abaixo da mensagem. |
843
+ | `data.action.label` | `string` | Rótulo da ação. |
844
+ | `data.action.icon` | `string` opcional | Classe de ícone PrimeIcons. |
845
+ | `data.action.onClick` | `() => void` opcional | Função chamada ao clicar na ação. |
846
+
847
+ | Slot | Parâmetros recebidos | Uso |
848
+ |---|---|---|
849
+ | `action` | `{ action, message, run }` | Substitui o botão; execute `run()` para chamar `action.onClick`. |
850
+ | `footer` | `{ message, footer }` | Substitui o rodapé. |
851
+
852
+ ## Tipos e exports públicos
853
+
854
+ O pacote raiz exporta todos os componentes, `TopSolutionsPreset`, `colors` e
855
+ os tipos abaixo. Use `import type` para não acrescentar código ao bundle.
856
+
857
+ ```ts
858
+ import {
859
+ colors,
860
+ TopSolutionsPreset,
861
+ type TopButtonSeverity,
862
+ type TopDatePickerSelectionMode,
863
+ type TopDatePickerValue,
864
+ type TopFileUploadError,
865
+ type TopFileUploadValue,
866
+ type TopNavItem,
867
+ type TopNavSection,
868
+ type TopTabItem,
869
+ type TopToastData,
870
+ } from '@topjoao/top-design-system'
871
+ ```
872
+
873
+ `TopNavItem` descreve um nó (`id`, `label`, `to?`, `icon?`, `disabled?`,
874
+ `children?`, `data?`); `TopNavSection` é o mesmo contrato com `children`
875
+ obrigatório. `TopNavAction`, `TopNavUser`, `TopNavClient` e
876
+ `TopNavUserMenuItem` correspondem às props de mesmo nome. A aparência da barra
877
+ é definida por `TopNavBarAppearance` e seus subtipos exportados:
878
+ `TopNavBarColors`, `TopNavBarSurfaces`, `TopNavBarBorders`, `TopNavBarShape` e
879
+ `TopNavBarFocus`.
880
+
881
+ `colors` expõe as escalas imutáveis `primary`, `secondary`, `success`, `warn` e
882
+ `danger`, cada uma com tons de `50` a `950`.
883
+
884
+ ## Desenvolvimento da biblioteca
290
885
 
291
886
  ```bash
292
887
  npm install
@@ -311,10 +906,10 @@ publicado.
311
906
  npm run storybook
312
907
  ```
313
908
 
314
- Abra `http://localhost:6006` para acessar as histórias de `TopButton`,
315
- `TopConfirmDialog`, `TopInputText` e `TopSelect`. Use o botão de contraste na
316
- barra superior para alternar o preview entre tema claro e escuro. Para gerar a
317
- versão estática da documentação, execute:
909
+ Abra `http://localhost:6006` para acessar as histórias de `TopButton`,
910
+ `TopConfirmDialog`, `TopDatePicker`, `TopInputText`, `TopSelect` e `TopTabs`. Use o botão de
911
+ contraste na barra superior para alternar o preview entre tema claro e escuro.
912
+ Para gerar a versão estática da documentação, execute:
318
913
 
319
914
  ```bash
320
915
  npm run build-storybook
@@ -326,10 +921,14 @@ Para gerar um pacote local instalável:
326
921
  npm pack
327
922
  ```
328
923
 
329
- Para publicar uma nova versão beta, primeiro altere a versão; versões
330
- publicadas no npm não podem ser sobrescritas.
331
-
332
- ```bash
333
- npm version prerelease --preid=beta --no-git-tag-version
334
- npm publish --access public --tag beta
335
- ```
924
+ Para publicar uma nova versão estável, primeiro incremente a versão seguindo o
925
+ versionamento semântico; versões já publicadas no npm não podem ser
926
+ sobrescritas. Use `patch`, `minor` ou `major` conforme o impacto da mudança.
927
+
928
+ ```bash
929
+ npm version patch --no-git-tag-version
930
+ npm publish --access public --tag latest
931
+ ```
932
+
933
+ A tag `latest` é a padrão do npm. Assim, consumidores instalam a versão estável
934
+ simplesmente com `npm install @topjoao/top-design-system`.