Skip to content

Commit

Permalink
Merge pull request #595 from dnum-mi/feat/component-tooltip
Browse files Browse the repository at this point in the history
feat(DsfrTooltip): ✨ Nouveau composant Bulle d'aide
  • Loading branch information
laruiss authored Dec 22, 2023
2 parents f09021f + 12fd150 commit 81ed3f7
Show file tree
Hide file tree
Showing 22 changed files with 865 additions and 230 deletions.
17 changes: 17 additions & 0 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default defineConfig({
appearance: { listenToStorageChanges: false }, // handling this in Story.vue itself to avoid flickering

rewrites: {
'src/components/DsfrButton/DsfrButtonGroup.md': 'composants/DsfrButtonGroup.md',
'src/components/DsfrSegmented/DsfrSegmentedSet.md': 'composants/DsfrSegmentedSet.md',
'src/components/:comp/:comp.md': 'composants/:comp.md',
'docs/:splat*': ':splat*',
Expand Down Expand Up @@ -101,6 +102,14 @@ export default defineConfig({
text: 'DsfrBadge',
link: '/composants/DsfrBadge.md',
},
{
text: 'DsfrButton',
link: '/composants/DsfrButton.md',
},
{
text: 'DsfrButtonGroup',
link: '/composants/DsfrButtonGroup.md',
},
{
text: 'DsfrCard',
link: '/composants/DsfrCard.md',
Expand All @@ -121,6 +130,14 @@ export default defineConfig({
text: 'DsfrSegmentedSet',
link: '/composants/DsfrSegmentedSet.md',
},
{
text: 'DsfrTag',
link: '/composants/DsfrTag.md',
},
{
text: 'DsfrTooltip',
link: '/composants/DsfrTooltip.md',
},
]
},
{
Expand Down
14 changes: 13 additions & 1 deletion .vitepress/theme/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* Colors
* -------------------------------------------------------------------------- */

@font-face {
@font-face {
font-family: Marianne;
src: url("@gouvfr/dsfr/dist/fonts/Marianne-Light.woff2") format("woff2"), url("@gouvfr/dsfr/dist/fonts/Marianne-Light.woff") format("woff");
font-weight: 300;
Expand Down Expand Up @@ -190,3 +190,15 @@
.w-full {
width: 100%;
}

.h-full {
width: 100%;
}

.flex {
display: flex;
}

.flex-end {
justify-content: flex-end;
}
2 changes: 1 addition & 1 deletion docs/_frame.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
layout: false
---

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

<script setup lang="ts">
import { useStyleTag } from '@vueuse/core'
Expand Down
19 changes: 17 additions & 2 deletions docs/composants.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
# Liste des composants

## Alertes
## Accordéon

- [DsfrAccordion](./composants/DsfrAccordion)

## Alertes et notices

- [DsfrAlert](./composants/DsfrAlert)
- [DsfrNotice](./composants/DsfrNotice)

## Badges et tags

- [Badge](./composants/DsfrBadge)
- [DsfrBadge](./composants/DsfrBadge)

## Boutons et contrôles segmentés

- [DsfrButton](./composants/DsfrButton)
- [DsfrSegmented](./composants/DsfrSegmented) (Contrôle segmenté)
- [DsfrSegmentedSet](./composants/DsfrSegmentedSet) (Contrôles segmentés)

## Carte

- [DsfrCard](./composants/DsfrCard)

## Infobulle (Information contextuelle)

- [DsfrTooltip](./composants/DsfrTooltip)
65 changes: 43 additions & 22 deletions src/components/DsfrButton/DsfrButton.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
# DsfrButton

🌟 **Introduction**
## 🌟 Introduction

Le bouton est un élément d’interaction avec l’interface permettant à l’utilisateur d’effectuer une action.

Le `DsfrButton` est un composant Vue élégant et réutilisable, conçu pour simplifier la création de boutons personnalisés. Il intègre des tailles ajustables, des icônes optionnelles et un gestionnaire de clics, tout en respectant le style de `DSFR`. Son utilisation est simple, avec une grande flexibilité pour s'adapter à différents contextes.

🏅 La documentation sur l’alerte sur le [DSFR](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/bouton)

<VIcon name="vi-file-type-storybook" /> La story sur l’alerte sur le storybook de [VueDsfr](https://vue-dsfr.netlify.app/?path=/docs/composants-dsfrbutton--docs)

## 📐 Structure

Les boutons sont composés de :

- Un label - obligatoire, soit en utilisant la prop `label` soit en utilisant le slot par défaut ;
- Une icône, pouvant être modifiée (voir les icônes disponibles) - optionnelle.

## 🛠️ Les props

| Nom | Type | Défaut | Obligatoire | Description |
|-------------|----------------------------|------------|:-----------:|-------------------------------------------------------|
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | | Taille du bouton. Peut être 'sm', 'md', ou 'lg'. |
| `icon` | `string \| object` | `undefined`| | Icône à afficher dans le bouton. Peut être un nom ou une configuration d'icône. |
| `label` | `string` | `undefined`| | Étiquette textuelle du bouton. |
| `label` | `string` | `undefined`| | Étiquette textuelle du bouton. Si le label est laissé à undefined, le slot par défaut doit contenir du texte ! |
| `onClick` | `Function` | `() => {}` | | Fonction appelée lors du clic sur le bouton. |

## 📡 Les événements
Expand All @@ -21,29 +34,33 @@ Le `DsfrButton` est un composant Vue élégant et réutilisable, conçu pour sim

- `default` : Emplacement pour le contenu personnalisé du bouton. Inséré dans `<button class="fr-btn"><span">`.

## ✨ Les groupes de boutons

Cf. [documentation dédiée](/composants/DsfrButtonGroup)

## 📝 Des exemples

```vue
<DsfrButton
size="lg"
icon="fr-icon-home-4-fill"
label="Accueil"
@click="handleClick()"
/>
Un bouton large avec une icône 'maison' à gauche et le texte 'Accueil' :

Un bouton large avec une icône 'maison' et le texte 'Accueil'. L'événement de clic est géré par la méthode `handleClick()`.
::: code-group

```vue
<DsfrButton
size="sm"
label="Petit Bouton"
@click="handleClick()"
>
Contenu supplémentaire
</DsfrButton>
```
<Story data-title="Démo">
<DsfrButtonExample1 />
</Story>

Un petit bouton avec le texte 'Petit Bouton' et du contenu supplémentaire dans le slot par défaut. L'événement de clic est géré par la méthode `handleClick()`.
<<< docs-demo/DsfrButtonExample1.vue [Code de la démo]
:::

Un petit bouton avec le texte 'Aller plus loin', du contenu supplémentaire dans le slot par défaut, et une icône à droite :

::: code-group

<Story data-title="Démo">
<DsfrButtonExample2 />
</Story>

<<< docs-demo/DsfrButtonExample2.vue [Code de la démo]
:::

## 📝 (Presque) toutes les variantes 🌈 de boutons

Expand All @@ -53,13 +70,17 @@ Un petit bouton avec le texte 'Petit Bouton' et du contenu supplémentaire dans
<DsfrButtonDemo />
</Story>

<<< docs-demo/DsfrButtonExample.vue [Code de la démo]
<<< docs-demo/DsfrButtonDemo.vue [Code de la démo]

<<< DsfrButton.vue

<<< DsfrButton.types.ts
:::

Et voilà ! Notre DsfrButton est prêt à illuminer votre interface avec style et fonctionnalité. N'oubliez pas d'appuyer sur ces boutons avec panache ! 🚀

<script setup lang="ts">
import DsfrButtonDemo from './docs-demo/DsfrButtonExample.vue'
import DsfrButtonDemo from './docs-demo/DsfrButtonDemo.vue'
import DsfrButtonExample1 from './docs-demo/DsfrButtonExample1.vue'
import DsfrButtonExample2 from './docs-demo/DsfrButtonExample2.vue'
</script>
5 changes: 3 additions & 2 deletions src/components/DsfrButton/DsfrButton.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ export type DsfrButtonProps = {
export type DsfrButtonGroupProps = {
buttons?: (DsfrButtonProps & ButtonHTMLAttributes)[]
reverse?: boolean
equisized?: boolean
iconRight?: boolean
align?: 'right' | 'center' | '' | undefined
inlineLayoutWhen?: 'always' | 'never' | 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | undefined | boolean
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | undefined
inlineLayoutWhen?: 'always' | 'never' | 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | true | undefined
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | undefined
}
2 changes: 1 addition & 1 deletion src/components/DsfrButton/DsfrButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const iconProps = computed(() => typeof props.icon === 'string' ? { name: props.
:title="iconOnly ? label : undefined"
:disabled="disabled"
:aria-disabled="disabled"
:style="(!dsfr && iconOnly) ? { 'padding-inline': '0.5rem' } : {}"
:style="(!dsfrIcon && iconOnly) ? { 'padding-inline': '0.5rem' } : {}"
@click="onClick ? onClick($event) : () => {}"
>
<VIcon
Expand Down
58 changes: 58 additions & 0 deletions src/components/DsfrButton/DsfrButtonGroup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Groupe de boutons - DsfrButtonGroup

## 🌟 Introduction

Les boutons dans le contexte d'un groupe suivent les même règles que le composant bouton :

- Il prend en charge les 2 types de boutons (primaire, secondaire) ;
- Il gère les 3 tailles (prop `size` valeurs `sm`, `md`, `lg`) et les variantes ( Icônes / texte seul, avec icônes à gauche / droite).

## 📐 Structure

Ce composant est une simple balise `ul` qui peut recevoir un tableau de `DsfrButtonProps & ButtonHTMLAttributes` pour mettre chaque bouton dans un `li`.

Le slot par défaut peut être utilisé pour mettre vos boutons si la prop `buttons` est absente (ou un tableau vide).

## 🛠️ Les props

Aucune prop n’est obligatoire

| Nom | Type | Défaut | Description |
|------------------|--------------------------|--------------- |-------------|
| align | 'right' / 'center' / String | undefined | Définit l'alignement des boutons dans le groupe. Peut être 'right' ou 'center'. |
| buttons | `(DsfrButtonProps & ButtonHTMLAttributes)[]` | () => [] | Liste des boutons à afficher. Chaque bouton est un objet qui peut inclure toutes les pros d’un [DsfrButton](/composants/DsfrButton), y compris un gestionnaire `onClick`. |
| equisized | `boolean` | false | Si `true`, tous les boutons du groupe auront la même largeur. |
| inlineLayoutWhen | `string \| boolean` | 'never' | Détermine quand les boutons doivent être affichés sur une seule linge. Peut être `'always'`, `'never'`, ou correspondre à une taille spécifique (`'sm'`, `'md'`, `'lg'`). |
| iconRight | `boolean` | false | Si `true`, place les icônes à droite du texte dans tous les boutons. |
| size | `'sm' \| 'md' \| 'lg'` | 'md' | Détermine la taille des boutons. Peut être `'sm'` (petit), `'md`' (moyen, défaut), `'lg'` (grand). |

## 🧩 Les slots

Le slot par défaut peut être utilisé pour mettre des boutons personnalisés.

::: warning Important

Si vous utilisez le slot, il faut bien envelopper chaque bouton dans une balise `<li>` Cf. les exemples

:::

## 📝 Des exemples

::: code-group

<Story data-title="Démo" min-h="620px">
<DsfrButtonGroupDemo />
</Story>

<<< docs-demo/DsfrButtonGroupDemo.vue [Code de la démo]

<<< DsfrButtonGroup.vue

<<< DsfrButton.types.ts
:::

Et voilà ! Vous êtes prêt à ajouter une touche de sophistication à votre interface avec DsfrButtonGroup. Bonne création ! 🎨✨

<script setup lang="ts">
import DsfrButtonGroupDemo from './docs-demo/DsfrButtonGroupDemo.vue'
</script>
6 changes: 5 additions & 1 deletion src/components/DsfrButton/DsfrButtonGroup.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export default {
options: ['never', 'always', 'small', 'medium', 'large'],
description: 'Indique si le groupe de boutons doit apparaître en empilement horizontal (toujours, ou seulement sur les tailles de vue spécifiées)',
},
equisized: {
control: 'boolean',
description: 'Indique si la taille des boutons doit être la même pour tous les boutons (prend la taille du plus large) si est à `true`, ou si chaque bouton doit avoir sa propre taille si est à `false`',
},
reverse: {
control: 'boolean',
description: 'Indique si l’ordre des boutons doit être inversé par rapport au DOM.\n\n *N.B. : Ne fonctionne que si `align` est à `right`*',
Expand All @@ -43,7 +47,7 @@ export default {
align: {
control: 'radio',
options: ['default', 'center', 'right'],
description: 'Indique l\'alignement du groupe de boutons',
description: 'Indique lalignement du groupe de boutons',
},
onClick: { action: 'clicked' },
},
Expand Down
46 changes: 39 additions & 7 deletions src/components/DsfrButton/DsfrButtonGroup.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { computed, onMounted, ref } from 'vue'
import type { DsfrButtonGroupProps } from './DsfrButton.types'
import DsfrButton from './DsfrButton.vue'
Expand All @@ -9,33 +9,64 @@ export type { DsfrButtonGroupProps }
const props = withDefaults(defineProps<DsfrButtonGroupProps>(), {
buttons: () => [],
inlineLayoutWhen: 'never',
size: '',
size: 'md',
align: undefined,
})
const buttonsEl = ref<HTMLUListElement | null>(null)
const sm = computed(() => ['sm', 'small'].includes(props.size))
const md = computed(() => ['md', 'medium'].includes(props.size))
const lg = computed(() => ['lg', 'large'].includes(props.size))
const inlineAlways = computed(() => ['always', true].includes(props.inlineLayoutWhen))
const inlineAlways = computed(() => ['always', '', true].includes(props.inlineLayoutWhen))
const inlineSm = computed(() => ['sm', 'small'].includes(props.inlineLayoutWhen as string))
const inlineMd = computed(() => ['md', 'medium'].includes(props.inlineLayoutWhen as string))
const inlineLg = computed(() => ['lg', 'large'].includes(props.inlineLayoutWhen as string))
const center = computed(() => props.align === 'center')
const right = computed(() => props.align === 'right')
const equisizedWidth = ref('auto')
const groupStyle = computed(() => `--equisized-width: ${equisizedWidth.value};`)
const computeEquisizedWidth = async () => {
let maxWidth = 0
await new Promise((resolve) => setTimeout(resolve, 100))
buttonsEl.value?.querySelectorAll('.fr-btn').forEach((button: Element) => {
const width = button.offsetWidth
const buttonStyle = window.getComputedStyle(button)
const marginLeft = +buttonStyle.marginLeft.replace('px', '')
const marginRight = +buttonStyle.marginRight.replace('px', '')
button.style.width = 'var(--equisized-width)'
const newWidth = width + marginLeft + marginRight
if (newWidth > maxWidth) {
maxWidth = newWidth
}
})
equisizedWidth.value = maxWidth + 'px'
}
onMounted(async () => {
if (!buttonsEl.value || !props.equisized) {
return
}
await computeEquisizedWidth()
})
</script>

<template>
<ul
ref="buttonsEl"
:style="groupStyle"
class="fr-btns-group"
:class="{
'fr-btns-group--inline': inlineAlways,
'fr-btns-group--equisized': equisized,
'fr-btns-group--sm': sm,
'fr-btns-group--md': md,
'fr-btns-group--lg': lg,
'fr-btns-group--inline-sm': inlineSm,
'fr-btns-group--inline-md': inlineMd,
'fr-btns-group--inline-lg': inlineLg,
'fr-btns-group--inline-sm': inlineAlways || inlineSm,
'fr-btns-group--inline-md': inlineAlways || inlineMd,
'fr-btns-group--inline-lg': inlineAlways || inlineLg,
'fr-btns-group--center': center,
'fr-btns-group--right': right,
'fr-btns-group--icon-right': iconRight,
Expand All @@ -52,5 +83,6 @@ const right = computed(() => props.align === 'right')
@click="onClick"
/>
</li>
<slot />
</ul>
</template>
Loading

0 comments on commit 81ed3f7

Please sign in to comment.