This commit is contained in:
Luiz Silva 2026-01-29 16:10:58 -03:00
parent 1e3c4026e8
commit 8c5a31ef30
5 changed files with 185 additions and 59 deletions

39
.agent
View file

@ -40,6 +40,27 @@ Construir um Design System de componentes em **Vue 3** para reutilização em m
--- ---
## Convenção atual de entradas (IMPORTANTE)
O componente **`EliInput` foi removido**. O padrão atual é a família **`EliEntrada*`**:
- `EliEntradaTexto`
- `EliEntradaNumero`
- `EliEntradaDataHora`
E o contrato padrão para entradas é:
- prop `value`
- evento `update:value`
- prop obrigatória `opcoes` (contém `rotulo` e outras opções)
Exemplo:
```vue
<EliEntradaTexto v-model:value="nome" :opcoes="{ rotulo: 'Nome' }" />
```
---
## Estrutura obrigatória do repositório ## Estrutura obrigatória do repositório
- Cada componente deve possuir **sua própria pasta** em `src/componentes/` - Cada componente deve possuir **sua própria pasta** em `src/componentes/`
- Dentro de cada pasta do componente: - Dentro de cada pasta do componente:
@ -162,6 +183,24 @@ Evitar comentários óbvios (“isso é um botão”).
--- ---
## Convenção atual de EliTabela (IMPORTANTE)
### Filtro avançado
O filtro avançado da `EliTabela` é configurado via `tabela.filtroAvancado`.
Regras:
- O **operador é travado na definição** (o usuário não escolhe operador)
- Cada filtro pode ser usado **no máximo 1 vez**
- UI: modal mostra **apenas os componentes de entrada** definidos no filtro
- Persistência: salva em `localStorage` apenas `{ coluna, valor }[]` por `tabela.nome`
Se você for evoluir isso para backend:
- usar `parametrosConsulta.filtros` (`tipoFiltro[]`) no `tabela.consulta`
- manter compatibilidade com simulação local (quando necessário)
---
## Publicação do pacote (npm) ## Publicação do pacote (npm)
### Regra de publicação (sem usar `package.json.files`) ### Regra de publicação (sem usar `package.json.files`)

114
IA.md
View file

