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

gpr-ui

v0.4.7

Published

GPR Design System — componentes e tokens compartilhados entre os produtos da GPR (Brand Book 2026).

Readme

gpr-ui

Sistema de componentes e tokens compartilhados entre os produtos da GPR. Materializa o Brand Book 2026 e o UI Kit 2026 em uma biblioteca React pronta para consumo em qualquer produto.


Sumário

  1. O que tem aqui
  2. Passo a passo — projeto novo do zero
  3. Setup em projeto existente
  4. API dos componentes
  5. Tokens
  6. Dark mode
  7. Regras do design system
  8. Desenvolvimento local da lib

O que tem aqui

Primitivos: Button (com loading e asChild), Input, PasswordInput, Label, Badge, StatusBadge, Card (+ subcomponentes).

Overlays: Dialog (genérico) e ConfirmDialog (com isLoading + variantes).

Formulário: Select (seleção única, wrapper do Radix), MultiSelect (seleção múltipla com chips, busca opcional e ações rápidas).

Dados: Table (primitivos) e DataTable (opinado, com loading/empty/sort/paginação).

Layout: AppShell (com GprLogo default), SidebarItem, SidebarSection, useSidebar, UserMenu, ThemeToggle (+ useTheme), GprLogo, PageHeader, AuthLayout.

Preset Tailwind com toda a paleta GPR, tipografia Inter e escalas de radius/elevation/z-index/motion.

Tokens semânticos em CSS variables (light + dark) — --primary, --foreground, --sidebar, etc.

Helpers: cn() e Slot.


Passo a passo — projeto novo do zero

Comece um produto GPR novo em ~5 minutos.

1. Criar o projeto

pnpm create vite@latest meu-app -- --template react-ts
cd meu-app
pnpm install

2. Instalar Tailwind 3 + gpr-ui

pnpm add -D tailwindcss@^3 postcss autoprefixer
pnpm dlx tailwindcss@^3 init -p
pnpm add gpr-ui

# Se for usar roteamento (exemplo do passo 6):
pnpm add react-router-dom

Importante: o preset foi escrito para Tailwind 3. Em Tailwind 4 o tailwind.config.js e a sintaxe de presets mudam — mantenha a v3 por ora. Note o @^3 também no pnpm dlx pra garantir que o binário do init seja da v3.

Se pnpm add gpr-ui falhar (pacote não publicado no npm público ainda), use o fallback local:

// package.json do projeto consumidor
{ "dependencies": { "gpr-ui": "file:../caminho/para/gpr-ui" } }

Ajuste o path e rode pnpm install. Veja também a seção Desenvolvimento local.

3. Configurar tailwind.config.js

Substitua o conteúdo gerado por:

import gprPreset from 'gpr-ui/tailwind-preset'

export default {
  presets: [gprPreset],
  content: [
    './index.html',
    './src/**/*.{ts,tsx}',
    './node_modules/gpr-ui/dist/**/*.{js,cjs}',
  ],
}

A última linha é crítica — sem ela, Tailwind faz purge das classes usadas dentro dos componentes da lib.

4. Importar os estilos (uma única vez, no entry)

// src/main.tsx
import 'gpr-ui/styles.css'   // tokens CSS + base GPR
import './index.css'         // seu CSS com @tailwind directives

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

E no seu src/index.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

5. Primeiro componente

// src/App.tsx
import { Button, Card, CardHeader, CardTitle, CardContent } from 'gpr-ui'

export default function App() {
  return (
    <main className="min-h-screen bg-background text-foreground p-8">
      <Card className="max-w-md mx-auto">
        <CardHeader>
          <CardTitle>Olá, GPR</CardTitle>
        </CardHeader>
        <CardContent className="flex gap-2">
          <Button>Salvar</Button>
          <Button variant="outline">Cancelar</Button>
        </CardContent>
      </Card>
    </main>
  )
}
pnpm dev

6. Montar o app com layout completo (quando for produto real)

