From 5eb8d7b181936220e79cad1d1630701eba813089 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Fri, 30 May 2025 12:38:40 -0400 Subject: [PATCH] [CL-208][CL-339] Enhance Storybook docs pages (#14838) * rearrange button docs * Enhance avatar docs * Enhance badge docs * Enhance banner docs * add util to format args for snippets * update banner snippets * WIP * bind boolean args so they work correctly in Storybook * simplify button stories * Update callout docs * use title component for checkbox docs * use title and description component for chip select docs * update color password story docs * update disclosure docs * add import to icon docs * updated icon-button docs * update link docs * Update prgress docs * updated search field docs * remove html type definitions * add import for progress * updated toast docs * remove example from docs. format args for snippet * Update badges docs * handle array arg values correctly * Update badges list docs * fix dupe key error from taost story * remove unnecessary typeof check * remove banner usage example * add breadcrumbs import statement and jsdoc * add color password import statement * fixing type mismaches * fix typos * Add missing generics to format function * fix typo * update callout icon spacing to match Figma * add back max width container --- .storybook/format-args-for-code-snippet.ts | 33 ++++++ .storybook/preview.tsx | 7 +- .../components/src/avatar/avatar.component.ts | 6 ++ libs/components/src/avatar/avatar.mdx | 12 +-- libs/components/src/avatar/avatar.stories.ts | 16 +++ .../src/badge-list/badge-list.stories.ts | 3 +- libs/components/src/badge/badge.component.ts | 15 ++- libs/components/src/badge/badge.mdx | 18 +--- libs/components/src/badge/badge.stories.ts | 100 ++++++++++-------- .../components/src/banner/banner.component.ts | 15 ++- libs/components/src/banner/banner.mdx | 23 ++-- libs/components/src/banner/banner.stories.ts | 37 ++++--- .../src/breadcrumbs/breadcrumbs.component.ts | 5 + .../src/breadcrumbs/breadcrumbs.mdx | 11 +- libs/components/src/button/button.mdx | 39 ++++--- libs/components/src/button/button.stories.ts | 73 +++++++------ .../src/callout/callout.component.html | 5 +- .../src/callout/callout.component.ts | 5 + libs/components/src/callout/callout.mdx | 10 +- .../components/src/callout/callout.stories.ts | 23 ++-- libs/components/src/checkbox/checkbox.mdx | 5 +- .../src/chip-select/chip-select.component.ts | 3 + .../src/chip-select/chip-select.mdx | 7 +- .../color-password.component.ts | 6 +- .../src/color-password/color-password.mdx | 11 +- .../color-password/color-password.stories.ts | 6 +- .../src/disclosure/disclosure.component.ts | 24 +++++ libs/components/src/disclosure/disclosure.mdx | 32 +----- .../src/icon-button/icon-button.component.ts | 6 ++ .../src/icon-button/icon-button.mdx | 14 +-- .../src/icon-button/icon-button.stories.ts | 48 +++------ libs/components/src/icon/icon.mdx | 4 + libs/components/src/link/link.directive.ts | 8 ++ libs/components/src/link/link.mdx | 15 +-- libs/components/src/link/link.stories.ts | 10 ++ .../src/progress/progress.component.ts | 15 +-- libs/components/src/progress/progress.mdx | 10 +- .../src/progress/progress.stories.ts | 17 +++ libs/components/src/search/search.mdx | 4 +- libs/components/src/search/search.stories.ts | 7 +- libs/components/src/toast/toast.mdx | 18 ++-- libs/components/src/toast/toast.stories.ts | 26 +++-- libs/components/src/toast/toastr.component.ts | 3 + tsconfig.eslint.json | 1 + 44 files changed, 454 insertions(+), 302 deletions(-) create mode 100644 .storybook/format-args-for-code-snippet.ts diff --git a/.storybook/format-args-for-code-snippet.ts b/.storybook/format-args-for-code-snippet.ts new file mode 100644 index 0000000000..bf36c153c0 --- /dev/null +++ b/.storybook/format-args-for-code-snippet.ts @@ -0,0 +1,33 @@ +import { argsToTemplate, StoryObj } from "@storybook/angular"; + +type RenderArgType = StoryObj["args"]; + +export const formatArgsForCodeSnippet = >( + args: RenderArgType, +) => { + const nonNullArgs = Object.entries(args as ComponentType).filter( + ([_, value]) => value !== null && value !== undefined, + ); + const functionArgs = nonNullArgs.filter(([_, value]) => typeof value === "function"); + const argsToFormat = nonNullArgs.filter(([_, value]) => typeof value !== "function"); + + const argsToTemplateIncludeKeys = [...functionArgs].map( + ([key, _]) => key as keyof RenderArgType, + ); + + const formattedNonFunctionArgs = argsToFormat + .map(([key, value]) => { + if (typeof value === "boolean") { + return `[${key}]="${value}"`; + } + + if (Array.isArray(value)) { + const formattedArray = value.map((v) => `'${v}'`).join(", "); + return `[${key}]="[${formattedArray}]"`; + } + return `${key}="${value}"`; + }) + .join(" "); + + return `${formattedNonFunctionArgs} ${argsToTemplate(args as ComponentType, { include: argsToTemplateIncludeKeys })}`; +}; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index a948fce042..59b5287f3a 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -41,7 +41,12 @@ const preview: Preview = { order: ["Documentation", ["Introduction", "Colors", "Icons"], "Component Library"], }, }, - docs: { source: { type: "dynamic", excludeDecorators: true } }, + docs: { + source: { + type: "dynamic", + excludeDecorators: true, + }, + }, backgrounds: { disable: true, }, diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 554f55636f..c66bba1c46 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -16,6 +16,12 @@ const SizeClasses: Record = { xsmall: ["tw-h-6", "tw-w-6"], }; +/** + * Avatars display a unique color that helps a user visually recognize their logged in account. + + * A variance in color across the avatar component is important as it is used in Account Switching as a + * visual indicator to recognize which of a personal or work account a user is logged into. +*/ @Component({ selector: "bit-avatar", template: `@if (src) { diff --git a/libs/components/src/avatar/avatar.mdx b/libs/components/src/avatar/avatar.mdx index 627ba526ed..bbf356f96f 100644 --- a/libs/components/src/avatar/avatar.mdx +++ b/libs/components/src/avatar/avatar.mdx @@ -1,15 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Description, Meta, Canvas, Primary, Controls, Title } from "@storybook/addon-docs"; import * as stories from "./avatar.stories"; -# Avatar +```ts +import { AvatarModule } from "@bitwarden/components"; +``` -Avatars display a unique color that helps a user visually recognize their logged in account. - -A variance in color across the avatar component is important as it is used in Account Switching as a -visual indicator to recognize which of a personal or work account a user is logged into. + +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/avatar/avatar.stories.ts b/libs/components/src/avatar/avatar.stories.ts index 19a6f86d89..9b0d4e4aa8 100644 --- a/libs/components/src/avatar/avatar.stories.ts +++ b/libs/components/src/avatar/avatar.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { AvatarComponent } from "./avatar.component"; export default { @@ -21,42 +23,56 @@ export default { type Story = StoryObj<AvatarComponent>; export const Default: Story = { + render: (args) => { + return { + props: args, + template: ` + <bit-avatar ${formatArgsForCodeSnippet<AvatarComponent>(args)}></bit-avatar> + `, + }; + }, args: { color: "#175ddc", }, }; export const Large: Story = { + ...Default, args: { size: "large", }, }; export const Small: Story = { + ...Default, args: { size: "small", }, }; export const LightBackground: Story = { + ...Default, args: { color: "#d2ffcf", }, }; export const Border: Story = { + ...Default, args: { border: true, }, }; export const ColorByID: Story = { + ...Default, args: { id: "236478", }, }; export const ColorByText: Story = { + ...Default, args: { text: "Jason Doe", }, diff --git a/libs/components/src/badge-list/badge-list.stories.ts b/libs/components/src/badge-list/badge-list.stories.ts index f69ecde837..504871f950 100644 --- a/libs/components/src/badge-list/badge-list.stories.ts +++ b/libs/components/src/badge-list/badge-list.stories.ts @@ -2,6 +2,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { BadgeModule } from "../badge"; import { SharedModule } from "../shared"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -44,7 +45,7 @@ export const Default: Story = { render: (args) => ({ props: args, template: ` - <bit-badge-list [variant]="variant" [maxItems]="maxItems" [items]="items" [truncate]="truncate"></bit-badge-list> + <bit-badge-list ${formatArgsForCodeSnippet<BadgeListComponent>(args)}></bit-badge-list> `, }), diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts index 893257ff22..3612827eff 100644 --- a/libs/components/src/badge/badge.component.ts +++ b/libs/components/src/badge/badge.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, ElementRef, HostBinding, Input } from "@angular/core"; @@ -45,7 +43,18 @@ const hoverStyles: Record<BadgeVariant, string[]> = { "hover:!tw-text-contrast", ], }; +/** + * Badges are primarily used as labels, counters, and small buttons. + * Typically Badges are only used with text set to `text-xs`. If additional sizes are needed, the component configurations may be reviewed and adjusted. + + * The Badge directive can be used on a `<span>` (non clickable events), or an `<a>` or `<button>` tag + + * > `NOTE:` The Focus and Hover states only apply to badges used for interactive events. + * + * > `NOTE:` The `disabled` state only applies to buttons. + * +*/ @Component({ selector: "span[bitBadge], a[bitBadge], button[bitBadge]", providers: [{ provide: FocusableElement, useExisting: BadgeComponent }], @@ -89,7 +98,7 @@ export class BadgeComponent implements FocusableElement { if (this.title !== undefined) { return this.title; } - return this.truncate ? this.el.nativeElement.textContent.trim() : null; + return this.truncate ? this?.el?.nativeElement?.textContent?.trim() : null; } /** diff --git a/libs/components/src/badge/badge.mdx b/libs/components/src/badge/badge.mdx index 55f3218389..957a3256cb 100644 --- a/libs/components/src/badge/badge.mdx +++ b/libs/components/src/badge/badge.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./badge.stories"; @@ -8,25 +8,15 @@ import * as stories from "./badge.stories"; import { BadgeModule } from "@bitwarden/components"; ``` -# Badge - -Badges are primarily used as labels, counters, and small buttons. - -Typically Badges are only used with text set to `text-xs`. If additional sizes are needed, the -component configurations may be reviewed and adjusted. - -The Badge directive can be used on a `<span>` (non clickable events), or an `<a>` or `<button>` tag -for interactive events. The Focus and Hover states only apply to badges used for interactive events. -The `disabled` state only applies to buttons. - -The story below uses the `<button>` element to demonstrate all the possible states. +<Title /> +<Description /> <Primary /> <Controls /> ## Styles -### Primary +### Default / Primary The primary badge is used to indicate an active status (example: device management page) or provide additional information (example: type of emergency access granted). diff --git a/libs/components/src/badge/badge.stories.ts b/libs/components/src/badge/badge.stories.ts index 6473ba8c86..a151547ef6 100644 --- a/libs/components/src/badge/badge.stories.ts +++ b/libs/components/src/badge/badge.stories.ts @@ -1,6 +1,8 @@ import { CommonModule } from "@angular/common"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { BadgeComponent } from "./badge.component"; export default { @@ -12,7 +14,6 @@ export default { }), ], args: { - variant: "primary", truncate: false, }, parameters: { @@ -25,45 +26,11 @@ export default { type Story = StoryObj<BadgeComponent>; -export const Variants: Story = { +export const Default: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <span class="tw-text-main tw-mx-1">Default</span> - <button class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> - <button class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> - <button class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> - <br/><br/> - <span class="tw-text-main tw-mx-1">Hover</span> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="success" [truncate]="truncate">Success</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="info" [truncate]="truncate">Info</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="notification" [truncate]="truncate">Notification</button> - <br/><br/> - <span class="tw-text-main tw-mx-1">Focus Visible</span> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="success" [truncate]="truncate">Success</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="info" [truncate]="truncate">Info</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="notification" [truncate]="truncate">Notification</button> - <br/><br/> - <span class="tw-text-main tw-mx-1">Disabled</span> - <button disabled class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button disabled class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button disabled class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> - <button disabled class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button disabled class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button disabled class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> - <button disabled class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <span bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge containing lengthy text</span> `, }), }; @@ -72,11 +39,17 @@ export const Primary: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <span class="tw-text-main">Span </span><span bitBadge [variant]="variant" [truncate]="truncate">Badge containing lengthy text</span> - <br /><br /> - <span class="tw-text-main">Link </span><a href="#" bitBadge [variant]="variant" [truncate]="truncate">Badge</a> - <br /><br /> - <span class="tw-text-main">Button </span><button bitBadge [variant]="variant" [truncate]="truncate">Badge</button> + <div class="tw-flex tw-flex-col tw-gap-4"> + <div class="tw-flex tw-items-center tw-gap-2"> + <span class="tw-text-main">span</span><span bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge containing lengthy text</span> + </div> + <div class="tw-flex tw-items-center tw-gap-2"> + <span class="tw-text-main">link </span><a href="#" bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge</a> + </div> + <div class="tw-flex tw-items-center tw-gap-2"> + <span class="tw-text-main">button </span><button bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge</button> + </div> + </div> `, }), }; @@ -129,3 +102,46 @@ export const Truncated: Story = { truncate: true, }, }; + +export const VariantsAndInteractionStates: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <span class="tw-text-main tw-mx-1">Default</span> + <button class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> + <button class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> + <button class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <br/><br/> + <span class="tw-text-main tw-mx-1">Hover</span> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="success" [truncate]="truncate">Success</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="info" [truncate]="truncate">Info</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <br/><br/> + <span class="tw-text-main tw-mx-1">Focus Visible</span> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="success" [truncate]="truncate">Success</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="info" [truncate]="truncate">Info</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <br/><br/> + <span class="tw-text-main tw-mx-1">Disabled</span> + <button disabled class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button disabled class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button disabled class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> + <button disabled class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button disabled class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button disabled class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> + <button disabled class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> + `, + }), +}; diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index a7b710d6a7..a6719f2598 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -7,15 +7,24 @@ import { I18nPipe } from "@bitwarden/ui-common"; import { IconButtonModule } from "../icon-button"; -type BannerTypes = "premium" | "info" | "warning" | "danger"; +type BannerType = "premium" | "info" | "warning" | "danger"; -const defaultIcon: Record<BannerTypes, string> = { +const defaultIcon: Record<BannerType, string> = { premium: "bwi-star", info: "bwi-info-circle", warning: "bwi-exclamation-triangle", danger: "bwi-error", }; +/** + * Banners are used for important communication with the user that needs to be seen right away, but has + * little effect on the experience. Banners appear at the top of the user's screen on page load and + * persist across all pages a user navigates to. + * - They should always be dismissible and never use a timeout. If a user dismisses a banner, it should not reappear during that same active session. + * - Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their effectiveness may decrease if too many are used. + * - Avoid stacking multiple banners. + * - Banners can contain a button or anchor that uses the `bitLink` directive with `linkType="secondary"`. + */ @Component({ selector: "bit-banner", templateUrl: "./banner.component.html", @@ -23,7 +32,7 @@ const defaultIcon: Record<BannerTypes, string> = { imports: [CommonModule, IconButtonModule, I18nPipe], }) export class BannerComponent implements OnInit { - @Input("bannerType") bannerType: BannerTypes = "info"; + @Input("bannerType") bannerType: BannerType = "info"; @Input() icon: string; @Input() useAlertRole = true; @Input() showClose = true; diff --git a/libs/components/src/banner/banner.mdx b/libs/components/src/banner/banner.mdx index 67fb796a54..f37fe90e11 100644 --- a/libs/components/src/banner/banner.mdx +++ b/libs/components/src/banner/banner.mdx @@ -1,25 +1,16 @@ -import { Meta, Controls, Canvas, Primary } from "@storybook/addon-docs"; +import { Canvas, Controls, Description, Meta, Primary, Title } from "@storybook/addon-docs"; import * as stories from "./banner.stories"; <Meta of={stories} /> -# Banner - -Banners are used for important communication with the user that needs to be seen right away, but has -little effect on the experience. Banners appear at the top of the user's screen on page load and -persist across all pages a user navigates to. - -- They should always be dismissible and never use a timeout. If a user dismisses a banner, it should - not reappear during that same active session. -- Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their - effectiveness may decrease if too many are used. -- Avoid stacking multiple banners. -- Banners can contain a button or anchor that uses the `bitLink` directive with - `linkType="secondary"`. +```ts +import { BannerModule } from "@bitwarden/components"; +``` +<Title /> +<Description /> <Primary /> - <Controls /> ## Types @@ -56,5 +47,5 @@ Rarely used, but may be used to alert users over critical messages or very outda ## Accessibility Banners sets the `role="status"` and `aria-live="polite"` attributes to ensure screen readers -announce the content prior to the test of the page. This behaviour can be disabled by setting +announce the content prior to the test of the page. This behavior can be disabled by setting `[useAlertRole]="false"`. diff --git a/libs/components/src/banner/banner.stories.ts b/libs/components/src/banner/banner.stories.ts index 105d30bc04..8338c9240b 100644 --- a/libs/components/src/banner/banner.stories.ts +++ b/libs/components/src/banner/banner.stories.ts @@ -2,6 +2,7 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { IconButtonModule } from "../icon-button"; import { LinkModule } from "../link"; import { SharedModule } from "../shared/shared.module"; @@ -44,48 +45,50 @@ export default { type Story = StoryObj<BannerComponent>; +export const Base: Story = { + render: (args) => { + return { + props: args, + template: ` + <bit-banner ${formatArgsForCodeSnippet<BannerComponent>(args)}> + Content Really Long Text Lorem Ipsum Ipsum Ipsum + <button bitLink linkType="secondary">Button</button> + </bit-banner> + `, + }; + }, +}; + export const Premium: Story = { + ...Base, args: { bannerType: "premium", }, - render: (args) => ({ - props: args, - template: ` - <bit-banner [bannerType]="bannerType" (onClose)="onClose($event)" [showClose]=showClose> - Content Really Long Text Lorem Ipsum Ipsum Ipsum - <button bitLink linkType="secondary">Button</button> - </bit-banner> - `, - }), -}; - -Premium.args = { - bannerType: "premium", }; export const Info: Story = { - ...Premium, + ...Base, args: { bannerType: "info", }, }; export const Warning: Story = { - ...Premium, + ...Base, args: { bannerType: "warning", }, }; export const Danger: Story = { - ...Premium, + ...Base, args: { bannerType: "danger", }, }; export const HideClose: Story = { - ...Premium, + ...Base, args: { showClose: false, }, diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts index 6e8fbf5c25..2426521296 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -8,6 +8,11 @@ import { MenuModule } from "../menu"; import { BreadcrumbComponent } from "./breadcrumb.component"; +/** + * Breadcrumbs are used to help users understand where they are in a products navigation. Typically + * Bitwarden uses this component to indicate the user's current location in a set of data organized in + * containers (Collections, Folders, or Projects). + */ @Component({ selector: "bit-breadcrumbs", templateUrl: "./breadcrumbs.component.html", diff --git a/libs/components/src/breadcrumbs/breadcrumbs.mdx b/libs/components/src/breadcrumbs/breadcrumbs.mdx index 1ea0aff8c3..cd1d022638 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.mdx +++ b/libs/components/src/breadcrumbs/breadcrumbs.mdx @@ -1,14 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./breadcrumbs.stories"; <Meta of={stories} /> -# Breadcrumbs +```ts +import { BreadcrumbsModule } from "@bitwarden/components"; +``` -Breadcrumbs are used to help users understand where they are in a products navigation. Typically -Bitwarden uses this component to indicate the user's current location in a set of data organized in -containers (Collections, Folders, or Projects). +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/button/button.mdx b/libs/components/src/button/button.mdx index 61874922fc..b0f347ba33 100644 --- a/libs/components/src/button/button.mdx +++ b/libs/components/src/button/button.mdx @@ -1,4 +1,12 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { + Markdown, + Meta, + Canvas, + Primary, + Controls, + Title, + Description, +} from "@storybook/addon-docs"; import * as stories from "./button.stories"; @@ -8,10 +16,9 @@ import * as stories from "./button.stories"; import { ButtonModule } from "@bitwarden/components"; ``` -# Button +<Title /> -Buttons are interactive elements that can be triggered using a mouse, keyboard, or touch. They are -used to indicate actions that can be performed by a user such as submitting a form. +### Default / Secondary <Primary /> @@ -30,7 +37,7 @@ takes: ### Groups -Groups of buttons should be seperated by a `0.5` rem gap. Usually acomplished by using the +Groups of buttons should be separated by a `0.5` rem gap. Usually accomplished by using the `tw-gap-2` class in the button group container. Groups within page content, dialog footers or forms should have the `primary` call to action placed @@ -41,26 +48,24 @@ right. There are 3 main styles for the button: Primary, Secondary, and Danger. -### Primary +### Default / Secondary -<Canvas of={stories.Primary} /> +The secondary styling(shown above) should be used for secondary calls to action. An action is +"secondary" if it relates indirectly to the purpose of a page. There may be multiple secondary +buttons next to each other; however, generally there should only be 1 or 2 calls to action per page. + +### Primary Use the primary button styling for all Primary call to actions. An action is "primary" if it relates to the main purpose of a page. There should never be 2 primary styled buttons next to each other. -### Secondary - -<Canvas of={stories.Secondary} /> - -The secondary styling should be used for secondary calls to action. An action is "secondary" if it -relates indirectly to the purpose of a page. There may be multiple secondary buttons next to each -other; however, generally there should only be 1 or 2 calls to action per page. +<Canvas of={stories.Primary} /> ### Danger -<Canvas of={stories.Danger} /> +Use the danger styling only in settings when the user may perform a permanent destructive action. -Use the danger styling only in settings when the user may preform a permanent action. +<Canvas of={stories.Danger} /> ## Disabled UI @@ -114,7 +119,7 @@ success toast). ### Submit and async actions Both submit and async action buttons use a loading button state while an action is taken. If your -button is preforming a long running task in the background like a server API call, be sure to review +button is performing a long running task in the background like a server API call, be sure to review the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page). <Canvas of={stories.Loading} /> diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 759bd1a352..d0a4354f37 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -1,15 +1,15 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { ButtonComponent } from "./button.component"; export default { title: "Component Library/Button", component: ButtonComponent, args: { - buttonType: "primary", disabled: false, loading: false, - size: "default", }, argTypes: { size: { @@ -27,40 +27,27 @@ export default { type Story = StoryObj<ButtonComponent>; -export const Primary: Story = { +export const Default: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <div class="tw-flex tw-gap-4 tw-mb-6 tw-items-center"> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Button</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Button:hover</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Button:focus-visible</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Button:hover:focus-visible</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Button:active</button> - </div> - <div class="tw-flex tw-gap-4 tw-items-center"> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Anchor</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Anchor:hover</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Anchor:focus-visible</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Anchor:hover:focus-visible</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Anchor:active</a> - </div> + <button bitButton ${formatArgsForCodeSnippet<ButtonComponent>(args)}>Button</button> `, }), - args: { - buttonType: "primary", - }, -}; - -export const Secondary: Story = { - ...Primary, args: { buttonType: "secondary", }, }; +export const Primary: Story = { + ...Default, + args: { + buttonType: "primary", + }, +}; + export const Danger: Story = { - ...Primary, + ...Default, args: { buttonType: "danger", }, @@ -83,16 +70,8 @@ export const Small: Story = { }; export const Loading: Story = { - render: (args) => ({ - props: args, - template: ` - <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button> - <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button> - <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> - `, - }), + ...Default, args: { - disabled: false, loading: true, }, }; @@ -101,7 +80,6 @@ export const Disabled: Story = { ...Loading, args: { disabled: true, - loading: false, }, }; @@ -165,3 +143,28 @@ export const WithIcon: Story = { `, }), }; + +export const InteractionStates: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <div class="tw-flex tw-gap-4 tw-mb-6 tw-items-center"> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Button</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Button:hover</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Button:focus-visible</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Button:hover:focus-visible</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Button:active</button> + </div> + <div class="tw-flex tw-gap-4 tw-items-center"> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Anchor</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Anchor:hover</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Anchor:focus-visible</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Anchor:hover:focus-visible</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Anchor:active</a> + </div> + `, + }), + args: { + buttonType: "primary", + }, +}; diff --git a/libs/components/src/callout/callout.component.html b/libs/components/src/callout/callout.component.html index bb7f918df3..4e7b5f2a0c 100644 --- a/libs/components/src/callout/callout.component.html +++ b/libs/components/src/callout/callout.component.html @@ -4,7 +4,10 @@ [attr.aria-labelledby]="titleId" > @if (title) { - <header id="{{ titleId }}" class="tw-mb-1 tw-mt-0 tw-text-base tw-font-semibold"> + <header + id="{{ titleId }}" + class="tw-mb-1 tw-mt-0 tw-text-base tw-font-semibold tw-flex tw-gap-2 tw-items-center" + > @if (icon) { <i class="bwi" [ngClass]="[icon, headerClass]" aria-hidden="true"></i> } diff --git a/libs/components/src/callout/callout.component.ts b/libs/components/src/callout/callout.component.ts index 6ffd8d2d0e..e1bd7f1a59 100644 --- a/libs/components/src/callout/callout.component.ts +++ b/libs/components/src/callout/callout.component.ts @@ -24,6 +24,11 @@ const defaultI18n: Partial<Record<CalloutTypes, string>> = { // Increments for each instance of this component let nextId = 0; +/** + * Callouts are used to communicate important information to the user. Callouts should be used + * sparingly, as they command a large amount of visual attention. Avoid using more than 1 callout in + * the same location. + */ @Component({ selector: "bit-callout", templateUrl: "callout.component.html", diff --git a/libs/components/src/callout/callout.mdx b/libs/components/src/callout/callout.mdx index 160b1e1cc3..a1254b3f69 100644 --- a/libs/components/src/callout/callout.mdx +++ b/libs/components/src/callout/callout.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./callout.stories"; @@ -8,11 +8,11 @@ import { CalloutModule } from "@bitwarden/components"; <Meta of={stories} /> -# Callouts +<Title /> +<Description /> -Callouts are used to communicate important information to the user. Callouts should be used -sparingly, as they command a large amount of visual attention. Avoid using more than 1 callout in -the same location. +<Primary /> +<Controls /> ## Styles diff --git a/libs/components/src/callout/callout.stories.ts b/libs/components/src/callout/callout.stories.ts index 3101d4316f..5f22bf9570 100644 --- a/libs/components/src/callout/callout.stories.ts +++ b/libs/components/src/callout/callout.stories.ts @@ -2,6 +2,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { I18nMockService } from "../utils/i18n-mock.service"; import { CalloutComponent } from "./callout.component"; @@ -24,9 +25,6 @@ export default { ], }), ], - args: { - type: "warning", - }, parameters: { design: { type: "figma", @@ -37,36 +35,35 @@ export default { type Story = StoryObj<CalloutComponent>; -export const Success: Story = { +export const Info: Story = { render: (args) => ({ props: args, template: ` - <bit-callout [type]="type" [title]="title">Content</bit-callout> + <bit-callout ${formatArgsForCodeSnippet<CalloutComponent>(args)}>Content</bit-callout> `, }), args: { - type: "success", - title: "Success", + title: "Title", }, }; -export const Info: Story = { - ...Success, +export const Success: Story = { + ...Info, args: { - type: "info", - title: "Info", + ...Info.args, + type: "success", }, }; export const Warning: Story = { - ...Success, + ...Info, args: { type: "warning", }, }; export const Danger: Story = { - ...Success, + ...Info, args: { type: "danger", }, diff --git a/libs/components/src/checkbox/checkbox.mdx b/libs/components/src/checkbox/checkbox.mdx index f3ce0d8fd0..ba5de4d234 100644 --- a/libs/components/src/checkbox/checkbox.mdx +++ b/libs/components/src/checkbox/checkbox.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./checkbox.stories"; @@ -8,7 +8,8 @@ import * as stories from "./checkbox.stories"; import { CheckboxModule } from "@bitwarden/components"; ``` -# Checkbox +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index d1f3bba262..270249ade0 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -33,6 +33,9 @@ export type ChipSelectOption<T> = Option<T> & { children?: ChipSelectOption<T>[]; }; +/** + * `<bit-chip-select>` is a select element that is commonly used to filter items in lists or tables. + */ @Component({ selector: "bit-chip-select", templateUrl: "chip-select.component.html", diff --git a/libs/components/src/chip-select/chip-select.mdx b/libs/components/src/chip-select/chip-select.mdx index d569158b75..b09b9664f8 100644 --- a/libs/components/src/chip-select/chip-select.mdx +++ b/libs/components/src/chip-select/chip-select.mdx @@ -1,4 +1,4 @@ -import { Meta, Primary, Controls, Canvas } from "@storybook/addon-docs"; +import { Meta, Primary, Controls, Canvas, Title, Description } from "@storybook/addon-docs"; import * as stories from "./chip-select.stories"; @@ -8,9 +8,8 @@ import * as stories from "./chip-select.stories"; import { ChipSelectComponent } from "@bitwarden/components"; ``` -# Chip Select - -`<bit-chip-select>` is a select element that is commonly used to filter items in lists or tables. +<Title /> +<Description /> <Canvas of={stories.Default} /> diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index 2dd78e8525..a6cd58044a 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -10,7 +10,11 @@ enum CharacterType { Special, Number, } - +/** + * The color password is used primarily in the Generator pages and in the Login type form. It includes + * the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as + * `danger`. + */ @Component({ selector: "bit-color-password", template: `@for (character of passwordCharArray(); track $index; let i = $index) { diff --git a/libs/components/src/color-password/color-password.mdx b/libs/components/src/color-password/color-password.mdx index 8f3746715e..4deeace9b9 100644 --- a/libs/components/src/color-password/color-password.mdx +++ b/libs/components/src/color-password/color-password.mdx @@ -1,14 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./color-password.stories"; <Meta of={stories} /> -# Color password +```ts +import { ColorPasswordModule } from "@bitwarden/components"; +``` -The color password is used primarily in the Generator pages and in the Login type form. It includes -the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as -`danger`. +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/color-password/color-password.stories.ts b/libs/components/src/color-password/color-password.stories.ts index bb835d97d4..5a544dcb22 100644 --- a/libs/components/src/color-password/color-password.stories.ts +++ b/libs/components/src/color-password/color-password.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { ColorPasswordComponent } from "./color-password.component"; const examplePassword = "Wq$Jk😀7j DX#rS5Sdi!z "; @@ -25,7 +27,7 @@ export const ColorPassword: Story = { render: (args) => ({ props: args, template: ` - <bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password> + <bit-color-password ${formatArgsForCodeSnippet<ColorPasswordComponent>(args)}></bit-color-password> `, }), }; @@ -35,7 +37,7 @@ export const WrappedColorPassword: Story = { props: args, template: ` <div class="tw-max-w-32"> - <bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password> + <bit-color-password ${formatArgsForCodeSnippet<ColorPasswordComponent>(args)}></bit-color-password> </div> `, }), diff --git a/libs/components/src/disclosure/disclosure.component.ts b/libs/components/src/disclosure/disclosure.component.ts index 6de06b48b3..c18a2e31ea 100644 --- a/libs/components/src/disclosure/disclosure.component.ts +++ b/libs/components/src/disclosure/disclosure.component.ts @@ -11,6 +11,30 @@ import { let nextId = 0; +/** + * The `bit-disclosure` component is used in tandem with the `bitDisclosureTriggerFor` directive to create an accessible content area whose visibility is controlled by a trigger button. + + * To compose a disclosure and trigger: + + * 1. Create a trigger component (see "Supported Trigger Components" section below) + * 2. Create a `bit-disclosure` + * 3. Set a template reference on the `bit-disclosure` + * 4. Use the `bitDisclosureTriggerFor` directive on the trigger component, and pass it the `bit-disclosure` template reference + * 5. Set the `open` property on the `bit-disclosure` to init the disclosure as either currently expanded or currently collapsed. The disclosure will default to `false`, meaning it defaults to being hidden. + * + * @example + * + * ```html + * <button + * type="button" + * bitIconButton="bwi-sliders" + * [buttonType]="'muted'" + * [bitDisclosureTriggerFor]="disclosureRef" + * ></button> + * <bit-disclosure #disclosureRef open>click button to hide this content</bit-disclosure> + * ``` + * + */ @Component({ selector: "bit-disclosure", standalone: true, diff --git a/libs/components/src/disclosure/disclosure.mdx b/libs/components/src/disclosure/disclosure.mdx index 2fcff6f598..50ccf936ac 100644 --- a/libs/components/src/disclosure/disclosure.mdx +++ b/libs/components/src/disclosure/disclosure.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./disclosure.stories"; @@ -8,37 +8,11 @@ import * as stories from "./disclosure.stories"; import { DisclosureComponent, DisclosureTriggerForDirective } from "@bitwarden/components"; ``` -# Disclosure - -The `bit-disclosure` component is used in tandem with the `bitDisclosureTriggerFor` directive to -create an accessible content area whose visibility is controlled by a trigger button. - -To compose a disclosure and trigger: - -1. Create a trigger component (see "Supported Trigger Components" section below) -2. Create a `bit-disclosure` -3. Set a template reference on the `bit-disclosure` -4. Use the `bitDisclosureTriggerFor` directive on the trigger component, and pass it the - `bit-disclosure` template reference -5. Set the `open` property on the `bit-disclosure` to init the disclosure as either currently - expanded or currently collapsed. The disclosure will default to `false`, meaning it defaults to - being hidden. - -``` -<button - type="button" - bitIconButton="bwi-sliders" - [buttonType]="'muted'" - [bitDisclosureTriggerFor]="disclosureRef" -></button> -<bit-disclosure #disclosureRef open>click button to hide this content</bit-disclosure> -``` +<Title /> +<Description /> <Canvas of={stories.DisclosureWithIconButton} /> -<br /> -<br /> - ## Supported Trigger Components This is the list of currently supported trigger components: diff --git a/libs/components/src/icon-button/icon-button.component.ts b/libs/components/src/icon-button/icon-button.component.ts index 60877070e2..573708b1e4 100644 --- a/libs/components/src/icon-button/icon-button.component.ts +++ b/libs/components/src/icon-button/icon-button.component.ts @@ -147,7 +147,13 @@ const sizes: Record<IconButtonSize, string[]> = { default: ["tw-px-2.5", "tw-py-1.5"], small: ["tw-leading-none", "tw-text-base", "tw-p-1"], }; +/** + * Icon buttons are used when no text accompanies the button. It consists of an icon that may be updated to any icon in the `bwi-font`, a `title` attribute, and an `aria-label`. + * The most common use of the icon button is in the banner, toast, and modal components as a close button. It can also be found in tables as the 3 dot option menu, or on navigation list items when there are options that need to be collapsed into a menu. + + * Similar to the main button components, spacing between multiple icon buttons should be .5rem. + */ @Component({ selector: "button[bitIconButton]:not(button[bitButton])", templateUrl: "icon-button.component.html", diff --git a/libs/components/src/icon-button/icon-button.mdx b/libs/components/src/icon-button/icon-button.mdx index 85164717de..637a9d7daa 100644 --- a/libs/components/src/icon-button/icon-button.mdx +++ b/libs/components/src/icon-button/icon-button.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./icon-button.stories"; @@ -8,16 +8,8 @@ import * as stories from "./icon-button.stories"; import { IconButtonModule } from "@bitwarden/components"; ``` -# Icon Button - -Icon buttons are used when no text accompanies the button. It consists of an icon that may be -updated to any icon in the `bwi-font`, a `title` attribute, and an `aria-label`. - -The most common use of the icon button is in the banner, toast, and modal components as a close -button. It can also be found in tables as the 3 dot option menu, or on navigation list items when -there are options that need to be collapsed into a menu. - -Similar to the main button components, spacing between multiple icon buttons should be .5rem. +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/icon-button/icon-button.stories.ts b/libs/components/src/icon-button/icon-button.stories.ts index 08c95c5d64..f63c494f7d 100644 --- a/libs/components/src/icon-button/icon-button.stories.ts +++ b/libs/components/src/icon-button/icon-button.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { BitIconButtonComponent } from "./icon-button.component"; export default { @@ -7,8 +9,11 @@ export default { component: BitIconButtonComponent, args: { bitIconButton: "bwi-plus", - size: "default", - disabled: false, + }, + argTypes: { + buttonType: { + options: ["primary", "secondary", "danger", "unstyled", "contrast", "main", "muted", "light"], + }, }, parameters: { design: { @@ -24,25 +29,9 @@ export const Default: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <div class="tw-space-x-4"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="main" [size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="muted" [size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="primary" [size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="secondary"[size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="danger" [size]="size">Button</button> - <div class="tw-bg-primary-600 tw-p-2 tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="contrast" [size]="size">Button</button> - </div> - <div class="tw-bg-background-alt2 tw-p-2 tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="light" [size]="size">Button</button> - </div> - </div> + <button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button> `, }), - args: { - size: "default", - buttonType: "primary", - }, }; export const Small: Story = { @@ -54,40 +43,35 @@ export const Small: Story = { }; export const Primary: Story = { - render: (args) => ({ - props: args, - template: /*html*/ ` - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button> - `, - }), + ...Default, args: { buttonType: "primary", }, }; export const Secondary: Story = { - ...Primary, + ...Default, args: { buttonType: "secondary", }, }; export const Danger: Story = { - ...Primary, + ...Default, args: { buttonType: "danger", }, }; export const Main: Story = { - ...Primary, + ...Default, args: { buttonType: "main", }, }; export const Muted: Story = { - ...Primary, + ...Default, args: { buttonType: "muted", }, @@ -98,7 +82,8 @@ export const Light: Story = { props: args, template: /*html*/ ` <div class="tw-bg-background-alt2 tw-p-6 tw-w-full tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button> + <!-- <div> used only to provide dark background color --> + <button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button> </div> `, }), @@ -112,7 +97,8 @@ export const Contrast: Story = { props: args, template: /*html*/ ` <div class="tw-bg-primary-600 tw-p-6 tw-w-full tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button> + <!-- <div> used only to provide dark background color --> + <button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button> </div> `, }), diff --git a/libs/components/src/icon/icon.mdx b/libs/components/src/icon/icon.mdx index 6435fc2494..d1809c81cd 100644 --- a/libs/components/src/icon/icon.mdx +++ b/libs/components/src/icon/icon.mdx @@ -4,6 +4,10 @@ import * as stories from "./icon.stories"; <Meta of={stories} /> +```ts +import { IconModule } from "@bitwarden/components"; +``` + # Icon Use Instructions - Icons will generally be attached to the associated Jira task. diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index 52aba55766..ca25e5fef5 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -66,6 +66,14 @@ abstract class LinkDirective { linkType: LinkType = "primary"; } +/** + * Text Links and Buttons can use either the `<a>` or `<button>` tags. Choose which based on the action the button takes: + + * - if navigating to a new page, use a `<a>` + * - if taking an action on the current page, use a `<button>` + + * Text buttons or links are most commonly used in paragraphs of text or in forms to customize actions or show/hide additional form options. + */ @Directive({ selector: "a[bitLink]", standalone: true, diff --git a/libs/components/src/link/link.mdx b/libs/components/src/link/link.mdx index e509ddb991..8fb5f693f1 100644 --- a/libs/components/src/link/link.mdx +++ b/libs/components/src/link/link.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Story, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./link.stories"; @@ -8,18 +8,11 @@ import * as stories from "./link.stories"; import { LinkModule } from "@bitwarden/components"; ``` -# Link / Text button - -Text Links and Buttons can use either the `<a>` or `<button>` tags. Choose which based on the action -the button takes: - -- if navigating to a new page, use a `<a>` -- if taking an action on the current page, use a `<button>` - -Text buttons or links are most commonly used in paragraphs of text or in forms to customize actions -or show/hide additional form options. +<Title>Link / Text button + + ## Variants diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index d07d33ae58..edf2cb14cd 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; import { LinkModule } from "./link.module"; @@ -27,6 +29,14 @@ export default { type Story = StoryObj; export const Default: Story = { + render: (args) => ({ + template: /*html*/ ` + (args)}>Your text here + `, + }), +}; + +export const InteractionStates: Story = { render: () => ({ template: /*html*/ `
diff --git a/libs/components/src/progress/progress.component.ts b/libs/components/src/progress/progress.component.ts index 04e535158b..cc2a6df734 100644 --- a/libs/components/src/progress/progress.component.ts +++ b/libs/components/src/progress/progress.component.ts @@ -1,22 +1,25 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; -type SizeTypes = "small" | "default" | "large"; -type BackgroundTypes = "danger" | "primary" | "success" | "warning"; +type ProgressSizeType = "small" | "default" | "large"; +type BackgroundType = "danger" | "primary" | "success" | "warning"; -const SizeClasses: Record = { +const SizeClasses: Record = { small: ["tw-h-1"], default: ["tw-h-4"], large: ["tw-h-6"], }; -const BackgroundClasses: Record = { +const BackgroundClasses: Record = { danger: ["tw-bg-danger-600"], primary: ["tw-bg-primary-600"], success: ["tw-bg-success-600"], warning: ["tw-bg-warning-600"], }; +/** + * Progress indicators may be used to visually indicate progress or to visually measure some other value, such as a password strength indicator. + */ @Component({ selector: "bit-progress", templateUrl: "./progress.component.html", @@ -25,9 +28,9 @@ const BackgroundClasses: Record = { }) export class ProgressComponent { @Input() barWidth = 0; - @Input() bgColor: BackgroundTypes = "primary"; + @Input() bgColor: BackgroundType = "primary"; @Input() showText = true; - @Input() size: SizeTypes = "default"; + @Input() size: ProgressSizeType = "default"; @Input() text?: string; get displayText() { diff --git a/libs/components/src/progress/progress.mdx b/libs/components/src/progress/progress.mdx index 9a75f8ae1f..def2f23912 100644 --- a/libs/components/src/progress/progress.mdx +++ b/libs/components/src/progress/progress.mdx @@ -1,13 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./progress.stories"; -# Progress +```ts +import { ProgressModule } from "@bitwarden/components"; +``` -Progress indicators may be used to visually indicate progress or to visually measure some other -value, such as a password strength indicator. + +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/progress/progress.stories.ts b/libs/components/src/progress/progress.stories.ts index 1484dab0a2..5c7eb066cd 100644 --- a/libs/components/src/progress/progress.stories.ts +++ b/libs/components/src/progress/progress.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { ProgressComponent } from "./progress.component"; export default { @@ -20,19 +22,34 @@ export default { type Story = StoryObj<ProgressComponent>; +export const Base: Story = { + render: (args) => ({ + props: args, + template: ` + <bit-progress ${formatArgsForCodeSnippet<ProgressComponent>(args)}></bit-progress> + `, + }), + args: { + barWidth: 50, + }, +}; + export const Empty: Story = { + ...Base, args: { barWidth: 0, }, }; export const Full: Story = { + ...Base, args: { barWidth: 100, }, }; export const CustomText: Story = { + ...Base, args: { barWidth: 25, text: "Loading...", diff --git a/libs/components/src/search/search.mdx b/libs/components/src/search/search.mdx index 492fd0dda2..7775225b8c 100644 --- a/libs/components/src/search/search.mdx +++ b/libs/components/src/search/search.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Source, Primary, Controls, Title } from "@storybook/addon-docs"; import * as stories from "./search.stories"; @@ -8,7 +8,7 @@ import * as stories from "./search.stories"; import { SearchModule } from "@bitwarden/components"; ``` -# Search +<Title>Search field diff --git a/libs/components/src/search/search.stories.ts b/libs/components/src/search/search.stories.ts index a6cd714d43..526e1381d7 100644 --- a/libs/components/src/search/search.stories.ts +++ b/libs/components/src/search/search.stories.ts @@ -3,6 +3,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { InputModule } from "../input/input.module"; import { SharedModule } from "../shared"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -27,6 +28,10 @@ export default { ], }), ], + args: { + placeholder: "search", + disabled: false, + }, } as Meta; type Story = StoryObj; @@ -35,7 +40,7 @@ export const Default: Story = { render: (args) => ({ props: args, template: ` - + (args)}> `, }), args: {}, diff --git a/libs/components/src/toast/toast.mdx b/libs/components/src/toast/toast.mdx index d27109b477..6d9d80c6ae 100644 --- a/libs/components/src/toast/toast.mdx +++ b/libs/components/src/toast/toast.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./toast.stories"; @@ -8,12 +8,16 @@ import * as stories from "./toast.stories"; import { ToastService } from "@bitwarden/components"; ``` -# Toast + -Toasts are ephemeral notifications. They most often communicate the result of a user action. Due to -their ephemeral nature, long messages and critical alerts should not utilize toasts. +<Primary /> +<Controls /> -<Canvas of={stories.Default} /> +### Variants + +<Canvas of={stories.Variants} /> + +### Long content <Canvas of={stories.LongContent} /> @@ -38,7 +42,7 @@ The following options are accepted: <Canvas of={stories.Service} /> -## Toast container +### Toast container `bit-toast-container` should be added to the app root of consuming clients to ensure toasts are properly announced to screenreaders. @@ -48,7 +52,7 @@ properly announced to screenreaders. <bit-toast-container></bit-toast-container> ``` -## Accessibility +### Accessibility In addition to the accessibility provided by the `bit-toast-container` component, the toast itself will apply `aria-alert="true"` if the toast is of type `error`. diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts index 0af4974eea..b4a80cd327 100644 --- a/libs/components/src/toast/toast.stories.ts +++ b/libs/components/src/toast/toast.stories.ts @@ -6,6 +6,7 @@ import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/an import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { ButtonModule } from "../button"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -75,11 +76,22 @@ export const Default: Story = { render: (args) => ({ props: args, template: ` - <div class="tw-flex tw-flex-col tw-min-w tw-max-w-[--bit-toast-width]"> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="success"></bit-toast> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="info"></bit-toast> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="warning"></bit-toast> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="error"></bit-toast> + <div class="tw-min-w tw-max-w-[--bit-toast-width]"> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)}></bit-toast> + </div> + `, + }), +}; + +export const Variants: Story = { + render: (args) => ({ + props: args, + template: ` + <div class="tw-flex tw-flex-col tw-min-w tw-max-w-[--bit-toast-width] tw-gap-2"> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="success"></bit-toast> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="info"></bit-toast> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="warning"></bit-toast> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="error"></bit-toast> </div> `, }), @@ -93,8 +105,8 @@ export const LongContent: Story = { args: { title: "Foo", message: [ - "Lorem ipsum dolor sit amet, consectetur adipisci", - "Lorem ipsum dolor sit amet, consectetur adipisci", + "Maecenas commodo posuere quam, vel malesuada nulla accumsan ac.", + "Pellentesque interdum ligula ante, eget bibendum ante lacinia congue.", ], }, }; diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts index 75124ceb4b..06182f094a 100644 --- a/libs/components/src/toast/toastr.component.ts +++ b/libs/components/src/toast/toastr.component.ts @@ -4,6 +4,9 @@ import { Toast as BaseToastrComponent, ToastPackage, ToastrService } from "ngx-t import { ToastComponent } from "./toast.component"; +/** + * Toasts are ephemeral notifications. They most often communicate the result of a user action. Due to their ephemeral nature, long messages and critical alerts should not utilize toasts. + */ @Component({ template: ` <bit-toast diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 90b95ff54b..a60a705318 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -46,6 +46,7 @@ ".storybook/main.ts", ".storybook/manager.js", ".storybook/test-runner.ts", + ".storybook/format-args-for-code-snippet.ts", "apps/browser/src/autofill/content/components/.lit-storybook/main.ts" ], "include": ["apps/**/*", "libs/**/*", "bitwarden_license/**/*", "scripts/**/*"],