Adicionado celular do tipo componente

This commit is contained in:
Luiz Silva 2026-06-03 16:25:19 -03:00
parent 1446d6225b
commit 9b2651d5c7
17 changed files with 1386 additions and 1075 deletions

1968
dist/eli-vue.es.js vendored

File diff suppressed because one or more lines are too long

12
dist/eli-vue.umd.js vendored

File diff suppressed because one or more lines are too long

View file

@ -646,6 +646,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
valor: string;
formato: "data" | "data_hora" | "relativo";
acao?: () => void;
} | {
componente: import("vue").Component;
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {
@ -734,6 +737,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
valor: string;
formato: "data" | "data_hora" | "relativo";
acao?: () => void;
} | {
componente: import("vue").Component;
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {

View file

@ -143,6 +143,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
valor: string;
formato: "data" | "data_hora" | "relativo";
acao?: () => void;
} | {
componente: import("vue").Component;
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {
@ -198,6 +201,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
valor: string;
formato: "data" | "data_hora" | "relativo";
acao?: () => void;
} | {
componente: import("vue").Component;
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {

View file

@ -50,6 +50,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
valor: string;
formato: "data" | "data_hora" | "relativo";
acao?: () => void;
} | {
componente: import("vue").Component;
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {

View file

@ -32,6 +32,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
valor: string;
formato: "data" | "data_hora" | "relativo";
acao?: () => void;
} | {
componente: Component;
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {

View file

@ -0,0 +1,15 @@
import { type PropType } from "vue";
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
declare const _default: typeof __VLS_export;
export default _default;
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
dados: {
type: PropType<tiposTabelaCelulas["componente"]>;
};
}>, {
propsResolvidas: import("vue").Ref<Record<string, unknown> | undefined, Record<string, unknown> | undefined>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
dados: {
type: PropType<tiposTabelaCelulas["componente"]>;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;

View file

@ -721,4 +721,15 @@ export declare const registryTabelaCelulas: {
required: false;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
readonly componente: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
dados: {
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["componente"]>;
};
}>, {
propsResolvidas: import("vue").Ref<Record<string, unknown> | undefined, Record<string, unknown> | undefined>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
dados: {
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["componente"]>;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
};

View file

@ -2,6 +2,7 @@
* Tipagem dos dados de entrada dos componentes de celulas
*/
import type { LucideIcon } from "@lucide/vue";
import type { Component } from "vue";
export type tiposTabelaCelulas = {
textoSimples: {
texto: string;
@ -39,5 +40,17 @@ export type tiposTabelaCelulas = {
/** Ação opcional ao clicar no valor. */
acao?: () => void;
};
/** Célula que renderiza um componente Vue arbitrário com props dinâmicas. */
componente: {
/** Componente Vue a ser renderizado na célula. */
componente: Component;
/**
* Props passadas ao componente via `v-bind`.
* Aceita:
* - objeto direto: `{ status: "ativo" }`
* - função assíncrona: `() => api.get(...)` — útil quando é necessário resolver dados antes de renderizar
*/
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
};
};
export type tipoTabelaCelula = keyof tiposTabelaCelulas;

View file

@ -1,6 +1,6 @@
{
"name": "eli-vue",
"version": "0.1.187",
"version": "0.1.191",
"private": false,
"main": "./dist/eli-vue.umd.js",
"module": "./dist/eli-vue.es.js",

View file

@ -0,0 +1,28 @@
<template>
<component v-if="propsResolvidas" :is="dados?.componente" v-bind="propsResolvidas" />
</template>
<script lang="ts">
import { defineComponent, onMounted, type PropType, ref } from "vue"
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
export default defineComponent({
name: "EliTabelaCelulaComponente",
props: {
dados: {
type: Object as PropType<tiposTabelaCelulas["componente"]>,
},
},
setup(props) {
const propsResolvidas = ref<Record<string, unknown> | undefined>(undefined)
onMounted(async () => {
if (!props.dados?.props) return
const resultado = props.dados.props()
propsResolvidas.value = resultado instanceof Promise ? await resultado : resultado
})
return { propsResolvidas }
},
})
</script>

View file

@ -73,6 +73,118 @@ celula: (l) => celulaTabela("data", { valor: l.criado_em, formato: "data_hora" }
celula: (l) => celulaTabela("data", { valor: l.atualizado_em, formato: "relativo" })
```
### 6) `componente`
Renderiza um **componente Vue arbitrário** como conteúdo da célula.
Use quando nenhum tipo pré-definido atende e você precisa de controle total sobre a renderização.
```ts
{
/** Componente Vue a ser renderizado na célula. */
componente: Component;
/**
* Função que retorna as props do componente.
* Pode retornar um objeto direto ou uma Promise (para resolver dados de API).
*/
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>;
}
```
#### Uso básico (síncrono)
```ts
import MeuBadge from "./MeuBadge.vue"
celula: (l) => celulaTabela("componente", {
componente: MeuBadge,
props: () => ({ status: l.status, cor: l.cor }),
})
```
#### Uso com API (assíncrono)
```ts
import MeuDetalhe from "./MeuDetalhe.vue"
celula: (l) => celulaTabela("componente", {
componente: MeuDetalhe,
props: async () => {
const dados = await api.get(`/detalhe/${l.codigo}`)
return { detalhe: dados }
},
})
```
#### Exemplo: criando o componente `.vue`
```vue
<!-- CelulaOlaNome.vue -->
<template>
<span class="celula-ola-nome">
Olá, <strong>{{ nome }}</strong>!
</span>
</template>
<script lang="ts">
import { defineComponent } from "vue"
export default defineComponent({
name: "CelulaOlaNome",
props: {
nome: {
type: String,
required: true,
},
},
})
</script>
<style>
.celula-ola-nome {
font-size: 13px;
color: #1e293b;
}
</style>
```
#### Exemplo: usando na definição de colunas
```ts
import CelulaOlaNome from "./CelulaOlaNome.vue"
celula: (l) => celulaTabela("componente", {
componente: CelulaOlaNome,
props: () => ({ nome: l.empreendedor }),
})
```
#### Quando usar
| Cenário | Use `componente`? |
|---|---|
| Texto simples ou truncado | ❌ Use `textoSimples` / `textoTruncado` |
| Número com prefixo/sufixo | ❌ Use `numero` |
| Chips/badges com ação | ❌ Use `tags` |
| Data com formatação | ❌ Use `data` |
| Barra de progresso, gráfico inline, badge complexo | ✅ Sim |
| Componente com lógica interna (hover, tooltip, estado) | ✅ Sim |
#### Observações
- As **props não são tipadas** no contrato — é `Record<string, unknown>`. A responsabilidade de passar props corretas é do consumidor.
- O componente é renderizado via `<component :is>` + `v-bind`, então suporta qualquer componente Vue válido (SFC, `defineComponent`, funcional).
- Prefira sempre os tipos pré-definidos quando possível. O tipo `componente` é uma **escotilha de escape**, não o padrão.
---
## Como criar um novo tipo de célula pré-definido
Se o padrão de renderização se repete em múltiplas tabelas, considere promover de `componente` para um tipo próprio:
1. Criar `EliTabelaCelula<Tipo>.vue` neste diretório
2. Adicionar a chave e tipagem em `tiposTabelaCelulas.ts`
3. Registrar no mapa em `registryTabelaCelulas.ts`
---
## Onde ficam os tipos e o registry

View file

@ -1,4 +1,5 @@
import type { Component } from "vue"
import EliTabelaCelulaComponente from "./EliTabelaCelulaComponente.vue"
import EliTabelaCelulaData from "./EliTabelaCelulaData.vue"
import EliTabelaCelulaNumero from "./EliTabelaCelulaNumero.vue"
import EliTabelaCelulaTags from "./EliTabelaCelulaTags.vue"
@ -22,4 +23,5 @@ export const registryTabelaCelulas = {
numero: EliTabelaCelulaNumero,
tags: EliTabelaCelulaTags,
data: EliTabelaCelulaData,
componente: EliTabelaCelulaComponente,
} as const satisfies Record<tipoTabelaCelula, Component>

View file

@ -3,6 +3,7 @@
*/
import type { LucideIcon } from "@lucide/vue"
import type { Component } from "vue"
export type tiposTabelaCelulas = {
textoSimples: {
@ -43,6 +44,19 @@ export type tiposTabelaCelulas = {
/** Ação opcional ao clicar no valor. */
acao?: () => void
}
/** Célula que renderiza um componente Vue arbitrário com props dinâmicas. */
componente: {
/** Componente Vue a ser renderizado na célula. */
componente: Component
/**
* Props passadas ao componente via `v-bind`.
* Aceita:
* - objeto direto: `{ status: "ativo" }`
* - função assíncrona: `() => api.get(...)` — útil quando é necessário resolver dados antes de renderizar
*/
props?: () => Promise<Record<string, unknown>> | Record<string, unknown>
}
}
export type tipoTabelaCelula = keyof tiposTabelaCelulas

View file

@ -0,0 +1,53 @@
<template>
<div class="celula-ativo-custom">
<span
class="celula-ativo-custom__indicador"
:class="[ativo ? 'celula-ativo-custom__indicador--ativo' : 'celula-ativo-custom__indicador--inativo']"
>
<component :is="ativo ? CircleCheck : CircleX" :size="16" />
{{ ativo ? 'Ativo' : 'Inativo' }}
</span>
</div>
</template>
<script lang="ts">
import { CircleCheck, CircleX } from "@lucide/vue"
import { defineComponent } from "vue"
export default defineComponent({
name: "CelulaAtivoCustom",
components: { CircleCheck, CircleX },
props: {
ativo: {
type: Boolean,
required: true,
},
},
setup() {
return { CircleCheck, CircleX }
},
})
</script>
<style>
.celula-ativo-custom__indicador {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
}
.celula-ativo-custom__indicador--ativo {
background: #dcfce7;
color: #166534;
}
.celula-ativo-custom__indicador--inativo {
background: #fee2e2;
color: #991b1b;
}
</style>

View file

@ -7,11 +7,11 @@
*/
import { BadgeCheck, Eye, Pencil, Trash2 } from "@lucide/vue"
import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas"
import type { tipoEliTabelaConsulta } from "@/componentes/EliTabela"
import { celulaTabela } from "@/componentes/EliTabela"
import { AreaAtuacaoDetalhes, type tipoEmpreendedores } from "./api_simulador/dados"
import CelulaAtivoCustom from "./CelulaAtivoCustom.vue"
/** Mapeamento de cor por área de atuação para uso nos chips da tabela */
const corAreaAtuacao: Record<tipoEmpreendedores["areas_atuacao"][number], string> = {
@ -112,6 +112,15 @@ export const colunasTabela: tipoEliTabelaConsulta<tipoEmpreendedores>["colunas"]
celula: (l) => celulaTabela("numero", { numero: l.peso_kg, sufixo: "kg" }),
visivel: false,
},
{
rotulo: "Ativo (Custom)",
celula: (l) =>
celulaTabela("componente", {
componente: CelulaAtivoCustom,
props: () => Promise.resolve({ ativo: l.ativo }),
}),
visivel: true,
},
{
rotulo: "Status",
celula: (l) =>
@ -145,100 +154,7 @@ export const colunasTabela: tipoEliTabelaConsulta<tipoEmpreendedores>["colunas"]
},
]
/** Filtros avançados da tabela de empreendedores */
export const filtrosAvancados: tipoEliTabelaConsulta<tipoEmpreendedores>["filtroAvancado"] = [
{
chave: "empreendedor",
rotulo: "Empreendedor",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "empreendedor", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendedor" }] as ComponenteEntrada,
},
{
chave: "empreendimento",
rotulo: "Empreendimento",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "empreendimento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendimento" }] as ComponenteEntrada,
},
{
chave: "documento",
rotulo: "Documento",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "documento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as ComponenteEntrada,
},
{
chave: "email",
rotulo: "E-mail",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "email", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "E-mail", formato: "email" }] as ComponenteEntrada,
},
{
chave: "ativo",
rotulo: "Ativo",
filtro: (valor: unknown) => {
return { coluna: "ativo", operador: "=" as const, valor: valor as boolean }
},
entrada: [
"confirmacao",
{ rotulo: "Ativo", rotuloAtivo: "Apenas ativos", rotuloInativo: "Apenas inativos" },
] as ComponenteEntrada,
},
{
chave: "faturamento_min",
rotulo: "Faturamento mínimo",
filtro: (valor: unknown) => {
if (valor === null || valor === undefined) return undefined
return { coluna: "faturamento", operador: ">=" as const, valor: valor as number }
},
entrada: ["numero", { rotulo: "Faturamento mínimo (R$)" }] as ComponenteEntrada,
},
{
chave: "areas_atuacao",
rotulo: "Área de Atuação",
filtro: (valor: unknown) => {
const arr = valor as string[]
if (!arr.length) return undefined
return { coluna: "areas_atuacao", operador: "∩" as const, valor: arr }
},
entrada: [
"selecaoMultipla",
{
rotulo: "Área de Atuação",
limpavel: true,
itens: () =>
Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
},
] as ComponenteEntrada,
},
{
chave: "areas_atuacao2",
rotulo: "Área de Atuação (2)",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "areas_atuacao", operador: "in" as const, valor }
},
entrada: [
"selecao",
{
rotulo: "Área de Atuação",
limpavel: true,
itens: () =>
Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
},
] as ComponenteEntrada,
},
]
export { filtrosAvancados } from "./useTabelaPlaygroundFiltros"
/**
* Executa a consulta na ApiSimulacao, traduzindo busca textual e filtros avançados para FiltroDelta.

View file

@ -0,0 +1,107 @@
/**
* useTabelaPlaygroundFiltros
* Definições de filtros avançados do playground da EliTabela.
* Extraído de useTabelaPlaygroundConfig.ts para manter cada arquivo abaixo de 250 linhas.
*
* @see useTabelaPlaygroundConfig.ts — definições de colunas e ações
* @see useTabelaPlayground.ts — composable que consome estas configurações
*/
import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas"
import type { tipoEliTabelaConsulta } from "@/componentes/EliTabela"
import { AreaAtuacaoDetalhes, type tipoEmpreendedores } from "./api_simulador/dados"
/** Filtros avançados da tabela de empreendedores */
export const filtrosAvancados: tipoEliTabelaConsulta<tipoEmpreendedores>["filtroAvancado"] = [
{
chave: "empreendedor",
rotulo: "Empreendedor",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "empreendedor", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendedor" }] as ComponenteEntrada,
},
{
chave: "empreendimento",
rotulo: "Empreendimento",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "empreendimento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendimento" }] as ComponenteEntrada,
},
{
chave: "documento",
rotulo: "Documento",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "documento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as ComponenteEntrada,
},
{
chave: "email",
rotulo: "E-mail",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "email", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "E-mail", formato: "email" }] as ComponenteEntrada,
},
{
chave: "ativo",
rotulo: "Ativo",
filtro: (valor: unknown) => {
return { coluna: "ativo", operador: "=" as const, valor: valor as boolean }
},
entrada: [
"confirmacao",
{ rotulo: "Ativo", rotuloAtivo: "Apenas ativos", rotuloInativo: "Apenas inativos" },
] as ComponenteEntrada,
},
{
chave: "faturamento_min",
rotulo: "Faturamento mínimo",
filtro: (valor: unknown) => {
if (valor === null || valor === undefined) return undefined
return { coluna: "faturamento", operador: ">=" as const, valor: valor as number }
},
entrada: ["numero", { rotulo: "Faturamento mínimo (R$)" }] as ComponenteEntrada,
},
{
chave: "areas_atuacao",
rotulo: "Área de Atuação",
filtro: (valor: unknown) => {
const arr = valor as string[]
if (!arr.length) return undefined
return { coluna: "areas_atuacao", operador: "∩" as const, valor: arr }
},
entrada: [
"selecaoMultipla",
{
rotulo: "Área de Atuação",
limpavel: true,
itens: () =>
Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
},
] as ComponenteEntrada,
},
{
chave: "areas_atuacao2",
rotulo: "Área de Atuação (2)",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "areas_atuacao", operador: "in" as const, valor }
},
entrada: [
"selecao",
{
rotulo: "Área de Atuação",
limpavel: true,
itens: () =>
Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
},
] as ComponenteEntrada,
},
]