import { Link, useLocation } from 'react-router-dom'
import {
  AppShell,
  SidebarItem,
  SidebarSection,
  UserMenu,
  ThemeToggle,
} from 'gpr-ui'
import { HomeIcon, SettingsIcon } from 'lucide-react'

export function App({ children }) {
  const { pathname } = useLocation()

  return (
    <AppShell
      // logo opcional — default é <GprLogo /> (wordmark oficial inline)
      navbarRight={
        <>
          <ThemeToggle />
          <UserMenu
            name="Fábio Garcia"
            email="[email protected]"
            onLogout={() => { /* ... */ }}
          />
        </>
      }
      sidebar={
        <>
          <SidebarItem asChild icon={HomeIcon} active={pathname === '/'}>
            <Link to="/">Início</Link>
          </SidebarItem>

          <SidebarSection label="Configurações">
            <SidebarItem
              asChild
              icon={SettingsIcon}
              active={pathname.startsWith('/settings')}
            >
              <Link to="/settings">Preferências</Link>
            </SidebarItem>
          </SidebarSection>
        </>
      }
    >
      {children}
    </AppShell>
  )
}

7. Checklist final

  • [ ] gpr-ui/styles.css importado uma vez no entry
  • [ ] presets: [gprPreset] no tailwind.config.js
  • [ ] ./node_modules/gpr-ui/dist/**/*.{js,cjs} no content
  • [ ] Peer deps instaladas: react ≥18, react-dom ≥18, tailwindcss ≥3
  • [ ] Se usa dark mode: ThemeToggle montado ou adicionar classe .dark no <html> manualmente

Setup em projeto existente

Se você já tem React + Tailwind configurado, pule pros passos 2→4 acima:

pnpm add gpr-ui
// tailwind.config.js
import gprPreset from 'gpr-ui/tailwind-preset'

export default {
  presets: [gprPreset],
  content: [
    './index.html',
    './src/**/*.{ts,tsx}',
    './node_modules/gpr-ui/dist/**/*.{js,cjs}',
  ],
}
// entry
import 'gpr-ui/styles.css'

Peer deps: react ≥18, react-dom ≥18, tailwindcss ≥3.


API dos componentes

Button

<Button>Salvar</Button>
<Button variant="outline" size="sm">Cancelar</Button>
<Button variant="destructive">Excluir</Button>

// loading: desabilita + spinner
<Button loading={mutation.isPending}>Salvar</Button>

// asChild: aplica as classes no filho único (ideal pra Link)
<Button asChild>
  <Link to="/planos">Ver planos</Link>
</Button>

| Prop | Valores | |---|---| | variant | defaultdestructiveoutlinesecondaryghostlink | | size | sm (32px) • default (36px) • lg (40px) • icon (36×36) | | loading | boolean — prepende spinner e desabilita | | asChild | boolean — clona o filho aplicando classes |

Input / Label

Altura universal de 44px. aria-invalid="true" aciona ring destructive.

<Label htmlFor="email">E-mail</Label>
<Input id="email" type="email" placeholder="[email protected]" />

PasswordInput

Mesmo visual do Input, com botão olho pra alternar visibilidade da senha. Herda todos os comportamentos do Input (aria-invalid, disabled, altura 44px).

<Label htmlFor="password">Senha</Label>
<PasswordInput id="password" placeholder="••••••••" />

// Com validação
<PasswordInput aria-invalid={hasError} />

// Disabled (desabilita o toggle junto)
<PasswordInput disabled />

// Labels de acessibilidade customizáveis (default: "Mostrar senha" / "Ocultar senha")
<PasswordInput showLabel="Show password" hideLabel="Hide password" />

Badge

<Badge>Ativo</Badge>
<Badge variant="success">Verificado</Badge>
<Badge variant="warning">Atenção</Badge>
<Badge variant="destructive">Expirado</Badge>

Variantes: default, secondary, destructive, outline, success, warning.

StatusBadge

Variante especializada do Badge para estado de um dado (ativo/pendente/falhou). Tem ponto colorido indicador e aceita ícone customizado (ex.: spinner pra "processando").

<StatusBadge tone="success">Ativo</StatusBadge>
<StatusBadge tone="destructive">Falhou</StatusBadge>
<StatusBadge tone="warning">Pendente</StatusBadge>
<StatusBadge tone="info">Novo</StatusBadge>
<StatusBadge tone="neutral">Arquivado</StatusBadge>

// Com ícone no lugar do dot
<StatusBadge tone="info" icon={<Loader2 className="w-3 h-3 animate-spin" />}>
  Processando
</StatusBadge>

// Só texto
<StatusBadge tone="neutral" hideIndicator>Rascunho</StatusBadge>

Tons: success, destructive, warning, info, neutral.

Card

Composição via subcomponentes — nunca adicione mt-* manual entre eles.

<Card>
  <CardHeader>
    <CardTitle>Faturamento</CardTitle>
    <CardDescription>Último trimestre</CardDescription>
  </CardHeader>
  <CardContent>R$ 48.000</CardContent>
  <CardFooter>
    <Button variant="outline">Ver detalhes</Button>
  </CardFooter>
</Card>

Dialog

Wrapper do Radix com tokens do DS. Use para qualquer modal que não seja de confirmação (pra confirmação, prefira ConfirmDialog abaixo).

<Dialog>
  <DialogTrigger asChild>
    <Button>Editar cliente</Button>
  </DialogTrigger>
  <DialogContent size="md">
    <DialogHeader>
      <DialogTitle>Editar cliente</DialogTitle>
      <DialogDescription>Atualize os dados abaixo.</DialogDescription>
    </DialogHeader>

    {/* form aqui */}

    <DialogFooter>
      <DialogClose asChild>
        <Button variant="outline">Cancelar</Button>
      </DialogClose>
      <Button onClick={save}>Salvar</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Props de DialogContent: size (smmdlgxl), hideCloseButton.

ConfirmDialog

<ConfirmDialog
  open={confirmOpen}
  title="Apagar cliente?"
  message="Esta ação é irreversível."
  variant="danger"
  isLoading={deleteMutation.isPending}
  onConfirm={() => deleteMutation.mutate()}
  onCancel={() => setConfirmOpen(false)}
/>

Variantes: danger, warning, info.

Select

Wrapper do Radix Select com tokens GPR. aria-invalid aciona anel destructive.

<Select value={value} onValueChange={setValue}>
  <SelectTrigger>
    <SelectValue placeholder="Escolha um plano" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Planos</SelectLabel>
      <SelectItem value="basic">Básico</SelectItem>
      <SelectItem value="pro">Pro</SelectItem>
      <SelectItem value="enterprise">Enterprise</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>

// Altura reduzida (inputs inline em listagens)
<SelectTrigger size="sm">...</SelectTrigger>

MultiSelect

Seleção múltipla com chips removíveis no trigger, busca opcional e ações rápidas. Construído sobre Radix Popover.

const [selected, setSelected] = useState<string[]>([])

<MultiSelect
  options={[
    { value: 'sp', label: 'São Paulo' },
    { value: 'rj', label: 'Rio de Janeiro' },
    { value: 'mg', label: 'Minas Gerais' },
    { value: 'rs', label: 'Rio Grande do Sul' },
  ]}
  value={selected}
  onValueChange={setSelected}
  placeholder="Selecione estados..."
/>

// Com busca (default: desligada)
<MultiSelect
  options={options}
  value={selected}
  onValueChange={setSelected}
  searchable
/>

// Customizando ações / tamanho
<MultiSelect
  options={options}
  value={selected}
  onValueChange={setSelected}
  size="sm"
  maxChips={5}           // default: 3
  showSelectAll={false}
  showClear
  emptyMessage="Nenhuma opção disponível."
/>

| Prop | Tipo | Default | Descrição | |---|---|---|---| | options | MultiSelectOption[] | — | { value, label, disabled? }[] | | value | string[] | — | Valores selecionados (controlado) | | onValueChange | (value: string[]) => void | — | Callback de mudança | | placeholder | string | "Selecione..." | Texto quando nada selecionado | | searchable | boolean | false | Mostra campo de busca | | searchPlaceholder | string | "Buscar..." | Placeholder da busca | | showSelectAll | boolean | true | Botão "Selecionar todos" | | showClear | boolean | true | Botão "Limpar" | | maxChips | number | 3 | Chips no trigger antes de virar "+N" | | size | 'default' \| 'sm' | 'default' | Altura do trigger | | emptyMessage | ReactNode | "Nenhum resultado." | Texto quando busca vazia |

Table (primitivos)

Sem lógica — só apresentação. Use quando precisar de controle total (colunas expansíveis, seleção múltipla, virtualização).

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Nome</TableHead>
      <TableHead>E-mail</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Fábio</TableCell>
      <TableCell>[email protected]</TableCell>
    </TableRow>
  </TableBody>
</Table>

DataTable

Listagem opinada com loading, empty state, sort e paginação prontos. Cobre 90% dos casos.

type Client = { id: string; name: string; status: string }

const columns: DataTableColumn<Client>[] = [
  { key: 'name', header: 'Nome', sortable: true },
  {
    key: 'status',
    header: 'Status',
    render: (c) => <StatusBadge tone="success">{c.status}</StatusBadge>,
  },
  { key: 'actions', header: '', align: 'right', render: (c) => (
    <Button size="sm" variant="outline">Abrir</Button>
  )},
]

<DataTable
  columns={columns}
  data={clients}
  loading={query.isPending}
  emptyMessage="Nenhum cliente cadastrado."
  rowKey={(c) => c.id}
  onRowClick={(c) => navigate(`/clientes/${c.id}`)}

  // Sort controlado (opcional)
  sort={sort}
  onSortChange={setSort}

  // Paginação controlada (opcional)
  pagination={{ page, pageSize: 20, total: query.data?.total ?? 0 }}
  onPageChange={setPage}
/>

Props principais:

| Prop | Descrição | |---|---| | columns | Array de DataTableColumn<T> com key, header, render?, sortable?, align? | | data | Array de itens | | loading | Mostra skeleton (N linhas = loadingRows, default 5) | | emptyMessage | Mensagem quando data é vazio | | rowKey | (item, i) => key — default usa o índice | | onRowClick | Callback na linha (cursor pointer automático) | | sort + onSortChange | Estado controlado de ordenação | | pagination + onPageChange | Controles inferiores (só aparecem se total > pageSize) |

AppShell + Sidebar

Layout completo com navbar (77px) + sidebar (220/76px) + drawer mobile. Persiste estado colapsado em cookie por 7 dias.

<AppShell
  // logo default: <GprLogo /> — passe algo só se quiser sobrescrever
  navbarRight={<>{/* ThemeToggle, UserMenu, etc */}</>}
  sidebar={<>{/* SidebarItem, SidebarSection */}</>}
  defaultSidebarOpen={true}        // opcional
  cookieName="meuapp.sidebar_open" // opcional
>
  {children}
</AppShell>

SidebarItem — item clicável da nav.

// Com router Link (Slot pattern)
<SidebarItem asChild icon={HomeIcon} active={isHome}>
  <Link to="/">Início</Link>
</SidebarItem>

// Botão com onClick
<SidebarItem icon={BellIcon} onClick={() => openNotifications()}>
  Notificações
</SidebarItem>

SidebarSection — agrupa items com label (vira divisor quando colapsado).

<SidebarSection label="Administração">
  <SidebarItem ...>Usuários</SidebarItem>
  <SidebarItem ...>Permissões</SidebarItem>
</SidebarSection>

useSidebar() — hook pra ler/controlar estado.

const { expanded, toggle, mobileOpen, setMobileOpen } = useSidebar()

