@be-enlighten/enspace-sdk-ui
v0.11.1
Published
Vue 3 component library and Nuxt 4 module for the Enspace SDK — dumb base components + entity components wired to the /query data layer.
Maintainers
Readme
@be-enlighten/enspace-sdk-ui
Biblioteca de componentes Vue 3 do Enspace, sobre Nuxt UI 4 e Tailwind 4.
Dois tipos de componente:
- Base (dumb): só props, slots e emits. Zero data fetching, zero router.
- Entidade (wired): consomem a data layer
/querydo@be-enlighten/enspace-sdk-vueinternamente, com override por props. As regras de domínio do Enspace (owner, membro pendente, self-exclusion) vivem no componente, não na sua app.
Navegação sempre sai por emits: a app decide a rota.
Instalação
pnpm add @be-enlighten/enspace-sdk-uiPeers:
| Peer | Obrigatória? | Para quê |
|---|---|---|
| vue@^3.5 | sim | runtime dos componentes |
| @nuxt/ui@^4 | sim | stack de estilo (Nuxt UI 4 + Tailwind 4) |
| @be-enlighten/enspace-sdk-vue | sim | composables /query dos componentes de entidade |
| @be-enlighten/enspace-sdk-core | sim | tipos de domínio na API pública |
| @tanstack/vue-table@^8 | sim | motor do EnTable |
| pinia, @pinia/colada | opcional | só para componentes wired |
| streamdown-vue, @be-enlighten/beni-avatar | opcional | componentes de AI |
| ai, @ai-sdk/vue | opcional | chat de AI com streaming |
| nuxt, @nuxt/kit | opcional | módulo /nuxt |
A app consumidora precisa do plugin @nuxt/ui/vite (Vite) ou do módulo Nuxt UI.
Setup
1. EnApp na raiz
Os componentes En* precisam do UApp do Nuxt UI na raiz (modal, tooltip, toast). O EnApp abraça o UApp por dentro e provê locale e mensagens globais, então é o único wrapper obrigatório:
<script setup lang="ts">
import { EnApp } from '@be-enlighten/enspace-sdk-ui'
</script>
<template>
<EnApp locale="pt-BR">
<RouterView />
</EnApp>
</template>locale?: MaybeRefOrGetter<EnUiLocale> (default 'pt-BR') aceita valor direto, ref ou getter. Com vue-i18n na app, a ponte é direta e não exige peer de vue-i18n no pacote:
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
</script>
<template>
<EnApp :locale="locale">
<RouterView />
</EnApp>
</template>O locale também é mapeado para o @nuxt/ui/locale (pt-BR → pt_br, en → en, es → es) e repassado ao UApp interno: um knob controla os textos do pacote e os do Nuxt UI.
messages?: EnUiMessagesOverrides sobrescreve textos por locale (Partial<Record<EnUiLocale, DeepPartial<EnUiMessages>>>), aplicados por deep-merge sobre o dicionário interno:
<EnApp
:locale="locale"
:messages="{ 'pt-BR': { members: { table: { invite: 'Convidar colegas' } } } }"
>Resolução do locale em cada componente: prop locale → inject do EnApp → 'pt-BR'. Sem EnApp, tudo continua funcionando com o fallback. O contexto injetado é exportado (EnUiContextKey, tipo EnUiContext) para casos avançados.
2. CSS
O build extrai o CSS dos componentes (handle de resize do EnTable, regras contain de performance) para um arquivo separado, que precisa ser importado uma vez:
import '@be-enlighten/enspace-sdk-ui/style.css'Sem esse import, a feature resizable do EnTable fica sem o handle visual e as otimizações de contain não se aplicam.
3. Registro global (opcional)
O padrão é importar por nome (import { EnMembersViewManyTable } from '@be-enlighten/enspace-sdk-ui'). Para registro global:
import { EnspaceUi } from '@be-enlighten/enspace-sdk-ui'
import '@be-enlighten/enspace-sdk-ui/style.css'
app.use(EnspaceUi, { locale: 'pt-BR' }) // opções: `locale` e `messages`, mesma semântica das props do EnAppO plugin registra todos os componentes públicos, provê o contexto de i18n no nível da app e chama o shim de NuxtLink. Ele não abraça o UApp: o <EnApp> na raiz continua necessário.
installNuxtLinkShim(app) — componentes do Nuxt UI com to (ULink, UButton) renderizam <NuxtLink custom>, que só o Nuxt registra. Em apps Vite + vue-router, o shim registra um NuxtLink que delega ao RouterLink com o mesmo contrato de slot (href, navigate, isActive). É no-op sem vue-router ou quando NuxtLink já existe. O plugin EnspaceUi chama o shim automaticamente; com imports nomeados, chame manualmente.
4. Nuxt 4
export default defineNuxtConfig({
modules: [
'@be-enlighten/enspace-sdk-vue/nuxt', // data layer, necessário para os componentes de entidade
'@be-enlighten/enspace-sdk-ui/nuxt',
],
})O módulo auto-importa todos os componentes e injeta o CSS. Opções na chave enspaceUi:
| Opção | Default | Descrição |
|---|---|---|
| prefix | '' | Prefixo dos nomes registrados ('Ui' → <UiEnTable />). |
| global | false | Registra tudo globalmente em vez de auto-import sob demanda. Necessário fora de templates Vue (ex.: MDC do @nuxt/content); custo: tudo entra no bundle. |
| dataModuleCheck | true | Avisa no build quando o módulo de dados não está registrado. Desligue em apps que usam só componentes base. |
O <EnApp> na raiz continua necessário no Nuxt.
Componentes
| Componente | Grupo | Tipo | Descrição | Props principais | Emits | Slots |
|---|---|---|---|---|---|---|
| EnApp | base | infra | Provider raiz: abraça o UApp e provê locale/messages globais (ver Setup) | locale?, messages? | — | default |
| EnLayout | layout | dumb | Shell de layout configurável (7 variants) com sidebar primária, sidebar secundária, inspector e navbar. Controla estrutura e visual em uma prop variant | variant, title?, side?, collapsible?, rail?, mode?, defaultOpen?, showNavbarToggle?, navbarHeight? | — | sidebar-header, sidebar-default, sidebar-footer, sidebar-secondary-header, sidebar-secondary-default, sidebar-secondary-footer, inspector-header, inspector-default, inspector-footer, navbar-leading, navbar-title, navbar-trailing, default |
| EnTable | base | dumb | Listagem padrão Enspace, genérica em R (ver detalhes abaixo) | columns, rows, loading?, selectable?, selected?, sort?, pagination?, emptyState?, resizable?, columnSizing?, columnVisibility?, columnOrder?, virtualize?, locale? | update:selected, page-change, sort-change, row-click, update:columnSizing, update:columnVisibility, update:columnOrder | #cell-{key}, #actions, #empty, #pagination |
| EnKanbanBoard | base | dumb | Board kanban genérico em R: agrupa por campo (groupBy, dot-path), DnD HTML5 entre colunas com drop zone destacada, colunas ocultáveis/ordenáveis (localStorage ou modo controlado), virtualização por coluna, skeleton, context menu por cartão (abrir/editar/mover/arquivar/lixeira/copiar link), badges de tag, meta de usuários (dumb) e rodapé de datas | data, cardMap, groupBy, columns?, storageKey?, primaryKey?, defaultColumnSortField?, defaultColumnSortDesc?, doneStatus?, permissions?, managedColumns?, columnVisibility?, columnOrder?, showHeaderColumnControls?, showSkeleton?, cardsLoading?, staggerColumnMs?, callbacks (onOpenPage?, onEdit?, onMoveTo?, onArchiveCard?, onTrashCard?, handleCopyExternalLink?), locale? | drop, done-drop, card-click, archive, create, context-mouse, ready | — |
| EnAccountSharedMenu | Shared | wired | Menu de conta (perfil + logout, itens custom) | profile?, items?, collapsed?, logoutRedirectUri?, locale? | logout, navigate | encaminha slots ao dropdown interno |
| EnAiAgentsViewManyGrid | ViewMany | wired | Grid de agentes com tabs todos/sistema/workspace, busca por nome/slug e CTA de criar | locale? | select ({ action, agent }), create | — |
| EnAiAgentsViewOneCard | ViewOne | dumb | Card de agente com avatar BENI temático, badges de source/tipo e menu edit/delete (desabilitado para agentes de sistema) | agent, locale? | select ({ action, agent }) | — |
| EnAiChatsSharedFloat | Shared | dumb | Widget flutuante de IA: botão BENI draggable (posição persistida) + painel redimensionável com sessão, nova conversa e histórico | open?, onBeforeSend?, onNotify?, types?, walletState?, locale? | update:open, openFullscreen ({ chatId, agent }) | — |
| EnAiChatsFormsCreate | Forms | wired | Tela de nova conversa: hero do agente, alertas de wallet, quick chats, composer com upload e seletores de modelo/mode/reasoning. Cria o chat e emite o resultado | compact?, showWalletAlerts?, showQuickChats?, showFileUpload?, showFooter?, showAgentSelector?, initialPrompt?, agentSlug?, walletState?, types?, locale? | chatCreated, update:agentSlug, goToBilling | header (+ header-left/header-center/header-right) |
| EnAiChatsFormsPrompt | Forms | dumb | Composer sobre UChatPrompt: v-model de texto, preview de arquivos no header, upload à esquerda e footers do caller | modelValue, placeholder, status?, error?, disabled?, variant?, showFileUpload?, files?, isUploading?, maxrows?, rows?, compact?, locale? | update:modelValue, submit, filesSelected, fileRemove, stop, reload | files-preview, footer-left, footer-right (scoped) |
| EnAiChatsSharedDockHeader | Shared | dumb | Ações do header do dock: histórico, nova conversa, tela cheia e fechar | creating?, sidebarOpen?, locale? | toggleSidebar, newChat, fullscreen, close | — |
| EnAiChatsSharedHeader | Shared | dumb | Barra de header do chat: avatar BENI à esquerda, seletor de agente ao centro, ações à direita | beniState, beniBodyColor, beniBodyColorTo, beniEyesColor, beniHeadItem?, agentsLoading?, agentOptions, agentDescriptions?, selectedAgentSlug?, showAgentSelector?, locale? | selectAgent | header-left, header-center, header-right |
| EnAiChatsSharedLoader | Shared | dumb | Indicador de carregamento do chat (três pontos + rótulo), inline ou overlay com blur | label?, message?, overlay? | — | — |
| EnAiChatsViewManyList | ViewMany | wired | Sidebar de histórico de chats: nav "Nova conversa"/"Buscar" + command palette (⌘K), grupos por recência, rename inline e delete com confirmação | selected?, collapsed?, searchShortcut?, locale? | update:selected, newChat, delete | — |
| EnAiChatsViewOneSession | ViewOne | wired | Conversa ao vivo: mensagens com streaming (reasoning/tool/texto/arquivo), composer com upload, seletores de modelo/mode/reasoning, regenerar/copiar e estado do avatar BENI | chatId, showFooter?, compact?, agentSlug?, initialMode?, initialModel?, initialReasoning?, onBeforeSend?, onNotify?, locale? | update:agentSlug, error, newChat | header (+ header-left/header-center/header-right) |
| EnAiChatsControlsModelBadge | Controls | dumb | Badge transitório quando o backend roteia o request para outro modelo | from, to, visionRouted, locale? | — | — |
| EnAiChatsControlsModelSelect | Controls | wired | Pill de seleção de modelo: entrada "modelo padrão" + modelos agrupados por provider, com ícones de capability e busca | v-model (string \| null), compact?, locale? | update:modelValue | — |
| EnAiChatsControlsModePill | Controls | dumb | Pill de alternância do modo do agente (Write/Plan). Clique cicla o modo | modelValue (AgentMode), locale? | update:modelValue | — |
| EnAiChatsControlsReasoningPill | Controls | dumb | Seletor do nível de reasoning (off/low/medium/high) em dropdown com checkbox | modelValue (AiReasoningSetting), options, locale? | update:modelValue | — |
| EnAiChatsFilesAvatar | Files | dumb | Tile de arquivo: thumbnail ou ícone por mime, overlay de status e botão remover no hover | name, type, previewUrl, status?, error?, removable?, locale? | remove | — |
| EnAiChatsFilesDragDropOverlay | Files | dumb | Overlay fullscreen "solte os arquivos aqui" durante o drag | show, locale? | — | — |
| EnAiChatsFilesPreview | Files | dumb | Linha de avatares dos arquivos pendentes de envio | v-model (AiChatFileWithStatus[]), locale? | remove (+ update:modelValue) | — |
| EnAiChatsFilesUploadButton | Files | dumb | Botão clipe + input file oculto (multiple; imagens/PDF/CSV/TXT/DOC/XLS) | disabled?, locale? | filesSelected | — |
| EnBillingFormsCreditRequest | Forms | wired | Formulário de solicitação de créditos (amount + reason), como modal ou inline | open?, workspace?, modal?, locale? | update:open, success | — |
| EnBillingSharedWalletCard | Shared | wired | Card de saldo da carteira do workspace: nome, badge de status, moeda, saldo formatado e CTA de créditos | workspace?, locale? | topup | — |
| EnBillingViewManyCreditRequests | ViewMany | wired | Lista de pedidos de crédito: valor, status, motivo, data e cancelamento (só pending) | workspace?, locale? | cancelled | — |
| EnBillingViewManyTransactions | ViewMany | wired | Histórico paginado de transações da wallet em tabela | workspace?, pageSize?, locale? | — | — |
| EnMembersViewManyTable | ViewMany | wired | Tabela de membros com busca e ações protegidas (troca de role, remoção) | workspace?, members?, loading?, searchable?, resizable?, columnVisibility?, locale? | invite, update-role, remove | — |
| EnMembersFormsCreate | Forms | wired | Modal de convite (email + role inicial; owner nunca é oferecido) | v-model:open, workspace?, locale? | created, cancel | — |
| EnNotificationsActionsCommentReply | Actions | wired | Card de ação para menção em comentário: cita o trecho e responde com comentário filho | notification, locale? | — | — |
| EnNotificationsActionsOpenLink | Actions | dumb | Card de ação genérico para notificações com link. Não renderiza nada sem link | notification, locale? | open | — |
| EnNotificationsActionsTaskCard | Actions | dumb | Card de ação para notificações de tarefa: nome, transição de status e botão de abrir | notification, locale? | open | — |
| EnNotificationsActionsThreadReply | Actions | wired | Card de ação para menção em thread: cita o trecho e responde na thread de origem | notification, locale? | — | — |
| EnNotificationsSharedBell | Shared | wired | Sino com badge de não lidas. Não abre painel: o estado é da app | count?, max?, shortcuts?, locale? | click | — |
| EnNotificationsSharedSlideover | Shared | wired | Painel lateral com as últimas limit notificações (marcar lida, dispensar, todas) | v-model:open, limit?, locale? | navigate, dismiss, mark-all-read, view-all | #item |
| EnNotificationsViewInbox | ViewInbox | wired | Inbox completo: lista com busca server-side, abas e teclado + painel de leitura com cards de ação + slideover mobile. Deep-link por prop, navegação por emits | v-model:selected, limit?, title?, openReference?, locale? | open-task, open-link, open-reference-resolved | navbar-leading |
| EnNotificationsViewManyList | ViewMany | dumb | Lista estilo inbox: ator, chip de não lida, título/corpo, data compacta, seleção por clique e navegação por ArrowUp/ArrowDown | notifications, v-model (NotificationExpanded \| null), locale? | update:modelValue | item (scoped) |
| EnNotificationsViewOneDetail | ViewOne | wired | Painel de leitura: navbar com ações (marcar lida, dispensar, copiar referência), cabeçalho, corpo e card de ação por type.key | notification, locale? | close, open-task, open-link | — |
| EnPlansFormsUpgrade | Forms | wired | Modal de upgrade/downgrade com preview de proration. Upgrade imediato, downgrade agendado para o fim do período | planCode?, workspace?, v-model:open, locale? | success, cancel | — |
| EnPlansSharedQuotaBars | Shared | wired | Barras de progresso read-only das cotas do workspace: ícone do produto, badge de modo e used/limit formatados | workspace?, locale? | — | — |
| EnPlansViewCompare | View | wired | Tabela comparativa de planos sobre UPricingTable, com seções de cotas inclusas e features | workspace?, locale? | select | — |
| EnPlansViewManyGrid | ViewMany | wired | Grade responsiva de cards de plano com banner de downgrade agendado e modo current/upgrade/downgrade por card | workspace?, locale? | select, cancel-pending | — |
| EnPlansViewOneCard | ViewOne | dumb | Card de plano sobre UPricingPlan: preço, features concedidas, quotas no footer e CTA por mode | plan, mode?, highlight?, locale? | select | — |
| EnWorkspacesSharedSelector | Shared | wired | Seletor de workspace ativo (lista cacheada, setActive persistido) | workspace?, creatable?, collapsed?, locale? | change, create | — |
Todo componente com texto embutido aceita a prop locale ('pt-BR' | 'en' | 'es') como override pontual. Na prática o locale vem do <EnApp>; a prop só é necessária para um componente divergir do resto da app.
EnTable
Motor: UTable + @tanstack/vue-table. A API pública não expõe tipos da lib de tabela.
- Colunas declarativas (
EnTableColumn):{ key, label, sortable?, align?, hidden? }. - Seleção em massa:
selectable+v-model:selected(array de linhas, por referência de objeto). Checkbox no header com estado indeterminado; a seleção é podada quandorowsmuda. - Ordenação controlada: coluna
sortablerenderiza botão no header, o estado vem pela propsorte as mudanças saem porsort-change. Quem ordena é o consumer ou o backend. - Paginação server-side: prop
pagination: { page, pageCount, total? }+page-change, ou override completo pelo slot#pagination. - Resize, visibilidade, ordem e virtualização, todos opt-in:
resizable(handle visual +update:columnSizing),columnSizing(larguras controladas, fecha o round-trip de persistência),columnVisibility+update:columnVisibility,columnOrder+update:columnOrder(as colunas estruturaisselect/actionsnão se movem),virtualize(padrão44/10quando ligado). - Estados:
loading(spinner +aria-busy) e vazio viaUEmptyinterno (emptyStateou slot#empty). - A11y: tabela semântica,
aria-sort,aria-labelnos checkboxes, linhas com foco e teclado (Enter/Espaço) quando há listener derow-click.
Componentes de entidade
- Regras de domínio implementadas:
ownernunca é oferecido como opção de role (convite e troca) e não pode ser modificado nem removido (o menu vira botão desabilitado com tooltip); membropendingnão muda de status nem de role; self-exclusion é detectada (remover o próprio usuário fica desabilitado); remoção sempre atrás de modal de confirmação. - Erro não é vazio: componentes wired renderizam estado de erro próprio (ícone, título, descrição e botão de retry). Falha de API nunca cai no empty state.
- Override controlado: passar a prop de dados (ex.:
members) desabilita a query interna, sem fetch desnecessário. - Família inbox (
EnNotificationsView*+EnNotificationsActions*): oEnNotificationsViewInboxmonta lista, leitura e slideover mobile, e delega aos cards de ação pelotype.keyda notificação. A busca é server-side com debounce de 300ms. Deep-link é da app: a propopen-referenceseleciona a notificação eopen-reference-resolvedavisa para limpar a URL. - SSR-safe: nenhum acesso a
window/documentfora de guards.
i18n
Dicionário interno pt-BR/en/es com fallback (match exato → prefixo de língua → pt-BR), sem peer de vue-i18n. O locale global vem do <EnApp>; componentes também aceitam a prop locale como override pontual. Todo texto pode ser sobrescrito globalmente pela prop messages do EnApp (deep-merge por locale) ou pontualmente por props e slots (ex.: emptyState.title).
import { resolveMessages } from '@be-enlighten/enspace-sdk-ui'
resolveMessages('es').table.emptyTitle // 'Sin resultados'Nomes dos componentes
En{Entidade}{Grupo}{Cardinalidade?}{Variante}
- Entidade: domínio do recurso (
Members,Workspaces,Account,Notifications, ...). - Grupo:
View,FormsouShared. - Cardinalidade:
ManyouOne, obrigatória emViewe ausente fora dele. - Variante (opcional):
Table,Selector,Bell,Slideover, ... - Bases:
En{Nome}simples (EnTable,EnApp,EnLayout), sempre dumb.
Exemplos: EnMembersViewManyTable, EnWorkspacesSharedSelector, EnAccountSharedMenu, EnNotificationsSharedBell.
Licença
MIT.
