From fb1320205abed97dc035a3a56a63655c5dc55188 Mon Sep 17 00:00:00 2001 From: Enguerran Weiss Date: Fri, 16 Dec 2022 08:59:04 +0100 Subject: [PATCH 1/2] first draft of ButtonsGroup --- package.json | 1 + src/ButtonsGroup.tsx | 54 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/ButtonsGroup.tsx diff --git a/package.json b/package.json index bdc41e235..bc5048d99 100755 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "./Header": "./dist/Header/index.js", "./Footer": "./dist/Footer.js", "./Display": "./dist/Display.js", + "./ButtonsGroup": "./dist/ButtonsGroup.js", "./Button": "./dist/Button.js", "./Breadcrumb": "./dist/Breadcrumb.js", "./Badge": "./dist/Badge.js", diff --git a/src/ButtonsGroup.tsx b/src/ButtonsGroup.tsx new file mode 100644 index 000000000..aa9d4b55b --- /dev/null +++ b/src/ButtonsGroup.tsx @@ -0,0 +1,54 @@ +import React, { memo, forwardRef } from "react"; +import { ButtonProps } from "./Button"; +import { symToStr } from "tsafe/symToStr"; +import { assert } from "tsafe/assert"; +import type { Equals } from "tsafe"; +import { fr } from "./lib"; +import { cx } from "./lib/tools/cx"; + +// We make users import dsfr.css, so we don't need to import the scoped CSS +// but in the future if we have a complete component coverage it +// we could stop requiring users to import the hole CSS and only import on a +// per component basis. +import "./dsfr/component/button/button.css"; + +export type ButtonsGroupProps = { + className?: string; + classes?: Partial>; + mode?: "inline" | "inline-sm" | "inline-md" | "inline-lg"; + children: React.ReactElement[]; + size?: ButtonsGroupProps.Size; +}; + +export namespace ButtonsGroupProps { + export type Size = "sm" | "lg"; +} + +/** @see */ +export const ButtonsGroup = memo( + forwardRef((props, ref) => { + const { className, classes, mode, children, size, ...rest } = props; + + assert>(); + + const buttonsGroupClassName = cx( + fr.cx("fr-btns-group"), + mode && fr.cx(`fr-btns-group--${mode}`), + size && fr.cx(`fr-btns-group--${size}`), + className, + classes?.root + ); + + return ( +
    + {children.map(child => ( +
  • {child}
  • + ))} +
+ ); + }) +); + +ButtonsGroup.displayName = symToStr({ ButtonsGroup }); + +export default ButtonsGroup; From 8bff5b261c31e70a009a67e776ac7fa1971db81b Mon Sep 17 00:00:00 2001 From: Enguerran Weiss Date: Fri, 16 Dec 2022 10:24:08 +0100 Subject: [PATCH 2/2] add stories, mode / align props conditionnal --- src/ButtonsGroup.tsx | 34 +++++++++--- stories/ButtonsGroup.stories.tsx | 91 ++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 stories/ButtonsGroup.stories.tsx diff --git a/src/ButtonsGroup.tsx b/src/ButtonsGroup.tsx index aa9d4b55b..053956eab 100644 --- a/src/ButtonsGroup.tsx +++ b/src/ButtonsGroup.tsx @@ -12,28 +12,48 @@ import { cx } from "./lib/tools/cx"; // per component basis. import "./dsfr/component/button/button.css"; -export type ButtonsGroupProps = { +export type ButtonsGroupCommonProps = { className?: string; classes?: Partial>; - mode?: "inline" | "inline-sm" | "inline-md" | "inline-lg"; - children: React.ReactElement[]; - size?: ButtonsGroupProps.Size; + children: [ + // this component (ul) should have at least 2 children (RGAA) + React.ReactElement, + React.ReactElement, + ...React.ReactElement[] + ]; + size?: ButtonsGroupCommonProps.Size; }; -export namespace ButtonsGroupProps { +export namespace ButtonsGroupCommonProps { export type Size = "sm" | "lg"; + export type Mode = "inline" | "inline-sm" | "inline-md" | "inline-lg"; + export type Align = "center" | "right" | "inline-reverse" | "equisized"; } -/** @see */ +type ButtonAlignProps = // align will take effect only on inline placements + + | { + mode?: false; + align?: never; + } + | { + mode: ButtonsGroupCommonProps.Mode; + align?: ButtonsGroupCommonProps.Align; + }; + +export type ButtonsGroupProps = ButtonsGroupCommonProps & ButtonAlignProps; + +/** @see */ export const ButtonsGroup = memo( forwardRef((props, ref) => { - const { className, classes, mode, children, size, ...rest } = props; + const { className, classes, mode, children, size, align, ...rest } = props; assert>(); const buttonsGroupClassName = cx( fr.cx("fr-btns-group"), mode && fr.cx(`fr-btns-group--${mode}`), + align && fr.cx(`fr-btns-group--${align}`), size && fr.cx(`fr-btns-group--${size}`), className, classes?.root diff --git a/stories/ButtonsGroup.stories.tsx b/stories/ButtonsGroup.stories.tsx new file mode 100644 index 000000000..1531618b1 --- /dev/null +++ b/stories/ButtonsGroup.stories.tsx @@ -0,0 +1,91 @@ +import React from "react"; +import { ButtonsGroup } from "../dist/ButtonsGroup"; +import { Button } from "../dist/Button"; +import type { ButtonsGroupProps, ButtonsGroupCommonProps } from "../dist/ButtonsGroup"; +import { sectionName } from "./sectionName"; +import { getStoryFactory } from "./getStory"; +import { assert } from "tsafe/assert"; +import type { Equals } from "tsafe"; + +const { meta, getStory } = getStoryFactory({ + sectionName, + wrappedComponent: { ButtonsGroup }, + description: ` +- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/badge) +- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Badge.tsx)`, + argTypes: { + size: { + options: (() => { + const sizes = ["sm", "lg"] as const; + + assert>(); + + return [null, ...sizes]; + })(), + control: { type: "select", labels: { null: "no size (md)" } } + }, + mode: { + options: (() => { + const modes = ["inline", "inline-sm", "inline-md", "inline-lg"] as const; + + assert>(); + + return [null, ...modes]; + })(), + control: { type: "select", labels: { null: "no mode (vertical)" } } + }, + align: { + options: (() => { + const aligns = ["center", "right", "inline-reverse", "equisized"] as const; + + assert>(); + + return [null, ...aligns]; + })(), + control: { type: "select", labels: { null: "no align (default to left)" } } + } + }, + disabledProps: ["lang"] +}); + +export default meta; + +export const Default = getStory({ + children: [ +