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

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