From 4fdc5a95ce7245c35db8e0c1d0dbfca3c2d4c4a5 Mon Sep 17 00:00:00 2001 From: andreLMpena Date: Tue, 23 Dec 2025 15:41:00 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20tipo=20cep=20e=20tipo=20select=20mais?= =?UTF-8?q?=20documenta=C3=A7=C3=A3o=20do=20eli=20input=20(ainda=20incompl?= =?UTF-8?q?eta)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/componentes/EliInput/EliInput.vue | 56 ++++++++++ src/componentes/EliInput/README.md | 116 ++++++++++++++++++++ src/componentes/EliInput/utils/cep.ts | 9 ++ src/componentes/EliOlaMundo/EliOlaMundo.vue | 40 ++++++- 4 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 src/componentes/EliInput/utils/cep.ts diff --git a/src/componentes/EliInput/EliInput.vue b/src/componentes/EliInput/EliInput.vue index 96c1b91..4a9827b 100644 --- a/src/componentes/EliInput/EliInput.vue +++ b/src/componentes/EliInput/EliInput.vue @@ -47,6 +47,28 @@ v-bind="attrs" /> + + + { + // Normaliza options para [{ label, value, disabled? }] + return (props.options || []).map((o: any) => + o && typeof o === "object" && ("label" in o || "value" in o) + ? { label: o.label ?? String(o.value), value: o.value, disabled: o.disabled } + : { label: String(o), value: o } + ); + }); + + function optLabel(opt: any) { + if (opt && typeof opt === "object") return opt.label ?? String(opt.value); + return String(opt); + } + + function optValue(opt: any) { + if (opt && typeof opt === "object") return opt.value; + return opt; + } + + return { attrs, value, @@ -234,6 +287,9 @@ export default defineComponent({ onInput, onFocus: () => emit("focus"), onBlur: () => emit("blur"), + computedItems, + optLabel, + optValue, }; }, }); diff --git a/src/componentes/EliInput/README.md b/src/componentes/EliInput/README.md index e69de29..5c4244b 100644 --- a/src/componentes/EliInput/README.md +++ b/src/componentes/EliInput/README.md @@ -0,0 +1,116 @@ +# EliInput + +**Componente base de input do design system** + +O EliInput unifica vários tipos de campo (v-text-field, v-textarea, v-select, v-radio-group, v-checkbox) em uma única API consistente. Ele encapsula comportamentos, máscaras e regras comuns (CPF/CNPJ, telefone, CEP, numéricos, formatação de moeda etc.) para manter coerência visual e de lógica em toda a aplicação. + +> ⚠️ Nunca use os componentes Vuetify diretamente fora do design system para esses casos. +> Utilize sempre EliInput para garantir formatação, tipagem e repasse de atributos padronizados. + +--- + +## Visão geral + +EliInput foi projetado para: + +* Centralizar formatação (máscaras) para tipos de entrada comuns (telefone, CPF/CNPJ, CEP, numéricos). +* Fornecer uma API única (type) que representa diferentes controles (text, textarea, select, radio, checkbox, etc.). +* Repassar atributos/props do pai para o componente Vuetify interno (v-bind="$attrs") mantendo inheritAttrs: false. +* Emitir eventos padronizados: update:modelValue, change, focus, blur. + +--- + +## Principais decisões de implementação + +* inheritAttrs: false — o componente controla explicitamente para onde os atributos são passados (v-bind="attrs" no elemento interno). +* Uso de um computed value que faz emit("update:modelValue", v) e emit("change", v) — assim qualquer v-model no pai funciona como esperado. +* Normalização de props.options para aceitar objetos { label, value, disabled } ou primitivos ('A', 1). +* Separação clara entre lógica de formatação (aplicada em onInput) e componentes que não devem ser formatados (ex.: v-select). + +--- + +## Tipagem (TypeScript) + +```ts +type Option = { label: string; value: any; disabled?: boolean }; + +type InputVariant = 'outlined' | 'filled' | 'plain' | 'solo' | 'solo-filled' | 'solo-inverted' | 'underlined'; +type Density = 'default' | 'comfortable' | 'compact'; +type TipoNumerico = 'numericoInteiro' | 'numericoDecimal' | 'numericoMoeda'; + +type InputType = + | 'text' | 'password' | 'email' | 'search' | 'url' | 'textarea' + | 'radio' | 'checkbox' | 'telefone' | 'cpfCnpj' | 'cep' | 'select' + | TipoNumerico; +``` + +--- + +## Props + +| Prop | Tipo | Default | Descrição | +| ---------------- | --------------------------- | --------------- | ------------------------------------------------------ | +| `modelValue` | `string \| number \| any[]` | `""` | Valor controlado (use com `v-model`). | +| `type` | `InputType` | `"text"` | Tipo do controle (ver `InputType`). | +| `label` | `string` | `-` | Rótulo do campo. | +| `placeholder` | `string` | `-` | Texto exibido quando o campo está vazio. | +| `disabled` | `boolean` | `false` | Desabilita o campo. | +| `error` | `boolean` | `false` | Força estado visual de erro. | +| `errorMessages` | `string \| string[]` | `[]` | Mensagem ou lista de mensagens de erro. | +| `hint` | `string` | `-` | Texto de ajuda exibido abaixo do campo. | +| `persistentHint` | `boolean` | `false` | Mantém o hint sempre visível. | +| `variant` | `InputVariant` | `"outlined"` | Variante visual do Vuetify. | +| `density` | `Density` | `"comfortable"` | Densidade visual do campo. | +| `color` | `string` | `"primary"` | Cor do campo quando focado (ou `error`, se aplicável). | +| `clearable` | `boolean` | `false` | Permite limpar o valor do campo. | + + +## Notas sobre props + +* options: aceita arrays como ['Frontend','Backend'] ou { label:'São Paulo', value:'SP' }. O componente normaliza para { label, value, disabled? }. +* type determina quais comportamentos internos são aplicados. Tipos numéricos e máscara (telefone, cpfCnpj, cep) passam por formatação em onInput. +* multiple/chips: úteis para type="select". O v-select interno recebe item-title="label" e item-value="value" para compatibilidade com objetos. + +--- + +## Emissões (events) + +* update:modelValue — padrão v-model. +* change — emitido sempre que o valor muda (alinha com update:modelValue). +* focus — quando o campo interno recebe foco. +* blur — quando perde o foco. +* Observação: como value é um computed com getter/setter que emite ambos, v-model e listeners de mudança no pai funcionarão normalmente. + +--- + +## Repasso de atributos e listeners + +* O componente define inheritAttrs: false e usa v-bind="attrs" nos componentes internos (v-text-field, v-select, etc.). Isso implica que: +* Atributos HTML (ex.: type, aria-label, class, style) passados para são aplicados ao componente Vuetify interno apropriado. +* Listeners (ex.: @click, @keydown) também fazem parte de $attrs e serão repassados para o componente interno — use o EliInput como se estivesse ouvindo eventos diretamente no input. + +## Exemplo: + +```vue + +``` + +--- + +## Comportamentos de formatação importantes + +* numericoInteiro — remove tudo que não for dígito. +* numericoDecimal — mantém separador decimal (aplica formatarDecimal). +* numericoMoeda — formata para moeda conforme util (formatarMoeda). +* telefone — aplica máscara/format formatTelefone. +* cpfCnpj — aplica formatarCpfCnpj. +* cep — aplica formatarCep. + +**Importante: a formatação ocorre no onInput (campos text-like). O v-select não passa por onInput — ele usa v-model="value" e o computed que emite o update. Se desejar formatação específica para itens do select (por exemplo, mostrar label formatado), trate nos options antes de passar.** + +--- + +## Slot + +* O componente não expõe slots customizados diretamente. Ele controla internamente o append para toggle de senha quando type === 'password' && showPasswordToggle (ícone de olho). +* Se você precisa de slots específicos do v-text-field/v-select, considere estender o componente ou criar uma variação que exponha os slots desejados. \ No newline at end of file diff --git a/src/componentes/EliInput/utils/cep.ts b/src/componentes/EliInput/utils/cep.ts new file mode 100644 index 0000000..701fd34 --- /dev/null +++ b/src/componentes/EliInput/utils/cep.ts @@ -0,0 +1,9 @@ +import { somenteNumeros } from "./numerico"; + +export function formatarCep(v: string): string { + const d = somenteNumeros(v).slice(0, 8); + + if (d.length <= 5) return d; + + return d.replace(/^(\d{5})(\d{1,3})$/, "$1-$2"); +} diff --git a/src/componentes/EliOlaMundo/EliOlaMundo.vue b/src/componentes/EliOlaMundo/EliOlaMundo.vue index f947815..f100efb 100644 --- a/src/componentes/EliOlaMundo/EliOlaMundo.vue +++ b/src/componentes/EliOlaMundo/EliOlaMundo.vue @@ -18,20 +18,44 @@ density="compact" /> - + - + + + + + ([]); return { nome, email, documento, + estado, telefone, mensagem, senha, @@ -129,6 +156,7 @@ export default defineComponent({ habilidades, idade, altura, + cep, valor, }; },