@topjoao/top-design-system
v0.1.4
Published
Design system e biblioteca de componentes Vue da TopSolutions
Readme
TopSolutions Design System
Biblioteca de componentes Vue 3, estilos e tokens visuais compartilhados pela TopSolutions.
Índice
- Instalação e configuração
- Tema e tokens
- Integração com Nuxt e Vue 3
- Componentes
- Tipos e exports públicos
- Desenvolvimento da biblioteca
Componentes públicos
| Componente | Finalidade | v-model |
|---|---|---|
| TopButton | Botão com estilos e estados da marca. | — |
| TopConfirmDialog | Confirmação modal controlada pela aplicação. | boolean |
| TopDatePicker | Campo de data, múltiplas datas ou intervalo. | Date/array de Date |
| TopFileUpload | Escolha, validação e prévia de arquivos; não faz upload. | File/File[]/null |
| TopInputText | Campo textual com label, erro e acessibilidade. | string |
| TopInputNumber | Campo numérico localizado, com moeda, limites e incrementos. | number \| null |
| TopNavBar | Navegação responsiva, breadcrumbs, favoritos e Drawer. | searchValue e mobileOpen opcionais |
| TopSelect | Autocomplete pesquisável com paginação virtual. | opção/opções |
| TopTabs | Abas com painéis nomeados. | string \| number |
| TopToast | Renderizador de notificações do serviço Toast do PrimeVue. | — |
Instalação
npm install @topjoao/top-design-systemA aplicação consumidora deve possuir Vue 3, PrimeVue, PrimeIcons e
@primeuix/themes em versões compatíveis com as peerDependencies do pacote.
Tema TopSolutions
A biblioteca exporta TopSolutionsPreset, o preset PrimeVue baseado na paleta
oficial da TopSolutions. Configure-o uma vez na aplicação consumidora:
import PrimeVue from 'primevue/config'
import { TopSolutionsPreset } from '@topjoao/top-design-system'
app.use(PrimeVue, {
theme: {
preset: TopSolutionsPreset,
options: {
darkModeSelector: '.dark',
cssLayer: {
name: 'primevue',
order: 'primevue',
},
},
},
})A camada primevue mantém os estilos estruturais do tema abaixo dos padrões do
Design System e das classes utilitárias da aplicação. Com isso, propriedades
como headerClass e contentClass conseguem sobrescrever os valores padrão sem
!important.
Para ativar o modo escuro, adicione ou remova .dark uma única vez no elemento
<html> da aplicação. Os componentes usam tokens semânticos de superfície, texto e
borda fornecidos por style.css; não é necessário passar classes dark: em cada
uso. A aplicação pode substituir esses tokens após importar o CSS da biblioteca.
Os tokens públicos de campos são --top-field-background,
--top-field-background-readonly, --top-field-background-disabled,
--top-field-text, --top-field-label, --top-field-placeholder,
--top-field-border, --top-field-border-hover e --top-field-icon.
O preset é independente dos ajustes de layout próprios do TopLicita; ele contém apenas tokens semânticos compartilháveis, como cores primárias, neutras, sucesso, alerta e erro.
style.css contém somente os estilos dos componentes públicos do Design System.
Regras que
alcançam o PrimeVue são encapsuladas pelas classes-raiz desses componentes;
componentes PrimeVue usados diretamente pela aplicação consumidora não são
sobrescritos pela biblioteca.
Uso com Nuxt
Adicione o módulo uma única vez ao nuxt.config.ts:
export default defineNuxtConfig({
modules: [
// outros módulos...
'@topjoao/top-design-system/nuxt',
],
})Depois de alterar a configuração, reinicie o servidor de desenvolvimento ou regenere os arquivos do Nuxt:
npm run postinstallO módulo registra os componentes, gera suas tipagens e inclui o CSS da biblioteca. Não é necessário importar o componente manualmente:
<template>
<TopButton label="Salvar" icon="pi pi-save" />
</template>Uso com Vue 3
Importação por componente
<script setup lang="ts">
import { TopButton } from '@topjoao/top-design-system'
import '@topjoao/top-design-system/style.css'
</script>
<template>
<TopButton label="Salvar" icon="pi pi-save" />
</template>Registro global
Na inicialização da aplicação:
import { createApp } from 'vue'
import { TopSolutionsDesignSystem } from '@topjoao/top-design-system/plugin'
import '@topjoao/top-design-system/style.css'
import App from './App.vue'
createApp(App).use(TopSolutionsDesignSystem).mount('#app')Após o registro, os componentes podem ser utilizados sem importação manual. A
entrada /plugin também fornece as declarações globais usadas pela IDE.
TopNavBar
Barra de navegação responsiva inspirada no AppSidebar do TopLicita. O componente fornece apenas layout e interação: a aplicação consumidora continua responsável por rotas, permissões, sessão, cliente/órgão, busca remota, favoritos, Aia, suporte e integrações. Nenhuma dessas ações é executada pela biblioteca; todas são comunicadas por eventos.
<script setup lang="ts">
import { ref } from 'vue'
import {
TopNavBar,
type TopNavAction,
type TopNavItem,
type TopNavSection,
} from '@topjoao/top-design-system'
const clienteAtual = ref('prefeitura-a')
const favorito = ref(false)
const secoes: TopNavSection[] = [
{
id: 'planejamento',
label: 'Planejamento',
icon: 'pi pi-book',
children: [
{
id: 'programacao',
label: 'Programação',
children: [
{ id: 'calendario', label: 'Calendário', to: '/programacao/calendario' },
],
},
],
},
]
function navegar(item: TopNavItem) {
if (item.to) router.push(item.to)
}
function executarIntegracao(action: TopNavAction) {
// Abra a integração identificada por action.id.
}
</script>
<template>
<TopNavBar
:sections="secoes"
active-item-id="calendario"
searchable
show-aia
show-support
show-favorite-toggle
:favorite-active="favorito"
:actions="[
{ id: 'integracoes', label: 'Integrações', icon: 'pi pi-th-large' },
]"
:user="{ name: 'João Silva', subtitle: 'Administrador', initials: 'JS' }"
:user-menu-items="[
{ id: 'perfil', label: 'Meu perfil', icon: 'pi pi-user' },
{ id: 'sair', label: 'Sair', icon: 'pi pi-sign-out' },
]"
@navigate="navegar"
@search="consultarMenusPermitidos"
@toggle-aia="alternarAia"
@open-support="abrirCentralSuporte"
@toggle-favorite="favorito = !favorito"
@action="executarIntegracao"
@user-action="executarAcaoDaSessao"
>
<template #brand="{ compact }">
<img src="/logo.svg" alt="Minha organização">
<span v-if="!compact">Sistema de Contratações</span>
</template>
<template #context="{ compact }">
<select v-model="clienteAtual" aria-label="Cliente atual">
<option value="prefeitura-a">Prefeitura A</option>
<option value="prefeitura-b">Prefeitura B</option>
</select>
<span v-if="!compact">Poder Executivo</span>
</template>
</TopNavBar>
</template>Menu e breadcrumbs
sections aceita uma árvore de profundidade arbitrária. Cada nó usa
TopNavItem (id, label, to?, icon?, disabled?, children?, data?).
Seções sem filhos são removidas e um children: [] nunca cria um painel vazio.
No desktop, a primeira coluna do painel mestre–detalhe tem 16rem; os itens de
cada nível são repartidos em colunas de 18rem, no máximo dez por coluna.
Subníveis aparecem sempre à direita do nível de origem.
Breadcrumbs estão habilitados por padrão e começam por Início / Navegação.
Há duas formas de fornecer a trilha:
- informe
breadcrumbscom itensTopNavBreadcrumb;sectionIdliga o item a uma seção emenuIdliga a qualquer nó com filhos; - omita
breadcrumbse informeactiveItemId; a trilha é derivada da árvore.
Por exemplo, calendario dentro de Programação em Planejamento gera
Início / Navegação / Planejamento / Programação / Calendário. A rota ativa
não recebe fundo permanente nos menus desktop; somente hover e o ramo que está
sendo explorado recebem destaque.
Props
| Prop | Tipo / padrão | Finalidade |
|---|---|---|
| sections | TopNavSection[] / [] | Árvore de navegação já filtrada pela aplicação. |
| breadcrumbs | TopNavBreadcrumb[] / [] | Trilha explícita; vazia permite derivação por activeItemId. |
| homeItem | TopNavItem / Início | Item inicial emitido ao clicar em Início. |
| navigationLabel | string / Navegação | Rótulo do menu mestre. |
| activeItemId | string | Nó atual, usado no breadcrumb e no Drawer. |
| searchable | boolean / false | Habilita busca desktop e busca própria do Drawer. |
| searchValue | string | Valor opcionalmente controlado com v-model:search-value. |
| searchResults | TopNavItem[] | Resultados controlados; sem a prop, a árvore é filtrada localmente. |
| searchPlaceholder | string | Placeholder das duas buscas. |
| showAia, showSupport | boolean / false | Exibem as ações opcionais. |
| aiaActive | boolean / false | Estado visual do botão Aia. |
| aiaLabel, supportLabel | string | Textos acessíveis e rótulos do Drawer. |
| actions | TopNavAction[] / [] | Ações genéricas, como integrações, emitidas por action. |
| client | TopNavClient | Organização, cliente ou escopo ativo exibido no cabeçalho e como contexto do usuário no rodapé do Drawer; não cria ação nem seletor. |
| user | TopNavUser | Dados exclusivamente visuais do usuário. |
| userMenuItems | TopNavUserMenuItem[] | Opções emitidas por user-action; suporta separadores. |
| favoriteItems | TopNavItem[] / [] | Favoritos fornecidos pelo pai para dropdown e Drawer. |
| showFavoriteToggle | boolean / false | Exibe a estrela da página atual. |
| favoriteActive | boolean / false | Estado visual da estrela atual. |
| mobileOpen | boolean | Controle opcional com v-model:mobile-open. |
| appearance | TopNavBarAppearance | Tokens visuais locais descritos abaixo. |
| spacing | TopNavBarSpacing | Espaçamento do conteúdo no cabeçalho e breadcrumbs; preserva os padrões atuais quando omitido. |
Eventos
| Evento | Payload | Quando ocorre |
|---|---|---|
| navigate | TopNavItem | Início, menu, resultado ou favorito é selecionado. |
| search | string | A consulta muda no desktop ou no Drawer. |
| update:searchValue | string | Atualização de v-model:search-value. |
| update:mobileOpen | boolean | Atualização de v-model:mobile-open. |
| toggle-aia | — | A ação Aia é acionada. |
| open-support | — | A ação de suporte é acionada. |
| toggle-favorite | — | A estrela da página atual é acionada. |
| action | TopNavAction | Uma ação genérica/integração é acionada. |
| user-action | TopNavUserMenuItem | Uma opção de usuário é selecionada. |
| user-click | TopNavUser \| undefined | A área de usuário sem menu é acionada. |
Ctrl+K e Cmd+K abrem/focam a busca. Em telas menores que 1024px, o
atalho abre primeiro o Drawer e foca a busca móvel. Escape fecha os painéis.
Slots
| Slot | Uso |
|---|---|
| brand | Marca; recebe { compact }. |
| 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. |
| context-compact | Variante explícita usada no último estágio de overflow. |
| actions | Conteúdo adicional do cabeçalho; recebe { compact, close }. |
| aia-icon, support-icon | Ícones customizados das ações quadradas. |
| user | Conteúdo do gatilho de usuário; recebe { user, compact }. |
| user-menu | Painel de usuário; recebe { items, select }. |
| search-results | Resultados customizados; recebe { items, select }. |
| breadcrumb-actions | Ações adicionais no fim da segunda faixa. |
| drawer-header | Cabeçalho inteiro; recebe { close }. |
| drawer-brand | Marca do Drawer; por padrão reutiliza brand. |
| drawer-search | Busca inteira; recebe { query, update, clear }. |
| drawer-actions | Ações; recebe { actions, select, close }. |
| drawer-before-menu, drawer-after-menu | Conteúdo antes/depois do trilho rolável. |
| drawer-menu | Substitui a árvore; recebe { sections, select, close }. |
| drawer-user | Rodapé de usuário; recebe { user, client, open, toggle }. |
| drawer-footer | Conteúdo final adicional; recebe { close }. |
Faixa de favoritos
No desktop, favoriteItems aparece primeiro em uma faixa horizontal abaixo dos
breadcrumbs. A biblioteca mede uma cópia invisível da faixa com
ResizeObserver: se a largura real não couber, ela é substituída pelo dropdown
Favoritos. O mesmo dropdown é usado depois que a página passa de 120px de
scroll e a faixa retorna apenas ao chegar a 24px ou menos. Os valores evitam
oscilações perto do topo e reproduzem o comportamento do AppSidebar.
Aparência e responsividade
Use spacing para criar uma zona segura horizontal sem limitar o fundo das
duas faixas. Os valores são aplicados no elemento raiz como
--top-nav-header-padding e --top-nav-breadcrumb-padding; o cabeçalho e o
breadcrumb continuam ocupando toda a largura da viewport.
Em telas móveis, esses valores também são usados, salvo se a aplicação já
definir as variáveis específicas --top-nav-header-padding-mobile ou
--top-nav-breadcrumb-padding-mobile.
<TopNavBar
:spacing="{
headerPadding: '0.625rem max(2rem, calc((100vw - 80rem) / 2 + 2rem))',
breadcrumbPadding: '0.25rem max(2rem, calc((100vw - 80rem) / 2 + 2rem))',
}"
/>appearance é propositalmente pequeno e semântico. Use colors para
navigation, text, mutedText, accent, border, menuText,
mobileActiveText e favoritesText; surfaces para breadcrumb, drawer,
drawerFooter, menu, menuHover, menuExplored, mobileActive,
favorites e currentPage; borders para menu (a borda externa de 6px),
menuOutline, mobileDivider, favorites e currentPage; shape para
radius, drawerRadius, shadow e favoritesShadow; e focus para
onDark e onLight.
const appearance = {
colors: { navigation: '#09090b', text: '#f4f4f5', border: 'rgb(255 255 255 / 14%)' },
surfaces: { drawer: '#09090b', menu: '#18181b', menuHover: '#27272a' },
shape: { radius: '8px', shadow: '0 12px 30px rgb(0 0 0 / 25%)' },
}A biblioteca não fixa tipografia inline. Por padrão, família, tamanho, peso e
altura de linha são herdados da aplicação consumidora. Além disso, appearance
só cria variáveis inline para propriedades que foram realmente informadas; os
valores fiéis ao AppSidebar existem apenas como fallbacks no CSS. Assim, um
tema global pode controlar o componente sem precisar usar !important:
:root {
--top-nav-font-family: var(--app-font-family);
--top-nav-font-size: var(--app-font-size);
--top-nav-strong-font-weight: 600;
--top-nav-action-size: 2.5rem;
--top-nav-header-padding: 0.625rem 1.5rem;
--top-nav-breadcrumb-padding: 0.25rem 1.5rem;
--top-nav-menu-item-padding: 0.5rem 0.75rem;
--top-nav-section-width: 16rem;
--top-nav-column-width: 18rem;
--top-nav-mobile-active-bg: #fff;
--top-nav-mobile-active-fg: #025a84;
--top-nav-mobile-divider: rgb(255 255 255 / 20%);
--top-nav-mobile-item-gap: 0.25rem;
--top-nav-mobile-submenu-padding: 0.5rem 0 0.375rem 0.35rem;
--top-nav-favorites-bg: #f8fafc;
--top-nav-favorites-fg: #0f172a;
--top-nav-favorites-border: #e2e8f0;
--top-nav-favorites-shadow: 0 1px 2px rgb(15 23 42 / 6%);
--top-nav-focus-ring: #dbeafe;
--top-nav-focus-ring-light: #1d4ed8;
--top-nav-current-bg: rgb(255 255 255 / 10%);
--top-nav-current-border: rgb(255 255 255 / 15%);
--top-nav-menu-scrollbar-thumb: #94a3b8;
}As variáveis globais também alcançam o Drawer teleportado. Use-as para ajustes
estruturais — tipografia, larguras, alturas, padding e espaçamentos do menu —
em vez de props no componente. Um valor passado por appearance tem precedência
local e não altera os demais tokens do tema.
O breakpoint móvel é 1024px. No desktop, um ResizeObserver reaplica a mesma
sequência progressiva do AppSidebar: ações colapsam abaixo de 1440px, busca
vira ícone abaixo de 1320px, nome do usuário some abaixo de 1180px e, se o
conteúdo ainda transbordar, o contexto recebe compact: true. O último estágio
também limita o contêiner do contexto a 3.25rem; use context-compact quando
quiser controlar exatamente o que permanece visível.
Abaixo de 1024px, a navegação desktop desaparece e o Drawer do PrimeVue assume.
Ele mantém cabeçalho, busca, ações, trilho translúcido rolável, árvore recursiva,
favoritos e usuário. No rodapé, a foto e o nome do usuário formam a primeira
linha; o cliente atual aparece abaixo e o subtítulo do cliente recebe uma
etiqueta quando houver. A transição menu-expand existe somente dentro do Drawer;
os dropdowns desktop abrem sem animação e suas áreas de hover incluem o espaço
entre gatilho e painel. Nessa largura, os breadcrumbs deixam de rolar
horizontalmente: eles quebram em linhas e o bloco da página atual ocupa sua
própria linha para permanecer legível.
TopButton
Exemplo
<template>
<TopButton label="Salvar" icon="pi pi-save" @click="salvar" />
<TopButton label="Cancelar" secondary />
<TopButton label="Excluir" severity="danger" />
<TopButton label="Consultar" outlined>
<template #icon>
<i class="pi pi-search" />
</template>
</TopButton>
</template>| Prop | Tipo | Padrão |
|---|---|---|
| tooltip | string | '' |
| tooltipClass | string | 'text-xs' |
| label | string | '' |
| icon | string | '' |
| loading | boolean | false |
| class | string | '' |
| outlined | boolean | false |
| severity | 'primary' \| 'secondary' \| 'success' \| 'warn' \| 'danger' | 'primary' |
| secondary | boolean | false |
| disabled | boolean | false |
| unstyled | boolean | false |
| type | 'button' \| 'submit' \| 'reset' | 'button' |
O componente emite click sem payload. O slot nomeado icon não recebe
parâmetros e substitui a prop icon — útil para SVGs, HugeiconsIcon ou um
ícone com estado próprio. secondary permanece como atalho compatível para
severity="secondary" e tem precedência sobre severity.
O botão clicável interno expõe a classe estável top-button__control, que pode
ser usada para customizar globalmente qualquer característica dos TopButton,
como tipografia, cores, espaçamento, dimensões e bordas. Defina a regra no CSS
global da aplicação, carregado depois do CSS do Design System:
.top-button__control {
font-size: 0.875rem;
font-weight: 600;
min-height: 44px;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
}Em um projeto com Tailwind, a mesma regra pode usar @apply:
@layer components {
.top-button__control {
@apply min-h-11 rounded-lg px-4 py-3 text-sm font-semibold;
}
}A classe top-button__control atua no botão clicável, enquanto top-button
atua no elemento externo que o envolve. A prop class continua disponível para
customizar somente uma ocorrência. Algumas regras internas que usam
!important podem exigir maior especificidade ou !important para serem
sobrescritas.
TopConfirmDialog
Diálogo de confirmação baseado no Dialog do PrimeVue e nos botões do Design
System. A visibilidade é controlada por v-model; a aplicação consumidora
decide o que executar e quando encerrar após a confirmação.
<script setup lang="ts">
import { ref } from 'vue'
import { TopConfirmDialog } from '@topjoao/top-design-system'
const showConfirm = ref(false)
function excluirRegistro() {
// Execute a ação e feche o diálogo quando apropriado.
showConfirm.value = false
}
</script>
<template>
<TopConfirmDialog
v-model="showConfirm"
title="Confirmar exclusão"
message="Deseja realmente excluir este registro?"
confirm-text="Excluir"
cancel-text="Cancelar"
@confirm="excluirRegistro"
/>
</template>| Prop | Tipo | Padrão |
|---|---|---|
| modelValue | boolean | false |
| title | string | 'Confirmar' |
| message | string | '' |
| confirmText / cancelText | string | 'Confirmar' / 'Cancelar' |
| loading | boolean | false |
| loadingText | string | 'Processando...' |
| severity | 'primary' \| 'danger' | 'danger' |
| icon | string | 'pi pi-exclamation-triangle' |
Eventos: update:modelValue, confirm, cancel e close. O evento confirm
não fecha automaticamente o diálogo, permitindo que a aplicação aguarde uma
operação assíncrona. Os slots message e default permitem substituir a mensagem
textual.
TopDatePicker
Seletor de datas baseado no DatePicker do PrimeVue. O valor permanece como
Date (ou arrays de Date nos modos multiple e range); dateFormat altera
somente a apresentação no campo e não converte o v-model para texto.
No modo padrão (single com dd/mm/yy), a digitação recebe automaticamente a
máscara brasileira dd/mm/aaaa.
<script setup lang="ts">
import { ref } from 'vue'
import { TopDatePicker } from '@topjoao/top-design-system'
const dataNascimento = ref<Date | null>(null)
</script>
<template>
<TopDatePicker
v-model="dataNascimento"
label="Data de nascimento"
placeholder="Selecione a data"
:max-date="new Date()"
required
error="Informe uma data válida."
@date-select="validarData"
/>
</template>| Prop | Tipo | Padrão |
|---|---|---|
| modelValue | Date \| Date[] \| (Date \| null)[] \| null | null |
| label | string | '' |
| placeholder | string | 'dd/mm/aaaa' |
| required | boolean | false |
| error | string | '' |
| invalid | boolean | false |
| disabled | boolean | false |
| readonly | boolean | false |
| selectionMode | 'single' \| 'multiple' \| 'range' | 'single' |
| dateFormat | string | 'dd/mm/yy' |
| minDate / maxDate | Date | undefined |
| showIcon | boolean | true |
| iconDisplay | 'button' \| 'input' | 'input' |
| manualInput | boolean | true |
| showButtonBar | boolean | false |
| appendTo | 'body' \| 'self' \| HTMLElement | 'body' |
Eventos: update:modelValue, input, change, date-select, show, hide,
today-click, clear-click, month-change, year-change, focus, blur e
keydown. Os slots do DatePicker do PrimeVue são repassados pelo wrapper,
incluindo date, header, footer, buttonbar, inputicon, dropdownicon,
previcon e nexticon.
O slot date recebe as props de dia disponibilizadas pelo DatePicker; os
slots de ícone recebem as props correspondentes do PrimeVue. Os slots header,
footer e buttonbar permitem substituir essas regiões. Todos são apenas
repassados: o wrapper preserva as props e o comportamento do componente-base.
required adiciona o atributo nativo e o asterisco visual; a validação continua
sob responsabilidade da aplicação. error ativa o estado inválido, associa a
mensagem ao input com atributos ARIA e a exibe abaixo do campo. readonly
impede edição e seleção sem desabilitar o controle, enquanto disabled remove a
interação. No modo escuro, o campo usa os tokens públicos --top-field-*.
TopFileUpload
Seletor de arquivos baseado no FileUpload do PrimeVue. O componente valida e
apresenta os arquivos, mas não os envia: a aplicação consumidora controla o
upload por v-model e pelos eventos.
<script setup lang="ts">
import { ref } from 'vue'
import { TopFileUpload } from '@topjoao/top-design-system'
const anexos = ref<File[]>([])
function enviarArquivos(files: File[]) {
// Envie os arquivos usando o serviço da aplicação.
}
</script>
<template>
<TopFileUpload
v-model="anexos"
label="Anexos"
accept=".pdf,image/*"
multiple
:max-file-size="5 * 1024 * 1024"
:max-files="5"
required
@select="enviarArquivos"
/>
</template>| Prop | Tipo | Padrão |
|---|---|---|
| modelValue | File \| File[] \| null | null |
| label | string | '' |
| placeholder | string | 'Arraste e solte o arquivo aqui' |
| required | boolean | false |
| disabled | boolean | false |
| accept | string | '' |
| multiple | boolean | false |
| maxFileSize | number \| null (bytes) | null |
| maxFiles | number \| null | null |
| error | string | '' |
| selectLabel | string | 'Selecionar arquivo' |
| removeLabel | string | 'Remover' |
| loading | boolean | false |
Eventos: update:modelValue, select, change, remove, clear e error.
Erros de tipo, tamanho e quantidade possuem code, message e o file
relacionado. Os slots empty e preview permitem customizar a área vazia e a
pré-visualização. Os métodos choose() e clear() ficam disponíveis pela ref
do componente.
| Slot | Parâmetros recebidos | Uso |
|---|---|---|
| empty | { choose, disabled } | Substitui a área vazia. Chame choose() para abrir o seletor nativo. |
| preview | { file, index, url } | Substitui a miniatura. url é uma object URL apenas para imagens; nos demais casos, é ''. |
<TopFileUpload ref="upload" v-model="anexos" multiple>
<template #empty="{ choose, disabled }">
<button type="button" :disabled="disabled" @click="choose()">Anexar documentos</button>
</template>
<template #preview="{ file, url }">
<img v-if="url" :src="url" :alt="file.name">
<span v-else>{{ file.name }}</span>
</template>
</TopFileUpload>accept aceita extensões (.pdf), MIME types (application/pdf) e curingas
MIME (image/*), separados por vírgula. maxFileSize é contado em bytes. Em
modo simples, a última seleção válida substitui a anterior; com multiple,
arquivos válidos são acumulados sem duplicar nome, tipo e tamanho.
TopInputNumber
Campo numérico baseado no InputNumber do PrimeVue, com a mesma estrutura de
label, erro e acessibilidade dos demais campos. Por padrão usa pt-BR. No modo
decimal, o usuário informa explicitamente o separador decimal; no modo
currency, a máscara de centavos vem habilitada e transforma 12345 em
R$ 123,45. O v-model permanece sempre numérico (number) ou null quando
vazio.
<script setup lang="ts">
import { ref } from 'vue'
import { TopInputNumber } from '@topjoao/top-design-system'
const quantidade = ref<number | null>(null)
const valorUnitario = ref<number | null>(null)
</script>
<template>
<TopInputNumber
v-model="quantidade"
label="Quantidade"
:min="1"
:min-fraction-digits="2"
:max-fraction-digits="4"
required
/>
<TopInputNumber
v-model="valorUnitario"
label="Valor unitário"
mode="currency"
currency="BRL"
:min-fraction-digits="2"
:max-fraction-digits="2"
/>
</template>| Prop | Tipo | Padrão | Finalidade |
|---|---|---|---|
| modelValue | number \| null | null | Valor numérico controlado. |
| label / placeholder | string | '' / '' | Rótulo e texto auxiliar do campo. |
| required | boolean | false | Asterisco visual e atributo nativo obrigatório. |
| error | string | '' | Mensagem e estado inválido. |
| invalid | boolean | false | Força estado inválido sem mostrar mensagem. |
| disabled / readonly | boolean | false / false | Remove interação / preserva leitura sem desabilitar. |
| locale | string | 'pt-BR' | Locale para separadores e moeda. |
| mode | 'decimal' \| 'currency' | 'decimal' | Formatação decimal ou monetária. |
| currency / currencyDisplay | string / 'symbol' \| 'code' \| 'name' | undefined / 'symbol' | Código ISO 4217 e modo de exibição da moeda. |
| currencyInputMode | 'decimal' \| 'cents' | 'cents' | Controla a entrada monetária; decimal desliga a máscara mesmo quando cents é true. Não afeta mode="decimal". |
| cents | boolean | true | Habilita a máscara automática somente em mode="currency"; false usa o InputNumber padrão. |
| centsFractionDigits | number | undefined (efetivo: 2) | Primeira opção para definir as casas da máscara monetária. |
| currencyFractionDigits | number | undefined | Fallback de casas da máscara, usado depois de centsFractionDigits. |
| useGrouping / format | boolean | true / true | Separadores de milhar e formatação do valor. |
| minFractionDigits / maxFractionDigits | number | undefined | Precisão do PrimeVue. Sem maxFractionDigits, o modo decimal aceita até 20 casas; a máscara monetária usa esse valor como último fallback antes de 2. |
| min / max / step | number | undefined / undefined / 1 | Limites e passo dos botões/teclado. |
| showButtons / buttonLayout | boolean / 'stacked' \| 'horizontal' \| 'vertical' | false / 'stacked' | Exibe e organiza os controles de incremento. |
| allowEmpty / showClear / highlightOnFocus | boolean | true / false / false | Permite limpar, mostra ícone de limpeza e seleciona valor ao focar. |
Eventos: update:modelValue (number | null), input, focus e blur. No
fluxo padrão do InputNumber, o componente também repassa value-change.
Atributos adicionais, como name, autocomplete, aria-* e data-*, são
repassados ao controle interno.
Quando a máscara de centavos não está ativa, os slots do InputNumber também
são repassados: incrementbutton e
decrementbutton recebem { listeners }; incrementicon e decrementicon
substituem os ícones; clearicon recebe { clearCallback }.
Para reproduzir a digitação monetária do TopLicita, a máscara de centavos já
vem ativa em mode="currency". O usuário digita somente dígitos: 12345
resulta em R$ 123,45 e o v-model recebe 123.45. Para exigir a vírgula
decimal digitada pelo usuário, passe :cents="false" ou
currency-input-mode="decimal".
Como a máscara controla o texto enquanto o usuário digita, nesse modo os
botões incrementais, o ícone de limpeza e seus slots do InputNumber não são
renderizados.
<TopInputNumber
v-model="valorUnitario"
label="Valor unitário"
mode="currency"
currency="BRL"
/>TopInputText
Campo textual baseado no InputText do PrimeVue. O v-model é sempre
string, inclusive para códigos, documentos e identificadores compostos
somente por dígitos; por exemplo, "001234" preserva os zeros à esquerda.
<script setup lang="ts">
import { ref } from 'vue'
import { TopInputText } from '@topjoao/top-design-system'
const codigo = ref('001234')
</script>
<template>
<TopInputText
id="codigo"
v-model="codigo"
label="Código"
placeholder="Digite o código"
required
maxlength="10"
autocomplete="off"
/>
</template>| Prop | Tipo | Padrão |
|---|---|---|
| modelValue | string | '' |
| label | string | '' |
| placeholder | string | '' |
| required | boolean | false |
| error | string | '' |
| disabled | boolean | false |
| readonly | boolean | false |
Atributos e eventos nativos adicionais, como name, maxlength,
autocomplete, inputmode, pattern, aria-*, data-*, focus e blur,
são repassados ao elemento input interno.
TopSelect
Seletor pesquisável baseado no AutoComplete do PrimeVue. Ele é genérico: a
aplicação fornece os itens, executa a busca e decide qualquer apresentação de
domínio por slots.
<script setup lang="ts">
import { ref } from 'vue'
import { TopSelect } from '@topjoao/top-design-system'
const selectedCustomer = ref(null)
const customers = ref([])
function searchCustomers({ query }: { query: string }) {
// Atualize customers com o resultado da sua fonte de dados.
}
</script>
<template>
<TopSelect
v-model="selectedCustomer"
:options="customers"
option-label="name"
option-key="id"
option-prefix="code"
show-option-prefix
:loading="false"
@search="searchCustomers"
>
<template #icon="{ loading }">
<i :class="loading ? 'pi pi-spin pi-spinner' : 'pi pi-users'" />
</template>
<template #footer>
<button type="button">Criar cliente</button>
</template>
</TopSelect>
</template>| Prop | Tipo | Padrão |
|---|---|---|
| modelValue | SelectOption \| SelectOption[] \| null | null |
| options | array | [] |
| optionLabel | string \| function | 'label' |
| optionKey | string | 'id' |
| optionPrefix | string | '' |
| showOptionPrefix | boolean | false |
| showSelectedPrefix | boolean | false |
| loading / disabled / invalid | boolean | false |
| placeholder | string | 'Pesquisar...' |
| minQueryLength | number | 1 |
| multiple / forceSelection | boolean | false / true |
| panelWidth / scrollHeight | string | null / '250px' |
| emptyMessage / loadingMessage | string | 'Nenhum resultado encontrado.' / 'Carregando...' |
| closeOnSelect | boolean | false |
Eventos: update:modelValue, search, loadMore, clear, select e
change.
Slots: icon, option, selected-item, chip, empty, option-group e
footer. O slot icon recebe loading; sem ele, o componente mostra uma lupa
ou um indicador de carregamento. Quando um slot não é informado, o componente usa sua apresentação padrão.
O texto das opções é limitado visualmente pela largura disponível do painel,
sem corte por quantidade fixa de caracteres; o tooltip padrão exibe o valor
completo quando showTooltip está ativo.
| Slot | Parâmetros recebidos | Uso |
|---|---|---|
| icon | { loading } | Ícone à esquerda do campo. |
| option | Props nativas, mais { option, label, prefix, query } | Linha de uma opção. |
| selected-item | Props nativas, incluindo item | Valor único escolhido. |
| chip | Props nativas, incluindo value e removeCallback | Tag no modo múltiplo. |
| empty | — | Conteúdo quando não há opções; também substitui o loading padrão. |
| option-group | Props nativas, incluindo option | Cabeçalho de um grupo. |
| footer | Props nativas do AutoComplete | Rodapé do painel. |
search recebe { originalEvent, query } a cada consulta; atualize options
com os resultados. loadMore repassa o evento do virtual scroller para busca
paginar. Pela ref, hideDropdown() fecha o painel.
TopTabs
Navegação em abas baseada em Tabs, TabList, Tab, TabPanels e TabPanel
do PrimeVue, com estrutura simplificada e estilos dos temas claro e escuro do
Design System.
<script setup lang="ts">
import { ref } from 'vue'
import {
TopTabs,
type TopTabItem,
type TopTabValue,
} from '@topjoao/top-design-system'
const activeTab = ref<TopTabValue>('dados')
const tabs: TopTabItem[] = [
{ value: 'dados', label: 'Dados' },
{ value: 'documentos', label: 'Documentos' },
{ value: 'auditoria', label: 'Auditoria', disabled: true },
]
</script>
<template>
<TopTabs v-model="activeTab" :tabs="tabs">
<template #dados>
Dados gerais do processo
</template>
<template #documentos>
Documentos anexados
</template>
<template #auditoria>
Histórico de auditoria
</template>
</TopTabs>
</template>| Prop | Tipo | Padrão |
|---|---|---|
| modelValue | string \| number | obrigatório |
| tabs | TopTabItem[] | obrigatório |
| lazy | boolean | false |
| scrollable | boolean | true |
Cada TopTabItem possui value, label e disabled?. O value deve ser
único e identifica tanto a seleção quanto o slot do painel; por exemplo,
value: 'documentos' utiliza #documentos. Cada slot recebe tab e active.
O componente emite somente update:modelValue. Atributos adicionais, incluindo
as opções de passthrough do PrimeVue, são repassados ao componente Tabs.
TopToast
TopToast personaliza o renderizador do serviço Toast do PrimeVue. Registre
ToastService uma vez, renderize um único TopToast perto da raiz e dispare
mensagens com useToast. Ele não recebe uma lista de mensagens por prop.
// main.ts
import PrimeVue from 'primevue/config'
import ToastService from 'primevue/toastservice'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).use(PrimeVue).use(ToastService).mount('#app')<script setup lang="ts">
import { useToast } from 'primevue/usetoast'
import { TopButton, TopToast } from '@topjoao/top-design-system'
const toast = useToast()
const abrirCadastro = () => { /* navegue para o cadastro */ }
function salvar() {
toast.add({
severity: 'success', summary: 'Cadastro concluído', detail: 'As alterações foram salvas.', life: 5000,
data: {
footer: 'Protocolo: CAD-2026-0042',
action: { label: 'Ver cadastro', icon: 'pi pi-arrow-right', onClick: () => abrirCadastro() },
},
})
}
</script>
<template>
<TopToast :base-z-index="30000" />
<TopButton label="Salvar" @click="salvar" />
</template>| Prop | Tipo | Padrão | Finalidade |
|---|---|---|---|
| baseZIndex | number | 30000 | Camada base das notificações. |
Além das opções usuais de ToastMessageOptions do PrimeVue (severity,
summary, detail, life, closable etc.), data aceita TopToastData:
| Campo | Tipo | Efeito |
|---|---|---|
| data.footer | string \| number | Texto discreto abaixo da mensagem. |
| data.action.label | string | Rótulo da ação. |
| data.action.icon | string opcional | Classe de ícone PrimeIcons. |
| data.action.onClick | () => void opcional | Função chamada ao clicar na ação. |
| Slot | Parâmetros recebidos | Uso |
|---|---|---|
| action | { action, message, run } | Substitui o botão; execute run() para chamar action.onClick. |
| footer | { message, footer } | Substitui o rodapé. |
Tipos e exports públicos
O pacote raiz exporta todos os componentes, TopSolutionsPreset, colors e
os tipos abaixo. Use import type para não acrescentar código ao bundle.
import {
colors,
TopSolutionsPreset,
type TopButtonSeverity,
type TopDatePickerSelectionMode,
type TopDatePickerValue,
type TopFileUploadError,
type TopFileUploadValue,
type TopInputNumberValue,
type TopInputNumberMode,
type TopInputNumberButtonLayout,
type TopInputNumberCurrencyInputMode,
type TopNavItem,
type TopNavSection,
type TopTabItem,
type TopToastData,
} from '@topjoao/top-design-system'TopNavItem descreve um nó (id, label, to?, icon?, disabled?,
children?, data?); TopNavSection é o mesmo contrato com children
obrigatório. TopNavAction, TopNavUser, TopNavClient e
TopNavUserMenuItem correspondem às props de mesmo nome. A aparência da barra
é definida por TopNavBarAppearance e seus subtipos exportados:
TopNavBarColors, TopNavBarSurfaces, TopNavBarBorders, TopNavBarShape e
TopNavBarFocus. TopNavBarSpacing tipa as propriedades opcionais
headerPadding e breadcrumbPadding da prop spacing.
TopInputNumberValue, TopInputNumberMode, TopInputNumberButtonLayout e
TopInputNumberCurrencyInputMode
tipam, respectivamente, o valor, o modo de formatação, o layout dos botões e o
modo de entrada monetária do campo numérico. colors expõe as escalas
imutáveis primary, secondary, success, warn e
danger, cada uma com tons de 50 a 950.
Desenvolvimento da biblioteca
npm install
npm run checkO comando check executa verificação de tipos, testes e build.
Para inspecionar o conteúdo que seria publicado:
npm pack --dry-runPlayground visual
O Storybook permite testar os componentes isoladamente e consultar seus exemplos. Ele é uma dependência de desenvolvimento e não é incluído no pacote publicado.
npm run storybookAbra http://localhost:6006 para acessar as histórias de TopButton,
TopConfirmDialog, TopDatePicker, TopInputText, TopInputNumber, TopSelect e TopTabs. Use o botão de
contraste na barra superior para alternar o preview entre tema claro e escuro.
Para gerar a versão estática da documentação, execute:
npm run build-storybookPara gerar um pacote local instalável:
npm packPara publicar uma nova versão estável, primeiro incremente a versão seguindo o
versionamento semântico; versões já publicadas no npm não podem ser
sobrescritas. Use patch, minor ou major conforme o impacto da mudança.
npm version patch --no-git-tag-version
npm publish --access public --tag latestA tag latest é a padrão do npm. Assim, consumidores instalam a versão estável
simplesmente com npm install @topjoao/top-design-system.