@ -61,7 +61,15 @@ createApp(App)
### 2) Importação direta (quando não quiser plugin) ### 2) Importação direta (quando não quiser plugin)
```ts ```ts
import { EliBotao, EliInput, EliBadge, EliCartao, EliDataHora } from "eli-vue"; import {
EliBotao,
EliBadge,
EliCartao,
EliTabela,
EliEntradaTexto,
EliEntradaNumero,
EliEntradaDataHora,
} from "eli-vue";
``` ```
> Observação: ainda pode ser necessário importar o CSS do pacote: > Observação: ainda pode ser necessário importar o CSS do pacote:
@ -82,11 +90,18 @@ import "eli-vue/dist/eli-vue.css";
</template> </template>
``` ```
### Input com v-model ### Entradas (EliEntrada*) com v-model
O `eli-vue` usa uma família de componentes `EliEntrada*` (em vez do antigo `EliInput`).
#### Texto
```vue ```vue
<template> <template>
<EliInput v-model="nome" label="Nome" placeholder="Digite seu nome" /> <EliEntradaTexto
v-model:value="nome"
:opcoes="{ rotulo: 'Nome', placeholder: 'Digite seu nome' }"
/>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -94,33 +109,37 @@ import { defineComponent, ref } from "vue";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const nome = ref(""); const nome = ref<string | null>("");
function salvar() { return { nome };
// ...
}
return { nome, salvar };
}, },
}); });
</script> </script>
``` ```
### Input de porcentagem #### Texto com formato/máscara
Quando precisar de um campo numérico com sufixo `%`, use `type="porcentagem"`. > Regra importante: o `value` emitido é **sempre o texto formatado** (igual ao que aparece no input).
```vue ```vue
<template> <template>
<EliInput v-model="taxa" type="porcentagem" label="Taxa" placeholder="0,00" /> <EliEntradaTexto
v-model:value="documento"
:opcoes="{ rotulo: 'CPF/CNPJ', formato: 'cpfCnpj' }"
/>
<EliEntradaTexto
v-model:value="telefone"
:opcoes="{ rotulo: 'Telefone', formato: 'telefone' }"
/>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const taxa = ref(""); const documento = ref<string | null>("");
return { taxa }; const telefone = ref<string | null>("");
return { documento, telefone };
}, },
}); });
</script> </script>
@ -131,10 +150,10 @@ export default defineComponent({
```vue ```vue
<template> <template>
<!-- Valor chega do backend em ISO 8601 (UTC/offset), e o componente exibe em horário local --> <!-- Valor chega do backend em ISO 8601 (UTC/offset), e o componente exibe em horário local -->
<EliDataHora v-model="dataHora" rotulo="Agendamento" /> <EliEntradaDataHora v-model:value="dataHora" :opcoes="{ rotulo: 'Agendamento' }" />
<!-- Somente data --> <!-- Somente data -->
<EliDataHora v-model="data" modo="data" rotulo="Nascimento" /> <EliEntradaDataHora v-model:value="data" :opcoes="{ rotulo: 'Nascimento', modo: 'data' }" />
</template> </template>
<script lang="ts"> <script lang="ts">
@ -152,6 +171,67 @@ export default defineComponent({
--- ---
## EliTabela (com filtro avançado)
O componente `EliTabela` suporta:
- ordenação
- paginação
- caixa de busca
- **filtro avançado (modal)**
### Contrato da tabela (resumo)
O tipo principal é `EliTabelaConsulta<T>` (genérico), exportado de `eli-vue`.
O `filtroAvancado` é uma lista de filtros pré-definidos (o usuário só escolhe quais usar e informa valores):
```ts
filtroAvancado?: {
rotulo: string
coluna: keyof T
operador: string // ex.: "like", "=", ">", "in", "isNull"
entrada: ["texto" | "numero" | "dataHora", { rotulo: string; ... }]
}[]
```
### Exemplo mínimo
```ts
import { EliTabela, celulaTabela } from "eli-vue";
import type { EliTabelaConsulta } from "eli-vue";
import type { ComponenteEntrada } from "eli-vue/dist/types/componentes/EliEntrada/tiposEntradas";
type Linha = { nome: string; documento: string; email: string };
const tabela: EliTabelaConsulta<Linha> = {
nome: "Exemplo",
mostrarCaixaDeBusca: true,
registros_por_consulta: 10,
colunas: [
{ rotulo: "Nome", celula: (l) => celulaTabela("textoSimples", { texto: l.nome }), visivel: true },
],
filtroAvancado: [
{
rotulo: "Documento",
coluna: "documento",
operador: "like",
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as unknown as ComponenteEntrada,
},
],
consulta: async () => ({
cod: 0,
eCerto: true,
eErro: false,
mensagem: undefined,
valor: { quantidade: 0, valores: [] },
}),
};
```
> Observação: em modo simulação/local, a tabela pode buscar uma lista completa e aplicar filtro/paginação localmente.
---
## Troubleshooting (para IAs) ## Troubleshooting (para IAs)
### 1) “Failed to resolve component” ### 1) “Failed to resolve component”

View file

@ -35,21 +35,26 @@ src/
EliBotao.vue EliBotao.vue
index.ts index.ts
README.md README.md
campo/ cartao/
EliInput.vue EliCartao.vue
index.ts index.ts
README.md README.md
indicador/ indicador/
EliBadge.vue EliBadge.vue
index.ts index.ts
README.md README.md
data_hora/ EliEntrada/
EliDataHora.vue EliEntradaTexto.vue
EliEntradaNumero.vue
EliEntradaDataHora.vue
index.ts
README.md
EliTabela/
EliTabela.vue
index.ts index.ts
README.md README.md
tipos/ tipos/
botao.ts botao.ts
campo.ts
indicador.ts indicador.ts
index.ts index.ts
playground/ playground/
@ -60,7 +65,7 @@ src/
### Convenções (nomenclatura) ### Convenções (nomenclatura)
- Componentes usam **prefixo `Eli`** (ex.: `EliBotao`, `EliInput`). - Componentes usam **prefixo `Eli`** (ex.: `EliBotao`, `EliEntradaTexto`).
- Pastas preferem **português** (ex.: `src/componentes/botao`, `src/componentes/campo`). - Pastas preferem **português** (ex.: `src/componentes/botao`, `src/componentes/campo`).
- Tipos compartilhados ficam em `src/tipos/`. - Tipos compartilhados ficam em `src/tipos/`.
- Sem TSX; padrão SFC: `<template>` + `<script lang="ts">` + `defineComponent`. - Sem TSX; padrão SFC: `<template>` + `<script lang="ts">` + `defineComponent`.
@ -128,7 +133,15 @@ createApp(App)
### 2) Importação direta de componentes ### 2) Importação direta de componentes
```ts ```ts
import { EliBotao, EliInput, EliBadge, EliDataHora } from "eli-vue"; import {
EliBotao,
EliBadge,
EliCartao,
EliTabela,
EliEntradaTexto,
EliEntradaNumero,
EliEntradaDataHora,
} from "eli-vue";
``` ```
## Exemplos rápidos de uso ## Exemplos rápidos de uso
@ -141,11 +154,14 @@ import { EliBotao, EliInput, EliBadge, EliDataHora } from "eli-vue";
</template> </template>
``` ```
### EliInput (v-model) ### Entradas (EliEntrada*) com v-model
```vue ```vue
<template> <template>
<EliInput v-model="nome" label="Nome" placeholder="Digite seu nome" /> <EliEntradaTexto
v-model:value="nome"
:opcoes="{ rotulo: 'Nome', placeholder: 'Digite seu nome' }"
/>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -153,20 +169,21 @@ import { defineComponent, ref } from "vue";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const nome = ref(""); const nome = ref<string | null>("");
return { nome }; return { nome };
}, },
}); });
</script> </script>
``` ```
### EliInput (porcentagem) ### EliEntradaNumero (exemplo)
Use `type="porcentagem"` quando precisar de um campo numérico com sufixo `%` embutido.
```vue ```vue
<template> <template>
<EliInput v-model="taxa" type="porcentagem" label="Taxa" placeholder="0,00" /> <EliEntradaNumero
v-model:value="taxa"
:opcoes="{ rotulo: 'Taxa', placeholder: '0,00' }"
/>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -174,7 +191,7 @@ import { defineComponent, ref } from "vue";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const taxa = ref(""); const taxa = ref<number | null>(null);
return { taxa }; return { taxa };
}, },
}); });
@ -191,7 +208,7 @@ export default defineComponent({
</template> </template>
``` ```
### EliDataHora ### EliEntradaDataHora
> Entrada/saída sempre em **ISO 8601**. > Entrada/saída sempre em **ISO 8601**.
> - Aceita UTC absoluto (`Z`) ou com offset. > - Aceita UTC absoluto (`Z`) ou com offset.
@ -199,16 +216,19 @@ export default defineComponent({
```vue ```vue
<template> <template>
<EliDataHora v-model="agendamento" rotulo="Agendamento" /> <EliEntradaDataHora v-model:value="agendamento" :opcoes="{ rotulo: 'Agendamento' }" />
<EliDataHora v-model="nascimento" modo="data" rotulo="Nascimento" /> <EliEntradaDataHora
v-model:value="nascimento"
:opcoes="{ rotulo: 'Nascimento', modo: 'data' }"
/>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
import { EliDataHora } from "eli-vue"; import { EliEntradaDataHora } from "eli-vue";
export default defineComponent({ export default defineComponent({
components: { EliDataHora }, components: { EliEntradaDataHora },
setup() { setup() {
const agendamento = ref<string | null>("2026-01-09T16:15:00Z"); const agendamento = ref<string | null>("2026-01-09T16:15:00Z");
const nascimento = ref<string | null>("2026-01-09T00:00:00-03:00"); const nascimento = ref<string | null>("2026-01-09T00:00:00-03:00");
@ -253,11 +273,9 @@ Exemplo: um **pipeline** em colunas (estilo Trello/Kanban) com cards de oportuni
<v-container class="py-6"> <v-container class="py-6">
<div class="toolbar"> <div class="toolbar">
<h2 class="text-h6">Pipeline de Oportunidades</h2> <h2 class="text-h6">Pipeline de Oportunidades</h2>
<EliInput <EliEntradaTexto
v-model="filtro" v-model:value="filtro"
label="Buscar" :opcoes="{ rotulo: 'Buscar', placeholder: 'Cliente, proposta, valor...' }"
placeholder="Cliente, proposta, valor..."
density="compact"
/> />
<EliBotao @click="criarOportunidade">Nova oportunidade</EliBotao> <EliBotao @click="criarOportunidade">Nova oportunidade</EliBotao>
</div> </div>
@ -313,7 +331,7 @@ Exemplo: um **pipeline** em colunas (estilo Trello/Kanban) com cards de oportuni
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
import { EliBadge, EliBotao, EliInput } from "eli-vue"; import { EliBadge, EliBotao, EliEntradaTexto } from "eli-vue";
type Oportunidade = { type Oportunidade = {
id: string; id: string;
@ -332,7 +350,7 @@ type Coluna = {
export default defineComponent({ export default defineComponent({
name: "PipelineExemplo", name: "PipelineExemplo",
components: { EliBadge, EliBotao, EliInput }, components: { EliBadge, EliBotao, EliEntradaTexto },
setup() { setup() {
const filtro = ref(""); const filtro = ref("");
@ -467,6 +485,9 @@ pnpm run build
Gera `dist/` (artefatos de build) e `dist/types` (declarações `.d.ts`). Gera `dist/` (artefatos de build) e `dist/types` (declarações `.d.ts`).
> Observação importante: este repositório roda `npm version patch --no-git-tag-version` no `prebuild`.
> Ou seja, ao rodar `pnpm run build` a versão do `package.json` é incrementada automaticamente.
## Guia rápido para IAs (antes de codar) ## Guia rápido para IAs (antes de codar)
1) **Leia** `.agent` e este README. 1) **Leia** `.agent` e este README.

View file

@ -1,6 +1,6 @@
{ {
"name": "eli-vue", "name": "eli-vue",
"version": "0.1.68", "version": "0.1.71",
"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",

View file

@ -259,20 +259,6 @@ export default defineComponent({
}); });
}; };
const aplicarPaginacao = (
linhas: Linha[],
parametros?: { offSet?: number; limit?: number }
) => {
const offset = Math.max(0, parametros?.offSet ?? 0);
const limit = parametros?.limit ?? linhas.length;
if (limit === undefined || limit <= 0) {
return linhas.slice(offset);
}
return linhas.slice(offset, offset + limit);
};
const tabelaOk: EliTabelaConsulta<Linha> = { const tabelaOk: EliTabelaConsulta<Linha> = {
nome: 'Exemplo', nome: 'Exemplo',
registros_por_consulta: 10, registros_por_consulta: 10,