@farm-investimentos/front-mfe-components 15.14.4 → 15.14.6
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.
- package/dist/front-mfe-components.common.js +365 -1042
- package/dist/front-mfe-components.common.js.map +1 -1
- package/dist/front-mfe-components.css +1 -1
- package/dist/front-mfe-components.umd.js +365 -1042
- package/dist/front-mfe-components.umd.js.map +1 -1
- package/dist/front-mfe-components.umd.min.js +1 -1
- package/dist/front-mfe-components.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Modal/Modal.scss +86 -85
- package/src/components/Tooltip/Tooltip.scss +90 -62
- package/src/components/Tooltip/Tooltip.stories.js +322 -158
- package/src/components/Tooltip/Tooltip.vue +265 -302
- package/src/components/Tooltip/docs/README.md +304 -0
- package/src/components/Tooltip/docs/SPECIFICATION.md +374 -0
- package/src/components/Tooltip/index.ts +16 -0
- package/src/components/Tooltip/types/tooltip.types.ts +49 -0
- package/src/components/Tooltip/utils/tooltip.utils.ts +70 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Tooltip Component
|
|
2
|
+
|
|
3
|
+
Componente de tooltip simples e eficiente para Vue 2.7, com suporte a 6 posições, controle via v-model e comportamento otimizado para modais.
|
|
4
|
+
|
|
5
|
+
## 🎯 Características
|
|
6
|
+
|
|
7
|
+
- ✅ **6 posições**: `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right`
|
|
8
|
+
- ✅ **Vue 2.7 compatível** com Composition API
|
|
9
|
+
- ✅ **v-model support** para tooltips controlados
|
|
10
|
+
- ✅ **Seta inteligente** que aponta para o centro do ativador
|
|
11
|
+
- ✅ **Scroll detection** automático em modais
|
|
12
|
+
- ✅ **Position fixed** para evitar problemas de overflow
|
|
13
|
+
- ✅ **Cleanup automático** de event listeners
|
|
14
|
+
|
|
15
|
+
## 📦 Instalação
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import Tooltip from '@/components/Tooltip';
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
components: { Tooltip },
|
|
22
|
+
};
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🚀 Uso Básico
|
|
26
|
+
|
|
27
|
+
### Tooltip Simples
|
|
28
|
+
|
|
29
|
+
```vue
|
|
30
|
+
<farm-tooltip placement="top-center">
|
|
31
|
+
<template v-slot:activator>
|
|
32
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
33
|
+
</template>
|
|
34
|
+
Este é um tooltip simples
|
|
35
|
+
</farm-tooltip>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Tooltip com Título
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<farm-tooltip placement="top-left">
|
|
42
|
+
<template v-slot:activator>
|
|
43
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
44
|
+
</template>
|
|
45
|
+
<template v-slot:title>Informação Importante</template>
|
|
46
|
+
Conteúdo do tooltip com título
|
|
47
|
+
</farm-tooltip>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Tooltip Fluido
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<farm-tooltip placement="top-center" :fluid="true">
|
|
54
|
+
<template v-slot:activator>
|
|
55
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
56
|
+
</template>
|
|
57
|
+
Este tooltip tem largura baseada no conteúdo e se adapta automaticamente
|
|
58
|
+
</farm-tooltip>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Tooltip com Posição Legacy
|
|
62
|
+
|
|
63
|
+
```vue
|
|
64
|
+
<farm-tooltip position="top-left">
|
|
65
|
+
<template v-slot:activator>
|
|
66
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
67
|
+
</template>
|
|
68
|
+
Tooltip usando a prop position (legacy)
|
|
69
|
+
</farm-tooltip>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Tooltip Controlado (v-model)
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<template>
|
|
76
|
+
<farm-tooltip v-model="showTooltip" placement="top-right">
|
|
77
|
+
<template v-slot:activator>
|
|
78
|
+
<farm-icon @click="showTooltip = !showTooltip" size="md" color="blue"
|
|
79
|
+
>help-circle</farm-icon
|
|
80
|
+
>
|
|
81
|
+
</template>
|
|
82
|
+
<template v-slot:title>Tooltip Controlado</template>
|
|
83
|
+
Este tooltip é controlado por v-model
|
|
84
|
+
</farm-tooltip>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script>
|
|
88
|
+
export default {
|
|
89
|
+
data() {
|
|
90
|
+
return {
|
|
91
|
+
showTooltip: false,
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
</script>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 📋 Props
|
|
99
|
+
|
|
100
|
+
| Prop | Tipo | Padrão | Descrição |
|
|
101
|
+
| ----------- | ------------------ | -------------- | ------------------------------- |
|
|
102
|
+
| `value` | `Boolean` | `undefined` | Controla visibilidade (v-model) |
|
|
103
|
+
| `trigger` | `TooltipTrigger` | `'hover'` | Como o tooltip é ativado |
|
|
104
|
+
| `placement` | `TooltipPlacement` | `'top-center'` | Posição do tooltip |
|
|
105
|
+
| `offset` | `Number` | `8` | Distância do ativador (px) |
|
|
106
|
+
| `variant` | `TooltipVariant` | `'dark'` | Variante visual do tooltip |
|
|
107
|
+
| `size` | `TooltipSize` | `'md'` | Tamanho do tooltip |
|
|
108
|
+
| `maxWidth` | `String \| Number` | `undefined` | Largura máxima |
|
|
109
|
+
| `delay` | `Number \| Array` | `[100, 50]` | Delay para show/hide |
|
|
110
|
+
| `disabled` | `Boolean` | `false` | Desabilita o tooltip |
|
|
111
|
+
| `fluid` | `Boolean` | `false` | Largura baseada no conteúdo |
|
|
112
|
+
| `position` | `String` | `undefined` | Posição alternativa (legacy) |
|
|
113
|
+
|
|
114
|
+
## 🎨 Posições Disponíveis
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
type TooltipPlacement =
|
|
118
|
+
| 'top-left' // Seta no canto esquerdo, aponta para centro do ativador
|
|
119
|
+
| 'top-center' // Seta no centro, aponta para centro do ativador
|
|
120
|
+
| 'top-right' // Seta no canto direito, aponta para centro do ativador
|
|
121
|
+
| 'bottom-left' // Seta no canto esquerdo, aponta para centro do ativador
|
|
122
|
+
| 'bottom-center' // Seta no centro, aponta para centro do ativador
|
|
123
|
+
| 'bottom-right'; // Seta no canto direito, aponta para centro do ativador
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 🎭 Slots
|
|
127
|
+
|
|
128
|
+
| Slot | Descrição |
|
|
129
|
+
| ----------- | ------------------------------------------ |
|
|
130
|
+
| `activator` | Elemento que ativa o tooltip (obrigatório) |
|
|
131
|
+
| `title` | Título do tooltip (opcional) |
|
|
132
|
+
| `default` | Conteúdo principal do tooltip |
|
|
133
|
+
|
|
134
|
+
## 📡 Events
|
|
135
|
+
|
|
136
|
+
| Event | Payload | Descrição |
|
|
137
|
+
| ------- | --------- | ------------------------------------------ |
|
|
138
|
+
| `input` | `boolean` | Emitido quando visibilidade muda (v-model) |
|
|
139
|
+
| `show` | - | Emitido quando tooltip aparece |
|
|
140
|
+
| `hide` | - | Emitido quando tooltip esconde |
|
|
141
|
+
|
|
142
|
+
## 🏗️ Arquitetura
|
|
143
|
+
|
|
144
|
+
### Estrutura de Arquivos
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
src/components/Tooltip/
|
|
148
|
+
├── Tooltip.vue # Componente principal
|
|
149
|
+
├── Tooltip.scss # Estilos
|
|
150
|
+
├── Tooltip.stories.js # Stories do Storybook
|
|
151
|
+
├── index.ts # Exports
|
|
152
|
+
├── utils/
|
|
153
|
+
│ └── tooltip.utils.ts # Utilitários de posicionamento
|
|
154
|
+
├── types/
|
|
155
|
+
│ └── tooltip.types.ts # Tipos TypeScript
|
|
156
|
+
└── docs/
|
|
157
|
+
└── README.md # Esta documentação
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Componentes Principais
|
|
161
|
+
|
|
162
|
+
#### `Tooltip.vue`
|
|
163
|
+
|
|
164
|
+
- **Lógica principal** com Composition API
|
|
165
|
+
- **Event handlers** para show/hide
|
|
166
|
+
- **Scroll detection** automático
|
|
167
|
+
- **Position calculation** via utils
|
|
168
|
+
- **Cleanup** automático de listeners
|
|
169
|
+
|
|
170
|
+
#### `tooltip.utils.ts`
|
|
171
|
+
|
|
172
|
+
- **`calculateTooltipPosition`** - Calcula posição baseada no ativador
|
|
173
|
+
- **`moveToBody`** - Move tooltip para body (portal effect)
|
|
174
|
+
- **`moveToContainer`** - Move tooltip de volta ao container
|
|
175
|
+
|
|
176
|
+
## 🎯 Casos de Uso
|
|
177
|
+
|
|
178
|
+
### Em Modais com Scroll
|
|
179
|
+
|
|
180
|
+
O tooltip detecta automaticamente scroll em modais e atualiza sua posição:
|
|
181
|
+
|
|
182
|
+
```vue
|
|
183
|
+
<farm-modal v-model="showModal">
|
|
184
|
+
<template v-slot:content>
|
|
185
|
+
<div style="height: 400px; overflow-y: auto;">
|
|
186
|
+
<farm-tooltip placement="top-left">
|
|
187
|
+
<template v-slot:activator>
|
|
188
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
189
|
+
</template>
|
|
190
|
+
Tooltip que se move com scroll
|
|
191
|
+
</farm-tooltip>
|
|
192
|
+
</div>
|
|
193
|
+
</template>
|
|
194
|
+
</farm-modal>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Múltiplos Tooltips
|
|
198
|
+
|
|
199
|
+
```vue
|
|
200
|
+
<div style="display: flex; gap: 20px;">
|
|
201
|
+
<farm-tooltip placement="top-left">
|
|
202
|
+
<template v-slot:activator>
|
|
203
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
204
|
+
</template>
|
|
205
|
+
Tooltip 1
|
|
206
|
+
</farm-tooltip>
|
|
207
|
+
|
|
208
|
+
<farm-tooltip placement="top-center">
|
|
209
|
+
<template v-slot:activator>
|
|
210
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
211
|
+
</template>
|
|
212
|
+
Tooltip 2
|
|
213
|
+
</farm-tooltip>
|
|
214
|
+
</div>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 🔧 Customização
|
|
218
|
+
|
|
219
|
+
### Estilos CSS
|
|
220
|
+
|
|
221
|
+
O tooltip usa classes CSS que podem ser customizadas:
|
|
222
|
+
|
|
223
|
+
```scss
|
|
224
|
+
.tooltip-popup {
|
|
225
|
+
background-color: #333333;
|
|
226
|
+
color: #f5f5f5;
|
|
227
|
+
border-radius: 5px;
|
|
228
|
+
font-family: 'Manrope', sans-serif;
|
|
229
|
+
font-size: 12px;
|
|
230
|
+
font-weight: 500;
|
|
231
|
+
padding: 16px;
|
|
232
|
+
position: fixed;
|
|
233
|
+
z-index: 9999;
|
|
234
|
+
|
|
235
|
+
&--visible {
|
|
236
|
+
opacity: 1;
|
|
237
|
+
visibility: visible;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.tooltip-header {
|
|
241
|
+
display: flex;
|
|
242
|
+
justify-content: space-between;
|
|
243
|
+
align-items: center;
|
|
244
|
+
margin-bottom: 8px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.tooltip-title {
|
|
248
|
+
font-weight: 600;
|
|
249
|
+
font-size: 13px;
|
|
250
|
+
margin-right: 16px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.tooltip-close {
|
|
254
|
+
cursor: pointer;
|
|
255
|
+
font-size: 16px;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Posicionamento da Seta
|
|
261
|
+
|
|
262
|
+
A seta é posicionada automaticamente para apontar para o centro do ativador:
|
|
263
|
+
|
|
264
|
+
- **`left/right`**: Seta a 24px da borda, aponta para centro do ativador
|
|
265
|
+
- **`center`**: Seta no centro do tooltip, aponta para centro do ativador
|
|
266
|
+
|
|
267
|
+
## 🐛 Troubleshooting
|
|
268
|
+
|
|
269
|
+
### Tooltip não aparece
|
|
270
|
+
|
|
271
|
+
- Verifique se o `activator` slot está definido
|
|
272
|
+
- Confirme que o elemento ativador está visível
|
|
273
|
+
- Verifique se não há `z-index` conflitantes
|
|
274
|
+
|
|
275
|
+
### Tooltip não se move com scroll
|
|
276
|
+
|
|
277
|
+
- O tooltip detecta scroll automaticamente
|
|
278
|
+
- Se não funcionar, verifique se o elemento com scroll tem `overflow-y: auto/scroll`
|
|
279
|
+
- O tooltip se esconde automaticamente se o ativador sair da viewport
|
|
280
|
+
|
|
281
|
+
### Seta mal posicionada
|
|
282
|
+
|
|
283
|
+
- A seta sempre aponta para o centro do ativador
|
|
284
|
+
- Para `left/right`, a seta fica a 18px da borda
|
|
285
|
+
- Para `center`, a seta fica no centro do tooltip
|
|
286
|
+
|
|
287
|
+
## 📚 Exemplos no Storybook
|
|
288
|
+
|
|
289
|
+
Acesse `http://localhost:6006` e navegue para **Display/Tooltip** para ver exemplos interativos:
|
|
290
|
+
|
|
291
|
+
- **Primary** - Tooltip básico
|
|
292
|
+
- **AllPositions** - Todas as 6 posições
|
|
293
|
+
- **ControlledTooltip** - Tooltip com v-model
|
|
294
|
+
- **FluidTooltip** - Tooltip com largura fluida
|
|
295
|
+
- **WithModal** - Tooltip em modal com scroll
|
|
296
|
+
- **MultipleTooltips** - Múltiplos tooltips
|
|
297
|
+
|
|
298
|
+
## 🤝 Contribuição
|
|
299
|
+
|
|
300
|
+
1. Mantenha a simplicidade do componente
|
|
301
|
+
2. Teste em modais com scroll
|
|
302
|
+
3. Verifique se a seta aponta corretamente
|
|
303
|
+
4. Use os stories para testar mudanças
|
|
304
|
+
5. Atualize esta documentação se necessário
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Tooltip Component - Especificação Técnica
|
|
2
|
+
|
|
3
|
+
## 📋 Visão Geral
|
|
4
|
+
|
|
5
|
+
O componente `Tooltip` é uma implementação simples e eficiente de tooltip para Vue 2.7, projetado para ser fácil de usar e manter, com foco em performance e compatibilidade.
|
|
6
|
+
|
|
7
|
+
## 🎯 Objetivos de Design
|
|
8
|
+
|
|
9
|
+
### Simplicidade
|
|
10
|
+
|
|
11
|
+
- **Lógica direta**: Sem cálculos complexos desnecessários
|
|
12
|
+
- **API simples**: Props e slots intuitivos
|
|
13
|
+
- **Manutenibilidade**: Código limpo e bem estruturado
|
|
14
|
+
|
|
15
|
+
### Performance
|
|
16
|
+
|
|
17
|
+
- **Event listeners otimizados**: Uso de `passive: true`
|
|
18
|
+
- **Cleanup automático**: Remoção de listeners no unmount
|
|
19
|
+
- **Position fixed**: Evita reflows desnecessários
|
|
20
|
+
|
|
21
|
+
### Compatibilidade
|
|
22
|
+
|
|
23
|
+
- **Vue 2.7**: Suporte completo com Composition API
|
|
24
|
+
- **Modais**: Funciona perfeitamente em contextos com scroll
|
|
25
|
+
- **Retrocompatibilidade**: Mantém props antigas quando possível
|
|
26
|
+
|
|
27
|
+
## 🏗️ Arquitetura
|
|
28
|
+
|
|
29
|
+
### Estrutura de Componentes
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Tooltip.vue (Principal)
|
|
33
|
+
├── Template
|
|
34
|
+
│ ├── Container (position: relative)
|
|
35
|
+
│ ├── Activator (slot)
|
|
36
|
+
│ └── Tooltip Content (position: fixed)
|
|
37
|
+
│ ├── Header (title + close button)
|
|
38
|
+
│ ├── Content (default slot)
|
|
39
|
+
│ └── Arrow (CSS-based)
|
|
40
|
+
├── Script (Composition API)
|
|
41
|
+
│ ├── Props & Emits
|
|
42
|
+
│ ├── Refs & State
|
|
43
|
+
│ ├── Computed Properties
|
|
44
|
+
│ ├── Event Handlers
|
|
45
|
+
│ ├── Scroll Detection
|
|
46
|
+
│ └── Lifecycle Hooks
|
|
47
|
+
└── Styles (SCSS)
|
|
48
|
+
├── Base styles
|
|
49
|
+
├── States (visible/hidden)
|
|
50
|
+
└── Arrow positioning
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Fluxo de Dados
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
User Interaction → Event Handler → State Update → DOM Update → Position Calculation
|
|
57
|
+
↓
|
|
58
|
+
Scroll Detection → Position Update → Viewport Check → Auto Hide (if needed)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🔧 Implementação Técnica
|
|
62
|
+
|
|
63
|
+
### Props Interface
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface TooltipProps {
|
|
67
|
+
// Visibility control (Vue 2.7 style)
|
|
68
|
+
value?: boolean; // v-model
|
|
69
|
+
trigger?: TooltipTrigger; // 'hover' | 'click' | 'manual'
|
|
70
|
+
|
|
71
|
+
// Posicionamento
|
|
72
|
+
placement?: TooltipPlacement; // 6 posições disponíveis
|
|
73
|
+
offset?: number; // Distância do ativador
|
|
74
|
+
|
|
75
|
+
// Aparência
|
|
76
|
+
variant?: TooltipVariant; // 'dark' | 'light'
|
|
77
|
+
size?: TooltipSize; // 'sm' | 'md' | 'lg'
|
|
78
|
+
fluid?: boolean; // Largura baseada no conteúdo
|
|
79
|
+
maxWidth?: string | number; // Largura máxima
|
|
80
|
+
|
|
81
|
+
// Comportamento
|
|
82
|
+
delay?: number | [number, number]; // Delay para show/hide
|
|
83
|
+
disabled?: boolean; // Desabilita o tooltip
|
|
84
|
+
|
|
85
|
+
// Legacy (mantido para compatibilidade)
|
|
86
|
+
position?: string; // Posição alternativa
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### State Management
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Refs principais
|
|
94
|
+
const isVisible = ref(false);
|
|
95
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
96
|
+
const activatorRef = ref<HTMLElement | null>(null);
|
|
97
|
+
const tooltipRef = ref<HTMLElement | null>(null);
|
|
98
|
+
|
|
99
|
+
// Computed properties
|
|
100
|
+
const isControlled = computed(() => props.value !== undefined);
|
|
101
|
+
const hasTitle = computed(() => !!slots.title);
|
|
102
|
+
const showCloseButton = computed(() => isControlled.value && hasTitle.value);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Event Handling
|
|
106
|
+
|
|
107
|
+
#### Show/Hide Logic
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const show = () => {
|
|
111
|
+
if (props.disabled || isControlled.value) return;
|
|
112
|
+
|
|
113
|
+
isVisible.value = true;
|
|
114
|
+
emit('show');
|
|
115
|
+
|
|
116
|
+
nextTick(() => {
|
|
117
|
+
if (tooltipRef.value && activatorRef.value) {
|
|
118
|
+
moveToBody(tooltipRef.value);
|
|
119
|
+
updatePosition();
|
|
120
|
+
addScrollListener();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const hide = () => {
|
|
126
|
+
if (props.disabled || isControlled.value) return;
|
|
127
|
+
|
|
128
|
+
isVisible.value = false;
|
|
129
|
+
emit('hide');
|
|
130
|
+
removeScrollListener();
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Scroll Detection
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const addScrollListener = () => {
|
|
138
|
+
window.addEventListener('scroll', updatePosition, { passive: true });
|
|
139
|
+
|
|
140
|
+
const scrollableElements = document.querySelectorAll(
|
|
141
|
+
'.farm-modal, .modal-content, [style*="overflow-y: auto"], [style*="overflow-y: scroll"]'
|
|
142
|
+
);
|
|
143
|
+
scrollableElements.forEach(element => {
|
|
144
|
+
element.addEventListener('scroll', updatePosition, { passive: true });
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Position Calculation
|
|
150
|
+
|
|
151
|
+
#### Utils Function
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
export function calculateTooltipPosition(
|
|
155
|
+
activatorRect: TooltipRect,
|
|
156
|
+
tooltipRect: TooltipRect,
|
|
157
|
+
placement: string,
|
|
158
|
+
offset: number = 8
|
|
159
|
+
): TooltipPosition {
|
|
160
|
+
const [verticalPos, horizontalAlign] = placement.split('-');
|
|
161
|
+
|
|
162
|
+
let left = 0;
|
|
163
|
+
let top = 0;
|
|
164
|
+
|
|
165
|
+
// Posição vertical
|
|
166
|
+
if (verticalPos === 'top') {
|
|
167
|
+
top = activatorRect.top - tooltipRect.height - offset;
|
|
168
|
+
} else {
|
|
169
|
+
top = activatorRect.bottom + offset;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Posição horizontal com seta inteligente
|
|
173
|
+
switch (horizontalAlign) {
|
|
174
|
+
case 'left':
|
|
175
|
+
left = activatorRect.left + activatorRect.width / 2 - 18;
|
|
176
|
+
break;
|
|
177
|
+
case 'right':
|
|
178
|
+
left = activatorRect.left + activatorRect.width / 2 - (tooltipRect.width - 18);
|
|
179
|
+
break;
|
|
180
|
+
case 'center':
|
|
181
|
+
default:
|
|
182
|
+
left = activatorRect.left + activatorRect.width / 2 - tooltipRect.width / 2;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { left, top };
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## 🎨 Sistema de Posicionamento
|
|
191
|
+
|
|
192
|
+
### 6 Posições Suportadas
|
|
193
|
+
|
|
194
|
+
| Posição | Seta | Comportamento |
|
|
195
|
+
| --------------- | ---------------- | ------------------------------ |
|
|
196
|
+
| `top-left` | 24px da esquerda | Aponta para centro do ativador |
|
|
197
|
+
| `top-center` | Centro | Aponta para centro do ativador |
|
|
198
|
+
| `top-right` | 24px da direita | Aponta para centro do ativador |
|
|
199
|
+
| `bottom-left` | 24px da esquerda | Aponta para centro do ativador |
|
|
200
|
+
| `bottom-center` | Centro | Aponta para centro do ativador |
|
|
201
|
+
| `bottom-right` | 24px da direita | Aponta para centro do ativador |
|
|
202
|
+
|
|
203
|
+
### Lógica da Seta
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const ARROW_OFFSET = 24;
|
|
207
|
+
|
|
208
|
+
const arrowStyles = computed(() => {
|
|
209
|
+
const [verticalPos, horizontalAlign] = normalizedPlacement.value.split('-');
|
|
210
|
+
|
|
211
|
+
const styles: Record<string, string> = {
|
|
212
|
+
position: 'absolute',
|
|
213
|
+
width: '0',
|
|
214
|
+
height: '0',
|
|
215
|
+
borderStyle: 'solid',
|
|
216
|
+
zIndex: '10000',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (verticalPos === 'top') {
|
|
220
|
+
styles.bottom = '-6px';
|
|
221
|
+
styles.borderWidth = '6px 6px 0 6px';
|
|
222
|
+
styles.borderColor = '#333333 transparent transparent transparent';
|
|
223
|
+
} else {
|
|
224
|
+
styles.top = '-6px';
|
|
225
|
+
styles.borderWidth = '0 6px 6px 6px';
|
|
226
|
+
styles.borderColor = 'transparent transparent #333333 transparent';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (horizontalAlign === 'left') {
|
|
230
|
+
styles.left = `${ARROW_OFFSET}px`;
|
|
231
|
+
} else if (horizontalAlign === 'right') {
|
|
232
|
+
styles.right = `${ARROW_OFFSET}px`;
|
|
233
|
+
} else {
|
|
234
|
+
styles.left = '50%';
|
|
235
|
+
styles.transform = 'translateX(-50%)';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return styles;
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## 🔄 Portal Effect
|
|
243
|
+
|
|
244
|
+
### DOM Manipulation
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
export function moveToBody(element: HTMLElement): void {
|
|
248
|
+
if (element.parentNode !== document.body) {
|
|
249
|
+
document.body.appendChild(element);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function moveToContainer(element: HTMLElement, container: HTMLElement): void {
|
|
254
|
+
if (element.parentNode === document.body) {
|
|
255
|
+
container.appendChild(element);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Por que Portal?
|
|
261
|
+
|
|
262
|
+
- **Evita overflow**: Tooltip não fica cortado por containers
|
|
263
|
+
- **Z-index**: Sempre fica acima de outros elementos
|
|
264
|
+
- **Scroll**: Não é afetado por scroll do container pai
|
|
265
|
+
|
|
266
|
+
## 🎯 Casos de Uso Específicos
|
|
267
|
+
|
|
268
|
+
### Modal com Scroll
|
|
269
|
+
|
|
270
|
+
```vue
|
|
271
|
+
<farm-modal v-model="showModal">
|
|
272
|
+
<template v-slot:content>
|
|
273
|
+
<div style="height: 400px; overflow-y: auto;">
|
|
274
|
+
<farm-tooltip placement="top-left">
|
|
275
|
+
<template v-slot:activator>
|
|
276
|
+
<farm-icon size="sm" color="gray">help-circle</farm-icon>
|
|
277
|
+
</template>
|
|
278
|
+
Tooltip que se move com scroll
|
|
279
|
+
</farm-tooltip>
|
|
280
|
+
</div>
|
|
281
|
+
</template>
|
|
282
|
+
</farm-modal>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Comportamento:**
|
|
286
|
+
|
|
287
|
+
1. Tooltip detecta scroll no modal
|
|
288
|
+
2. Atualiza posição automaticamente
|
|
289
|
+
3. Esconde se ativador sair da viewport
|
|
290
|
+
4. Remove listeners quando esconde
|
|
291
|
+
|
|
292
|
+
### Tooltip Controlado
|
|
293
|
+
|
|
294
|
+
```vue
|
|
295
|
+
<farm-tooltip v-model="showTooltip" placement="top-right">
|
|
296
|
+
<template v-slot:activator>
|
|
297
|
+
<farm-icon @click="showTooltip = !showTooltip">help-circle</farm-icon>
|
|
298
|
+
</template>
|
|
299
|
+
<template v-slot:title>Tooltip Controlado</template>
|
|
300
|
+
Conteúdo do tooltip
|
|
301
|
+
</farm-tooltip>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Comportamento:**
|
|
305
|
+
|
|
306
|
+
1. Visibilidade controlada por v-model
|
|
307
|
+
2. Botão de fechar aparece automaticamente
|
|
308
|
+
3. Eventos `show`/`hide` emitidos
|
|
309
|
+
4. Cleanup automático de listeners
|
|
310
|
+
|
|
311
|
+
## 🧪 Testes
|
|
312
|
+
|
|
313
|
+
### Cenários de Teste
|
|
314
|
+
|
|
315
|
+
1. **Posicionamento**
|
|
316
|
+
|
|
317
|
+
- [ ] Todas as 6 posições funcionam
|
|
318
|
+
- [ ] Seta aponta para centro do ativador
|
|
319
|
+
- [ ] Tooltip não sai da viewport
|
|
320
|
+
|
|
321
|
+
2. **Interação**
|
|
322
|
+
|
|
323
|
+
- [ ] Hover show/hide funciona
|
|
324
|
+
- [ ] v-model funciona corretamente
|
|
325
|
+
- [ ] Botão de fechar funciona
|
|
326
|
+
|
|
327
|
+
3. **Scroll**
|
|
328
|
+
|
|
329
|
+
- [ ] Tooltip se move com scroll
|
|
330
|
+
- [ ] Tooltip esconde quando ativador sai da viewport
|
|
331
|
+
- [ ] Listeners são removidos corretamente
|
|
332
|
+
|
|
333
|
+
4. **Modal**
|
|
334
|
+
- [ ] Funciona em modal com scroll
|
|
335
|
+
- [ ] Posição atualiza corretamente
|
|
336
|
+
- [ ] Não interfere com outros elementos
|
|
337
|
+
|
|
338
|
+
### Como Testar
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# Executar testes
|
|
342
|
+
npm run test:unit -- Tooltip
|
|
343
|
+
|
|
344
|
+
# Executar Storybook
|
|
345
|
+
npm run storybook
|
|
346
|
+
|
|
347
|
+
# Testar manualmente
|
|
348
|
+
# 1. Acesse http://localhost:6006
|
|
349
|
+
# 2. Navegue para Display/Tooltip
|
|
350
|
+
# 3. Teste todos os stories
|
|
351
|
+
# 4. Especialmente o "WithModal"
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## 🔮 Roadmap
|
|
355
|
+
|
|
356
|
+
### Melhorias Futuras
|
|
357
|
+
|
|
358
|
+
- [ ] **Auto-flip**: Inverter posição se não couber na viewport
|
|
359
|
+
- [ ] **Animations**: Transições suaves
|
|
360
|
+
- [ ] **Themes**: Suporte a temas diferentes
|
|
361
|
+
- [ ] **Accessibility**: Melhor suporte a acessibilidade
|
|
362
|
+
|
|
363
|
+
### Limitações Atuais
|
|
364
|
+
|
|
365
|
+
- **Vue 2.7**: Não usa Teleport nativo (não disponível)
|
|
366
|
+
- **Manual DOM**: Manipulação manual para portal effect
|
|
367
|
+
- **Fixed positioning**: Sempre usa position: fixed
|
|
368
|
+
|
|
369
|
+
## 📚 Referências
|
|
370
|
+
|
|
371
|
+
- [Vue 2.7 Composition API](https://v2.vuejs.org/v2/guide/composition-api.html)
|
|
372
|
+
- [CSS Position Fixed](https://developer.mozilla.org/en-US/docs/Web/CSS/position)
|
|
373
|
+
- [DOM Event Listeners](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
|
|
374
|
+
- [Bounding Client Rect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect)
|
|
@@ -3,3 +3,19 @@ import Tooltip from './Tooltip.vue';
|
|
|
3
3
|
export default Tooltip;
|
|
4
4
|
|
|
5
5
|
export { Tooltip };
|
|
6
|
+
|
|
7
|
+
// Export types for external use
|
|
8
|
+
export type {
|
|
9
|
+
TooltipPlacement,
|
|
10
|
+
TooltipTrigger,
|
|
11
|
+
TooltipVariant,
|
|
12
|
+
TooltipSize,
|
|
13
|
+
TooltipProps,
|
|
14
|
+
TooltipEvents,
|
|
15
|
+
Position,
|
|
16
|
+
} from './types/tooltip.types';
|
|
17
|
+
|
|
18
|
+
// Export utilities
|
|
19
|
+
export { calculateTooltipPosition, moveToBody, moveToContainer } from './utils/tooltip.utils';
|
|
20
|
+
|
|
21
|
+
export type { TooltipPosition, TooltipRect } from './utils/tooltip.utils';
|