UserMenu

Avatar circular com iniciais. Clique abre dropdown com info + logout.

<UserMenu
  name="Fábio Garcia"
  email="[email protected]"
  onLogout={() => logoutFn()}
  extraActions={/* opcional — slot antes do logout pra Perfil/Settings */}
/>

PageHeader

Título de página com descrição opcional e ação alinhada à direita. Use no topo de páginas inteiras (pra títulos dentro de Card, use CardHeader).

import { PageHeader, Button } from 'gpr-ui'
import { PlusIcon } from 'lucide-react'

<PageHeader
  title="Usuários"
  description="Gerencie usuários e seus acessos aos produtos."
  action={
    <Button onClick={openCreate}>
      <PlusIcon className="h-4 w-4" />
      Criar usuário
    </Button>
  }
/>

// Só título e ação (sem descrição)
<PageHeader
  title={user.name}
  action={<Button variant="secondary">Editar</Button>}
/>

// Sem linha decorativa
<PageHeader title="Dashboard" divider={false} />

// Com wrapper externo (ex.: back button) — anule o mb-8 padrão:
<div className="flex items-center gap-4 mb-6">
  <Button variant="ghost" size="icon" asChild>
    <Link to="/users"><ArrowLeftIcon className="h-5 w-5" /></Link>
  </Button>
  <PageHeader title={user.name} className="mb-0" />
</div>

| Prop | Tipo | Default | Descrição | |---|---|---|---| | title | ReactNode | — | Título da página (h1, text-display-md/lg) | | description | ReactNode | — | Subtítulo (text-sm text-muted-foreground) | | action | ReactNode | — | Conteúdo alinhado à direita (normalmente Button) | | divider | boolean | true | Linha decorativa de 48px entre título e descrição | | className | string | — | Override das classes (útil pra anular mb-8) |

AuthLayout

Shell visual pras páginas de autenticação (login, registro, recuperar senha). Fundo gradiente GPR, watermark do logo atrás e card branco centralizado. Não tem lógica — toda validação, submit, auth e roteamento fica no consumidor.

import { AuthLayout, Label, Input, PasswordInput, Button } from 'gpr-ui'
import { useForm } from 'react-hook-form'

export function LoginPage() {
  const { register, handleSubmit, formState: { errors } } = useForm()

  return (
    <AuthLayout
      productName="Authenticator"
      title="Acesse sua conta"
      description="Autenticação restrita a administradores autorizados"
      footer="© GPR Authenticator — Uso interno autorizado"
    >
      <form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
        <div className="space-y-2">
          <Label htmlFor="email">E-mail</Label>
          <Input id="email" type="email" {...register('email')} />
        </div>
        <div className="space-y-2">
          <Label htmlFor="password">Senha</Label>
          <PasswordInput id="password" {...register('password')} />
        </div>
        <Button type="submit" size="lg" className="w-full" loading={isLoading}>
          Entrar
        </Button>
      </form>
    </AuthLayout>
  )
}

Sobrescrever o logo (quando o produto tem identidade visual própria):

<AuthLayout
  logo={<img src="/plan-100-logo.svg" alt="Plan 100" className="h-10" />}
  productName="Plan 100"
  title="Entrar"
>
  {/* form */}
</AuthLayout>

| Prop | Tipo | Default | Descrição | |---|---|---|---| | children | ReactNode | — | Corpo do card (o <form> do consumidor) | | logo | ReactNode | <GprLogo height={40} /> | Logo no topo do card | | productName | string | — | Rótulo discreto abaixo do logo (text-label-sm em gpr-500) | | title | ReactNode | "Acesse sua conta" | Título principal do card | | description | ReactNode | "Faça login para acessar o painel" | Subtítulo abaixo do título | | footer | ReactNode | — | Texto abaixo do card (copyright, links) | | className | string | — | Classes extras no wrapper raiz |

Todas as props (menos children) são opcionais — se omitir, simplesmente não renderiza.

GprLogo

