melhoria de documentação
This commit is contained in:
parent
558a34ba0c
commit
eee275877d
3 changed files with 326 additions and 1120 deletions
595
IA.md
595
IA.md
|
|
@ -1,595 +0,0 @@
|
||||||
# IA.md — Guia para IAs (consumidoras) do pacote `eli-vue`
|
|
||||||
|
|
||||||
Este arquivo existe para **IAs e automações** que precisam **importar e usar** o pacote `eli-vue` em projetos Vue 3.
|
|
||||||
|
|
||||||
> Fonte da verdade das regras internas do repositório: **`.agent`**.
|
|
||||||
> Este `IA.md` é focado no **uso do pacote como dependência** (projeto consumidor).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## O que é o `eli-vue`
|
|
||||||
|
|
||||||
Biblioteca (Design System) de componentes para **Vue 3** com **TypeScript** e integração com **Vuetify 3**.
|
|
||||||
|
|
||||||
### Premissas importantes
|
|
||||||
|
|
||||||
- O `eli-vue` **não cria** nem configura Vuetify no seu projeto.
|
|
||||||
- `vue` e `vuetify` são **peerDependencies**: o projeto consumidor precisa ter ambos instalados.
|
|
||||||
- O pacote utiliza internamente **lucide-vue-next** para ícones.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Instalação (projeto consumidor)
|
|
||||||
|
|
||||||
> Preferência do repo: `pnpm`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add eli-vue
|
|
||||||
```
|
|
||||||
|
|
||||||
Se o projeto ainda não tiver as peer dependencies, instale também:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add vue vuetify lucide-vue-next
|
|
||||||
```
|
|
||||||
|
|
||||||
> Nota: `lucide-vue-next` é recomendado para passar ícones compatíveis aos componentes (como `EliTabela`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Uso recomendado
|
|
||||||
|
|
||||||
### 1) Registro global via plugin (recomendado)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { createApp } from "vue";
|
|
||||||
import App from "./App.vue";
|
|
||||||
|
|
||||||
// Vuetify
|
|
||||||
import "vuetify/styles";
|
|
||||||
import { createVuetify } from "vuetify";
|
|
||||||
|
|
||||||
// eli-vue
|
|
||||||
import EliVue from "eli-vue";
|
|
||||||
// eli-vue
|
|
||||||
import EliVue from "eli-vue";
|
|
||||||
|
|
||||||
const vuetify = createVuetify({});
|
|
||||||
|
|
||||||
createApp(App)
|
|
||||||
.use(vuetify)
|
|
||||||
.use(EliVue)
|
|
||||||
.mount("#app");
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Nota**: O CSS é **injetado automaticamente** no bundle JavaScript. Não é necessário importar arquivos `.css` manualmente.
|
|
||||||
|
|
||||||
|
|
||||||
### 2) Importação direta (quando não quiser plugin)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import {
|
|
||||||
EliBotao,
|
|
||||||
EliBadge,
|
|
||||||
EliCartao,
|
|
||||||
EliTabela,
|
|
||||||
EliEntradaTexto,
|
|
||||||
EliEntradaNumero,
|
|
||||||
EliEntradaDataHora,
|
|
||||||
EliEntradaParagrafo,
|
|
||||||
EliEntradaSelecao,
|
|
||||||
} from "eli-vue";
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tipagem e Helpers
|
|
||||||
|
|
||||||
O pacote exporta tipos e funções utilitárias essenciais para o desenvolvimento com TypeScript.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import {
|
|
||||||
// Componentes
|
|
||||||
EliTabela,
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
celulaTabela,
|
|
||||||
|
|
||||||
// Tipos
|
|
||||||
tipoEliTabelaConsulta,
|
|
||||||
tipoEliColuna,
|
|
||||||
tipoEliTabelaAcao,
|
|
||||||
CartaoStatus // Tipos compartilhados
|
|
||||||
} from "eli-vue";
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Exemplos mínimos
|
|
||||||
|
|
||||||
### Botão
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliBotao @click="salvar">Salvar</EliBotao>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Entradas (EliEntrada*) com v-model
|
|
||||||
|
|
||||||
O `eli-vue` usa uma família de componentes `EliEntrada*` (em vez do antigo `EliInput`).
|
|
||||||
|
|
||||||
#### Texto
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliEntradaTexto
|
|
||||||
v-model:value="nome"
|
|
||||||
:opcoes="{ rotulo: 'Nome', placeholder: 'Digite seu nome' }"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const nome = ref<string | null>("");
|
|
||||||
return { nome };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Parágrafo (textarea)
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliEntradaParagrafo
|
|
||||||
v-model:value="descricao"
|
|
||||||
:opcoes="{ rotulo: 'Descrição', placeholder: 'Digite...', linhas: 5 }"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const descricao = ref<string | null>("");
|
|
||||||
return { descricao };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Seleção (select)
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliEntradaSelecao
|
|
||||||
v-model:value="categoria"
|
|
||||||
:opcoes="{
|
|
||||||
rotulo: 'Categoria',
|
|
||||||
placeholder: 'Selecione...',
|
|
||||||
itens: async () => [
|
|
||||||
{ chave: 'a', rotulo: 'Categoria A' },
|
|
||||||
{ chave: 'b', rotulo: 'Categoria B' },
|
|
||||||
],
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const categoria = ref<string | null>(null);
|
|
||||||
return { categoria };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Texto com formato/máscara
|
|
||||||
|
|
||||||
> Regra importante: o `value` emitido é **sempre o texto formatado** (igual ao que aparece no input).
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliEntradaTexto
|
|
||||||
v-model:value="documento"
|
|
||||||
:opcoes="{ rotulo: 'CPF/CNPJ', formato: 'cpfCnpj' }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EliEntradaTexto
|
|
||||||
v-model:value="telefone"
|
|
||||||
:opcoes="{ rotulo: 'Telefone', formato: 'telefone' }"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const documento = ref<string | null>("");
|
|
||||||
const telefone = ref<string | null>("");
|
|
||||||
return { documento, telefone };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data e hora (entrada) com suporte a UTC/Z
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- Valor chega do backend em ISO 8601 (UTC/offset), e o componente exibe em horário local -->
|
|
||||||
<EliEntradaDataHora v-model:value="dataHora" :opcoes="{ rotulo: 'Agendamento' }" />
|
|
||||||
|
|
||||||
<!-- Somente data -->
|
|
||||||
<EliEntradaDataHora v-model:value="data" :opcoes="{ rotulo: 'Nascimento', modo: 'data' }" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
// Exemplo de valor ISO vindo do backend/banco
|
|
||||||
const dataHora = ref<string | null>("2026-01-09T16:15:00Z");
|
|
||||||
const data = ref<string | null>("2026-01-09T00:00:00-03:00");
|
|
||||||
return { dataHora, data };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Outros Componentes
|
|
||||||
|
|
||||||
### EliBadge (Indicador)
|
|
||||||
|
|
||||||
Badge aprimorado que suporta raios predefinidos ("suave" | "pill").
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliBadge :badge="3" color="error" radius="pill">
|
|
||||||
<v-icon>mdi-bell</v-icon>
|
|
||||||
</EliBadge>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### EliCartao
|
|
||||||
|
|
||||||
Cartão padronizado para representar itens de domínio (ex: propostas) com status coloridos automaticamente.
|
|
||||||
|
|
||||||
> **Importante**: O status deve ser um dos valores do tipo `CartaoStatus` ("novo" | "rascunho" | "vendido" | "cancelado").
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliCartao
|
|
||||||
titulo="Proposta #123"
|
|
||||||
status="novo"
|
|
||||||
variant="outlined"
|
|
||||||
>
|
|
||||||
<!-- Conteúdo principal (default slot) -->
|
|
||||||
<div class="text-body-2">Cliente: ACME Corp</div>
|
|
||||||
<div class="text-caption text-medium-emphasis">Criado há 2 dias</div>
|
|
||||||
|
|
||||||
<!-- Ações (slot 'acoes') -->
|
|
||||||
<template #acoes>
|
|
||||||
<EliBotao variant="text" size="small">Ver detalhes</EliBotao>
|
|
||||||
</template>
|
|
||||||
</EliCartao>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
// O tipo CartaoStatus é exportado para ajudar na tipagem
|
|
||||||
import type { CartaoStatus } from "eli-vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const statusAtual: CartaoStatus = "novo";
|
|
||||||
return { statusAtual };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## EliTabela (Tabela Avançada)
|
|
||||||
|
|
||||||
O componente `EliTabela` suporta ordenação, paginação, busca e **filtro avançado**.
|
|
||||||
Para type-safety, recomenda-se definir a estrutura da consulta usando `tipoEliTabelaConsulta<T>`.
|
|
||||||
|
|
||||||
### Barra de Busca e Filtros
|
|
||||||
|
|
||||||
O `EliTabela` oferece duas formas principais de filtrar dados:
|
|
||||||
1. **Busca Textual Simples**: Uma caixa de busca global.
|
|
||||||
2. **Filtro Avançado**: Um modal para composição de filtros específicos por coluna.
|
|
||||||
|
|
||||||
#### 1. Busca Textual Simples
|
|
||||||
|
|
||||||
Para habilitar, defina `mostrarCaixaDeBusca: true`.
|
|
||||||
O termo digitado será passado para a função `consulta` no parâmetro `texto_busca`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
mostrarCaixaDeBusca: true,
|
|
||||||
consulta: async ({ texto_busca }) => {
|
|
||||||
// Use 'texto_busca' para filtrar no backend (ex: ILIKE %texto%)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Filtro Avançado
|
|
||||||
|
|
||||||
Permite que o usuário crie filtros complexos (ex: "Data > X" E "Status = Y").
|
|
||||||
Você define os campos disponíveis em `filtroAvancado`.
|
|
||||||
|
|
||||||
Cada definição de filtro precisa de:
|
|
||||||
- `rotulo`: Nome exibido ao usuário.
|
|
||||||
- `coluna`: Chave do dado (`keyof T`) a ser filtrada.
|
|
||||||
- `operador`: Operação padrão (ex: `like`, `=`, `>`, `<`, `>=`, `<=`, `!=`, `in`, `between`).
|
|
||||||
- `entrada`: Definição do componente de entrada (`EliEntrada*`) usado para capturar o valor.
|
|
||||||
|
|
||||||
> **Dica de Tipagem**: O campo `entrada` espera uma tupla `[Tipo, Opcoes]`. Devido à complexidade do TypeScript, pode ser necessário usar `as any` ou castar para `ComponenteEntrada`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
filtroAvancado: [
|
|
||||||
{
|
|
||||||
rotulo: "Nome do Cliente",
|
|
||||||
coluna: "nome", // chave em T
|
|
||||||
operador: "like",
|
|
||||||
// Configuração do input (igual ao usar EliEntradaTexto)
|
|
||||||
entrada: ["texto", { rotulo: "Digite o nome", placeholder: "Ex: João" }] as any,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Data de Criação",
|
|
||||||
coluna: "criado_em",
|
|
||||||
operador: ">=",
|
|
||||||
// Configuração do input (EliEntradaDataHora)
|
|
||||||
entrada: ["dataHora", { rotulo: "A partir de", modo: "data" }] as any,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Status",
|
|
||||||
coluna: "status",
|
|
||||||
operador: "=",
|
|
||||||
// Configuração do input com seleção
|
|
||||||
entrada: ["selecao", {
|
|
||||||
rotulo: "Selecione",
|
|
||||||
itens: () => [
|
|
||||||
{ chave: "ativo", rotulo: "Ativo" },
|
|
||||||
{ chave: "inativo", rotulo: "Inativo" }
|
|
||||||
]
|
|
||||||
}] as any
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Botões de Ação da Tabela (`acoesTabela`)
|
|
||||||
|
|
||||||
Além das ações por linha, você pode adicionar botões de ação globais à tabela.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
acoesTabela: [
|
|
||||||
{
|
|
||||||
rotulo: "Novo Usuário",
|
|
||||||
icone: Plus,
|
|
||||||
posicao: "superior", // Ao lado da busca
|
|
||||||
acao: () => console.log("Criar novo")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Exportar Relatório",
|
|
||||||
icone: Download,
|
|
||||||
posicao: "inferior", // No rodapé, alinhado à esquerda
|
|
||||||
acao: () => console.log("Exportar")
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`posicao: 'superior'`**: Exibidos à direita da caixa de busca.
|
|
||||||
- **`posicao: 'inferior'`**: Exibidos no rodapé da tabela, **alinhados à esquerda** (enquanto a paginação fica à direita).
|
|
||||||
- **Estilo**: Por padrão, botões inferiores possuem fundo branco, **borda verde** (`outline`) e texto verde. Caso uma `cor` personalizada seja informada, ela será usada para a borda e texto.
|
|
||||||
|
|
||||||
### Processando a Consulta
|
|
||||||
|
|
||||||
A função `consulta` recebe um objeto com todos os parâmetros necessários para buscar os dados corretamente.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
consulta: async (params) => {
|
|
||||||
const {
|
|
||||||
offSet, // Paginação: pular N registros
|
|
||||||
limit, // Paginação: limite por página
|
|
||||||
texto_busca, // Busca simples (string | undefined)
|
|
||||||
coluna_ordem, // Ordenação: qual coluna
|
|
||||||
direcao_ordem, // Ordenação: "asc" | "desc"
|
|
||||||
filtros // Array de filtros aplicados pelo usuário
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
// Exemplo de estrutura de 'filtros':
|
|
||||||
// [
|
|
||||||
// { coluna: "nome", operador: "like", valor: "Ana" },
|
|
||||||
// { coluna: "criado_em", operador: ">=", valor: "2024-01-01" }
|
|
||||||
// ]
|
|
||||||
|
|
||||||
console.log("Buscando dados...", params);
|
|
||||||
|
|
||||||
// ... chamada à API ...
|
|
||||||
|
|
||||||
return {
|
|
||||||
cod: 0,
|
|
||||||
eCerto: true,
|
|
||||||
eErro: false,
|
|
||||||
mensagem: undefined,
|
|
||||||
valor: {
|
|
||||||
// Retorne SEMPRE o total real de registros para a paginação funcionar
|
|
||||||
quantidade: 100,
|
|
||||||
valores: [ /* ... array de T ... */ ]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
### Exemplo Completo Tipado
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import { EliTabela, celulaTabela } from "eli-vue";
|
|
||||||
import type { tipoEliTabelaConsulta } from "eli-vue";
|
|
||||||
// Tipos auxiliares (opcionais, mas úteis)
|
|
||||||
import { BadgeCheck, Pencil } from "lucide-vue-next";
|
|
||||||
|
|
||||||
// 1. Defina o tipo da linha
|
|
||||||
type Usuario = {
|
|
||||||
id: number;
|
|
||||||
nome: string;
|
|
||||||
documento: string;
|
|
||||||
email: string;
|
|
||||||
ativo: boolean;
|
|
||||||
criado_em: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. Defina a configuração da tabela
|
|
||||||
const tabelaUsuarios: tipoEliTabelaConsulta<Usuario> = {
|
|
||||||
nome: "Usuarios",
|
|
||||||
mostrarCaixaDeBusca: true,
|
|
||||||
registros_por_consulta: 10,
|
|
||||||
|
|
||||||
// Colunas Visuais
|
|
||||||
colunas: [
|
|
||||||
{
|
|
||||||
rotulo: "Nome",
|
|
||||||
celula: (row) => celulaTabela("textoSimples", { texto: row.nome }),
|
|
||||||
visivel: true,
|
|
||||||
coluna_ordem: "nome" // Habilita ordenação por esta coluna
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Status",
|
|
||||||
visivel: true,
|
|
||||||
celula: (row) => celulaTabela("tags", {
|
|
||||||
opcoes: [
|
|
||||||
row.ativo
|
|
||||||
? { rotulo: "Ativo", cor: "success", icone: BadgeCheck }
|
|
||||||
: { rotulo: "Inativo", cor: "error" }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Criado em",
|
|
||||||
visivel: true,
|
|
||||||
celula: (row) => celulaTabela("data", {
|
|
||||||
valor: row.criado_em,
|
|
||||||
formato: "data_hora"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Ações de linha (botões à direita)
|
|
||||||
acoesLinha: [
|
|
||||||
{
|
|
||||||
rotulo: "Editar",
|
|
||||||
icone: Pencil,
|
|
||||||
cor: "primary",
|
|
||||||
acao: (row) => console.log("Editar", row.id)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Definição dos Filtros Avançados disponíveis
|
|
||||||
filtroAvancado: [
|
|
||||||
{
|
|
||||||
rotulo: "Documento",
|
|
||||||
coluna: "documento",
|
|
||||||
operador: "like",
|
|
||||||
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as any,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Criado em (Início)",
|
|
||||||
coluna: "criado_em",
|
|
||||||
operador: ">=",
|
|
||||||
entrada: ["dataHora", { rotulo: "A partir de", modo: "data" }] as any
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Função de consulta
|
|
||||||
consulta: async (params) => {
|
|
||||||
// Aqui você conectaria com sua API, passando params.filtros, params.texto_busca, etc.
|
|
||||||
console.log("Filtros aplicados:", params.filtros);
|
|
||||||
|
|
||||||
return {
|
|
||||||
cod: 0,
|
|
||||||
eCerto: true,
|
|
||||||
eErro: false,
|
|
||||||
mensagem: undefined,
|
|
||||||
valor: {
|
|
||||||
quantidade: 1,
|
|
||||||
valores: [
|
|
||||||
{ id: 1, nome: "Luiz", documento: "123", email: "a@a.com", ativo: true, criado_em: "2024-01-01" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tipos de Células (`celulaTabela`)
|
|
||||||
|
|
||||||
Helper: `celulaTabela(tipo, dados)`
|
|
||||||
|
|
||||||
- `textoSimples`: `{ texto: string; acao?: () => void }`
|
|
||||||
- `textoTruncado`: `{ texto: string; acao?: () => void }`
|
|
||||||
- `numero`: `{ numero: number; prefixo?: string; sufixo?: string; acao?: () => void }`
|
|
||||||
- `tags`: `{ opcoes: { rotulo: string; cor?: string; icone?: LucideIcon; acao?: () => void }[] }`
|
|
||||||
- `data`: `{ valor: string; formato: "data" | "data_hora" | "relativo"; acao?: () => void }`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting (para IAs)
|
|
||||||
|
|
||||||
### 1) “Failed to resolve component”
|
|
||||||
|
|
||||||
Provável causa: componente não foi registrado.
|
|
||||||
- Se estiver usando plugin: confirme `app.use(EliVue)`
|
|
||||||
- Se estiver importando direto: registre localmente no componente
|
|
||||||
|
|
||||||
### 2) Estilos não aplicam ou componentes "quebrados"
|
|
||||||
|
|
||||||
**Causa provável**: O CSS não foi injetado corretamente pelo JavaScript.
|
|
||||||
|
|
||||||
O `eli-vue` injeta o CSS automaticamente via JS. Verifique se o bundle está sendo carregado corretamente e se não há bloqueios de Content Security Policy (CSP) para injeção de estilos `<style>`.
|
|
||||||
|
|
||||||
Anteriormente era necessário importar o CSS manualmente, mas **isso não é mais necessário**.
|
|
||||||
|
|
||||||
|
|
||||||
### 3) Erro de tipo em `celulaTabela`
|
|
||||||
|
|
||||||
Verifique se você importou `celulaTabela` de `eli-vue`.
|
|
||||||
Certifique-se de que o segundo argumento corresponde ao tipo da célula (ex: `textoSimples` pede `{ texto: string }`).
|
|
||||||
|
|
||||||
### 4) Ícones não aparecem
|
|
||||||
|
|
||||||
O `eli-vue` espera ícones do pacote `lucide-vue-next` (ou compatíveis com Component type do Vue).
|
|
||||||
Certifique-se de passar o componente do ícone (ex: `Pencil`), e não string.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Como a IA deve agir ao modificar um projeto consumidor
|
|
||||||
|
|
||||||
Quando for integrar `eli-vue` num projeto existente:
|
|
||||||
|
|
||||||
1) Verifique se **Vue 3**, **Vuetify 3** e **lucide-vue-next** estão instalados.
|
|
||||||
2) Prefira usar o **plugin** do `eli-vue` (simplifica registro global).
|
|
||||||
3) Use `celulaTabela` para construir colunas de tabelas de forma tipada.
|
|
||||||
5) Ao definir tabelas, use `tipoEliTabelaConsulta<T>` para garantir que colunas e filtros batam com o tipo de dados.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quando este arquivo deve ser atualizado
|
|
||||||
|
|
||||||
Atualize este `IA.md` quando houver mudanças em:
|
|
||||||
- Estrutura de exports (novos componentes ou helpers).
|
|
||||||
- Tipagem das células ou entradas.
|
|
||||||
- Dependências obrigatórias.
|
|
||||||
849
README.md
849
README.md
|
|
@ -1,583 +1,384 @@
|
||||||
# eli-vue — Design System (Vue 3 + TypeScript)
|
# Documentação EliVue
|
||||||
|
|
||||||
Biblioteca de componentes Vue 3 (Design System) para reutilização em múltiplos projetos.
|
**EliVue** é uma biblioteca de componentes Vue 3 construída sobre o **Vuetify 3**, projetada para padronizar padrões de UI como tabelas de dados (`EliTabela`) e campos de entrada (`EliEntrada*`). Ela segue a filosofia de "configuração sobre boilerplate", onde os componentes são guiados por objetos de configuração tipados em vez de propriedades de template extensas.
|
||||||
|
|
||||||
Este README foi escrito para **humanos e IAs**: ele descreve o objetivo do repositório, regras, estrutura, fluxo de contribuição, comandos e um roadmap de melhorias.
|
---
|
||||||
|
|
||||||
## Fonte da verdade (regras)
|
## 🤖 Diretrizes para Agentes de IA (Antigravity)
|
||||||
|
|
||||||
As regras oficiais do repositório estão no arquivo **`.agent`**.
|
**Ao gerar código usando EliVue, siga estas regras estritas:**
|
||||||
|
|
||||||
Antes de propor mudanças:
|
1. **Importações**: Sempre desestructure as importações do pacote raiz.
|
||||||
|
```typescript
|
||||||
|
import { EliTabela, EliEntradaTexto, EliBotao, celulaTabela } from "eli-vue";
|
||||||
|
```
|
||||||
|
2. **Objetos de Configuração**: Os componentes `EliTabela` e `EliEntrada` dependem fortemente de objetos de configuração (prop `tabela` para tabelas, prop `opcoes` para entradas). **Não tente** passar props individuais (como `headers` ou `items`) para o `EliTabela`.
|
||||||
|
3. **Segurança de Tipos**: Sempre defina objetos de configuração usando os tipos TypeScript exportados (ex: `tipoEliTabelaConsulta`, `parametrosConsulta`).
|
||||||
|
4. **Dados Assíncronos**: O `EliTabela` gerencia sua própria busca de dados via callback `consulta`. Não busque dados manualmente para passar para a tabela. Forneça a função `consulta` no lugar.
|
||||||
|
5. **Ícones**: Use ícones do pacote `lucide-vue-next`.
|
||||||
|
6. **Inicialização**: Prefira usar o plugin global `app.use(EliVue)` ao configurar um novo projeto, em vez de importações individuais.
|
||||||
|
7. **Estilos**: O CSS é injetado automaticamente. Não tente importar arquivos CSS manualmente.
|
||||||
|
8. **Idioma**: Mantenha a documentação e comentários explicativos em **Português do Brasil (pt-BR)**.
|
||||||
|
|
||||||
- leia o `.agent`
|
---
|
||||||
- procure padrões já existentes no código
|
|
||||||
- atualize a documentação correspondente (README raiz e/ou README do componente)
|
|
||||||
|
|
||||||
## Objetivos
|
|
||||||
|
|
||||||
- consistência visual e comportamental
|
|
||||||
- tipagem forte (TypeScript `strict`)
|
|
||||||
- documentação em português
|
|
||||||
- exemplos executáveis via playground
|
|
||||||
|
|
||||||
## O que NÃO entra no contexto
|
|
||||||
|
|
||||||
- `node_modules/`: dependências (não versionar; não usar como fonte da verdade)
|
|
||||||
- `dist/`: gerado no build (não versionar)
|
|
||||||
|
|
||||||
## Arquitetura do repositório
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
componentes/
|
|
||||||
botao/
|
|
||||||
EliBotao.vue
|
|
||||||
index.ts
|
|
||||||
README.md
|
|
||||||
cartao/
|
|
||||||
EliCartao.vue
|
|
||||||
index.ts
|
|
||||||
README.md
|
|
||||||
indicador/
|
|
||||||
EliBadge.vue
|
|
||||||
index.ts
|
|
||||||
README.md
|
|
||||||
EliEntrada/
|
|
||||||
EliEntradaTexto.vue
|
|
||||||
EliEntradaNumero.vue
|
|
||||||
EliEntradaDataHora.vue
|
|
||||||
index.ts
|
|
||||||
README.md
|
|
||||||
EliTabela/
|
|
||||||
EliTabela.vue
|
|
||||||
index.ts
|
|
||||||
README.md
|
|
||||||
celulas/
|
|
||||||
README.md
|
|
||||||
tipos/
|
|
||||||
botao.ts
|
|
||||||
indicador.ts
|
|
||||||
index.ts
|
|
||||||
playground/
|
|
||||||
App.vue
|
|
||||||
*.playground.vue
|
|
||||||
index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Convenções (nomenclatura)
|
|
||||||
|
|
||||||
- Componentes usam **prefixo `Eli`** (ex.: `EliBotao`, `EliEntradaTexto`).
|
|
||||||
- Pastas preferem **português** (ex.: `src/componentes/botao`, `src/componentes/campo`).
|
|
||||||
- Tipos compartilhados ficam em `src/tipos/`.
|
|
||||||
- Sem TSX; padrão SFC: `<template>` + `<script lang="ts">` + `defineComponent`.
|
|
||||||
- `any` é proibido.
|
|
||||||
|
|
||||||
## Instalação
|
## Instalação
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm add eli-vue
|
pnpm add eli-vue
|
||||||
|
# Dependências (Peer Dependencies)
|
||||||
|
pnpm add vue vuetify lucide-vue-next
|
||||||
```
|
```
|
||||||
|
|
||||||
> Observação: `vue` e `vuetify` são **peerDependencies**.
|
Certifique-se de que seu projeto esteja configurado com Vuetify 3.
|
||||||
|
|
||||||
### Instalação (projeto consumidor já usa Vuetify)
|
---
|
||||||
|
|
||||||
Se o seu projeto já está configurado com Vuetify 3, basta instalar este pacote:
|
## 📦 Referência de Componentes
|
||||||
|
|
||||||
```bash
|
### 1. EliTabela (Tabela de Dados)
|
||||||
pnpm add eli-vue
|
|
||||||
|
O componente `EliTabela` é uma tabela poderosa com paginação no servidor (server-side), busca integrada, ordenação e menus de ação.
|
||||||
|
|
||||||
|
#### API
|
||||||
|
- **Prop**: `tabela` (Obrigatório)
|
||||||
|
- **Tipo**: `tipoEliTabelaConsulta<T>`
|
||||||
|
|
||||||
|
#### Interface de Configuração (`tipoEliTabelaConsulta`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// De: eli-vue/src/componentes/EliTabela/types-eli-tabela.ts
|
||||||
|
|
||||||
|
export type tipoEliTabelaConsulta<T> = {
|
||||||
|
// Identificador único para a tabela (usado para persistência local de visibilidade das colunas)
|
||||||
|
nome: string;
|
||||||
|
|
||||||
|
// Função para buscar dados. DEVE retornar um objeto de resposta padrão.
|
||||||
|
consulta: (params: parametrosConsulta<T>) => Promise<tipoResposta<tipoEliConsultaPaginada<T>>>;
|
||||||
|
|
||||||
|
// Definições das colunas
|
||||||
|
colunas: tipoEliColuna<T>[];
|
||||||
|
|
||||||
|
// Opções de UI
|
||||||
|
mostrarCaixaDeBusca?: boolean;
|
||||||
|
registros_por_consulta?: number; // padrão: 10
|
||||||
|
maximo_botoes_paginacao?: number; // padrão: 7
|
||||||
|
mensagemVazio?: string;
|
||||||
|
|
||||||
|
// Ações
|
||||||
|
acoesLinha?: tipoEliTabelaAcao<T>[]; // Ações para cada linha (menu de contexto)
|
||||||
|
acoesTabela?: Array<{ // Ações globais (superior/inferior)
|
||||||
|
// Ações da Tabela (Botões Globais)
|
||||||
|
acoesTabela?: Array<{
|
||||||
|
/**
|
||||||
|
* Posição do botão:
|
||||||
|
* - "superior": Ao lado da caixa de busca (canto superior direito).
|
||||||
|
* - "inferior": No rodapé, alinhado à esquerda (canto inferior esquerdo).
|
||||||
|
*/
|
||||||
|
posicao: "superior" | "inferior";
|
||||||
|
|
||||||
|
/** Rótulo do botão */
|
||||||
|
rotulo: string;
|
||||||
|
|
||||||
|
/** Ícone (Lucide) */
|
||||||
|
icone?: LucideIcon;
|
||||||
|
|
||||||
|
/** Cor do botão (ex: "primary", "success", "error") */
|
||||||
|
cor?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Função executada ao clicar.
|
||||||
|
* Recebe um objeto contendo:
|
||||||
|
* - Parâmetros atuais da consulta (offSet, limit, filtros, etc.)
|
||||||
|
* - Helper `atualizarConsulta()`: Promise<void> para recarregar a tabela.
|
||||||
|
*/
|
||||||
|
acao: (params: parametrosConsulta<T> & { atualizarConsulta: () => Promise<void> }) => void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
// Definição de Filtro Avançado
|
||||||
|
filtroAvancado?: Array<{
|
||||||
|
rotulo: string;
|
||||||
|
coluna: keyof T;
|
||||||
|
operador: string; // ex: "=", "like", ">="
|
||||||
|
entrada: any; // Definição do componente, ex: ["texto", { ... }]
|
||||||
|
}>;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
> Dica: mantenha `vue` e `vuetify` atualizados dentro das faixas suportadas.
|
#### Definindo Colunas (helper `celulaTabela`)
|
||||||
|
|
||||||
## Uso
|
Use o helper `celulaTabela` para criar definições de células com segurança de tipo.
|
||||||
|
|
||||||
### 1) Registro global (plugin)
|
```typescript
|
||||||
|
// Tipos de células disponíveis e seus dados:
|
||||||
|
// "textoSimples": { texto: string; acao?: () => void }
|
||||||
|
// "textoTruncado": { texto: string; acao?: () => void }
|
||||||
|
// "numero": { numero: number; prefixo?: string; sufixo?: string; acao?: () => void }
|
||||||
|
// "tags": { opcoes: Array<{ rotulo: string; cor?: string; icone?: LucideIcon; acao?: () => void }> }
|
||||||
|
// "data": { valor: string; formato: "data" | "data_hora" | "relativo"; acao?: () => void }
|
||||||
|
// "badge": { texto: string; cor: string }
|
||||||
|
|
||||||
```ts
|
{
|
||||||
import { createApp } from "vue";
|
rotulo: "Nome",
|
||||||
import EliVue from "eli-vue";
|
celula: (row) => celulaTabela("textoSimples", { texto: row.nome }),
|
||||||
|
visivel: true
|
||||||
import App from "./App.vue";
|
}
|
||||||
|
|
||||||
createApp(App).use(EliVue).mount("#app");
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Exemplo completo (main.ts) — com Vuetify 3
|
#### Exemplo de Uso com Filtros Avançados
|
||||||
|
|
||||||
Este é o modelo recomendado para projetos consumidores (Vite + Vue 3) **que já usam Vuetify 3**:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { createApp } from "vue";
|
|
||||||
import App from "./App.vue";
|
|
||||||
|
|
||||||
// Vuetify
|
|
||||||
import "vuetify/styles";
|
|
||||||
import { createVuetify } from "vuetify";
|
|
||||||
|
|
||||||
// eli-vue (Design System)
|
|
||||||
import EliVue from "eli-vue";
|
|
||||||
|
|
||||||
const vuetify = createVuetify({
|
|
||||||
// opcional: componentes/directives/tema do seu projeto
|
|
||||||
});
|
|
||||||
|
|
||||||
createApp(App)
|
|
||||||
.use(vuetify)
|
|
||||||
.use(EliVue)
|
|
||||||
.mount("#app");
|
|
||||||
```
|
|
||||||
|
|
||||||
> Nota: o `eli-vue` **não cria** nem configura Vuetify; ele assume que o consumidor já tem Vuetify.
|
|
||||||
|
|
||||||
### 2) Importação direta de componentes
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import {
|
|
||||||
EliBotao,
|
|
||||||
EliBadge,
|
|
||||||
EliCartao,
|
|
||||||
EliTabela,
|
|
||||||
EliEntradaTexto,
|
|
||||||
EliEntradaNumero,
|
|
||||||
EliEntradaDataHora,
|
|
||||||
EliEntradaParagrafo,
|
|
||||||
EliEntradaSelecao,
|
|
||||||
} from "eli-vue";
|
|
||||||
```
|
|
||||||
|
|
||||||
## Exemplos rápidos de uso
|
|
||||||
|
|
||||||
### EliBotao
|
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { EliTabela, celulaTabela } from "eli-vue";
|
||||||
|
import type { tipoEliTabelaConsulta } from "eli-vue";
|
||||||
|
import { UsuarioService } from "@/services/UsuarioService";
|
||||||
|
import { BadgeCheck, Pencil, Plus } from "lucide-vue-next";
|
||||||
|
|
||||||
|
type Usuario = { id: number; nome: string; email: string; ativo: boolean; created_at: string };
|
||||||
|
|
||||||
|
const tabelaConfig: tipoEliTabelaConsulta<Usuario> = {
|
||||||
|
nome: "tabela-usuarios",
|
||||||
|
mostrarCaixaDeBusca: true,
|
||||||
|
registros_por_consulta: 10,
|
||||||
|
|
||||||
|
colunas: [
|
||||||
|
{
|
||||||
|
rotulo: "Nome",
|
||||||
|
celula: (usuario) => celulaTabela("textoSimples", { texto: usuario.nome }),
|
||||||
|
visivel: true,
|
||||||
|
coluna_ordem: "nome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rotulo: "Status",
|
||||||
|
visivel: true,
|
||||||
|
celula: (usuario) => celulaTabela("tags", {
|
||||||
|
opcoes: [
|
||||||
|
usuario.ativo
|
||||||
|
? { rotulo: "Ativo", cor: "success", icone: BadgeCheck }
|
||||||
|
: { rotulo: "Inativo", cor: "error" }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rotulo: "Criado em",
|
||||||
|
celula: (usuario) => celulaTabela("data", { valor: usuario.created_at, formato: "data_hora" }),
|
||||||
|
visivel: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Configuração de filtros avançados
|
||||||
|
filtroAvancado: [
|
||||||
|
{
|
||||||
|
rotulo: "Nome",
|
||||||
|
coluna: "nome",
|
||||||
|
operador: "like",
|
||||||
|
// definição do input: ["tipo", opcoes]
|
||||||
|
entrada: ["texto", { rotulo: "Nome do usuário" }] as any
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rotulo: "Ativo?",
|
||||||
|
coluna: "ativo",
|
||||||
|
operador: "=",
|
||||||
|
entrada: ["selecao", {
|
||||||
|
rotulo: "Status",
|
||||||
|
itens: () => [
|
||||||
|
{ chave: "true", rotulo: "Ativo" },
|
||||||
|
{ chave: "false", rotulo: "Inativo" }
|
||||||
|
]
|
||||||
|
}] as any
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
acoesTabela: [
|
||||||
|
{
|
||||||
|
rotulo: "Novo Usuário",
|
||||||
|
icone: Plus,
|
||||||
|
posicao: "superior",
|
||||||
|
cor: "primary",
|
||||||
|
// O callback recebe: offSet, limit, filtros, e o helper 'atualizarConsulta'
|
||||||
|
acao: async ({ atualizarConsulta }) => {
|
||||||
|
// Exemplo: Abrir modal de criação e depois atualizar a tabela
|
||||||
|
await openCreateUserModal();
|
||||||
|
await atualizarConsulta();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rotulo: "Exportar CSV",
|
||||||
|
icone: Download,
|
||||||
|
posicao: "inferior",
|
||||||
|
cor: "success",
|
||||||
|
acao: (params) => {
|
||||||
|
// 'params' contém os filtros atuais aplicados na tabela
|
||||||
|
UsuarioService.exportarCsv(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
consulta: async (params) => {
|
||||||
|
// params contém: offSet, limit, texto_busca, coluna_ordem, direcao_ordem, filtros
|
||||||
|
return await UsuarioService.listar(params);
|
||||||
|
},
|
||||||
|
|
||||||
|
acoesLinha: [
|
||||||
|
{
|
||||||
|
rotulo: "Editar",
|
||||||
|
icone: Pencil,
|
||||||
|
cor: "primary",
|
||||||
|
acao: (usuario) => console.log("Editar", usuario)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<EliBotao @click="salvar">Salvar</EliBotao>
|
<EliTabela :tabela="tabelaConfig" />
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Entradas (EliEntrada*) com v-model
|
---
|
||||||
|
|
||||||
|
### 2. EliEntrada (Inputs)
|
||||||
|
|
||||||
|
Um conjunto de wrappers padronizados em torno dos campos do Vuetify.
|
||||||
|
|
||||||
|
#### Variantes
|
||||||
|
- `EliEntradaTexto`: Texto, Email, URL, CPF, CNPJ, CEP.
|
||||||
|
- `EliEntradaNumero`: Inteiros, Decimais, Moeda.
|
||||||
|
- `EliEntradaDataHora`: Data, DataHora.
|
||||||
|
- `EliEntradaSelecao`: Select/Dropdown (pode ser assíncrono).
|
||||||
|
- `EliEntradaParagrafo`: Textarea.
|
||||||
|
|
||||||
|
#### API Comum
|
||||||
|
Todas as entradas aceitam duas props principais:
|
||||||
|
1. `value`: O binding v-model.
|
||||||
|
2. `opcoes`: Um objeto de configuração específico para o tipo de entrada.
|
||||||
|
|
||||||
|
#### Objetos de Configuração (`tiposEntradas.ts`)
|
||||||
|
|
||||||
|
**EliEntradaTexto**
|
||||||
|
```typescript
|
||||||
|
opcoes: {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
limiteCaracteres?: number;
|
||||||
|
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**EliEntradaNumero**
|
||||||
|
```typescript
|
||||||
|
opcoes: {
|
||||||
|
rotulo: string;
|
||||||
|
precisao?: number; // 0=inteiro (padrão se null), 0.1=1 decimal, 0.01=2 decimais. Se undefined/null, assume decimal livre.
|
||||||
|
prefixo?: string; // ex: "R$"
|
||||||
|
sufixo?: string; // ex: "kg"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**EliEntradaDataHora**
|
||||||
|
```typescript
|
||||||
|
opcoes: {
|
||||||
|
rotulo: string;
|
||||||
|
modo?: "data" | "dataHora"; // padrão: "dataHora"
|
||||||
|
min?: string; // String ISO
|
||||||
|
max?: string; // String ISO
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**EliEntradaSelecao**
|
||||||
|
```typescript
|
||||||
|
opcoes: {
|
||||||
|
rotulo: string;
|
||||||
|
// Função que retorna itens ou promise de itens.
|
||||||
|
// IMPORTANTE: Itens devem ser objetos { chave: string, rotulo: string }
|
||||||
|
itens: () => Array<{ chave: string; rotulo: string }> | Promise<Array<{ chave: string; rotulo: string }>>;
|
||||||
|
limpavel?: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Exemplo de Uso
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "eli-vue";
|
||||||
|
|
||||||
|
const nome = ref("");
|
||||||
|
const salario = ref<number | null>(null);
|
||||||
|
const nascimento = ref<string | null>(null); // ISO string
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<EliEntradaTexto
|
<EliEntradaTexto
|
||||||
v-model:value="nome"
|
:value="nome"
|
||||||
:opcoes="{ rotulo: 'Nome', placeholder: 'Digite seu nome' }"
|
@update:value="nome = $event"
|
||||||
|
:opcoes="{ rotulo: 'Nome Completo', formato: 'texto' }"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const nome = ref<string | null>("");
|
|
||||||
return { nome };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### EliEntradaNumero (exemplo)
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliEntradaNumero
|
<EliEntradaNumero
|
||||||
v-model:value="taxa"
|
:value="salario"
|
||||||
:opcoes="{ rotulo: 'Taxa', placeholder: '0,00' }"
|
@update:value="salario = $event"
|
||||||
|
:opcoes="{ rotulo: 'Salário', prefixo: 'R$', precisao: 0.01 }"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
|
<!-- Exibe como data local, vincula como string ISO -->
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const taxa = ref<number | null>(null);
|
|
||||||
return { taxa };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### EliBadge
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliBadge badge="3">
|
|
||||||
<v-icon>mdi-bell</v-icon>
|
|
||||||
</EliBadge>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### EliEntradaDataHora
|
|
||||||
|
|
||||||
> Entrada/saída sempre em **ISO 8601**.
|
|
||||||
> - Aceita UTC absoluto (`Z`) ou com offset.
|
|
||||||
> - Emite ISO com **offset local**.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<EliEntradaDataHora v-model:value="agendamento" :opcoes="{ rotulo: 'Agendamento' }" />
|
|
||||||
<EliEntradaDataHora
|
<EliEntradaDataHora
|
||||||
v-model:value="nascimento"
|
:value="nascimento"
|
||||||
:opcoes="{ rotulo: 'Nascimento', modo: 'data' }"
|
@update:value="nascimento = $event"
|
||||||
|
:opcoes="{ rotulo: 'Data de Nascimento', modo: 'data' }"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
import { EliEntradaDataHora } from "eli-vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { EliEntradaDataHora },
|
|
||||||
setup() {
|
|
||||||
const agendamento = ref<string | null>("2026-01-09T16:15:00Z");
|
|
||||||
const nascimento = ref<string | null>("2026-01-09T00:00:00-03:00");
|
|
||||||
return { agendamento, nascimento };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### EliTabela — célula `tags`
|
---
|
||||||
|
|
||||||
Você pode renderizar múltiplas tags/chips numa célula usando `celulaTabela("tags", ...)`.
|
### 3. EliBotao
|
||||||
|
|
||||||
```ts
|
Wrapper simples para `v-btn`.
|
||||||
import { celulaTabela } from "eli-vue";
|
|
||||||
import { BadgeCheck, Pencil } from "lucide-vue-next";
|
|
||||||
|
|
||||||
celula: (l) =>
|
- **Props**: `color`, `variant` (elevated, flat, text, etc.), `size`, `loading`, `disabled`.
|
||||||
celulaTabela("tags", {
|
- **Slots**: slot padrão para o conteúdo do botão.
|
||||||
opcoes: [
|
|
||||||
{ rotulo: "Ativo", cor: "success", icone: BadgeCheck },
|
|
||||||
{ rotulo: "Editar", cor: "primary", icone: Pencil, acao: () => editar(l) },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### EliTabela — célula `numero` com prefixo/sufixo
|
|
||||||
|
|
||||||
Para representar **moeda** ou **unidade de medida**, a célula `numero` aceita `prefixo` e `sufixo`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { celulaTabela } from "eli-vue";
|
|
||||||
|
|
||||||
// moeda
|
|
||||||
celula: (l) => celulaTabela("numero", { numero: l.total, prefixo: "R$" })
|
|
||||||
|
|
||||||
// unidade
|
|
||||||
celula: (l) => celulaTabela("numero", { numero: l.peso, sufixo: "kg" })
|
|
||||||
```
|
|
||||||
|
|
||||||
### EliTabela — célula `data`
|
|
||||||
|
|
||||||
Para exibir datas (ISO 8601) com diferentes formatos, use a célula `data`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { celulaTabela } from "eli-vue";
|
|
||||||
|
|
||||||
// somente data
|
|
||||||
celula: (l) => celulaTabela("data", { valor: l.criado_em, formato: "data" })
|
|
||||||
|
|
||||||
// data e hora
|
|
||||||
celula: (l) => celulaTabela("data", { valor: l.criado_em, formato: "data_hora" })
|
|
||||||
|
|
||||||
// relativo (dayjs)
|
|
||||||
celula: (l) => celulaTabela("data", { valor: l.atualizado_em, formato: "relativo" })
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting (problemas comuns)
|
|
||||||
|
|
||||||
### 1) "Failed to resolve component" / componente não registrado
|
|
||||||
|
|
||||||
Você provavelmente esqueceu de:
|
|
||||||
|
|
||||||
- usar o plugin: `app.use(EliVue)`
|
|
||||||
- ou importar e registrar o componente localmente
|
|
||||||
|
|
||||||
### 2) Estilos não aplicam / layout estranho
|
|
||||||
|
|
||||||
Confirme que o consumidor importou:
|
|
||||||
|
|
||||||
- `vuetify/styles`
|
|
||||||
|
|
||||||
### 3) Ícones não aparecem
|
|
||||||
|
|
||||||
Alguns exemplos usam `<v-icon>` com Material Design Icons. O `eli-vue` **não instala ícones** no seu projeto.
|
|
||||||
|
|
||||||
- garanta que o seu projeto consumidor está configurado com o pacote de ícones desejado (ex.: MDI)
|
|
||||||
- ou substitua o conteúdo do slot por outro elemento
|
|
||||||
|
|
||||||
## Exemplo de composição (pipeline estilo Trello)
|
|
||||||
|
|
||||||
O caso mais comum no **gestor de oportunidades e propostas comerciais** é compor UI a partir de componentes básicos.
|
|
||||||
Exemplo: um **pipeline** em colunas (estilo Trello/Kanban) com cards de oportunidades.
|
|
||||||
|
|
||||||
> Este exemplo usa Vuetify para layout/containers e o `eli-vue` para inputs, botões e indicadores.
|
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<EliBotao color="primary" @click="salvar">
|
||||||
<v-container class="py-6">
|
Salvar
|
||||||
<div class="toolbar">
|
</EliBotao>
|
||||||
<h2 class="text-h6">Pipeline de Oportunidades</h2>
|
|
||||||
<EliEntradaTexto
|
|
||||||
v-model:value="filtro"
|
|
||||||
:opcoes="{ rotulo: 'Buscar', placeholder: 'Cliente, proposta, valor...' }"
|
|
||||||
/>
|
|
||||||
<EliBotao @click="criarOportunidade">Nova oportunidade</EliBotao>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="colunas">
|
|
||||||
<section
|
|
||||||
v-for="coluna in colunas"
|
|
||||||
:key="coluna.id"
|
|
||||||
class="coluna"
|
|
||||||
>
|
|
||||||
<header class="coluna-header">
|
|
||||||
<strong>{{ coluna.titulo }}</strong>
|
|
||||||
<EliBadge :badge="coluna.itens.length" radius="pill" />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="cards">
|
|
||||||
<v-card
|
|
||||||
v-for="item in itensFiltrados(coluna.itens)"
|
|
||||||
:key="item.id"
|
|
||||||
class="card"
|
|
||||||
variant="outlined"
|
|
||||||
>
|
|
||||||
<v-card-title class="d-flex justify-space-between align-center">
|
|
||||||
<span class="text-subtitle-2">{{ item.titulo }}</span>
|
|
||||||
<EliBadge
|
|
||||||
v-if="item.alerta"
|
|
||||||
dot
|
|
||||||
color="error"
|
|
||||||
:visible="true"
|
|
||||||
>
|
|
||||||
<span />
|
|
||||||
</EliBadge>
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<v-card-text class="text-body-2">
|
|
||||||
<div><strong>Cliente:</strong> {{ item.cliente }}</div>
|
|
||||||
<div><strong>Valor:</strong> {{ item.valor }}</div>
|
|
||||||
<div><strong>Vencimento:</strong> {{ item.vencimento }}</div>
|
|
||||||
</v-card-text>
|
|
||||||
|
|
||||||
<v-card-actions>
|
|
||||||
<EliBotao variant="text" @click="abrir(item)">Abrir</EliBotao>
|
|
||||||
<EliBotao variant="text" color="secondary" @click="editar(item)">
|
|
||||||
Editar
|
|
||||||
</EliBotao>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
import { EliBadge, EliBotao, EliEntradaTexto } from "eli-vue";
|
|
||||||
|
|
||||||
type Oportunidade = {
|
|
||||||
id: string;
|
|
||||||
titulo: string;
|
|
||||||
cliente: string;
|
|
||||||
valor: string;
|
|
||||||
vencimento: string;
|
|
||||||
alerta?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Coluna = {
|
|
||||||
id: string;
|
|
||||||
titulo: string;
|
|
||||||
itens: Oportunidade[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "PipelineExemplo",
|
|
||||||
components: { EliBadge, EliBotao, EliEntradaTexto },
|
|
||||||
setup() {
|
|
||||||
const filtro = ref("");
|
|
||||||
|
|
||||||
const colunas = ref<Coluna[]>([
|
|
||||||
{
|
|
||||||
id: "novo",
|
|
||||||
titulo: "Novo",
|
|
||||||
itens: [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
titulo: "Proposta #1024",
|
|
||||||
cliente: "ACME Ltda",
|
|
||||||
valor: "R$ 12.500,00",
|
|
||||||
vencimento: "10/01/2026",
|
|
||||||
alerta: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "negociacao",
|
|
||||||
titulo: "Negociação",
|
|
||||||
itens: [
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
titulo: "Oportunidade #204",
|
|
||||||
cliente: "Empresa X",
|
|
||||||
valor: "R$ 8.000,00",
|
|
||||||
vencimento: "15/01/2026",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ganho",
|
|
||||||
titulo: "Ganho",
|
|
||||||
itens: [],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
function itensFiltrados(itens: Oportunidade[]) {
|
|
||||||
const q = filtro.value.trim().toLowerCase();
|
|
||||||
if (!q) return itens;
|
|
||||||
return itens.filter((i) =>
|
|
||||||
`${i.titulo} ${i.cliente} ${i.valor}`.toLowerCase().includes(q)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function criarOportunidade() {
|
|
||||||
// aqui você abriria um modal / rota de criação
|
|
||||||
}
|
|
||||||
|
|
||||||
function abrir(item: Oportunidade) {
|
|
||||||
// aqui você navega para detalhes da oportunidade
|
|
||||||
}
|
|
||||||
|
|
||||||
function editar(item: Oportunidade) {
|
|
||||||
// aqui você abre edição
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
filtro,
|
|
||||||
colunas,
|
|
||||||
itensFiltrados,
|
|
||||||
criarOportunidade,
|
|
||||||
abrir,
|
|
||||||
editar,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.toolbar {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr minmax(260px, 420px) auto;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colunas {
|
|
||||||
display: grid;
|
|
||||||
gap: 16px;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.coluna {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 12px;
|
|
||||||
background: rgba(0, 0, 0, 0.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
.coluna-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cards {
|
|
||||||
display: grid;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
.toolbar {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Como rodar localmente
|
---
|
||||||
|
|
||||||
### Playground
|
### 4. EliCartao & EliBadge
|
||||||
|
|
||||||
```bash
|
**EliCartao**
|
||||||
pnpm dev
|
Cartão padronizado para itens de domínio.
|
||||||
```
|
- **Props**: `titulo`, `status` (tipo `CartaoStatus`: "novo" | "rascunho" | "vendido" | "cancelado"), `variant`.
|
||||||
|
- **Slots**: `default` (conteúdo), `acoes` (ações de rodapé).
|
||||||
|
|
||||||
O playground (`src/playground`) valida visualmente variações e interações.
|
**EliBadge**
|
||||||
|
Badge aprimorado com raios.
|
||||||
|
- **Props**: `badge` (conteúdo), `color`, `radius` ("suave" | "pill").
|
||||||
|
|
||||||
### Build da lib + geração de tipos
|
---
|
||||||
|
|
||||||
```bash
|
## 🔧 Solução de Problemas e Dicas
|
||||||
pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Gera `dist/` (artefatos de build) e `dist/types` (declarações `.d.ts`).
|
### 1. "Failed to resolve component"
|
||||||
|
- Certifique-se de que `app.use(EliVue)` foi chamado no `main.ts`.
|
||||||
|
- Se importar diretamente, garanta importações padrão: `import { EliTabela } from 'eli-vue'`.
|
||||||
|
|
||||||
> Observação importante: este repositório roda `npm version patch --no-git-tag-version` no `prebuild`.
|
### 2. Problemas de Estilo
|
||||||
> Ou seja, ao rodar `pnpm run build` a versão do `package.json` é incrementada automaticamente.
|
- **Injeção de CSS**: O `eli-vue` injeta CSS automaticamente. Verifique sua CSP (Política de Segurança de Conteúdo) se os estilos estiverem bloqueados.
|
||||||
|
- **Vuetify**: Certifique-se de que o Vuetify 3 está corretamente instalado e os estilos (`vuetify/styles`) estão importados no seu arquivo de entrada principal.
|
||||||
|
|
||||||
## Guia rápido para IAs (antes de codar)
|
### 3. Erros de TypeScript no `celulaTabela`
|
||||||
|
- Garanta que o segundo argumento corresponda ao esquema específico do tipo de célula.
|
||||||
|
- Exemplo: `celulaTabela("textoSimples", { texto: ... })` funciona, mas `celulaTabela("textoSimples", { numero: ... })` falhará.
|
||||||
|
|
||||||
1) **Leia** `.agent` e este README.
|
### 4. Ícones não aparecem
|
||||||
2) **Busque padrões** antes de criar API nova (props/emits/slots).
|
- Passe o **objeto do componente** (ex: importado de `lucide-vue-next`), não o nome em string.
|
||||||
3) **Não use `any`**.
|
- Exemplo: `icone: Pencil` (Correto) vs `icone: "pencil"` (Incorreto).
|
||||||
4) **Centralize tipos** em `src/tipos/`.
|
|
||||||
5) Ao mudar API/comportamento:
|
|
||||||
- atualize o `README.md` do componente
|
|
||||||
- atualize ou crie um `*.playground.vue`
|
|
||||||
6) Rode `pnpm run build` antes de finalizar.
|
|
||||||
|
|
||||||
## Como criar/alterar um componente (checklist)
|
### 5. Valores do `EliEntrada`
|
||||||
|
- `EliEntradaTexto` com `formato='cpfCnpj'` emite a string **formatada** (ex: "123.456.789-00").
|
||||||
1. Criar pasta em `src/componentes/<nome_em_portugues>/`
|
- `EliEntradaNumero` emite um `number` ou `null`.
|
||||||
2. Criar `EliNomeDoComponente.vue` com `defineComponent` + comentários úteis
|
- `EliEntradaDataHora` trabalha com **Strings ISO** (ex: "2023-01-01T00:00:00Z"). A exibição é localizada, mas o valor do modelo é sempre ISO.
|
||||||
3. Criar `index.ts` com re-export (ex.: `export { default as EliX } from './EliX.vue'`)
|
|
||||||
4. Criar `README.md` do componente (API, exemplos, casos de borda, A11y)
|
|
||||||
5. Criar playground em `src/playground/<nome>.playground.vue` (mín. 3 variações)
|
|
||||||
6. Exportar no `src/index.ts`
|
|
||||||
|
|
||||||
### Critérios de aceite (qualidade)
|
|
||||||
|
|
||||||
- `pnpm run build` passa (inclui `vue-tsc`)
|
|
||||||
- zero `any`
|
|
||||||
- `defineComponent` em todos os componentes
|
|
||||||
- README do componente atualizado quando API mudar
|
|
||||||
- playground demonstrando variações + interação
|
|
||||||
|
|
||||||
## Roadmap de melhorias (a aplicar)
|
|
||||||
|
|
||||||
### Curto prazo (qualidade / consistência)
|
|
||||||
- [ ] Padronizar comentários úteis em todos os componentes (explicar decisões e estados)
|
|
||||||
- [ ] Tipar `emits` com validação de payload (quando houver)
|
|
||||||
- [ ] Consolidar nomenclatura de props/eventos em português (avaliar breaking changes)
|
|
||||||
|
|
||||||
### Médio prazo (DX + segurança)
|
|
||||||
- [ ] Adicionar ESLint + Prettier e regras (incluindo proibição de `any`)
|
|
||||||
- [ ] Adicionar testes unitários com Vitest + @vue/test-utils
|
|
||||||
- [ ] Criar pipeline de CI rodando `pnpm run build`
|
|
||||||
|
|
||||||
### Longo prazo (docs e maturidade)
|
|
||||||
- [ ] Documentação visual (Storybook ou VitePress)
|
|
||||||
- [ ] Tokens de design (cores/spacing) e guideline de acessibilidade
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "eli-vue",
|
"name": "eli-vue",
|
||||||
"version": "0.1.97",
|
"version": "0.1.98",
|
||||||
"private": false,
|
"private": false,
|
||||||
"main": "./dist/eli-vue.umd.js",
|
"main": "./dist/eli-vue.umd.js",
|
||||||
"module": "./dist/eli-vue.es.js",
|
"module": "./dist/eli-vue.es.js",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue