@farm-investimentos/front-mfe-components-vue3 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,204 @@
1
- import { shallowMount } from '@vue/test-utils';
1
+ import { nextTick } from 'vue';
2
2
 
3
- import Tooltip from '../Tooltip';
3
+ import { mount } from '@vue/test-utils';
4
+
5
+ import Tooltip from '../Tooltip.vue';
4
6
 
5
7
  describe('Tooltip component', () => {
6
8
  let wrapper;
7
9
 
8
- beforeEach(() => {
9
- wrapper = shallowMount(Tooltip, {});
10
+ afterEach(() => {
11
+ if (wrapper) {
12
+ wrapper.unmount();
13
+ }
10
14
  });
11
15
 
12
- test('Created hook', () => {
13
- expect(wrapper).toBeDefined();
14
- });
16
+ const createWrapper = (props = {}, slots = {}) => {
17
+ const finalProps = {
18
+ delay: 0,
19
+ ...props,
20
+ };
21
+
22
+ if (!Object.prototype.hasOwnProperty.call(props, 'modelValue')) {
23
+ delete finalProps.modelValue;
24
+ }
25
+
26
+ return mount(Tooltip, {
27
+ props: finalProps,
28
+ slots: {
29
+ activator: '<button>Hover me</button>',
30
+ default: 'Tooltip content',
31
+ ...slots,
32
+ },
33
+ });
34
+ };
15
35
 
16
- describe('mount component', () => {
36
+ describe('basic functionality', () => {
17
37
  it('renders correctly', () => {
18
- expect(wrapper.element).toBeDefined();
38
+ wrapper = createWrapper();
39
+ expect(wrapper.find('.tooltip-container').exists()).toBe(true);
40
+ expect(wrapper.find('.tooltip-activator').exists()).toBe(true);
41
+ });
42
+ });
43
+
44
+ describe('props', () => {
45
+ it('applies correct placement class', async () => {
46
+ wrapper = createWrapper({ placement: 'bottom-center', modelValue: true });
47
+ await nextTick();
48
+
49
+ expect(wrapper.find('.tooltip-popup--bottom-center').exists()).toBe(true);
50
+ });
51
+
52
+ it('applies correct variant class', async () => {
53
+ wrapper = createWrapper({ variant: 'dark', modelValue: true });
54
+ await nextTick();
55
+
56
+ expect(wrapper.find('.tooltip-popup--dark').exists()).toBe(true);
57
+ });
58
+
59
+ it('applies correct size class', async () => {
60
+ wrapper = createWrapper({ size: 'lg', modelValue: true });
61
+ await nextTick();
62
+
63
+ expect(wrapper.find('.tooltip-popup--lg').exists()).toBe(true);
64
+ });
65
+
66
+ it('respects disabled prop', () => {
67
+ wrapper = createWrapper({ disabled: true });
68
+ expect(wrapper.props('disabled')).toBe(true);
69
+ });
70
+ });
71
+
72
+ describe('controlled mode', () => {
73
+ it('shows tooltip when modelValue is true', async () => {
74
+ wrapper = createWrapper({ modelValue: true });
75
+ await nextTick();
76
+
77
+ expect(wrapper.vm.isVisible).toBe(true);
78
+ });
79
+
80
+ it('does not show tooltip when modelValue is false', async () => {
81
+ wrapper = createWrapper({ modelValue: false });
82
+ await nextTick();
83
+
84
+ expect(wrapper.vm.isVisible).toBe(false);
85
+ });
86
+
87
+ it('shows close button when controlled and has title', async () => {
88
+ wrapper = createWrapper({ modelValue: true }, { title: '<span>Title</span>' });
89
+ await nextTick();
90
+
91
+ expect(wrapper.find('.tooltip-close').exists()).toBe(true);
92
+ });
93
+
94
+ it('emits update:modelValue when close button is clicked', async () => {
95
+ wrapper = createWrapper({ modelValue: true }, { title: '<span>Title</span>' });
96
+ await nextTick();
97
+
98
+ await wrapper.find('.tooltip-close').trigger('click');
99
+
100
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
101
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([false]);
102
+ });
103
+ });
104
+
105
+ describe('slots', () => {
106
+ it('renders activator slot content', () => {
107
+ wrapper = createWrapper(
108
+ {},
109
+ {
110
+ activator: '<span class="custom-activator">Custom</span>',
111
+ }
112
+ );
113
+
114
+ expect(wrapper.find('.custom-activator').exists()).toBe(true);
115
+ expect(wrapper.find('.custom-activator').text()).toBe('Custom');
116
+ });
117
+
118
+ it('renders title slot when provided', async () => {
119
+ wrapper = createWrapper(
120
+ { modelValue: true },
121
+ { title: '<span class="custom-title">Custom Title</span>' }
122
+ );
123
+ await nextTick();
124
+
125
+ expect(wrapper.find('.tooltip-title').exists()).toBe(true);
126
+ expect(wrapper.find('.custom-title').exists()).toBe(true);
127
+ });
128
+
129
+ it('renders default slot content', async () => {
130
+ wrapper = createWrapper({ modelValue: true });
131
+ await nextTick();
132
+
133
+ expect(wrapper.find('.tooltip-content').text()).toContain('Tooltip content');
134
+ });
135
+ });
136
+
137
+ describe('computed properties', () => {
138
+ it('calculates tooltip classes correctly', async () => {
139
+ wrapper = createWrapper({
140
+ modelValue: true,
141
+ variant: 'dark',
142
+ size: 'lg',
143
+ placement: 'top-center',
144
+ });
145
+ await nextTick();
146
+
147
+ const classes = wrapper.vm.tooltipClasses;
148
+ expect(classes['tooltip-popup']).toBe(true);
149
+ expect(classes['tooltip-popup--visible']).toBe(true);
150
+ expect(classes['tooltip-popup--dark']).toBe(true);
151
+ expect(classes['tooltip-popup--lg']).toBe(true);
152
+ expect(classes['tooltip-popup--top-center']).toBe(true);
153
+ });
154
+
155
+ it('calculates arrow styles correctly', () => {
156
+ wrapper = createWrapper({ placement: 'top-center' });
157
+
158
+ const styles = wrapper.vm.arrowStyles;
159
+ expect(styles.left).toBe('50%');
160
+ expect(styles.transform).toBe('translateX(-50%)');
161
+ expect(styles.bottom).toBe('-6px');
162
+ });
163
+
164
+ it('calculates controlled state correctly', () => {
165
+ const uncontrolledWrapper = createWrapper();
166
+ expect(uncontrolledWrapper.vm.isControlled).toBe(false);
167
+
168
+ const controlledWrapper = createWrapper({ modelValue: true });
169
+ expect(controlledWrapper.vm.isControlled).toBe(true);
170
+
171
+ uncontrolledWrapper.unmount();
172
+ controlledWrapper.unmount();
173
+ });
174
+ });
175
+
176
+ describe('props validation', () => {
177
+ it('accepts valid placement values', () => {
178
+ const placements = [
179
+ 'top-left',
180
+ 'top-center',
181
+ 'top-right',
182
+ 'bottom-left',
183
+ 'bottom-center',
184
+ 'bottom-right',
185
+ ];
186
+
187
+ placements.forEach(placement => {
188
+ wrapper = createWrapper({ placement, modelValue: true });
189
+ expect(wrapper.find(`.tooltip-popup--${placement}`).exists()).toBe(true);
190
+ wrapper.unmount();
191
+ });
192
+ });
193
+
194
+ it('accepts valid size values', () => {
195
+ const sizes = ['sm', 'md', 'lg'];
196
+
197
+ sizes.forEach(size => {
198
+ wrapper = createWrapper({ size, modelValue: true });
199
+ expect(wrapper.find(`.tooltip-popup--${size}`).exists()).toBe(true);
200
+ wrapper.unmount();
201
+ });
19
202
  });
20
203
  });
21
204
  });
@@ -0,0 +1,287 @@
1
+ # 🎯 Tooltip Component
2
+
3
+ ## Visão Geral
4
+
5
+ O componente Tooltip fornece informações contextuais ao usuário através de uma pequena caixa flutuante que aparece ao interagir com um elemento. Ideal para exibir textos explicativos, dicas ou informações adicionais sem poluir a interface.
6
+
7
+ ## 🚀 Features
8
+
9
+ ### Posicionamento Inteligente
10
+ - 6 posições pré-definidas com detecção automática de colisão
11
+ - Reposicionamento automático quando próximo às bordas da viewport
12
+ - Seta indicadora que sempre aponta para o centro do elemento ativador
13
+
14
+ ### Modos de Ativação
15
+ - **Hover**: Aparece ao passar o mouse (padrão)
16
+ - **Click**: Ativado por clique
17
+ - **Manual**: Controle total via v-model
18
+
19
+ ### Comportamento Responsivo
20
+ - Z-index dinâmico que detecta modais e se ajusta automaticamente
21
+ - Scroll listeners para manter posicionamento correto
22
+ - Suporte a elementos scrolláveis dentro de modais
23
+
24
+ ### Performance
25
+ - Lazy rendering - só renderiza quando visível
26
+ - Cache inteligente para detecção de modais
27
+ - Event listeners otimizados com passive: true
28
+
29
+ ## 📦 Instalação e Importação
30
+
31
+ ```javascript
32
+ import { Tooltip } from '@farm-investimentos/front-mfe-components-vue3';
33
+ ```
34
+
35
+ ## 🎨 API
36
+
37
+ ### Props
38
+
39
+ | Prop | Tipo | Padrão | Descrição |
40
+ |------|------|---------|-----------|
41
+ | `modelValue` | `boolean` | `undefined` | Controle de visibilidade (v-model) |
42
+ | `trigger` | `'hover' \| 'click' \| 'manual'` | `'hover'` | Modo de ativação |
43
+ | `placement` | `TooltipPlacement` | `'top-center'` | Posição do tooltip |
44
+ | `offset` | `number` | `8` | Distância do elemento ativador (px) |
45
+ | `variant` | `'dark'` | `'dark'` | Tema visual (apenas dark disponível) |
46
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Tamanho do tooltip |
47
+ | `maxWidth` | `string \| number` | `undefined` | Largura máxima customizada |
48
+ | `delay` | `number \| [number, number]` | `[100, 50]` | Delay para mostrar/esconder (ms) |
49
+ | `disabled` | `boolean` | `false` | Desabilita o tooltip |
50
+ | `fluid` | `boolean` | `false` | Largura fluida (max 300px) |
51
+
52
+ ### Eventos
53
+
54
+ | Evento | Payload | Descrição |
55
+ |--------|---------|-----------|
56
+ | `update:modelValue` | `boolean` | Emitido ao mudar visibilidade |
57
+ | `show` | - | Emitido ao mostrar |
58
+ | `hide` | - | Emitido ao esconder |
59
+
60
+ ### Slots
61
+
62
+ | Slot | Props | Descrição |
63
+ |------|-------|-----------|
64
+ | `activator` | - | Elemento que ativa o tooltip |
65
+ | `default` | - | Conteúdo principal |
66
+ | `title` | - | Título opcional (só aparece em modo controlado) |
67
+
68
+ ## 💡 Exemplos de Uso
69
+
70
+ ### Básico
71
+ ```vue
72
+ <Tooltip>
73
+ <template #activator>
74
+ <button>Hover me</button>
75
+ </template>
76
+ Informação adicional sobre este botão
77
+ </Tooltip>
78
+ ```
79
+
80
+ ### Posicionamento Customizado
81
+ ```vue
82
+ <Tooltip placement="bottom-right" :offset="12">
83
+ <template #activator>
84
+ <IconHelp />
85
+ </template>
86
+ Ajuda contextual posicionada à direita
87
+ </Tooltip>
88
+ ```
89
+
90
+ ### Modo Controlado com Título
91
+ ```vue
92
+ <template>
93
+ <Tooltip v-model="showHelp">
94
+ <template #activator>
95
+ <button @click="showHelp = !showHelp">
96
+ Toggle Help
97
+ </button>
98
+ </template>
99
+ <template #title>
100
+ Informações Importantes
101
+ </template>
102
+ <ul>
103
+ <li>Primeiro ponto</li>
104
+ <li>Segundo ponto</li>
105
+ <li>Terceiro ponto</li>
106
+ </ul>
107
+ </Tooltip>
108
+ </template>
109
+
110
+ <script setup>
111
+ import { ref } from 'vue';
112
+ const showHelp = ref(false);
113
+ </script>
114
+ ```
115
+
116
+ ### Tooltip Fluido para Conteúdo Longo
117
+ ```vue
118
+ <Tooltip :fluid="true">
119
+ <template #activator>
120
+ <span>Ver descrição completa</span>
121
+ </template>
122
+ Este é um texto mais longo que se adapta automaticamente
123
+ à largura do conteúdo, expandindo até o máximo de 300px
124
+ antes de quebrar em múltiplas linhas.
125
+ </Tooltip>
126
+ ```
127
+
128
+ ### Diferentes Tamanhos
129
+ ```vue
130
+ <div class="flex gap-4">
131
+ <Tooltip size="sm">
132
+ <template #activator>
133
+ <Badge>SM</Badge>
134
+ </template>
135
+ Tooltip pequeno
136
+ </Tooltip>
137
+
138
+ <Tooltip size="md">
139
+ <template #activator>
140
+ <Badge>MD</Badge>
141
+ </template>
142
+ Tooltip médio (padrão)
143
+ </Tooltip>
144
+
145
+ <Tooltip size="lg">
146
+ <template #activator>
147
+ <Badge>LG</Badge>
148
+ </template>
149
+ Tooltip grande com mais espaçamento
150
+ </Tooltip>
151
+ </div>
152
+ ```
153
+
154
+ ### Com Delay Customizado
155
+ ```vue
156
+ <Tooltip :delay="[500, 0]">
157
+ <template #activator>
158
+ <button>Hover com delay</button>
159
+ </template>
160
+ Aparece após 500ms, desaparece instantaneamente
161
+ </Tooltip>
162
+ ```
163
+
164
+ ## 🎯 Casos de Uso Comuns
165
+
166
+ ### 1. Ícones de Ajuda
167
+ ```vue
168
+ <label>
169
+ CPF
170
+ <Tooltip placement="top-right">
171
+ <template #activator>
172
+ <IconQuestion class="ml-1 text-gray-400" />
173
+ </template>
174
+ Digite apenas números, sem pontos ou traços
175
+ </Tooltip>
176
+ </label>
177
+ ```
178
+
179
+ ### 2. Botões com Ações Desabilitadas
180
+ ```vue
181
+ <Tooltip :disabled="canDelete">
182
+ <template #activator>
183
+ <button :disabled="!canDelete" @click="handleDelete">
184
+ Excluir
185
+ </button>
186
+ </template>
187
+ Você não tem permissão para excluir este item
188
+ </Tooltip>
189
+ ```
190
+
191
+ ### 3. Truncate com Preview Completo
192
+ ```vue
193
+ <Tooltip>
194
+ <template #activator>
195
+ <p class="truncate max-w-xs">
196
+ {{ longText }}
197
+ </p>
198
+ </template>
199
+ {{ longText }}
200
+ </Tooltip>
201
+ ```
202
+
203
+ ### 4. Status com Detalhes
204
+ ```vue
205
+ <Tooltip placement="bottom-center">
206
+ <template #activator>
207
+ <StatusBadge :status="order.status" />
208
+ </template>
209
+ <div>
210
+ <p><strong>Status:</strong> {{ order.status }}</p>
211
+ <p><strong>Atualizado:</strong> {{ order.updatedAt }}</p>
212
+ <p><strong>Por:</strong> {{ order.updatedBy }}</p>
213
+ </div>
214
+ </Tooltip>
215
+ ```
216
+
217
+ ## ⚡ Performance e Boas Práticas
218
+
219
+ ### Do's ✅
220
+ - Use tooltips para informações secundárias e não-críticas
221
+ - Mantenha o conteúdo conciso e direto
222
+ - Use o placement adequado para não cobrir elementos importantes
223
+ - Para conteúdo complexo, considere usar um Popover ou Modal
224
+
225
+ ### Don'ts ❌
226
+ - Não use tooltips para informações essenciais
227
+ - Evite tooltips em elementos mobile/touch
228
+ - Não coloque formulários ou ações dentro de tooltips
229
+ - Evite tooltips aninhados
230
+
231
+ ## 🔧 Troubleshooting
232
+
233
+ ### Tooltip aparece atrás de outros elementos
234
+ O componente já calcula automaticamente o z-index baseado em modais presentes. Se ainda houver problemas, verifique se o elemento pai não tem `overflow: hidden`.
235
+
236
+ ### Tooltip não aparece
237
+ 1. Verifique se `disabled` não está `true`
238
+ 2. Em modo controlado, certifique-se que `modelValue` está sendo atualizado
239
+ 3. Verifique se o slot `activator` tem apenas um elemento raiz
240
+
241
+ ### Posicionamento incorreto
242
+ O tooltip se reposiciona automaticamente ao detectar scroll ou resize. Se o problema persistir:
243
+ 1. Verifique se o elemento activator não está com `position: fixed`
244
+ 2. Certifique-se que o container pai não tem transforms 3D
245
+
246
+ ## 🎨 Customização
247
+
248
+ ### CSS Variables
249
+ ```css
250
+ .tooltip-popup {
251
+ --tooltip-bg: #333333;
252
+ --tooltip-color: #f5f5f5;
253
+ --tooltip-radius: 5px;
254
+ --tooltip-font-size: 12px;
255
+ --tooltip-padding: 16px;
256
+ }
257
+ ```
258
+
259
+ ### Classes para Styling
260
+ ```css
261
+ .tooltip-popup--visible { /* Quando visível */ }
262
+ .tooltip-popup--sm { /* Tamanho pequeno */ }
263
+ .tooltip-popup--md { /* Tamanho médio */ }
264
+ .tooltip-popup--lg { /* Tamanho grande */ }
265
+ .tooltip-popup--has-title { /* Quando tem título */ }
266
+ ```
267
+
268
+ ## 📊 Especificações Técnicas
269
+
270
+ ### Posições Suportadas
271
+ - `top-left`: Acima e alinhado à esquerda
272
+ - `top-center`: Acima e centralizado
273
+ - `top-right`: Acima e alinhado à direita
274
+ - `bottom-left`: Abaixo e alinhado à esquerda
275
+ - `bottom-center`: Abaixo e centralizado
276
+ - `bottom-right`: Abaixo e alinhado à direita
277
+
278
+ ### Comportamento da Seta
279
+ - Sempre aponta para o centro do elemento ativador
280
+ - Ajusta-se dinamicamente quando o tooltip é reposicionado
281
+ - Mantém margem mínima de 12px das bordas
282
+
283
+ ### Detecção de Colisão
284
+ - Viewport boundaries detection
285
+ - Automatic repositioning
286
+ - Scroll containers support
287
+ - Modal-aware positioning
@@ -1,5 +1,20 @@
1
1
  import Tooltip from './Tooltip.vue';
2
-
3
- export default Tooltip;
2
+ import type {
3
+ TooltipProps,
4
+ TooltipEmits,
5
+ TooltipPlacement,
6
+ TooltipTrigger,
7
+ TooltipVariant,
8
+ TooltipSize,
9
+ } from './types';
4
10
 
5
11
  export { Tooltip };
12
+ export type {
13
+ TooltipProps,
14
+ TooltipEmits,
15
+ TooltipPlacement,
16
+ TooltipTrigger,
17
+ TooltipVariant,
18
+ TooltipSize,
19
+ };
20
+ export default Tooltip;
@@ -0,0 +1,51 @@
1
+ export type TooltipPlacement =
2
+ | 'top-left'
3
+ | 'top-center'
4
+ | 'top-right'
5
+ | 'bottom-left'
6
+ | 'bottom-center'
7
+ | 'bottom-right';
8
+
9
+ export type TooltipTrigger = 'hover' | 'click' | 'manual';
10
+
11
+ export type TooltipVariant = 'dark' | 'light';
12
+
13
+ export type TooltipSize = 'sm' | 'md' | 'lg';
14
+
15
+ export interface TooltipProps {
16
+ // Visibility control (Vue 3 style)
17
+ modelValue?: boolean; // v-model
18
+ trigger?: TooltipTrigger;
19
+
20
+ // Placement (all 6 positions are supported)
21
+ placement?: TooltipPlacement;
22
+ offset?: number;
23
+
24
+ // Aparência
25
+ variant?: TooltipVariant;
26
+ size?: TooltipSize;
27
+ maxWidth?: string | number;
28
+
29
+ // Comportamento
30
+ delay?: number | [number, number]; // [show, hide]
31
+ disabled?: boolean;
32
+ fluid?: boolean;
33
+ position?: string;
34
+ }
35
+
36
+ export interface TooltipEmits {
37
+ 'update:modelValue': [value: boolean]; // Vue 3 v-model
38
+ show: [];
39
+ hide: [];
40
+ }
41
+
42
+ export interface Position {
43
+ x: number;
44
+ y: number;
45
+ }
46
+
47
+ export interface TooltipState {
48
+ isVisible: boolean;
49
+ position: Position;
50
+ actualPlacement: TooltipPlacement;
51
+ }