Wordmark oficial da GPR inline (não depende de arquivo em /public). É o logo default do AppShell.

// Uso standalone (login, splash, footer, etc.)
<GprLogo />              // 32px, cor primary
<GprLogo height={48} />  // tamanho customizado
<GprLogo className="text-white" />  // inverter cor (fundo escuro)

Aceita todas as props de <svg> (exceto viewBox e xmlns). A cor usa currentColor — por isso className="text-*" funciona.

Altura default 32px; largura é sempre proporcional (ratio ≈ 1.72). Consumidores não devem ter mais gpr.svg no /public — importam daqui.

ThemeToggle

Botão pronto pra alternar light/dark. Persiste em localStorage, respeita prefers-color-scheme na primeira visita.

<ThemeToggle />

Se precisar controlar o tema fora do botão:

import { useTheme } from 'gpr-ui'

const { theme, toggle, setTheme } = useTheme()

Helpers

import { cn, Slot } from 'gpr-ui'

// cn — merge de classes com tailwind-merge
<div className={cn('p-4', isActive && 'bg-primary', className)} />

// Slot — clona o filho aplicando props/classes (usado internamente pelo asChild)

Tokens semânticos

Cores: bg-primary, bg-secondary, bg-muted, bg-accent, bg-destructive, bg-card, bg-popover, bg-sidebar, text-foreground, text-muted-foreground, border-border, border-input, ring, bg-success-*, bg-warning-*, bg-critical-*, e todas as escalas gpr-{100..900} / success-{100..900} / warning-{100..900} / critical-{100..900}.

Tipografia: text-display-lg/md, text-title-lg/md/sm, text-body-lg/md/sm, text-label-md/sm, text-caption.

Radius: rounded-xs (4px) • rounded-sm (8px) • rounded-md (10px) • rounded-lg (12px) • rounded-xl (12px) • rounded-2xl (16px).

Sombras: shadow-elevation-1 a shadow-elevation-4.

Motion: duration-fast (150ms) • duration-base (200ms) • duration-slow (300ms).

Z-index: z-base, z-raised, z-sticky, z-overlay, z-sidebar, z-modal, z-dropdown, z-toast.


Dark mode

Ativado adicionando a classe .dark no <html>. O ThemeToggle e o hook useTheme() fazem isso automaticamente. Se preferir controle manual:

document.documentElement.classList.toggle('dark')

A cor --primary (#008DFF) não muda entre temas — só os neutros e superfícies (regra do Brand Book cap. DS-06).


Regras do design system

  1. Neutros dominam, primary é acento — nunca use bg-primary como fundo de página.
  2. Tokens semânticos, nunca hexbg-primary, não #008DFF.
  3. Uma ação principal por tela — apenas um Button variant="default".
  4. Active ≠ Hover — active muda cor+peso; hover muda bg.
  5. Dimensões fixas — Input 44px, Button default 36px, SidebarItem 40px.
  6. Focus-visible global — já configurado pelo styles.css.

Desenvolvimento local

pnpm install
pnpm build     # single build
pnpm dev       # watch mode
pnpm typecheck

Para testar num projeto consumidor sem publicar:

{
  "dependencies": { "gpr-ui": "file:../gpr-ui" }
}

Ou via link simbólico:

# no gpr-ui
pnpm link --global

# no projeto consumidor
pnpm link --global gpr-ui

Publicando

npm version patch    # ou minor / major
pnpm publish --access public

Changelog

  • 0.3.xDialog genérico, Select, StatusBadge, Table (primitivos) e DataTable (com loading/empty/sort/paginação).
  • 0.2.0AppShell + Sidebar completo, UserMenu, ThemeToggle. Button ganhou loading + asChild. ConfirmDialog ganhou isLoading. Helper Slot exportado.
  • 0.1.0 — Primitivos iniciais (Button, Input, Label, Badge, Card, ConfirmDialog), preset Tailwind, tokens CSS.

Licença

Uso interno GPR.