diff --git a/components/button/stories/button.mdx b/components/button/stories/button.mdx new file mode 100644 index 00000000000..1516bed3a59 --- /dev/null +++ b/components/button/stories/button.mdx @@ -0,0 +1,108 @@ +import { + Meta, + Title, + Subtitle, + Description, + ArgTypes, + Canvas, +} from "@storybook/blocks"; +import { ComponentDetails, TaggedReleases } from "@spectrum-css/preview/blocks"; + +import * as ButtonStories from "./button.stories"; + + + + +<Subtitle of={ButtonStories} /> +<ComponentDetails of={ButtonStories} /> + +<Description of={ButtonStories} /> + +## Variants + +There are four available variants that are used for different levels of emphasis and different +types of actions. By default, a button uses the fill style with a solid background. The primary +and secondary variants also have an outline option. + +### Accent + +<Description of={ButtonStories.Accent} /> +<Canvas of={ButtonStories.Accent} /> + +### Primary + +<Description of={ButtonStories.Primary} /> +<Canvas of={ButtonStories.Primary} /> + +### Secondary + +<Description of={ButtonStories.Secondary} /> +<Canvas of={ButtonStories.Secondary} /> + +### Negative + +<Description of={ButtonStories.Negative} /> +<Canvas of={ButtonStories.Negative} /> + +## Static color + +When a button needs to be placed on top of a color background or a visual, use the static color +option. Static color buttons do not change shades or values depending upon the color theme. + +### Static white - primary + +<Canvas + of={ButtonStories.StaticWhitePrimary} + className="spectrum-examples-static-white" +/> + +### Static white - secondary + +<Canvas + of={ButtonStories.StaticWhiteSecondary} + className="spectrum-examples-static-white" +/> + +### Static black - primary + +<Canvas + of={ButtonStories.StaticBlackPrimary} + className="spectrum-examples-static-black" +/> + +### Static black - secondary + +<Canvas + of={ButtonStories.StaticBlackSecondary} + className="spectrum-examples-static-black" +/> + +## Sizing + +<Description of={ButtonStories.Sizing} /> +<Canvas of={ButtonStories.Sizing} /> + +## Pending state + +<Description of={ButtonStories.Pending} /> +<Canvas of={ButtonStories.Pending} /> + +## Disabled state + +<Description of={ButtonStories.Disabled} /> +<Canvas of={ButtonStories.Disabled} /> + +## Text overflow behavior + +<Description of={ButtonStories.WithWrapping} /> +<Canvas of={ButtonStories.WithWrapping} /> + +## Properties + +The component accepts the following inputs (properties): + +<ArgTypes of={ButtonStories} /> + +## Tagged releases + +<TaggedReleases of={ButtonStories} /> diff --git a/components/button/stories/button.stories.js b/components/button/stories/button.stories.js index d3b3a3e1928..2619df42249 100644 --- a/components/button/stories/button.stories.js +++ b/components/button/stories/button.stories.js @@ -1,8 +1,10 @@ import { default as IconStories } from "@spectrum-css/icon/stories/icon.stories.js"; +import { Sizes } from "@spectrum-css/preview/decorators"; import { disableDefaultModes } from "@spectrum-css/preview/modes"; import { isActive, isDisabled, isFocused, isHovered, isPending, size, staticColor } from "@spectrum-css/preview/types"; import pkgJson from "../package.json"; import { ButtonGroups } from "./button.test.js"; +import { ButtonsWithIconOptions, TextOverflowTemplate, TreatmentTemplate } from "./template.js"; /** * Buttons allow users to perform an action or to navigate to another page. They have multiple styles for various needs, and are ideal for calling attention to where a user needs to do something in order to move forward in a flow. @@ -75,6 +77,7 @@ export default { }, packageJson: pkgJson, }, + tags: ["!autodocs"], }; export const Default = ButtonGroups.bind({}); @@ -115,3 +118,171 @@ WithForcedColors.parameters = { WithForcedColors.args = { iconName: "Actions", }; + +// ********* DOCS ONLY ********* // + +/** + * Buttons come in four different sizes: small, medium, large, and extra large. The medium size is + * the default and most frequently used option. Use the other sizes sparingly; they should be used + * to create a hierarchy of importance within the page. + */ +export const Sizing = (args, context) => Sizes({ + Template: ButtonsWithIconOptions, + withHeading: false, + withBorder: false, + ...args, +}, context); +Sizing.args = {}; +Sizing.tags = ["!dev"]; +Sizing.parameters = { + chromatic: { disableSnapshot: true }, +}; + +/** + * The accent button communicates strong emphasis and is reserved for encouraging critical + * actions. In general, only use the emphasized option for the most important action on the page. + */ +export const Accent = ButtonsWithIconOptions.bind({}); +Accent.tags = ["!dev"]; +Accent.args = { + variant: "accent", +}; +Accent.parameters = { + chromatic: { disableSnapshot: true }, +}; + +/** + * The primary button is for medium emphasis. Use it in place of an accent button when the + * action requires less prominence, or if there are multiple primary actions of the same importance + * in the same view. Both the fill (default) and outline styles are demonstrated in this example. + */ +export const Primary = TreatmentTemplate.bind({}); +Primary.tags = ["!dev"]; +Primary.args = { + variant: "primary", + treatmentLayout: "stacked", +}; +Primary.parameters = { + chromatic: { disableSnapshot: true }, +}; + +/** + * The secondary button is for low emphasis. It’s paired with other button types to surface less + * prominent actions, and should never be the only button in a group. Both the fill (default) and + * outline styles are demonstrated in this example. + */ +export const Secondary = TreatmentTemplate.bind({}); +Secondary.tags = ["!dev"]; +Secondary.args = { + variant: "secondary", + treatmentLayout: "stacked", +}; +Secondary.parameters = { + chromatic: { disableSnapshot: true }, +}; + +/** + * The negative button is for emphasizing actions that can be destructive or have negative + * consequences if taken. Use it sparingly. + */ +export const Negative = ButtonsWithIconOptions.bind({}); +Negative.tags = ["!dev"]; +Negative.args = { + variant: "negative", +}; +Negative.parameters = { + chromatic: { disableSnapshot: true }, +}; + +export const StaticWhitePrimary = TreatmentTemplate.bind({}); +StaticWhitePrimary.tags = ["!dev"]; +StaticWhitePrimary.args = { + variant: "primary", + treatmentLayout: "stacked", + staticColor: "white", +}; +StaticWhitePrimary.parameters = { + chromatic: { disableSnapshot: true }, +}; + +export const StaticWhiteSecondary = TreatmentTemplate.bind({}); +StaticWhiteSecondary.tags = ["!dev"]; +StaticWhiteSecondary.args = { + variant: "secondary", + treatmentLayout: "stacked", + staticColor: "white", +}; +StaticWhiteSecondary.parameters = { + chromatic: { disableSnapshot: true }, +}; + +export const StaticBlackPrimary = TreatmentTemplate.bind({}); +StaticBlackPrimary.tags = ["!dev"]; +StaticBlackPrimary.args = { + variant: "primary", + treatmentLayout: "stacked", + staticColor: "black", +}; +StaticBlackPrimary.parameters = { + chromatic: { disableSnapshot: true }, +}; + +export const StaticBlackSecondary = TreatmentTemplate.bind({}); +StaticBlackSecondary.tags = ["!dev"]; +StaticBlackSecondary.args = { + variant: "secondary", + treatmentLayout: "stacked", + staticColor: "black", +}; +StaticBlackSecondary.parameters = { + chromatic: { disableSnapshot: true }, +}; + +/** + * The pending button is for indicating that a quick progress action is taking place. In this case, the + * label and optional icon disappear and a progress circle appears. The progress circle always shows an + * indeterminate progress. We recommend the use of the `.is-pending` class on the component’s parent + * container, but there is also an option to use an attribute of `pending` instead. Buttons should have + * the disabled attribute when the pending state is applied. + */ +export const Pending = TreatmentTemplate.bind({}); +Pending.tags = ["!dev"]; +Pending.args = { + variant: "accent", + isPending: true, + onclick: () => {}, +}; +Pending.parameters = { + chromatic: { disableSnapshot: true }, +}; + +/** + * A button in a disabled state shows that an action exists, but is not available in that circumstance. + * This state can be used to maintain layout continuity and to communicate that an action may become + * available later. + */ +export const Disabled = TreatmentTemplate.bind({}); +Disabled.tags = ["!dev"]; +Disabled.args = { + variant: "accent", + isDisabled: true, +}; +Disabled.parameters = { + chromatic: { disableSnapshot: true }, +}; + +/** + * When the button text is too long for the horizontal space available, it wraps to form another line. + * When there is no icon present, the text is aligned center. When there is an icon present, the text is + * aligned `start` (left with a writing direction of left-to-right) and the icon remains vertically aligned + * at the top. + */ +export const WithWrapping = TextOverflowTemplate.bind({}); +WithWrapping.tags = ["!dev"]; +WithWrapping.storyName = "Text overflow behavior"; +WithWrapping.args = { + variant: "primary", +}; +WithWrapping.parameters = { + chromatic: { disableSnapshot: true }, +}; diff --git a/components/button/stories/template.js b/components/button/stories/template.js index ce8064e58e6..b09fa874e24 100644 --- a/components/button/stories/template.js +++ b/components/button/stories/template.js @@ -1,5 +1,5 @@ import { Template as Icon } from "@spectrum-css/icon/stories/template.js"; -import { getRandomId } from "@spectrum-css/preview/decorators"; +import { Container, getRandomId } from "@spectrum-css/preview/decorators"; import { Template as ProgressCircle } from "@spectrum-css/progresscircle/stories/template.js"; import { html } from "lit"; import { classMap } from "lit/directives/class-map.js"; @@ -100,3 +100,73 @@ export const Template = ({ </button> `; }; + +/** + * Displays multiple buttons with text label, icon + text label, and icon only. + * Used in the display of some docs-only stories. + */ +export const ButtonsWithIconOptions = ({ + iconName, + ...args +}) => Container({ + withBorder: false, + direction: "row", + wrapperStyles: { + columnGap: "12px", + }, + content: html` + ${Template({ + ...args, + iconName: undefined, + })} + ${Template({ + ...args, + iconName: iconName ?? "Edit", + })} + ${Template({ + ...args, + hideLabel: true, + iconName: iconName ?? "Edit", + })} + `, +}); + +/** + * Display the buttons with icon options for each treatment option. + */ +export const TreatmentTemplate = (args) => Container({ + withBorder: false, + direction: "column", + wrapperStyles: { + rowGap: "12px", + }, + content: html`${["fill", "outline"].map((treatment) => ButtonsWithIconOptions({ ...args, treatment }))}`, +}); + +/** + * Display the text overflow behavior of buttons. + */ +export const TextOverflowTemplate = (args) => Container({ + withBorder: false, + direction: "column", + wrapperStyles: { + rowGap: "12px", + }, + content: html` + ${Template({ + ...args, + customStyles: { + "max-inline-size": "480px", + }, + label: "An example of text overflow behavior when there is no icon. When the button text is too long for the horizontal space available, it wraps to form another line.", + })} + ${Template({ + ...args, + customStyles: { + "max-inline-size": "480px", + }, + iconName: "Edit", + label: "An example of text overflow behavior when the button has an icon. When the button text is too long for the horizontal space available, it wraps to form another line.", + })} + `, +}); \ No newline at end of file