1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[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
This commit is contained in:
Bryan Cunningham
2025-05-30 12:38:40 -04:00
committed by GitHub
parent 4e07fd7666
commit 5eb8d7b181
44 changed files with 454 additions and 302 deletions

View File

@@ -0,0 +1,33 @@
import { argsToTemplate, StoryObj } from "@storybook/angular";
type RenderArgType<T> = StoryObj<T>["args"];
export const formatArgsForCodeSnippet = <ComponentType extends Record<string, any>>(
args: RenderArgType<ComponentType>,
) => {
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<ComponentType>,
);
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 })}`;
};

View File

@@ -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,
},

View File

@@ -16,6 +16,12 @@ const SizeClasses: Record<SizeTypes, string[]> = {
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) {

View File

@@ -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";
<Meta of={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.
<Title />
<Description />
<Primary />
<Controls />

View File

@@ -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",
},

View File

@@ -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>
`,
}),

View File

@@ -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;
}
/**

View File

@@ -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).

View File

@@ -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>
`,
}),
};

View File

@@ -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;

View File

@@ -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"`.

View File

@@ -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,
},

View File

@@ -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",

View File

@@ -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 />

View File

@@ -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} />

View File

@@ -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",
},
};

View File

@@ -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>
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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",
},

View File

@@ -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 />

View File

@@ -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",

View File

@@ -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} />

View File

@@ -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) {

View File

@@ -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 />

View File

@@ -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>
`,
}),

View File

@@ -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,

View File

@@ -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:

View File

@@ -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",

View File

@@ -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 />

View File

@@ -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>
`,
}),

View File

@@ -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.

View File

@@ -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,

View File

@@ -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</Title>
<Description />
<Primary />
<Controls />
## Variants

View File

@@ -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<ButtonLinkDirective>;
export const Default: Story = {
render: (args) => ({
template: /*html*/ `
<a bitLink ${formatArgsForCodeSnippet<ButtonLinkDirective>(args)}>Your text here</a>
`,
}),
};
export const InteractionStates: Story = {
render: () => ({
template: /*html*/ `
<div class="tw-flex tw-gap-4 tw-p-2 tw-mb-6">

View File

@@ -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<SizeTypes, string[]> = {
const SizeClasses: Record<ProgressSizeType, string[]> = {
small: ["tw-h-1"],
default: ["tw-h-4"],
large: ["tw-h-6"],
};
const BackgroundClasses: Record<BackgroundTypes, string[]> = {
const BackgroundClasses: Record<BackgroundType, string[]> = {
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<BackgroundTypes, string[]> = {
})
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() {

View File

@@ -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";
<Meta of={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.
<Title />
<Description />
<Primary />
<Controls />

View File

@@ -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...",

View File

@@ -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</Title>
<Primary />
<Controls />

View File

@@ -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<SearchComponent>;
@@ -35,7 +40,7 @@ export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-search [(ngModel)]="searchText" [placeholder]="placeholder" [disabled]="disabled"></bit-search>
<bit-search [(ngModel)]="searchText"${formatArgsForCodeSnippet<SearchComponent>(args)}></bit-search>
`,
}),
args: {},

View File

@@ -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
<Title />
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`.

View File

@@ -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.",
],
},
};

View File

@@ -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

View File

@@ -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/**/*"],