npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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-system

A 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 postinstall

O 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 breadcrumbs com itens TopNavBreadcrumb; sectionId liga o item a uma seção e menuId liga a qualquer nó com filhos;
  • omita breadcrumbs e informe activeItemId; 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 check

O comando check executa verificação de tipos, testes e build.

Para inspecionar o conteúdo que seria publicado:

npm pack --dry-run

Playground 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 storybook

Abra 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-storybook

Para gerar um pacote local instalável:

npm pack

Para 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 latest

A tag latest é a padrão do npm. Assim, consumidores instalam a versão estável simplesmente com npm install @topjoao/top-design-system.