diff --git a/src/stories/components/Button.mdx b/src/stories/components/Button.mdx new file mode 100644 index 0000000..b6a0e4a --- /dev/null +++ b/src/stories/components/Button.mdx @@ -0,0 +1,98 @@ +import { Canvas, Controls, Meta, Source } from "@storybook/blocks"; +import * as ButtonStories from "./Button.stories"; + + + +# Button + +## Usage notes + +Please refer to [GOV.UK Design System documentation on +buttons](https://design-system.service.gov.uk/components/button/) for more +information. + +## Example + + + + +## Variants + +### Small size + + + +### Large size + + + +## Icons + +### with left icon + + + +### with right icon + + + +### with icon only + + + +## States + +### Disabled + + + +## Button as link + + + +## Implementation notes + +- add `ds-button` as base class to a `button` element +- combine with one size modifier: `ds-button-small` or `ds-button-large` +- combine with one appearance modifier: `ds-button-secondary`, `ds-button-tertiary`, `ds-button-ghost` + + + +- for full with add `ds-button-full-width` + + + +### with icons + +- use `svg` for the icon, add `ds-button-icon` class to the icon +- add `ds-button-with-icon` class to the button +- wrap text in a `span` with the class `ds-button-label` + + + +### with icon only + +- add `ds-button-with-icon-only` class +- wrap (invisible) text in a `span` with class `sr-only` +- have a look at [Accessible Icon Buttons](https://www.sarasoueidan.com/blog/accessible-icon-buttons/) + + + +### links that look like buttons + +- add `role="button"` to the link +- have a look at this [discussion on links styled as buttons and a11y implications](https://github.com/alphagov/govuk_elements/pull/272) + + + +### "disabled" buttons + +- use a `div` as tag and add `is-disabled` class + + + +## Further documentation + +Please check out the code on the given examples and otherwise +refer to [mdn web docs on \](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button) +for more technical information. diff --git a/src/stories/components/Button.stories.ts b/src/stories/components/Button.stories.ts new file mode 100644 index 0000000..c633e04 --- /dev/null +++ b/src/stories/components/Button.stories.ts @@ -0,0 +1,251 @@ +import type { Meta, StoryObj } from "@storybook/html"; +import { clsx } from "clsx"; +import dedent from "dedent"; +import { + htmlAttrs, + loremSentences, + loremWords, +} from "../../../.storybook/utils"; + +type ButtonArgs = { + label: string; + size?: "default" | "small" | "large"; + appearance?: "default" | "secondary" | "tertiary" | "ghost"; + fullWidth?: boolean; + withIcon?: boolean; + withIconRight?: boolean; + withIconOnly?: boolean; + disabled?: boolean; + isLink?: boolean; +}; + +const meta = { + title: "Components/Button", + render: ({ + label, + size, + appearance, + fullWidth, + withIcon, + withIconRight, + withIconOnly, + disabled, + isLink, + }) => { + const cssClasses = clsx("ds-button", { + "ds-button-large": size === "large", + "ds-button-small": size === "small", + "ds-button-secondary": appearance === "secondary", + "ds-button-tertiary": appearance === "tertiary", + "ds-button-ghost": appearance === "ghost", + "is-disabled": disabled, + "ds-button-with-icon": withIcon || withIconRight, + "ds-button-with-icon-only": withIconOnly, + "ds-button-full-width": fullWidth, + }); + const attrs = htmlAttrs({ className: cssClasses }); + + const icon = dedent` + + + + `; + + if (disabled) { + return dedent` +
+ ${label} +
+ `; + } + + if (isLink) { + return dedent` + + ${label} + + `; + } + + if (withIcon || withIconRight) { + return dedent` + + `; + } + + if (withIconOnly) { + return dedent` + + `; + } + + return dedent` + + `; + }, + argTypes: { + disabled: { + description: "Is the button not available for interaction?", + }, + label: { control: "text" }, + withIcon: { control: "boolean" }, + withIconRight: { control: "boolean" }, + withIconOnly: { control: "boolean" }, + size: { + options: ["default", "small", "large"], + control: { type: "radio" }, + }, + appearance: { + options: ["default", "secondary", "tertiary", "ghost"], + control: { type: "radio" }, + }, + }, + args: { + disabled: false, + label: loremWords(4), + size: "default", + appearance: "default", + withIcon: false, + withIconRight: false, + withIconOnly: false, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default = {} satisfies Story; +export const Small = { args: { size: "small" } } satisfies Story; +export const Large = { args: { size: "large" } } satisfies Story; +export const Disabled = { args: { disabled: true } } satisfies Story; +export const Link = { args: { isLink: true } } satisfies Story; +export const WithIcon = { args: { withIcon: true } } satisfies Story; +export const WithIconRight = { args: { withIconRight: true } } satisfies Story; +export const WithIconOnly = { args: { withIconOnly: true } } satisfies Story; +export const FullWidth = { args: { fullWidth: true } } satisfies Story; +export const FullWidthWithIcon = { + args: { fullWidth: true, withIcon: true }, +} satisfies Story; +export const LongText = { args: { label: loremSentences(4) } } satisfies Story; +export const LongTextWithIcon = { + args: { label: loremSentences(4), withIcon: true }, +} satisfies Story; + +export const Secondary = { args: { appearance: "secondary" } } satisfies Story; +export const SecondarySmall = { + args: { appearance: "secondary", size: "small" }, +} satisfies Story; +export const SecondaryLarge = { + args: { appearance: "secondary", size: "large" }, +} satisfies Story; +export const SecondaryDisabled = { + args: { appearance: "secondary", disabled: true }, +} satisfies Story; +export const SecondaryLink = { + args: { appearance: "secondary", isLink: true }, +} satisfies Story; +export const SecondaryWithIcon = { + args: { appearance: "secondary", withIcon: true }, +} satisfies Story; +export const SecondaryWithIconRight = { + args: { appearance: "secondary", withIconRight: true }, +} satisfies Story; +export const SecondaryWithIconOnly = { + args: { appearance: "secondary", withIconOnly: true }, +} satisfies Story; +export const SecondaryFullWidth = { + args: { appearance: "secondary", fullWidth: true }, +} satisfies Story; +export const SecondaryFullWidthWithIcon = { + args: { appearance: "secondary", fullWidth: true, withIcon: true }, +} satisfies Story; +export const SecondaryLongText = { + args: { appearance: "secondary", label: loremSentences(4) }, +} satisfies Story; +export const SecondaryLongTextWithIcon = { + args: { appearance: "secondary", label: loremSentences(4), withIcon: true }, +} satisfies Story; + +export const Tertiary = { args: { appearance: "tertiary" } } satisfies Story; +export const TertiarySmall = { + args: { appearance: "tertiary", size: "small" }, +} satisfies Story; +export const TertiaryLarge = { + args: { appearance: "tertiary", size: "large" }, +} satisfies Story; +export const TertiaryDisabled = { + args: { appearance: "tertiary", disabled: true }, +} satisfies Story; +export const TertiaryLink = { + args: { appearance: "tertiary", isLink: true }, +} satisfies Story; +export const TertiaryWithIcon = { + args: { appearance: "tertiary", withIcon: true }, +} satisfies Story; +export const TertiaryWithIconRight = { + args: { appearance: "tertiary", withIconRight: true }, +} satisfies Story; +export const TertiaryWithIconOnly = { + args: { appearance: "tertiary", withIconOnly: true }, +} satisfies Story; +export const TertiaryFullWidth = { + args: { appearance: "tertiary", fullWidth: true }, +} satisfies Story; +export const TertiaryFullWidthWithIcon = { + args: { appearance: "tertiary", fullWidth: true, withIcon: true }, +} satisfies Story; +export const TertiaryLongText = { + args: { appearance: "tertiary", label: loremSentences(4) }, +} satisfies Story; +export const TertiaryLongTextWithIcon = { + args: { appearance: "tertiary", label: loremSentences(4), withIcon: true }, +} satisfies Story; + +export const Ghost = { args: { appearance: "ghost" } } satisfies Story; +export const GhostSmall = { + args: { appearance: "ghost", size: "small" }, +} satisfies Story; +export const GhostLarge = { + args: { appearance: "ghost", size: "large" }, +} satisfies Story; +export const GhostDisabled = { + args: { appearance: "ghost", disabled: true }, +} satisfies Story; +export const GhostLink = { + args: { appearance: "ghost", isLink: true }, +} satisfies Story; +export const GhostWithIcon = { + args: { appearance: "ghost", withIcon: true }, +} satisfies Story; +export const GhostWithIconRight = { + args: { appearance: "ghost", withIconRight: true }, +} satisfies Story; +export const GhostWithIconOnly = { + args: { appearance: "ghost", withIconOnly: true }, +} satisfies Story; +export const GhostFullWidth = { + args: { appearance: "ghost", fullWidth: true }, +} satisfies Story; +export const GhostFullWidthWithIcon = { + args: { appearance: "ghost", fullWidth: true, withIcon: true }, +} satisfies Story; +export const GhostLongText = { + args: { appearance: "ghost", label: loremSentences(4) }, +} satisfies Story; +export const GhostLongTextWithIcon = { + args: { appearance: "ghost", label: loremSentences(4), withIcon: true }, +} satisfies Story;