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

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,313 @@
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
+ // Importar o componente
33
+ import { Tooltip } from '@farm-investimentos/front-mfe-components-vue3';
34
+
35
+ // Registrar no app Vue (global)
36
+ app.component('farm-tooltip', Tooltip);
37
+
38
+ // Ou registrar localmente no componente
39
+ export default {
40
+ components: {
41
+ 'farm-tooltip': Tooltip
42
+ }
43
+ }
44
+
45
+ // Com script setup
46
+ import { Tooltip } from '@farm-investimentos/front-mfe-components-vue3';
47
+ ```
48
+
49
+ ### Uso com npm link
50
+ Após fazer `npm link` no projeto e `npm link @farm-investimentos/front-mfe-components-vue3` no projeto de destino, você pode usar:
51
+
52
+ ```javascript
53
+ // main.js ou main.ts
54
+ import { createApp } from 'vue';
55
+ import { Tooltip } from '@farm-investimentos/front-mfe-components-vue3';
56
+
57
+ const app = createApp(App);
58
+ app.component('farm-tooltip', Tooltip);
59
+ ```
60
+
61
+ ## 🎨 API
62
+
63
+ ### Props
64
+
65
+ | Prop | Tipo | Padrão | Descrição |
66
+ |------|------|---------|-----------|
67
+ | `modelValue` | `boolean` | `undefined` | Controle de visibilidade (v-model) |
68
+ | `trigger` | `'hover' \| 'click' \| 'manual'` | `'hover'` | Modo de ativação |
69
+ | `placement` | `TooltipPlacement` | `'top-center'` | Posição do tooltip |
70
+ | `offset` | `number` | `8` | Distância do elemento ativador (px) |
71
+ | `variant` | `'dark'` | `'dark'` | Tema visual (apenas dark disponível) |
72
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Tamanho do tooltip |
73
+ | `maxWidth` | `string \| number` | `undefined` | Largura máxima customizada |
74
+ | `delay` | `number \| [number, number]` | `[100, 50]` | Delay para mostrar/esconder (ms) |
75
+ | `disabled` | `boolean` | `false` | Desabilita o tooltip |
76
+ | `fluid` | `boolean` | `false` | Largura fluida (max 300px) |
77
+
78
+ ### Eventos
79
+
80
+ | Evento | Payload | Descrição |
81
+ |--------|---------|-----------|
82
+ | `update:modelValue` | `boolean` | Emitido ao mudar visibilidade |
83
+ | `show` | - | Emitido ao mostrar |
84
+ | `hide` | - | Emitido ao esconder |
85
+
86
+ ### Slots
87
+
88
+ | Slot | Props | Descrição |
89
+ |------|-------|-----------|
90
+ | `activator` | - | Elemento que ativa o tooltip |
91
+ | `default` | - | Conteúdo principal |
92
+ | `title` | - | Título opcional (só aparece em modo controlado) |
93
+
94
+ ## 💡 Exemplos de Uso
95
+
96
+ ### Básico
97
+ ```vue
98
+ <farm-tooltip>
99
+ <template #activator>
100
+ <button>Hover me</button>
101
+ </template>
102
+ Informação adicional sobre este botão
103
+ </farm-tooltip>
104
+ ```
105
+
106
+ ### Posicionamento Customizado
107
+ ```vue
108
+ <farm-tooltip placement="bottom-right" :offset="12">
109
+ <template #activator>
110
+ <IconHelp />
111
+ </template>
112
+ Ajuda contextual posicionada à direita
113
+ </farm-tooltip>
114
+ ```
115
+
116
+ ### Modo Controlado com Título
117
+ ```vue
118
+ <template>
119
+ <farm-tooltip v-model="showHelp">
120
+ <template #activator>
121
+ <button @click="showHelp = !showHelp">
122
+ Toggle Help
123
+ </button>
124
+ </template>
125
+ <template #title>
126
+ Informações Importantes
127
+ </template>
128
+ <ul>
129
+ <li>Primeiro ponto</li>
130
+ <li>Segundo ponto</li>
131
+ <li>Terceiro ponto</li>
132
+ </ul>
133
+ </farm-tooltip>
134
+ </template>
135
+
136
+ <script setup>
137
+ import { ref } from 'vue';
138
+ const showHelp = ref(false);
139
+ </script>
140
+ ```
141
+
142
+ ### Tooltip Fluido para Conteúdo Longo
143
+ ```vue
144
+ <farm-tooltip :fluid="true">
145
+ <template #activator>
146
+ <span>Ver descrição completa</span>
147
+ </template>
148
+ Este é um texto mais longo que se adapta automaticamente
149
+ à largura do conteúdo, expandindo até o máximo de 300px
150
+ antes de quebrar em múltiplas linhas.
151
+ </farm-tooltip>
152
+ ```
153
+
154
+ ### Diferentes Tamanhos
155
+ ```vue
156
+ <div class="flex gap-4">
157
+ <farm-tooltip size="sm">
158
+ <template #activator>
159
+ <Badge>SM</Badge>
160
+ </template>
161
+ Tooltip pequeno
162
+ </farm-tooltip>
163
+
164
+ <farm-tooltip size="md">
165
+ <template #activator>
166
+ <Badge>MD</Badge>
167
+ </template>
168
+ Tooltip médio (padrão)
169
+ </farm-tooltip>
170
+
171
+ <farm-tooltip size="lg">
172
+ <template #activator>
173
+ <Badge>LG</Badge>
174
+ </template>
175
+ Tooltip grande com mais espaçamento
176
+ </farm-tooltip>
177
+ </div>
178
+ ```
179
+
180
+ ### Com Delay Customizado
181
+ ```vue
182
+ <farm-tooltip :delay="[500, 0]">
183
+ <template #activator>
184
+ <button>Hover com delay</button>
185
+ </template>
186
+ Aparece após 500ms, desaparece instantaneamente
187
+ </farm-tooltip>
188
+ ```
189
+
190
+ ## 🎯 Casos de Uso Comuns
191
+
192
+ ### 1. Ícones de Ajuda
193
+ ```vue
194
+ <label>
195
+ CPF
196
+ <farm-tooltip placement="top-right">
197
+ <template #activator>
198
+ <IconQuestion class="ml-1 text-gray-400" />
199
+ </template>
200
+ Digite apenas números, sem pontos ou traços
201
+ </farm-tooltip>
202
+ </label>
203
+ ```
204
+
205
+ ### 2. Botões com Ações Desabilitadas
206
+ ```vue
207
+ <farm-tooltip :disabled="canDelete">
208
+ <template #activator>
209
+ <button :disabled="!canDelete" @click="handleDelete">
210
+ Excluir
211
+ </button>
212
+ </template>
213
+ Você não tem permissão para excluir este item
214
+ </farm-tooltip>
215
+ ```
216
+
217
+ ### 3. Truncate com Preview Completo
218
+ ```vue
219
+ <farm-tooltip>
220
+ <template #activator>
221
+ <p class="truncate max-w-xs">
222
+ {{ longText }}
223
+ </p>
224
+ </template>
225
+ {{ longText }}
226
+ </farm-tooltip>
227
+ ```
228
+
229
+ ### 4. Status com Detalhes
230
+ ```vue
231
+ <farm-tooltip placement="bottom-center">
232
+ <template #activator>
233
+ <StatusBadge :status="order.status" />
234
+ </template>
235
+ <div>
236
+ <p><strong>Status:</strong> {{ order.status }}</p>
237
+ <p><strong>Atualizado:</strong> {{ order.updatedAt }}</p>
238
+ <p><strong>Por:</strong> {{ order.updatedBy }}</p>
239
+ </div>
240
+ </farm-tooltip>
241
+ ```
242
+
243
+ ## ⚡ Performance e Boas Práticas
244
+
245
+ ### Do's ✅
246
+ - Use tooltips para informações secundárias e não-críticas
247
+ - Mantenha o conteúdo conciso e direto
248
+ - Use o placement adequado para não cobrir elementos importantes
249
+ - Para conteúdo complexo, considere usar um Popover ou Modal
250
+
251
+ ### Don'ts ❌
252
+ - Não use tooltips para informações essenciais
253
+ - Evite tooltips em elementos mobile/touch
254
+ - Não coloque formulários ou ações dentro de tooltips
255
+ - Evite tooltips aninhados
256
+
257
+ ## 🔧 Troubleshooting
258
+
259
+ ### Tooltip aparece atrás de outros elementos
260
+ 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`.
261
+
262
+ ### Tooltip não aparece
263
+ 1. Verifique se `disabled` não está `true`
264
+ 2. Em modo controlado, certifique-se que `modelValue` está sendo atualizado
265
+ 3. Verifique se o slot `activator` tem apenas um elemento raiz
266
+
267
+ ### Posicionamento incorreto
268
+ O tooltip se reposiciona automaticamente ao detectar scroll ou resize. Se o problema persistir:
269
+ 1. Verifique se o elemento activator não está com `position: fixed`
270
+ 2. Certifique-se que o container pai não tem transforms 3D
271
+
272
+ ## 🎨 Customização
273
+
274
+ ### CSS Variables
275
+ ```css
276
+ .tooltip-popup {
277
+ --tooltip-bg: #333333;
278
+ --tooltip-color: #f5f5f5;
279
+ --tooltip-radius: 5px;
280
+ --tooltip-font-size: 12px;
281
+ --tooltip-padding: 16px;
282
+ }
283
+ ```
284
+
285
+ ### Classes para Styling
286
+ ```css
287
+ .tooltip-popup--visible { /* Quando visível */ }
288
+ .tooltip-popup--sm { /* Tamanho pequeno */ }
289
+ .tooltip-popup--md { /* Tamanho médio */ }
290
+ .tooltip-popup--lg { /* Tamanho grande */ }
291
+ .tooltip-popup--has-title { /* Quando tem título */ }
292
+ ```
293
+
294
+ ## 📊 Especificações Técnicas
295
+
296
+ ### Posições Suportadas
297
+ - `top-left`: Acima e alinhado à esquerda
298
+ - `top-center`: Acima e centralizado
299
+ - `top-right`: Acima e alinhado à direita
300
+ - `bottom-left`: Abaixo e alinhado à esquerda
301
+ - `bottom-center`: Abaixo e centralizado
302
+ - `bottom-right`: Abaixo e alinhado à direita
303
+
304
+ ### Comportamento da Seta
305
+ - Sempre aponta para o centro do elemento ativador
306
+ - Ajusta-se dinamicamente quando o tooltip é reposicionado
307
+ - Mantém margem mínima de 12px das bordas
308
+
309
+ ### Detecção de Colisão
310
+ - Viewport boundaries detection
311
+ - Automatic repositioning
312
+ - Scroll containers support
313
+ - 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,43 @@
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
+ modelValue?: boolean;
17
+ trigger?: TooltipTrigger;
18
+ placement?: TooltipPlacement;
19
+ offset?: number;
20
+ variant?: TooltipVariant;
21
+ size?: TooltipSize;
22
+ maxWidth?: string | number;
23
+ delay?: number | [number, number];
24
+ disabled?: boolean;
25
+ fluid?: boolean;
26
+ }
27
+
28
+ export interface TooltipEmits {
29
+ 'update:modelValue': [value: boolean];
30
+ show: [];
31
+ hide: [];
32
+ }
33
+
34
+ export interface Position {
35
+ x: number;
36
+ y: number;
37
+ }
38
+
39
+ export interface TooltipState {
40
+ isVisible: boolean;
41
+ position: Position;
42
+ actualPlacement: TooltipPlacement;
43
+ }
package/src/main.ts CHANGED
@@ -18,6 +18,7 @@ import PromptUserToConfirm from './components/PromptUserToConfirm';
18
18
  import RangeDatePicker from './components/RangeDatePicker';
19
19
  import ResourceMetaInfo from './components/ResourceMetaInfo';
20
20
  import TableContextMenu from './components/TableContextMenu';
21
+ import Tooltip from './components/Tooltip';
21
22
 
22
23
  export {
23
24
  DataTablePaginator,
@@ -38,6 +39,7 @@ export {
38
39
  Collapsible,
39
40
  IdCaption,
40
41
  ResourceMetaInfo,
42
+ Tooltip,
41
43
  };
42
44
 
43
45
  export * from './components/AlertBox';