Skip to content

Commit

Permalink
feat(DsfrButtonGroup): ✨ ajoute la prop equisized
Browse files Browse the repository at this point in the history
  • Loading branch information
laruiss committed Dec 22, 2023
1 parent 4dce669 commit caf7996
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 214 deletions.
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
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 caf7996

Please sign in to comment.