feat: componente padrao de botao
This commit is contained in:
parent
35f9d57ce7
commit
751857b170
7 changed files with 416 additions and 17 deletions
64
src/componentes/EliBotao/EliBotao.vue
Normal file
64
src/componentes/EliBotao/EliBotao.vue
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<template>
|
||||||
|
<v-btn
|
||||||
|
:color="color"
|
||||||
|
:variant="variant"
|
||||||
|
:size="size"
|
||||||
|
:disabled="disabled"
|
||||||
|
:loading="loading"
|
||||||
|
v-bind="$attrs"
|
||||||
|
class="text-none pt-1"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
type BotaoVariant =
|
||||||
|
| "elevated"
|
||||||
|
| "flat"
|
||||||
|
| "outlined"
|
||||||
|
| "text"
|
||||||
|
| "tonal"
|
||||||
|
|
||||||
|
type BotaoSize =
|
||||||
|
| "x-small"
|
||||||
|
| "small"
|
||||||
|
| "default"
|
||||||
|
| "large"
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "EliBotao",
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
|
props: {
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "primary"
|
||||||
|
},
|
||||||
|
|
||||||
|
variant: {
|
||||||
|
type: String as PropType<BotaoVariant>,
|
||||||
|
default: "elevated",
|
||||||
|
},
|
||||||
|
|
||||||
|
size: {
|
||||||
|
type: String as PropType<BotaoSize>,
|
||||||
|
default: "default",
|
||||||
|
},
|
||||||
|
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
173
src/componentes/EliBotao/README.md
Normal file
173
src/componentes/EliBotao/README.md
Normal file
|
|
@ -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 `<EliBotao>` 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
|
||||||
|
<EliBotao @click="onClick" type="button">Salvar</EliBotao>
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
<EliBotao>Salvar</EliBotao>
|
||||||
|
|
||||||
|
<EliBotao>
|
||||||
|
<v-icon left>mdi-check</v-icon>
|
||||||
|
Salvar
|
||||||
|
</EliBotao>
|
||||||
|
|
||||||
|
<EliBotao>
|
||||||
|
<span>Texto com <strong>destaque</strong></span>
|
||||||
|
</EliBotao>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exemplo de uso completo
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<EliBotao
|
||||||
|
color="primary"
|
||||||
|
variant="elevated"
|
||||||
|
:loading="isSaving"
|
||||||
|
aria-label="Salvar formulário"
|
||||||
|
@click="salvar"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<v-icon left>mdi-content-save</v-icon>
|
||||||
|
Salvar
|
||||||
|
</EliBotao>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref } from 'vue'
|
||||||
|
import EliBotao from '@/components/EliBotao.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { EliBotao },
|
||||||
|
setup() {
|
||||||
|
const isSaving = ref(false)
|
||||||
|
function salvar() {
|
||||||
|
// lógica de salvamento
|
||||||
|
}
|
||||||
|
return { salvar, isSaving }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.: `<EliBotao class="ma-2">`).
|
||||||
|
* **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.
|
||||||
|
|
||||||
|
---
|
||||||
4
src/componentes/EliBotao/index.ts
Normal file
4
src/componentes/EliBotao/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import EliBotao from "./EliBotao.vue";
|
||||||
|
|
||||||
|
export { EliBotao };
|
||||||
|
export default EliBotao;
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
Este é um componente de exemplo integrado com Vuetify.
|
Este é um componente de exemplo integrado com Vuetify.
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="primary" block>
|
<v-btn color="primary" variant="elevated" block style="padding: 10px;">
|
||||||
Botão Vuetify
|
Botão Vuetify
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
|
|
||||||
14
src/index.ts
14
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"
|
export { EliOlaMundo };
|
||||||
import { EliOlaMundo } from './componentes/EliOlaMundo'
|
export { EliBotao };
|
||||||
|
|
||||||
export { EliOlaMundo }
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App) {
|
install(app: App) {
|
||||||
app.component('EliOlaMundo', EliOlaMundo)
|
app.component("EliOlaMundo", EliOlaMundo);
|
||||||
|
app.component("EliBotao", EliBotao);
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<EliOlaMundo />
|
<EliOlaMundo />
|
||||||
|
<EliBotao
|
||||||
|
color="primary"
|
||||||
|
@click="() => {console.log('xxx')}"
|
||||||
|
>
|
||||||
|
Button
|
||||||
|
</EliBotao>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { EliOlaMundo } from '@/componentes/EliOlaMundo'
|
import { EliOlaMundo } from '@/componentes/EliOlaMundo'
|
||||||
|
import {EliBotao} from '@/componentes/EliBotao';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
EliOlaMundo
|
EliOlaMundo,
|
||||||
|
EliBotao,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,164 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from "vue";
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
|
|
||||||
// Vuetify
|
// Vuetify
|
||||||
import 'vuetify/styles'
|
import "vuetify/styles";
|
||||||
import '@mdi/font/css/materialdesignicons.css'
|
import "@mdi/font/css/materialdesignicons.css";
|
||||||
import { createVuetify } from 'vuetify'
|
import { createVuetify } from "vuetify";
|
||||||
import * as components from 'vuetify/components'
|
import * as components from "vuetify/components";
|
||||||
import * as directives from 'vuetify/directives'
|
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({
|
const vuetify = createVuetify({
|
||||||
components,
|
components,
|
||||||
directives,
|
directives,
|
||||||
})
|
theme: {
|
||||||
|
defaultTheme: "themeDefault",
|
||||||
|
themes: {
|
||||||
|
themeDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
createApp(App).use(vuetify).mount('#app')
|
createApp(App).use(vuetify).mount("#app");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue