From 751857b170031f141d543b03040df8c2f0c57a52 Mon Sep 17 00:00:00 2001 From: andreLMpena Date: Thu, 18 Dec 2025 09:46:32 -0300 Subject: [PATCH] feat: componente padrao de botao --- src/componentes/EliBotao/EliBotao.vue | 64 ++++++++ src/componentes/EliBotao/README.md | 173 ++++++++++++++++++++ src/componentes/EliBotao/index.ts | 4 + src/componentes/EliOlaMundo/EliOlaMundo.vue | 2 +- src/index.ts | 14 +- src/playground/App.vue | 10 +- src/playground/main.ts | 166 ++++++++++++++++++- 7 files changed, 416 insertions(+), 17 deletions(-) create mode 100644 src/componentes/EliBotao/EliBotao.vue create mode 100644 src/componentes/EliBotao/README.md create mode 100644 src/componentes/EliBotao/index.ts diff --git a/src/componentes/EliBotao/EliBotao.vue b/src/componentes/EliBotao/EliBotao.vue new file mode 100644 index 0000000..8ad179c --- /dev/null +++ b/src/componentes/EliBotao/EliBotao.vue @@ -0,0 +1,64 @@ + + + \ No newline at end of file diff --git a/src/componentes/EliBotao/README.md b/src/componentes/EliBotao/README.md new file mode 100644 index 0000000..8a846f7 --- /dev/null +++ b/src/componentes/EliBotao/README.md @@ -0,0 +1,173 @@ +# EliBotao + +**Componente base de botão do design system** + +O `EliBotao` encapsula o `v-btn` do Vuetify para aplicar padrões visuais, acessibilidade e comportamentos esperados em toda a aplicação. + +> ⚠️ **Nunca use `v-btn` diretamente fora do design system.** +> Utilize sempre o `EliBotao`. + +--- + +## Visão geral + +O `EliBotao` oferece uma API simples e tipada para botões, controlando atributos, listeners e estilos por padrão. Foi projetado para: + +* Garantir consistência visual e de comportamento; +* Facilitar a adoção de boas práticas de A11y; +* Permitir repasse transparente de atributos e listeners ao `v-btn` interno. + +### Principais decisões de implementação + +* `inheritAttrs: false` — o componente faz `v-bind="$attrs"` manualmente no `v-btn` filho. +* Slot padrão para conteúdo (texto, ícones ou markup). +* Classes base aplicadas: `text-none pt-1` (podem ser sobrescritas via `class`). + +--- + +## Tipagem (TypeScript) + +```ts +type BotaoVariant = 'elevated' | 'flat' | 'outlined' | 'text' | 'tonal' +type BotaoSize = 'x-small' | 'small' | 'default' | 'large' + +interface EliBotaoProps { + color?: string + variant?: BotaoVariant + size?: BotaoSize + disabled?: boolean + loading?: boolean +} +``` + +--- + +## Props + +| Prop | Tipo | Default | Descrição | +| ---------- | -------------- | ------------ | ----------------------------------------------------------------- | +| `color` | `string` | `"primary"` | Cor visual do botão (usa as cores registradas no Vuetify). | +| `variant` | `BotaoVariant` | `"elevated"` | Variante visual: `elevated`, `flat`, `outlined`, `text`, `tonal`. | +| `size` | `BotaoSize` | `"default"` | Tamanho do botão (`x-small`, `small`, `default`, `large`). | +| `disabled` | `boolean` | `false` | Desabilita interação e aplica estilo inativo. | +| `loading` | `boolean` | `false` | Exibe indicador de carregamento e bloqueia interação. | + +### Notas sobre props + +* **`color`**: utilize as cores definidas na configuração do Vuetify para manter consistência. +* **`disabled`** e **`loading`**: quando `true`, o botão deixa de responder a eventos e apresenta feedback visual apropriado. + +--- + +## Repasso de atributos e listeners + +O componente define `inheritAttrs: false` e aplica `v-bind="$attrs"` ao `v-btn` interno. Isso implica que: + +1. **Atributos HTML** (ex.: `type`, `aria-label`, `class`, `style`) passados para `` serão aplicados ao `v-btn` filho. +2. **Listeners** (ex.: `@click`, `@mouseover`) também fazem parte de `$attrs` no Vue 3 e são repassados ao `v-btn` — use o componente como se estivesse escutando eventos diretamente no botão. + +**Exemplo:** + +```vue +Salvar +``` + +O `onClick` será invocado quando o `v-btn` emitir o evento. + +--- + +## Slot + +O `EliBotao` expõe um slot padrão para o conteúdo do botão. Pode ser texto, ícone ou markup mais complexo. + +Exemplos: + +```vue +Salvar + + + mdi-check + Salvar + + + + Texto com destaque + +``` + +--- + +## Exemplo de uso completo + +```vue + + + +``` + +--- + +## Acessibilidade (A11y) + +* Sempre forneça `aria-label` quando o botão exibir apenas um ícone. +* Para botões de ação em formulários, defina explicitamente `type="button"` ou `type="submit"` conforme apropriado. +* Evite usar apenas `color` para transmitir significado; combine com texto e atributos ARIA quando necessário. + +--- + +## Boas práticas + +* **Consistência visual**: não aplique estilos inline no lugar de tokens do design system — prefira classes e variáveis do Vuetify. +* **Overrides de classe**: a classe padrão `text-none pt-1` pode ser sobrescrita via `class` em `$attrs` (ex.: ``). +* **Estados**: gerencie `loading` e `disabled` no nível do componente pai quando necessário para evitar condições de corrida. + +--- + +## Testes + +O `EliBotao` é intencionalmente simples e fácil de testar: + +* Monte o componente em testes unitários e verifique que `$attrs` é repassado ao `v-btn`. +* Asserte que `loading` exibe o spinner e que `disabled` impede emissão de eventos. + +Exemplo (pseudocódigo): + +```ts +const wrapper = mount(EliBotao, { attrs: { 'aria-label': 'Salvar', type: 'button' } }) +expect(wrapper.findComponent({ name: 'v-btn' }).attributes('aria-label')).toBe('Salvar') +``` + +--- + +## Observações finais + +* O componente foi projetado para ser um adaptador seguro para `v-btn`: aplica padrões do design system e centraliza comportamento comum. +* Se precisar estender o componente (por exemplo, adicionar variantes visuais específicas do produto), prefira criar wrappers ou classes utilitárias no design system ao invés de alterar o `EliBotao` diretamente. + +--- diff --git a/src/componentes/EliBotao/index.ts b/src/componentes/EliBotao/index.ts new file mode 100644 index 0000000..52e69d0 --- /dev/null +++ b/src/componentes/EliBotao/index.ts @@ -0,0 +1,4 @@ +import EliBotao from "./EliBotao.vue"; + +export { EliBotao }; +export default EliBotao; diff --git a/src/componentes/EliOlaMundo/EliOlaMundo.vue b/src/componentes/EliOlaMundo/EliOlaMundo.vue index 29c231c..7949159 100644 --- a/src/componentes/EliOlaMundo/EliOlaMundo.vue +++ b/src/componentes/EliOlaMundo/EliOlaMundo.vue @@ -6,7 +6,7 @@ Este é um componente de exemplo integrado com Vuetify. - + Botão Vuetify diff --git a/src/index.ts b/src/index.ts index e3b7f10..1caa83d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,13 @@ +import type { App } from "vue"; +import { EliOlaMundo } from "./componentes/EliOlaMundo"; +import { EliBotao } from "./componentes/EliBotao"; -import type { App } from "vue" -import { EliOlaMundo } from './componentes/EliOlaMundo' - -export { EliOlaMundo } +export { EliOlaMundo }; +export { EliBotao }; export default { install(app: App) { - app.component('EliOlaMundo', EliOlaMundo) + app.component("EliOlaMundo", EliOlaMundo); + app.component("EliBotao", EliBotao); }, -} +}; diff --git a/src/playground/App.vue b/src/playground/App.vue index 152ebbc..cb10261 100644 --- a/src/playground/App.vue +++ b/src/playground/App.vue @@ -1,15 +1,23 @@ diff --git a/src/playground/main.ts b/src/playground/main.ts index 7751ec5..b249341 100644 --- a/src/playground/main.ts +++ b/src/playground/main.ts @@ -1,16 +1,164 @@ -import { createApp } from 'vue' -import App from './App.vue' +import { createApp } from "vue"; +import App from "./App.vue"; // Vuetify -import 'vuetify/styles' -import '@mdi/font/css/materialdesignicons.css' -import { createVuetify } from 'vuetify' -import * as components from 'vuetify/components' -import * as directives from 'vuetify/directives' +import "vuetify/styles"; +import "@mdi/font/css/materialdesignicons.css"; +import { createVuetify } from "vuetify"; +import * as components from "vuetify/components"; +import * as directives from "vuetify/directives"; + +const themeDefault = { + dark: false, + colors: { + //Primary Color + primary: "#146C43", + "primary-dark": "#0F5132", + "primary-light": "#D2F4EA", + + //Secondary Color + secondary: "#6C757D", + "secondary-dark": "#54595E", + "secondary-light": "#ABB5BE", + + //Status Color + success: "#28A745", + error: "#DC3545", + warning: "#FFC107", + info: "#17A2B8", + + //Gray Color + "gray-100": "#F8F9FA", + "gray-200": "#E9ECEF", + "gray-300": "#DEE2E6", + "gray-400": "#CED4DA", + "gray-500": "#ADB5BD", + "gray-600": "#6C757D", + "gray-700": "#495057", + "gray-800": "#343A40", + "gray-900": "#212529", + + //blue Color + "blue-100": "#CFE2FF", + "blue-200": "#9EC5FE", + "blue-300": "#6EA8FE", + "blue-400": "#3D8BFD", + "blue-500": "#0D6EFD", + "blue-600": "#0A58CA", + "blue-700": "#084298", + "blue-800": "#052C65", + "blue-900": "#031633", + + //Indigo Color + "indigo-100": "#E0CFFC", + "indigo-200": "#C29FFA", + "indigo-300": "#A370F7", + "indigo-400": "#8540F5", + "indigo-500": "#6610F2", + "indigo-600": "#520DC2", + "indigo-700": "#3D0A91", + "indigo-800": "#290661", + "indigo-900": "#140330", + + //Purple Color + "purple-100": "#E2D9F3", + "purple-200": "#C5B3E6", + "purple-300": "#A98EDA", + "purple-400": "#8C68CD", + "purple-500": "#6F42C1", + "purple-600": "#59359A", + "purple-700": "#432874", + "purple-800": "#2C1A4D", + "purple-900": "#160D27", + + //Pink Color + "pink-100": "#F7D6E6", + "pink-200": "#EFADCE", + "pink-300": "#E685B5", + "pink-400": "#DE5C9D", + "pink-500": "#D63384", + "pink-600": "#AB296A", + "pink-700": "#801F4F", + "pink-800": "#561435", + "pink-900": "#2B0A1A", + + //Red Color + "red-100": "#F8D7DA", + "red-200": "#F1AEB5", + "red-300": "#EA868F", + "red-400": "#E35D6A", + "red-500": "#DC3545", + "red-600": "#B02A37", + "red-700": "#842029", + "red-800": "#58151C", + "red-900": "#2C0B0E", + + //Orange Color + "orange-100": "#FFE5D0", + "orange-200": "#FECBA1", + "orange-300": "#FEB272", + "orange-400": "#FD9843", + "orange-500": "#FD7E14", + "orange-600": "#CA6510", + "orange-700": "#984C0C", + "orange-800": "#653208", + "orange-900": "#331904", + + //Yellow Color + "yellow-100": "#FFF3CD", + "yellow-200": "#FFE69C", + "yellow-300": "#FFDA6A", + "yellow-400": "#FFCD39", + "yellow-500": "#FFC107", + "yellow-600": "#CC9A06", + "yellow-700": "#997404", + "yellow-800": "#664D03", + "yellow-900": "#664D03", + + //Green Color + "green-100": "#D1E7DD", + "green-200": "#A3CFBB", + "green-300": "#75B798", + "green-400": "#479F76", + "green-500": "#198754", + "green-600": "#146C43", + "green-700": "#0F5132", + "green-800": "#0A3622", + "green-900": "#051B11", + + //Teal Color + "teal-100": "#D2F4EA", + "teal-200": "#A6E9D5", + "teal-300": "#79DFC1", + "teal-400": "#4DD4AC", + "teal-500": "#20C997", + "teal-600": "#1AA179", + "teal-700": "#13795B", + "teal-800": "#0D503C", + "teal-900": "#06281E", + + //Cyan Color + "cyan-100": "#CFF4FC", + "cyan-200": "#9EEAF9", + "cyan-300": "#6EDFF6", + "cyan-400": "#3DD5F3", + "cyan-500": "#0DCAF0", + "cyan-600": "#0AA2C0", + "cyan-700": "#087990", + "cyan-800": "#055160", + "cyan-900": "#032830", + }, +}; const vuetify = createVuetify({ components, directives, -}) + theme: { + defaultTheme: "themeDefault", + themes: { + themeDefault, + }, + }, +}); -createApp(App).use(vuetify).mount('#app') +createApp(App).use(vuetify).mount("#app");