Skip to content

Commit 64d1c11

Browse files
DaBadBunnylaruiss
authored andcommitted
feat: ✨ Nouveau composant Bulle d'aide
1 parent f09021f commit 64d1c11

File tree

7 files changed

+338
-4
lines changed

7 files changed

+338
-4
lines changed

.vitepress/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ export default defineConfig({
118118
link: '/composants/DsfrSegmented.md',
119119
},
120120
{
121-
text: 'DsfrSegmentedSet',
122-
link: '/composants/DsfrSegmentedSet.md',
121+
text: 'DsfrTooltip',
122+
link: '/composants/DsfrTooltip.md',
123123
},
124124
]
125125
},

.vitepress/theme/style.css

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Colors
88
* -------------------------------------------------------------------------- */
99

10-
@font-face {
10+
@font-face {
1111
font-family: Marianne;
1212
src: url("@gouvfr/dsfr/dist/fonts/Marianne-Light.woff2") format("woff2"), url("@gouvfr/dsfr/dist/fonts/Marianne-Light.woff") format("woff");
1313
font-weight: 300;
@@ -190,3 +190,15 @@
190190
.w-full {
191191
width: 100%;
192192
}
193+
194+
.h-full {
195+
width: 100%;
196+
}
197+
198+
.flex {
199+
display: flex;
200+
}
201+
202+
.flex-end {
203+
justify-content: flex-end;
204+
}

docs/_frame.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
layout: false
33
---
44

5-
<div ref="el" class="flex h-4 flex-col px-6 pb-5"></div>
5+
<div ref="el" class="flex h-full flex-col px-6 pb-5"></div>
66

77
<script setup lang="ts">
88
import { useStyleTag } from '@vueuse/core'
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Information contextuelle et Infobulle - DsfrTooltip
2+
3+
## 🌟 Introduction
4+
5+
Le `DsfrTooltip` est un composant Vue versatile, conçu pour fournir des infobulles contextuelles. Il supporte le déclenchement au survol ou au clic, et s'adapte automatiquement à la position de l'élément source pour une visibilité optimale. Ce composant est idéal pour ajouter des explications ou des informations supplémentaires sans encombrer l'interface utilisateur.
6+
7+
## 🛠️ Les props
8+
9+
| Nom | Type | Défaut | Obligatoire | Description |
10+
|------------|-----------|----------------------|:-----------:|-------------------------------------------------------------|
11+
| `content` | `string` | || Le texte à afficher dans l'infobulle. |
12+
| `onHover` | `boolean` | `false` | | Si `true`, l'infobulle s'affiche au survol. |
13+
| `id` | `string` | `getRandomId('tooltip')` | | Identifiant unique pour l'infobulle. Utilisé pour l'accessibilité. |
14+
15+
## 📡 Événements
16+
17+
- Aucun événement personnalisé n'est émis par ce composant.
18+
19+
## 🧩 Les slots
20+
21+
- `default` : Contenu personnalisé pour l'élément déclencheur de l'infobulle (peut être un lien ou un bouton selon `onHover`).
22+
23+
## 📝 Exemples
24+
25+
```vue
26+
<DsfrTooltip content="Voici une infobulle">
27+
Survolez-moi
28+
</DsfrTooltip>
29+
```
30+
31+
## 📝 Toutes les variantes 🌈 d’info-bulles
32+
33+
::: code-group
34+
35+
<Story data-title="Démo" min-h="300px">
36+
<DsfrTooltipExample />
37+
</Story>
38+
39+
<<< docs-demo/DsfrTooltipExample.vue [Code de la démo]
40+
41+
<<< DsfrTooltip.vue
42+
:::
43+
44+
Avec DsfrTooltip, révélez des informations cachées comme un magicien sort un lapin de son chapeau ! 🎩🐇✨
45+
46+
<script setup lang="ts">
47+
import DsfrTooltipExample from './docs-demo/DsfrTooltipExample.vue'
48+
</script>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import DsfrTooltip from './DsfrTooltip.vue'
2+
export default {
3+
component: DsfrTooltip,
4+
title: 'Composants/DsfrTooltip',
5+
argTypes: {
6+
id: {
7+
control: 'text',
8+
description: '(optionnel) Valeur de l’attribut `id` du tooltip. Par défaut, un id pseudo-aléatoire sera donné.',
9+
},
10+
content: {
11+
control: 'text',
12+
description: 'Contenu de votre bulle d’aide : il s’agit d’un texte sans mise en forme.',
13+
},
14+
onHover: {
15+
control: 'boolean',
16+
description: 'Permet de définir si l’infobulle doit s’afficher au survol de l’élément (`true`) ou au clic (`false`, défaut).',
17+
},
18+
},
19+
}
20+
21+
export const Infobulle = (args) => ({
22+
components: {
23+
DsfrTooltip,
24+
},
25+
26+
data () {
27+
return {
28+
...args,
29+
}
30+
},
31+
32+
template: `
33+
<DsfrTooltip
34+
:content="content"
35+
:on-hover="onHover"
36+
>
37+
Un élément intriguant
38+
</DsfrTooltip>
39+
`,
40+
41+
})
42+
Infobulle.args = {
43+
content: 'Un élément assez intriguant',
44+
onHover: false,
45+
}
46+
47+
export const InfobulleParDefaut = (args) => ({
48+
components: {
49+
DsfrTooltip,
50+
},
51+
52+
data () {
53+
return {
54+
...args,
55+
}
56+
},
57+
58+
template: `
59+
<DsfrTooltip
60+
:content="content"
61+
>
62+
Un contenu qui n’apparaîtra que si hover est à \`true\`
63+
</DsfrTooltip>
64+
`,
65+
66+
})
67+
InfobulleParDefaut.args = {
68+
content: 'Un élément assez intriguant',
69+
}
70+
71+
export const InfobulleAuSurvol = (args) => ({
72+
components: {
73+
DsfrTooltip,
74+
},
75+
76+
data () {
77+
return {
78+
...args,
79+
}
80+
},
81+
82+
template: `
83+
Du texte
84+
<DsfrTooltip
85+
:content="content"
86+
onHover
87+
>
88+
avec une précision à donner ici
89+
</DsfrTooltip>
90+
`,
91+
92+
})
93+
InfobulleAuSurvol.args = {
94+
content: 'Texte précisant pourquoi ce texte est là',
95+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<script setup lang="ts">
2+
import { computed, defineProps, ref, watch, onUnmounted, onMounted } from 'vue'
3+
4+
import { getRandomId } from '../../utils/random-utils'
5+
6+
const props = withDefaults(defineProps<{
7+
content: string
8+
onHover?: boolean
9+
id?: string,
10+
}>(), {
11+
id: () => getRandomId('tooltip'),
12+
})
13+
14+
const show = ref(false)
15+
16+
const source = ref<HTMLElement | null>(null)
17+
const tooltip = ref<HTMLElement | null>(null)
18+
19+
const translateX = ref('0px')
20+
const translateY = ref('0px')
21+
const arrowX = ref('0px')
22+
const top = ref(false)
23+
const opacity = ref(0)
24+
25+
watch(show, async (value) => {
26+
if (typeof document === 'undefined') {
27+
return
28+
}
29+
if (!value) {
30+
return
31+
}
32+
33+
opacity.value = 0
34+
await new Promise(resolve => setTimeout(resolve, 100))
35+
const sourceTop = source.value?.offsetTop
36+
const sourceHeight = source.value?.offsetHeight
37+
const sourceWidth = source.value?.offsetWidth
38+
const sourceLeft = source.value?.offsetLeft
39+
const tooltipHeight = tooltip.value?.offsetHeight
40+
const tooltipWidth = tooltip.value?.offsetWidth
41+
const isSourceAtTop = (sourceTop - tooltipHeight) < 0
42+
const isSourceAtBottom = !isSourceAtTop && (sourceTop + sourceHeight + tooltipHeight) >= document.documentElement.offsetHeight
43+
top.value = isSourceAtBottom
44+
const isSourceOnRightSide = (sourceLeft + sourceWidth) >= document.documentElement.offsetWidth
45+
const isSourceOnLeftSide = (sourceLeft + (sourceWidth / 2) - (tooltipWidth / 2)) <= 0
46+
47+
translateY.value = isSourceAtBottom
48+
? `${sourceTop - tooltipHeight + 8}px`
49+
: `${sourceTop + sourceHeight - 8}px`
50+
opacity.value = 1
51+
translateX.value = isSourceOnRightSide
52+
? `${sourceLeft + sourceWidth - tooltipWidth - 4}px`
53+
: isSourceOnLeftSide
54+
? `${sourceLeft + 4}px`
55+
: `${sourceLeft + (sourceWidth / 2) - (tooltipWidth / 2)}px`
56+
57+
arrowX.value = isSourceOnRightSide
58+
? `${(tooltipWidth / 2) - (sourceWidth / 2) + 4}px`
59+
: isSourceOnLeftSide
60+
? `${-(tooltipWidth / 2) + (sourceWidth / 2) - 4}px`
61+
: '0px'
62+
})
63+
64+
const tooltipStyle = computed(() => (`transform: translate(${translateX.value}, ${translateY.value}); --arrow-x: ${arrowX.value}; opacity: ${opacity.value};'`))
65+
const tooltipClass = computed(() => ({
66+
'fr-tooltip--shown': show.value,
67+
'fr-placement--top': top.value,
68+
'fr-placement--bottom': !top.value,
69+
}))
70+
71+
const clickListener = (event: MouseEvent) => {
72+
if (!show.value) {
73+
return
74+
}
75+
if (event.target === source.value || source.value?.contains(event.target as Node)) {
76+
return
77+
}
78+
if (event.target === tooltip.value || tooltip.value?.contains(event.target as Node)) {
79+
return
80+
}
81+
show.value = false
82+
}
83+
84+
onMounted(() => {
85+
document.documentElement.addEventListener('click', clickListener)
86+
})
87+
88+
onUnmounted(() => {
89+
document.documentElement.removeEventListener('click', clickListener)
90+
})
91+
92+
const onMouseEnter = () => {
93+
if (props.onHover) {
94+
show.value = true
95+
}
96+
}
97+
98+
const onMouseLeave = () => {
99+
if (props.onHover) {
100+
show.value = false
101+
}
102+
}
103+
104+
const onClick = () => {
105+
if (!props.onHover) {
106+
show.value = !show.value
107+
}
108+
}
109+
</script>
110+
111+
<template>
112+
<component
113+
:is="onHover ? 'a' : 'button'"
114+
:id="'link-' + id"
115+
ref="source"
116+
:class="onHover ? 'fr-link' : 'fr-btn fr-btn--tooltip'"
117+
:aria-describedby="id"
118+
:href="onHover ? '#' : undefined"
119+
@click="onClick()"
120+
@mouseenter="onMouseEnter()"
121+
@mouseleave="onMouseLeave()"
122+
>
123+
<slot />
124+
</component>
125+
<span
126+
:id="id"
127+
ref="tooltip"
128+
class="fr-tooltip fr-placement"
129+
:class="tooltipClass"
130+
:style="tooltipStyle"
131+
role="tooltip"
132+
aria-hidden="true"
133+
>
134+
{{ content }}
135+
</span>
136+
</template>
137+
138+
<style scoped>
139+
.fr-tooltip {
140+
transition: opacity 0.3s ease-in-out;
141+
}
142+
</style>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script lang="ts" setup>
2+
import DsfrTooltip from '../DsfrTooltip.vue'
3+
</script>
4+
5+
<template>
6+
<div
7+
class="flex flex-col justify-between w-full"
8+
style="height: 300px"
9+
>
10+
<div class="flex justify-between w-full">
11+
<DsfrTooltip
12+
on-hover
13+
content="Texte de l’info-bulle en haut à gauche qui peut être très très long"
14+
>
15+
Avec du texte ici
16+
</DsfrTooltip>
17+
<DsfrTooltip
18+
content="Texte de l’info-bulle en haut à droite qui peut être très très long"
19+
/>
20+
</div>
21+
22+
<div class="flex justify-center w-full">
23+
<DsfrTooltip
24+
content="Texte de l’info-bulle au centre qui peut être très très long"
25+
/>
26+
</div>
27+
28+
<div class="flex justify-between w-full">
29+
<DsfrTooltip
30+
content="Texte de l’info-bulle en bas à gauche qui peut être très très long"
31+
/>
32+
<DsfrTooltip
33+
content="Texte de l’info-bulle en bas à droite qui peut être très très long"
34+
/>
35+
</div>
36+
</div>
37+
</template>

0 commit comments

Comments
 (0)