[PM-2276] Upgrade Storybook to v7 (#5258)

This commit is contained in:
Oscar Hinton 2023-05-26 15:58:06 +02:00 committed by GitHub
parent 1638a1d6f5
commit f7b372a0b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 6340 additions and 16409 deletions

View File

@ -24,7 +24,6 @@
./libs/angular/src/validators/notAllowedValueAsync.validator.ts
./libs/angular/src/services/theming/themeBuilder.ts
./libs/angular/src/interfaces/selectOptions.ts
./libs/components/src/stories/Introduction.stories.mdx
./libs/common/src/misc/nodeUtils.ts
./libs/common/src/misc/linkedFieldOption.decorator.ts
./libs/common/src/misc/serviceUtils.ts

View File

@ -31,12 +31,17 @@ jobs:
- name: Install Node dependencies
run: npm ci
# Manual build the storybook to resolve a chromatic/storybook bug related to TurboSnap
- name: Build Storybook
run: npm run build-storybook:ci
- name: Publish to Chromatic
uses: chromaui/action@a89b674adf766dbde41ad9ea2b2b60b91188a0f0
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
storybookBuildDir: ./storybook-static
exitOnceUploaded: true
onlyChanged: true
externals: "[\"libs/components/**/*.scss\", \"libs/components/tailwind.config*.js\"]"

View File

@ -1,33 +0,0 @@
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
module.exports = {
stories: [
"../libs/components/src/**/*.stories.mdx",
"../libs/components/src/**/*.stories.@(js|jsx|ts|tsx)",
"../apps/web/src/**/*.stories.mdx",
"../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)",
"../bitwarden_license/bit-web/src/**/*.stories.mdx",
"../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-a11y",
"storybook-addon-designs",
],
framework: "@storybook/angular",
core: {
builder: "webpack5",
disableTelemetry: true,
},
env: (config) => ({
...config,
FLAGS: JSON.stringify({
secretsManager: true,
}),
}),
webpackFinal: async (config, { configType }) => {
config.resolve.plugins = [new TsconfigPathsPlugin()];
return config;
},
};

53
.storybook/main.ts Normal file
View File

@ -0,0 +1,53 @@
import { StorybookConfig } from "@storybook/angular";
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
import remarkGfm from "remark-gfm";
const config: StorybookConfig = {
stories: [
"../libs/components/src/**/*.mdx",
"../libs/components/src/**/*.stories.@(js|jsx|ts|tsx)",
"../apps/web/src/**/*.mdx",
"../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)",
"../bitwarden_license/bit-web/src/**/*.mdx",
"../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-a11y",
{
name: "@storybook/addon-docs",
options: {
mdxPluginOptions: {
mdxCompileOptions: {
remarkPlugins: [remarkGfm],
},
},
},
},
],
framework: {
name: "@storybook/angular",
options: {},
},
core: {
disableTelemetry: true,
},
env: (config) => ({
...config,
FLAGS: JSON.stringify({
secretsManager: true,
}),
}),
webpackFinal: async (config, { configType }) => {
if (config.resolve) {
config.resolve.plugins = [new TsconfigPathsPlugin()] as any;
}
return config;
},
docs: {
autodocs: true,
},
};
export default config;

View File

@ -1,38 +0,0 @@
import { setCompodocJson } from "@storybook/addon-docs/angular";
import { componentWrapperDecorator, addDecorator } from "@storybook/angular";
import docJson from "../documentation.json";
setCompodocJson(docJson);
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
options: {
storySort: {
order: ["Documentation", ["Introduction", "Colors", "Icons"], "Component Library"],
},
},
docs: { inlineStories: true },
};
// ng-template is used to scope any template reference variables and isolate the previews
const decorator = componentWrapperDecorator(
(story) => `
<ng-template #lightPreview>
<div class="theme_light tw-px-5 tw-py-10 tw-border-2 tw-border-solid tw-border-secondary-300 tw-bg-[#ffffff]">${story}</div>
</ng-template>
<ng-template #darkPreview>
<div class="theme_dark tw-mt-5 tw-px-5 tw-py-10 tw-bg-[#1f242e]">${story}</div>
</ng-template>
<ng-container *ngTemplateOutlet="lightPreview"></ng-container>
<ng-container *ngTemplateOutlet="darkPreview"></ng-container>
`
);
addDecorator(decorator);

39
.storybook/preview.tsx Normal file
View File

@ -0,0 +1,39 @@
import { setCompodocJson } from "@storybook/addon-docs/angular";
import { componentWrapperDecorator } from "@storybook/angular";
import type { Preview } from "@storybook/angular";
import docJson from "../documentation.json";
setCompodocJson(docJson);
const decorator = componentWrapperDecorator(
(story) => `
<ng-template #lightPreview>
<div class="theme_light tw-px-5 tw-py-10 tw-border-2 tw-border-solid tw-border-secondary-300 tw-bg-[#ffffff]">${story}</div>
</ng-template>
<ng-template #darkPreview>
<div class="theme_dark tw-mt-5 tw-px-5 tw-py-10 tw-bg-[#1f242e]">${story}</div>
</ng-template>
<ng-container *ngTemplateOutlet="lightPreview"></ng-container>
<ng-container *ngTemplateOutlet="darkPreview"></ng-container>`
);
const preview: Preview = {
decorators: [decorator],
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
options: {
storySort: {
order: ["Documentation", ["Introduction", "Colors", "Icons"], "Component Library"],
},
},
docs: { source: { type: "dynamic", excludeDecorators: true } },
},
};
export default preview;

View File

@ -7,6 +7,7 @@
"exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"],
"files": [
"./typings.d.ts",
"./preview.tsx",
"../libs/components/src/main.ts",
"../libs/components/src/polyfills.ts"
]

View File

@ -135,20 +135,25 @@
}
},
"defaultConfiguration": "development"
}
}
},
"storybook": {
"projectType": "application",
"root": "libs/components",
"sourceRoot": "libs/components/src",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
},
"storybook": {
"builder": "@storybook/angular:start-storybook",
"options": {
"tsConfig": ".storybook/tsconfig.json",
"styles": ["libs/components/src/styles.scss", "libs/components/src/styles.css"],
"scripts": []
"configDir": ".storybook",
"browserTarget": "components:build",
"compodoc": true,
"compodocArgs": ["-p", "./tsconfig.json", "-e", "json", "-d", "."],
"port": 6006
}
},
"build-storybook": {
"builder": "@storybook/angular:build-storybook",
"options": {
"configDir": ".storybook",
"browserTarget": "components:build",
"compodoc": true,
"compodocArgs": ["-e", "json", "-d", "."],
"outputDir": "storybook-static"
}
}
}

View File

@ -1,6 +1,7 @@
import { importProvidersFrom } from "@angular/core";
import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { action } from "@storybook/addon-actions";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { applicationConfig, Meta, moduleMetadata, Story } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
@ -39,12 +40,14 @@ export default {
FormsModule,
TabsModule,
TableModule,
PreloadedEnglishI18nModule,
JslibModule,
IconButtonModule,
],
providers: [],
}),
applicationConfig({
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
}),
],
parameters: {},
argTypes: {

View File

@ -1,6 +1,6 @@
import { Component, Directive, Input } from "@angular/core";
import { Component, Directive, Input, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -49,36 +49,7 @@ export default {
StoryLayoutComponent,
StoryContentComponent,
],
imports: [
JslibModule,
MenuModule,
IconButtonModule,
LinkModule,
RouterModule.forRoot(
[
{
path: "",
component: StoryLayoutComponent,
children: [
{
path: "",
redirectTo: "vault",
pathMatch: "full",
},
{
path: "sm/:organizationId",
component: StoryContentComponent,
},
{
path: "vault",
component: StoryContentComponent,
},
],
},
],
{ useHash: true }
),
],
imports: [JslibModule, MenuModule, IconButtonModule, LinkModule, RouterModule],
providers: [
{ provide: OrganizationService, useClass: MockOrganizationService },
MockOrganizationService,
@ -93,6 +64,36 @@ export default {
},
],
}),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot(
[
{
path: "",
component: StoryLayoutComponent,
children: [
{
path: "",
redirectTo: "vault",
pathMatch: "full",
},
{
path: "sm/:organizationId",
component: StoryContentComponent,
},
{
path: "vault",
component: StoryContentComponent,
},
],
},
],
{ useHash: true }
)
),
],
}),
],
} as Meta;

View File

@ -1,5 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterTestingModule } from "@angular/router/testing";
import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
@ -15,15 +16,12 @@ export default {
component: ReportCardComponent,
decorators: [
moduleMetadata({
imports: [
JslibModule,
BadgeModule,
IconModule,
RouterTestingModule,
PreloadedEnglishI18nModule,
],
imports: [JslibModule, BadgeModule, IconModule, RouterTestingModule],
declarations: [PremiumBadgeComponent],
}),
applicationConfig({
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
}),
],
args: {
title: "Exposed Passwords",

View File

@ -1,5 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterTestingModule } from "@angular/router/testing";
import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
@ -17,15 +18,12 @@ export default {
component: ReportListComponent,
decorators: [
moduleMetadata({
imports: [
JslibModule,
BadgeModule,
RouterTestingModule,
PreloadedEnglishI18nModule,
IconModule,
],
imports: [JslibModule, BadgeModule, RouterTestingModule, IconModule],
declarations: [PremiumBadgeComponent, ReportCardComponent],
}),
applicationConfig({
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
}),
],
args: {
reports: Object.values(reports).map((report) => ({

View File

@ -1,6 +1,6 @@
import { Component } from "@angular/core";
import { importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { applicationConfig, Meta, moduleMetadata, Story } from "@storybook/angular";
import { BehaviorSubject } from "rxjs";
import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
@ -28,11 +28,6 @@ import { Unassigned } from "../../individual-vault/vault-filter/shared/models/ro
import { VaultItemsComponent } from "./vault-items.component";
import { VaultItemsModule } from "./vault-items.module";
@Component({
template: "",
})
class EmptyComponent {}
const organizations = [...new Array(3).keys()].map(createOrganization);
const groups = [...Array(3).keys()].map(createGroupView);
const collections = [...Array(5).keys()].map(createCollectionView);
@ -46,11 +41,7 @@ export default {
component: VaultItemsComponent,
decorators: [
moduleMetadata({
imports: [
VaultItemsModule,
PreloadedEnglishI18nModule,
RouterModule.forRoot([{ path: "**", component: EmptyComponent }], { useHash: true }),
],
imports: [VaultItemsModule, RouterModule],
providers: [
{
provide: EnvironmentService,
@ -103,6 +94,12 @@ export default {
},
],
}),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
],
args: {
disabled: false,

View File

@ -1,6 +1,6 @@
import { Component } from "@angular/core";
import { Component, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { BehaviorSubject } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@ -28,43 +28,46 @@ export default {
component: LayoutComponent,
decorators: [
moduleMetadata({
imports: [
RouterModule.forRoot(
[
{
path: "",
component: LayoutComponent,
children: [
{
path: "",
redirectTo: "secrets",
pathMatch: "full",
},
{
path: "secrets",
component: StoryContentComponent,
data: {
title: "secrets",
searchTitle: "searchSecrets",
},
},
{
outlet: "sidebar",
path: "",
component: NavigationComponent,
},
],
},
],
{ useHash: true }
),
LayoutModule,
IconModule,
PreloadedEnglishI18nModule,
],
imports: [RouterModule, LayoutModule, IconModule],
declarations: [StoryContentComponent],
providers: [{ provide: OrganizationService, useClass: MockOrganizationService }],
}),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot(
[
{
path: "",
component: LayoutComponent,
children: [
{
path: "",
redirectTo: "secrets",
pathMatch: "full",
},
{
path: "secrets",
component: StoryContentComponent,
data: {
title: "secrets",
searchTitle: "searchSecrets",
},
},
{
outlet: "sidebar",
path: "",
component: NavigationComponent,
},
],
},
],
{ useHash: true }
)
),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
],
} as Meta;

View File

@ -1,5 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { delay, of, startWith } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -14,24 +15,15 @@ export default {
component: OnboardingComponent,
decorators: [
moduleMetadata({
imports: [
JslibModule,
RouterModule.forRoot(
[
{
path: "",
component: OnboardingComponent,
},
],
{ useHash: true }
),
LinkModule,
IconModule,
ProgressModule,
PreloadedEnglishI18nModule,
],
imports: [JslibModule, RouterModule, LinkModule, IconModule, ProgressModule],
declarations: [OnboardingTaskComponent],
}),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
],
} as Meta;

View File

@ -1,6 +1,12 @@
import { Component, Injectable } from "@angular/core";
import { Component, Injectable, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, Story, moduleMetadata, componentWrapperDecorator } from "@storybook/angular";
import {
Meta,
Story,
moduleMetadata,
componentWrapperDecorator,
applicationConfig,
} from "@storybook/angular";
import { BehaviorSubject, combineLatest, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -16,8 +22,8 @@ import {
NavigationModule,
TabsModule,
TypographyModule,
InputModule,
} from "@bitwarden/components";
import { InputModule } from "@bitwarden/components/src/input/input.module";
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/app/tests/preloaded-english-i18n.module";
import { HeaderComponent } from "./header.component";
@ -68,15 +74,7 @@ export default {
moduleMetadata({
imports: [
JslibModule,
RouterModule.forRoot(
[
{
path: "",
component: HeaderComponent,
},
],
{ useHash: true }
),
RouterModule,
AvatarModule,
BreadcrumbsModule,
ButtonModule,
@ -87,7 +85,6 @@ export default {
TabsModule,
TypographyModule,
NavigationModule,
PreloadedEnglishI18nModule,
],
declarations: [HeaderComponent, MockProductSwitcher, MockDynamicAvatar],
providers: [
@ -100,6 +97,12 @@ export default {
},
],
}),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
],
} as Meta;

View File

@ -1,7 +1,7 @@
import { Component } from "@angular/core";
import { FormsModule, ReactiveFormsModule, Validators, FormBuilder } from "@angular/forms";
import { action } from "@storybook/addon-actions";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { delay, of } from "rxjs";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
@ -145,16 +145,18 @@ export default {
],
} as Meta;
const PromiseTemplate: Story<PromiseExampleComponent> = (args: PromiseExampleComponent) => ({
props: args,
template: `<app-promise-example></app-promise-example>`,
});
type Story = StoryObj<PromiseExampleComponent>;
export const UsingPromise = PromiseTemplate.bind({});
export const UsingPromise: Story = {
render: (args) => ({
props: args,
template: `<app-promise-example></app-promise-example>`,
}),
};
const ObservableTemplate: Story<PromiseExampleComponent> = (args: PromiseExampleComponent) => ({
props: args,
template: `<app-observable-example></app-observable-example>`,
});
export const UsingObservable = ObservableTemplate.bind({});
export const UsingObservable: Story = {
render: (args) => ({
props: args,
template: `<app-observable-example></app-observable-example>`,
}),
};

View File

@ -1,6 +1,6 @@
import { Component } from "@angular/core";
import { action } from "@storybook/addon-actions";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { delay, of } from "rxjs";
import { LogService } from "@bitwarden/common/abstractions/log.service";
@ -80,25 +80,24 @@ export default {
],
} as Meta;
const PromiseTemplate: Story<PromiseExampleComponent> = (args: PromiseExampleComponent) => ({
props: args,
template: `<app-promise-example></app-promise-example>`,
});
type PromiseStory = StoryObj<PromiseExampleComponent>;
type ObservableStory = StoryObj<ObservableExampleComponent>;
export const UsingPromise = PromiseTemplate.bind({});
export const UsingPromise: PromiseStory = {
render: (args) => ({
props: args,
template: `<app-promise-example></app-promise-example>`,
}),
};
const ObservableTemplate: Story<ObservableExampleComponent> = (
args: ObservableExampleComponent
) => ({
template: `<app-observable-example></app-observable-example>`,
});
export const UsingObservable: ObservableStory = {
render: (args) => ({
template: `<app-observable-example></app-observable-example>`,
}),
};
export const UsingObservable = ObservableTemplate.bind({});
const RejectedPromiseTemplate: Story<ObservableExampleComponent> = (
args: ObservableExampleComponent
) => ({
template: `<app-rejected-promise-example></app-rejected-promise-example>`,
});
export const RejectedPromise = RejectedPromiseTemplate.bind({});
export const RejectedPromise: ObservableStory = {
render: (args) => ({
template: `<app-rejected-promise-example></app-rejected-promise-example>`,
}),
};

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, StoryObj } from "@storybook/angular";
import { AvatarComponent } from "./avatar.component";
@ -18,41 +18,46 @@ export default {
},
} as Meta;
const Template: Story<AvatarComponent> = (args: AvatarComponent) => ({
props: args,
});
type Story = StoryObj<AvatarComponent>;
export const Default = Template.bind({});
Default.args = {
color: "#175ddc",
export const Default: Story = {
args: {
color: "#175ddc",
},
};
export const Large = Template.bind({});
Large.args = {
size: "large",
export const Large: Story = {
args: {
size: "large",
},
};
export const Small = Template.bind({});
Small.args = {
size: "small",
export const Small: Story = {
args: {
size: "small",
},
};
export const LightBackground = Template.bind({});
LightBackground.args = {
color: "#d2ffcf",
export const LightBackground: Story = {
args: {
color: "#d2ffcf",
},
};
export const Border = Template.bind({});
Border.args = {
border: true,
export const Border: Story = {
args: {
border: true,
},
};
export const ColorByID = Template.bind({});
ColorByID.args = {
id: 236478,
export const ColorByID: Story = {
args: {
id: "236478",
},
};
export const ColorByText = Template.bind({});
ColorByText.args = {
text: "Jason Doe",
export const ColorByText: Story = {
args: {
text: "Jason Doe",
},
};

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -38,16 +38,19 @@ export default {
},
} as Meta;
const ListTemplate: Story<BadgeListComponent> = (args: BadgeListComponent) => ({
props: args,
template: `
<bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>
`,
});
type Story = StoryObj<BadgeListComponent>;
export const Default = ListTemplate.bind({});
Default.args = {
badgeType: "info",
maxItems: 3,
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"],
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>
`,
}),
args: {
badgeType: "info",
maxItems: 3,
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"],
},
};

View File

@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { BadgeDirective } from "./badge.directive";
@ -21,43 +21,54 @@ export default {
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16956",
},
},
} as Meta;
} as Meta<BadgeDirective>;
const Template: Story<BadgeDirective> = (args: BadgeDirective) => ({
props: args,
template: `
<span class="tw-text-main">Span </span><span bitBadge [badgeType]="badgeType">Badge</span>
<br><br>
<span class="tw-text-main">Link </span><a href="#" bitBadge [badgeType]="badgeType">Badge</a>
<br><br>
<span class="tw-text-main">Button </span><button bitBadge [badgeType]="badgeType">Badge</button>
`,
});
type Story = StoryObj<BadgeDirective>;
export const Primary = Template.bind({});
Primary.args = {};
export const Secondary = Template.bind({});
Secondary.args = {
badgeType: "secondary",
export const Primary: Story = {
render: (args) => ({
props: args,
template: `
<span class="tw-text-main">Span </span><span bitBadge [badgeType]="badgeType">Badge</span>
<br><br>
<span class="tw-text-main">Link </span><a href="#" bitBadge [badgeType]="badgeType">Badge</a>
<br><br>
<span class="tw-text-main">Button </span><button bitBadge [badgeType]="badgeType">Badge</button>
`,
}),
};
export const Success = Template.bind({});
Success.args = {
badgeType: "success",
export const Secondary: Story = {
...Primary,
args: {
badgeType: "secondary",
},
};
export const Danger = Template.bind({});
Danger.args = {
badgeType: "danger",
export const Success: Story = {
...Primary,
args: {
badgeType: "success",
},
};
export const Warning = Template.bind({});
Warning.args = {
badgeType: "warning",
export const Danger: Story = {
...Primary,
args: {
badgeType: "danger",
},
};
export const Info = Template.bind({});
Info.args = {
badgeType: "info",
export const Warning: Story = {
...Primary,
args: {
badgeType: "warning",
},
};
export const Info: Story = {
...Primary,
args: {
badgeType: "info",
},
};

View File

@ -1,6 +1,8 @@
import { Meta, Story } from "@storybook/addon-docs";
import { Meta, Story, Controls, Canvas, Primary } from "@storybook/addon-docs";
<Meta title="Documentation/Banner" />
import * as stories from "./banner.stories";
<Meta of={stories} />
# Banner
@ -15,6 +17,10 @@ persist across all pages a user navigates to.
- Avoid stacking multiple banners.
- Banners support a button link (text button).
<Primary />
<Controls />
## Types
Icons should remain consistent across these types. Do not change the icon without consulting
@ -24,25 +30,25 @@ Use the following guidelines to help choose the correct type of banner.
### Premium
<Story id="component-library-banner--premium" />
<Story of={stories.Premium} />
Used primarily to encourage user to upgrade to premium.
### Info
<Story id="component-library-banner--info" />
<Story of={stories.Info} />
Used to communicate release notes, server maintenance or other informative event.
### Warning
<Story id="component-library-banner--warning" />
<Story of={stories.Warning} />
Used to alert the user of outdated info or versions.
### Danger
<Story id="component-library-banner--danger" />
<Story of={stories.Danger} />
Rarely used, but may be used to alert users over critical messages or very outdated versions.

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -39,34 +39,46 @@ export default {
argTypes: {
onClose: { action: "onClose" },
},
} as Meta;
} as Meta<BannerComponent>;
const Template: Story<BannerComponent> = (args: BannerComponent) => ({
props: args,
template: `
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)">
type Story = StoryObj<BannerComponent>;
export const Premium: Story = {
args: {
bannerType: "premium",
},
render: (args: BannerComponent) => ({
props: args,
template: `
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)">
Content Really Long Text Lorem Ipsum Ipsum Ipsum
<button bitLink linkType="contrast">Button</button>
</bit-banner>
`,
});
</bit-banner>
`,
}),
};
export const Premium = Template.bind({});
Premium.args = {
bannerType: "premium",
};
export const Info = Template.bind({});
Info.args = {
bannerType: "info",
export const Info: Story = {
...Premium,
args: {
bannerType: "info",
},
};
export const Warning = Template.bind({});
Warning.args = {
bannerType: "warning",
export const Warning: Story = {
...Premium,
args: {
bannerType: "warning",
},
};
export const Danger = Template.bind({});
Danger.args = {
bannerType: "danger",
export const Danger: Story = {
...Premium,
args: {
bannerType: "danger",
},
};

View File

@ -1,6 +1,6 @@
import { Component } from "@angular/core";
import { Component, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
import { IconButtonModule } from "../icon-button";
import { LinkModule } from "../link";
@ -26,16 +26,19 @@ export default {
decorators: [
moduleMetadata({
declarations: [BreadcrumbComponent],
imports: [
LinkModule,
MenuModule,
IconButtonModule,
RouterModule.forRoot([{ path: "**", component: EmptyComponent }], { useHash: true }),
imports: [LinkModule, MenuModule, IconButtonModule, RouterModule],
}),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot([{ path: "**", component: EmptyComponent }], { useHash: true })
),
],
}),
],
args: {
items: [],
show: 3,
},
argTypes: {
breadcrumbs: {
@ -45,47 +48,54 @@ export default {
},
} as Meta;
const Template: Story<BreadcrumbsComponent> = (args: BreadcrumbsComponent) => ({
props: args,
template: `
<h3 class="tw-text-main">Router links</h3>
<p>
<bit-breadcrumbs [show]="show">
<bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" [route]="[item.route]">{{item.name}}</bit-breadcrumb>
</bit-breadcrumbs>
</p>
type Story = StoryObj<BreadcrumbsComponent & { items: Breadcrumb[] }>;
<h3 class="tw-text-main">Click emit</h3>
<p>
<bit-breadcrumbs [show]="show">
<bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" (click)="click($event)">{{item.name}}</bit-breadcrumb>
</bit-breadcrumbs>
</p>
`,
});
export const TopLevel: Story = {
render: (args) => ({
props: args,
template: `
<h3 class="tw-text-main">Router links</h3>
<p>
<bit-breadcrumbs [show]="show">
<bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" [route]="[item.route]">{{item.name}}</bit-breadcrumb>
</bit-breadcrumbs>
</p>
<h3 class="tw-text-main">Click emit</h3>
<p>
<bit-breadcrumbs [show]="show">
<bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" (click)="click($event)">{{item.name}}</bit-breadcrumb>
</bit-breadcrumbs>
</p>
`,
}),
export const TopLevel = Template.bind({});
TopLevel.args = {
items: [{ icon: "bwi-star", name: "Top Level" }] as Breadcrumb[],
args: {
items: [{ icon: "bwi-star", name: "Top Level" }] as Breadcrumb[],
},
};
export const SecondLevel = Template.bind({});
SecondLevel.args = {
items: [
{ name: "Acme Vault", route: "/" },
{ icon: "bwi-collection", name: "Collection", route: "collection" },
] as Breadcrumb[],
export const SecondLevel: Story = {
...TopLevel,
args: {
items: [
{ name: "Acme Vault", route: "/" },
{ icon: "bwi-collection", name: "Collection", route: "collection" },
] as Breadcrumb[],
},
};
export const Overflow = Template.bind({});
Overflow.args = {
items: [
{ name: "Acme Vault", route: "" },
{ icon: "bwi-collection", name: "Collection", route: "collection" },
{ icon: "bwi-collection", name: "Middle-Collection 1", route: "middle-collection-1" },
{ icon: "bwi-collection", name: "Middle-Collection 2", route: "middle-collection-2" },
{ icon: "bwi-collection", name: "Middle-Collection 3", route: "middle-collection-3" },
{ icon: "bwi-collection", name: "Middle-Collection 4", route: "middle-collection-4" },
{ icon: "bwi-collection", name: "End Collection", route: "end-collection" },
] as Breadcrumb[],
export const Overflow: Story = {
...TopLevel,
args: {
items: [
{ name: "Acme Vault", route: "" },
{ icon: "bwi-collection", name: "Collection", route: "collection" },
{ icon: "bwi-collection", name: "Middle-Collection 1", route: "middle-collection-1" },
{ icon: "bwi-collection", name: "Middle-Collection 2", route: "middle-collection-2" },
{ icon: "bwi-collection", name: "Middle-Collection 3", route: "middle-collection-3" },
{ icon: "bwi-collection", name: "Middle-Collection 4", route: "middle-collection-4" },
{ icon: "bwi-collection", name: "End Collection", route: "end-collection" },
] as Breadcrumb[],
},
};

View File

@ -1,12 +1,18 @@
import { Meta, Story } from "@storybook/addon-docs";
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
<Meta title="Documentation/Button" />
import * as stories from "./button.stories";
<Meta of={stories} />
# Button
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.
<Primary />
<Controls />
## Guidelines
### Choosing the `<a>` or `<button>`
@ -50,7 +56,7 @@ Both submit and async action buttons use a loading button state while an action
button is preforming 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).
<Story id="component-library-button--loading" />
<Story of={stories.Loading} />
## Styles
@ -58,14 +64,14 @@ There are 3 main styles for the button: Primary, Secondary, and Danger.
### Primary
<Story id="component-library-button--primary" />
<Story of={stories.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
<Story id="component-library-button--secondary" />
<Story 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
@ -73,7 +79,7 @@ other; however, generally there should only be 1 or 2 calls to action per page.
### Danger
<Story id="component-library-button--danger" />
<Story of={stories.Danger} />
Use the danger styling only in settings when the user may preform a permanent action.
@ -82,11 +88,11 @@ Use the danger styling only in settings when the user may preform a permanent ac
Both the disabled and loading states use the default states color with a 60% opacity or
`tw-opacity-60`.
<Story id="component-library-button--disabled" />
<Story of={stories.Disabled} />
## Block
Typically button widths expand with their text. In some causes though buttons may need to be block
where the width is fixed and the text wraps to 2 lines if exceeding the buttons width.
<Story id="component-library-button--block" />
<Story of={stories.Block} />

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, StoryObj } from "@storybook/angular";
import { ButtonComponent } from "./button.component";
@ -16,88 +16,96 @@ export default {
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=5115%3A26950",
},
},
} as Meta;
} as Meta<ButtonComponent>;
const Template: Story<ButtonComponent> = (args: ButtonComponent) => ({
props: args,
template: `
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block">Button</button>
<a bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">Link</a>
`,
});
type Story = StoryObj<ButtonComponent>;
export const Primary = Template.bind({});
Primary.args = {
buttonType: "primary",
export const Primary: Story = {
render: (args) => ({
props: args,
template: `
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block">Button</button>
<a bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">Link</a>
`,
}),
args: {
buttonType: "primary",
},
};
export const Secondary = Template.bind({});
Secondary.args = {
buttonType: "secondary",
export const Secondary: Story = {
...Primary,
args: {
buttonType: "secondary",
},
};
export const Danger = Template.bind({});
Danger.args = {
buttonType: "danger",
export const Danger: Story = {
...Primary,
args: {
buttonType: "danger",
},
};
const AllStylesTemplate: Story = (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>
`,
});
export const Loading = AllStylesTemplate.bind({});
Loading.args = {
disabled: false,
loading: true,
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>
`,
}),
args: {
disabled: false,
loading: true,
},
};
export const Disabled = AllStylesTemplate.bind({});
Disabled.args = {
disabled: true,
loading: false,
export const Disabled: Story = {
...Loading,
args: {
disabled: true,
loading: false,
},
};
const DisabledWithAttributeTemplate: Story = (args) => ({
props: args,
template: `
<ng-container *ngIf="disabled">
<button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
<button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
</ng-container>
<ng-container *ngIf="!disabled">
<button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
<button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
</ng-container>
`,
});
export const DisabledWithAttribute = DisabledWithAttributeTemplate.bind({});
DisabledWithAttribute.args = {
disabled: true,
loading: false,
export const DisabledWithAttribute: Story = {
render: (args) => ({
props: args,
template: `
<ng-container *ngIf="disabled">
<button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
<button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
</ng-container>
<ng-container *ngIf="!disabled">
<button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
<button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
</ng-container>
`,
}),
args: {
disabled: true,
loading: false,
},
};
const BlockTemplate: Story<ButtonComponent> = (args: ButtonComponent) => ({
props: args,
template: `
<span class="tw-flex">
<button bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button>
<a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">[block]="true" Link</a>
<button bitButton [buttonType]="buttonType" block class="tw-ml-2">block Button</button>
<a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a>
</span>
`,
});
export const Block = BlockTemplate.bind({});
Block.args = {
block: true,
export const Block: Story = {
render: (args: ButtonComponent) => ({
props: args,
template: `
<span class="tw-flex">
<button bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button>
<a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">[block]="true" Link</a>
<button bitButton [buttonType]="buttonType" block class="tw-ml-2">block Button</button>
<a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a>
</span>
`,
}),
args: {
block: true,
},
};

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -35,31 +35,39 @@ export default {
},
} as Meta;
const Template: Story<CalloutComponent> = (args: CalloutComponent) => ({
props: args,
template: `
<bit-callout [type]="type" [title]="title">Content</bit-callout>
`,
});
type Story = StoryObj<CalloutComponent>;
export const Success = Template.bind({});
Success.args = {
type: "success",
title: "Success",
export const Success: Story = {
render: (args) => ({
props: args,
template: `
<bit-callout [type]="type" [title]="title">Content</bit-callout>
`,
}),
args: {
type: "success",
title: "Success",
},
};
export const Info = Template.bind({});
Info.args = {
type: "info",
title: "Info",
export const Info: Story = {
...Success,
args: {
type: "info",
title: "Info",
},
};
export const Warning = Template.bind({});
Warning.args = {
type: "warning",
export const Warning: Story = {
...Success,
args: {
type: "warning",
},
};
export const Danger = Template.bind({});
Danger.args = {
type: "danger",
export const Danger: Story = {
...Success,
args: {
type: "danger",
},
};

View File

@ -1,6 +1,6 @@
import { Component, Input } from "@angular/core";
import { FormsModule, ReactiveFormsModule, FormBuilder, Validators } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/src/abstractions/i18n.service";
@ -69,43 +69,44 @@ export default {
},
} as Meta;
const DefaultTemplate: Story<ExampleComponent> = (args: ExampleComponent) => ({
props: args,
template: `<app-example [checked]="checked" [disabled]="disabled"></app-example>`,
});
type Story = StoryObj<ExampleComponent>;
export const Default = DefaultTemplate.bind({});
Default.parameters = {
docs: {
source: {
code: template,
export const Default: Story = {
render: (args) => ({
props: args,
template: `<app-example [checked]="checked" [disabled]="disabled"></app-example>`,
}),
parameters: {
docs: {
source: {
code: template,
},
},
},
};
Default.args = {
checked: false,
disabled: false,
args: {
checked: false,
disabled: false,
},
};
const CustomTemplate: Story = (args) => ({
props: args,
template: `
<div class="tw-flex tw-flex-col tw-w-32">
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
A-Z
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
a-z
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
0-9
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
</div>
`,
});
CustomTemplate.args = {};
export const Custom = CustomTemplate.bind({});
export const Custom: Story = {
render: (args) => ({
props: args,
template: `
<div class="tw-flex tw-flex-col tw-w-32">
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
A-Z
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
a-z
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
0-9
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
</div>
`,
}),
};

View File

@ -23,7 +23,7 @@ enum CharacterType {
preserveWhitespaces: false,
})
export class ColorPasswordComponent {
@Input() private password: string = null;
@Input() password: string = null;
@Input() showCount = false;
characterStyles: Record<CharacterType, string[]> = {

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, StoryObj } from "@storybook/angular";
import { ColorPasswordComponent } from "./color-password.component";
@ -19,34 +19,40 @@ export default {
},
} as Meta;
const Template: Story<ColorPasswordComponent> = (args: ColorPasswordComponent) => ({
props: args,
template: `
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
`,
});
type Story = StoryObj<ColorPasswordComponent>;
const WrappedTemplate: Story<ColorPasswordComponent> = (args: ColorPasswordComponent) => ({
props: args,
template: `
<div class="tw-max-w-32">
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
</div>
`,
});
export const ColorPassword = Template.bind({});
export const WrappedColorPassword = WrappedTemplate.bind({});
export const ColorPasswordCount = Template.bind({});
ColorPasswordCount.args = {
password: examplePassword,
showCount: true,
export const ColorPassword: Story = {
render: (args) => ({
props: args,
template: `
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
`,
}),
};
export const WrappedColorPasswordCount = WrappedTemplate.bind({});
WrappedColorPasswordCount.args = {
password: examplePassword,
showCount: true,
export const WrappedColorPassword: Story = {
render: (args) => ({
props: args,
template: `
<div class="tw-max-w-32">
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
</div>
`,
}),
};
export const ColorPasswordCount: Story = {
...ColorPassword,
args: {
password: examplePassword,
showCount: true,
},
};
export const WrappedColorPasswordCount: Story = {
...WrappedColorPassword,
args: {
password: examplePassword,
showCount: true,
},
};

View File

@ -1,6 +1,6 @@
import { DIALOG_DATA, DialogModule, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -90,8 +90,6 @@ export default {
},
} as Meta;
const Template: Story<StoryDialogComponent> = (args: StoryDialogComponent) => ({
props: args,
});
type Story = StoryObj<StoryDialogComponent>;
export const Default = Template.bind({});
export const Default: Story = {};

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -50,110 +50,118 @@ export default {
},
} as Meta;
const Template: Story<DialogComponent> = (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>{{title}}</span>
<ng-container bitDialogContent>Dialog body text goes here.</ng-container>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
<button
[disabled]="loading"
class="tw-ml-auto"
bitIconButton="bwi-trash"
buttonType="danger"
size="default"
title="Delete"
aria-label="Delete"></button>
</ng-container>
</bit-dialog>
`,
});
type Story = StoryObj<DialogComponent & { title: string }>;
export const Default = Template.bind({});
Default.args = {
dialogSize: "default",
title: "Default",
};
export const Small = Template.bind({});
Small.args = {
dialogSize: "small",
title: "Small",
};
export const LongTitle = Template.bind({});
LongTitle.args = {
dialogSize: "small",
title: "Long_Title_That_Should_Be_Truncated",
};
export const Large = Template.bind({});
Large.args = {
dialogSize: "large",
title: "Large",
};
export const Loading = Template.bind({});
Loading.args = {
dialogSize: "large",
loading: true,
title: "Loading",
};
const TemplateScrolling: Story<DialogComponent> = (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>Scrolling Example</span>
<span bitDialogContent>
Dialog body text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
export const Default: Story = {
render: (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>{{title}}</span>
<ng-container bitDialogContent>Dialog body text goes here.</ng-container>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
<button
[disabled]="loading"
class="tw-ml-auto"
bitIconButton="bwi-trash"
buttonType="danger"
size="default"
title="Delete"
aria-label="Delete"></button>
</ng-container>
end of sequence!
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
</ng-container>
</bit-dialog>
`,
});
export const ScrollingContent = TemplateScrolling.bind({});
ScrollingContent.args = {
dialogSize: "small",
</bit-dialog>
`,
}),
args: {
dialogSize: "default",
title: "Default",
},
};
const TemplateTabbed: Story<DialogComponent> = (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
<span bitDialogTitle>Tab Content Example</span>
<span bitDialogContent>
<bit-tab-group>
<bit-tab label="First Tab">First Tab Content</bit-tab>
<bit-tab label="Second Tab">Second Tab Content</bit-tab>
<bit-tab label="Third Tab">Third Tab Content</bit-tab>
</bit-tab-group>
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
</ng-container>
</bit-dialog>
`,
});
export const TabContent = TemplateTabbed.bind({});
TabContent.args = {
dialogSize: "large",
disablePadding: true,
export const Small: Story = {
...Default,
args: {
dialogSize: "small",
title: "Small",
},
};
TabContent.story = {
export const LongTitle: Story = {
...Default,
args: {
dialogSize: "small",
title: "Long_Title_That_Should_Be_Truncated",
},
};
export const Large: Story = {
...Default,
args: {
dialogSize: "large",
title: "Large",
},
};
export const Loading: Story = {
...Default,
args: {
dialogSize: "large",
loading: true,
title: "Loading",
},
};
export const ScrollingContent: Story = {
render: (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>Scrolling Example</span>
<span bitDialogContent>
Dialog body text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
</ng-container>
</bit-dialog>
`,
}),
args: {
dialogSize: "small",
},
};
export const TabContent: Story = {
render: (args) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
<span bitDialogTitle>Tab Content Example</span>
<span bitDialogContent>
<bit-tab-group>
<bit-tab label="First Tab">First Tab Content</bit-tab>
<bit-tab label="Second Tab">Second Tab Content</bit-tab>
<bit-tab label="Third Tab">Third Tab Content</bit-tab>
</bit-tab-group>
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
</ng-container>
</bit-dialog>
`,
}),
args: {
dialogSize: "large",
disablePadding: true,
},
parameters: {
docs: {
storyDescription: `An example of using the \`bitTabGroup\` component within the Dialog. The content padding should be

View File

@ -1,6 +1,6 @@
import { DialogModule } from "@angular/cdk/dialog";
import { Component } from "@angular/core";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { SimpleDialogType, SimpleDialogOptions } from "@bitwarden/angular/services/dialog";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -239,8 +239,6 @@ export default {
},
} as Meta;
const Template: Story<StoryDialogComponent> = (args: StoryDialogComponent) => ({
props: args,
});
type Story = StoryObj<StoryDialogComponent>;
export const Default = Template.bind({});
export const Default: Story = {};

View File

@ -1,6 +1,6 @@
import { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -90,8 +90,6 @@ export default {
},
} as Meta;
const Template: Story<StoryDialogComponent> = (args: StoryDialogComponent) => ({
props: args,
});
type Story = StoryObj<StoryDialogComponent>;
export const Default = Template.bind({});
export const Default: Story = {};

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { ButtonModule } from "../../button";
import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive";
@ -22,61 +22,63 @@ export default {
},
} as Meta;
const Template: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args,
template: `
<bit-simple-dialog>
<span bitDialogTitle>Alert Dialog</span>
<span bitDialogContent>Message Content</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
});
type Story = StoryObj<SimpleDialogComponent & { useDefaultIcon: boolean }>;
export const Default = Template.bind({});
const TemplateWithIcon: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args,
template: `
<bit-simple-dialog>
<i bitDialogIcon class="bwi bwi-star tw-text-3xl tw-text-success" aria-hidden="true"></i>
<span bitDialogTitle>Premium Subscription Available</span>
<span bitDialogContent> Message Content</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
});
export const CustomIcon = TemplateWithIcon.bind({});
const TemplateScroll: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args,
template: `
<bit-simple-dialog>
<span bitDialogTitle>Alert Dialog</span>
<span bitDialogContent>
Message Content
Message text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
});
export const ScrollingContent = TemplateScroll.bind({});
ScrollingContent.args = {
useDefaultIcon: true,
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-simple-dialog>
<span bitDialogTitle>Alert Dialog</span>
<span bitDialogContent>Message Content</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
}),
};
export const CustomIcon: Story = {
render: (args) => ({
props: args,
template: `
<bit-simple-dialog>
<i bitDialogIcon class="bwi bwi-star tw-text-3xl tw-text-success" aria-hidden="true"></i>
<span bitDialogTitle>Premium Subscription Available</span>
<span bitDialogContent> Message Content</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
}),
};
export const ScrollingContent: Story = {
render: (args: SimpleDialogComponent) => ({
props: args,
template: `
<bit-simple-dialog>
<span bitDialogTitle>Alert Dialog</span>
<span bitDialogContent>
Message Content
Message text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
}),
args: {
useDefaultIcon: true,
},
};

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule, FormBuilder } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { StoryObj, Meta, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -46,11 +46,13 @@ const template = `
</bit-form-field>
</form>`;
export const ForbiddenCharacters: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: {
formObj: new FormBuilder().group({
name: ["", forbiddenCharacters(["\\", "/", "@", "#", "$", "%", "^", "&", "*", "(", ")"])],
}),
},
template,
});
export const ForbiddenCharacters: StoryObj<BitFormFieldComponent> = {
render: (args: BitFormFieldComponent) => ({
props: {
formObj: new FormBuilder().group({
name: ["", forbiddenCharacters(["\\", "/", "@", "#", "$", "%", "^", "&", "*", "(", ")"])],
}),
},
template,
}),
};

View File

@ -1,5 +1,5 @@
import { UntypedFormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -50,29 +50,28 @@ function submit() {
formObj.markAllAsTouched();
}
const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: {
formObj: formObj,
submit: submit,
...args,
},
template: `
<form [formGroup]="formObj" (ngSubmit)="submit()">
<bit-form-field>
<bit-label>Name</bit-label>
<input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" />
</bit-form-field>
<button type="submit" bitButton buttonType="primary">Submit</button>
<bit-error-summary [formGroup]="formObj"></bit-error-summary>
</form>
`,
});
export const Default = Template.bind({});
Default.props = {};
export const Default: StoryObj<BitFormFieldComponent> = {
render: (args) => ({
props: {
formObj: formObj,
submit: submit,
...args,
},
template: `
<form [formGroup]="formObj" (ngSubmit)="submit()">
<bit-form-field>
<bit-label>Name</bit-label>
<input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" />
</bit-form-field>
<button type="submit" bitButton buttonType="primary">Submit</button>
<bit-error-summary [formGroup]="formObj"></bit-error-summary>
</form>
`,
}),
};

View File

@ -7,7 +7,7 @@ import {
ValidatorFn,
Validators,
} from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -87,173 +87,168 @@ function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
function submit() {
defaultFormObj.markAllAsTouched();
}
type Story = StoryObj<BitFormFieldComponent>;
const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: {
formObj: defaultFormObj,
submit: submit,
...args,
},
template: `
<form [formGroup]="formObj">
export const Default: Story = {
render: (args) => ({
props: {
formObj: defaultFormObj,
submit: submit,
...args,
},
template: `
<form [formGroup]="formObj">
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput formControlName="name" />
<bit-hint>Optional Hint</bit-hint>
</bit-form-field>
</form>
`,
}),
};
export const Required: Story = {
render: (args) => ({
props: {
formObj: formObj,
...args,
},
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput formControlName="name" />
<bit-hint>Optional Hint</bit-hint>
<input bitInput required placeholder="Placeholder" />
</bit-form-field>
</form>
`,
});
<bit-form-field [formGroup]="formObj">
<bit-label>FormControl</bit-label>
<input bitInput formControlName="required" placeholder="Placeholder" />
</bit-form-field>
`,
}),
};
export const Default = Template.bind({});
Default.props = {};
export const Hint: Story = {
render: (args: BitFormFieldComponent) => ({
props: {
formObj: formObj,
...args,
},
template: `
<bit-form-field [formGroup]="formObj">
<bit-label>FormControl</bit-label>
<input bitInput formControlName="required" placeholder="Placeholder" />
<bit-hint>Long hint text</bit-hint>
</bit-form-field>
`,
}),
};
const RequiredTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: {
formObj: formObj,
...args,
},
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput required placeholder="Placeholder" />
</bit-form-field>
export const Disabled: Story = {
render: (args) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" disabled />
</bit-form-field>
`,
}),
args: {},
};
<bit-form-field [formGroup]="formObj">
<bit-label>FormControl</bit-label>
<input bitInput formControlName="required" placeholder="Placeholder" />
</bit-form-field>
`,
});
export const InputGroup: Story = {
render: (args) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" />
<span bitPrefix>$</span>
<span bitSuffix>USD</span>
</bit-form-field>
`,
}),
args: {},
};
export const Required = RequiredTemplate.bind({});
Required.props = {};
export const ButtonInputGroup: Story = {
render: (args) => ({
props: args,
template: `
<bit-form-field>
<button bitPrefix bitIconButton="bwi-star"></button>
<input bitInput placeholder="Placeholder" />
<button bitSuffix bitIconButton="bwi-eye"></button>
<button bitSuffix bitIconButton="bwi-clone"></button>
<button bitSuffix bitButton>
Apply
</button>
</bit-form-field>
`,
}),
args: {},
};
const HintTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: {
formObj: formObj,
...args,
},
template: `
<bit-form-field [formGroup]="formObj">
<bit-label>FormControl</bit-label>
<input bitInput formControlName="required" placeholder="Placeholder" />
<bit-hint>Long hint text</bit-hint>
</bit-form-field>
`,
});
export const DisabledButtonInputGroup: Story = {
render: (args) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<button bitPrefix bitIconButton="bwi-star" disabled></button>
<input bitInput placeholder="Placeholder" disabled />
<button bitSuffix bitIconButton="bwi-eye" disabled></button>
<button bitSuffix bitIconButton="bwi-clone" disabled></button>
<button bitSuffix bitButton disabled>
Apply
</button>
</bit-form-field>
`,
}),
args: {},
};
export const Hint = HintTemplate.bind({});
Required.props = {};
export const Select: Story = {
render: (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<select bitInput>
<option>Select</option>
<option>Other</option>
</select>
</bit-form-field>
`,
}),
args: {},
};
const DisabledTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" disabled />
</bit-form-field>
`,
});
export const AdvancedSelect: Story = {
render: (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<bit-select>
<bit-option label="Select"></bit-option>
<bit-option label="Other"></bit-option>
</bit-select>
</bit-form-field>
`,
}),
};
export const Disabled = DisabledTemplate.bind({});
Disabled.args = {};
const GroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" />
<span bitPrefix>$</span>
<span bitSuffix>USD</span>
</bit-form-field>
`,
});
export const InputGroup = GroupTemplate.bind({});
InputGroup.args = {};
const ButtonGroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<button bitPrefix bitIconButton="bwi-star"></button>
<input bitInput placeholder="Placeholder" />
<button bitSuffix bitIconButton="bwi-eye"></button>
<button bitSuffix bitIconButton="bwi-clone"></button>
<button bitSuffix bitButton>
Apply
</button>
</bit-form-field>
`,
});
export const ButtonInputGroup = ButtonGroupTemplate.bind({});
ButtonInputGroup.args = {};
const DisabledButtonInputGroupTemplate: Story<BitFormFieldComponent> = (
args: BitFormFieldComponent
) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<button bitPrefix bitIconButton="bwi-star" disabled></button>
<input bitInput placeholder="Placeholder" disabled />
<button bitSuffix bitIconButton="bwi-eye" disabled></button>
<button bitSuffix bitIconButton="bwi-clone" disabled></button>
<button bitSuffix bitButton disabled>
Apply
</button>
</bit-form-field>
`,
});
export const DisabledButtonInputGroup = DisabledButtonInputGroupTemplate.bind({});
DisabledButtonInputGroup.args = {};
const SelectTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<select bitInput>
<option>Select</option>
<option>Other</option>
</select>
</bit-form-field>
`,
});
export const Select = SelectTemplate.bind({});
Select.args = {};
const AdvancedSelectTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<bit-select>
<bit-option label="Select"></bit-option>
<bit-option label="Other"></bit-option>
</bit-select>
</bit-form-field>
`,
});
export const AdvancedSelect = AdvancedSelectTemplate.bind({});
AdvancedSelectTemplate.args = {};
const TextareaTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Textarea</bit-label>
<textarea bitInput rows="4"></textarea>
</bit-form-field>
`,
});
export const Textarea = TextareaTemplate.bind({});
Textarea.args = {};
export const Textarea: Story = {
render: (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Textarea</bit-label>
<textarea bitInput rows="4"></textarea>
</bit-form-field>
`,
}),
args: {},
};

View File

@ -7,7 +7,7 @@ import {
} from "@angular/forms";
import { NgSelectModule } from "@ng-select/ng-select";
import { action } from "@storybook/addon-actions";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -75,209 +75,228 @@ function submit(formObj: FormGroup) {
formObj.markAllAsTouched();
}
const MultiSelectTemplate: Story<MultiSelectComponent> = (args: MultiSelectComponent) => ({
props: {
formObj: formObjFactory(),
submit: submit,
...args,
onItemsConfirmed: actionsData.onItemsConfirmed,
type Story = StoryObj<MultiSelectComponent & { name: string; hint: string }>;
export const Loading: Story = {
render: (args) => ({
props: {
formObj: formObjFactory(),
submit: submit,
...args,
onItemsConfirmed: actionsData.onItemsConfirmed,
},
template: `
<form [formGroup]="formObj" (ngSubmit)="submit(formObj)">
<bit-form-field>
<bit-label>{{ name }}</bit-label>
<bit-multi-select
class="tw-w-full"
formControlName="select"
[baseItems]="baseItems"
[removeSelectedItems]="removeSelectedItems"
[loading]="loading"
[disabled]="disabled"
(onItemsConfirmed)="onItemsConfirmed($event)">
</bit-multi-select>
<bit-hint>{{ hint }}</bit-hint>
</bit-form-field>
<button type="submit" bitButton buttonType="primary">Submit</button>
</form>
`,
}),
args: {
baseItems: [] as any,
name: "Loading",
hint: "This is what a loading multi-select looks like",
loading: true,
},
template: `
<form [formGroup]="formObj" (ngSubmit)="submit(formObj)">
<bit-form-field>
<bit-label>{{ name }}</bit-label>
<bit-multi-select
class="tw-w-full"
formControlName="select"
[baseItems]="baseItems"
[removeSelectedItems]="removeSelectedItems"
[loading]="loading"
[disabled]="disabled"
(onItemsConfirmed)="onItemsConfirmed($event)">
</bit-multi-select>
<bit-hint>{{ hint }}</bit-hint>
</bit-form-field>
<button type="submit" bitButton buttonType="primary">Submit</button>
</form>
`,
});
export const Loading = MultiSelectTemplate.bind({});
Loading.args = {
baseItems: [],
name: "Loading",
hint: "This is what a loading multi-select looks like",
loading: "true",
};
export const Disabled = MultiSelectTemplate.bind({});
Disabled.args = {
name: "Disabled",
disabled: "true",
hint: "This is what a disabled multi-select looks like",
};
export const Groups = MultiSelectTemplate.bind({});
Groups.args = {
name: "Select groups",
hint: "Groups will be assigned to the associated member",
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" },
{ id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
],
};
export const Members = MultiSelectTemplate.bind({});
Members.args = {
name: "Select members",
hint: "Members will be assigned to the associated group/collection",
baseItems: [
{ id: "1", listName: "Joe Smith (jsmith@mail.me)", labelName: "Joe Smith", icon: "bwi-user" },
{
id: "2",
listName: "Tania Stone (tstone@mail.me)",
labelName: "Tania Stone",
icon: "bwi-user",
},
{
id: "3",
listName: "Matt Matters (mmatters@mail.me)",
labelName: "Matt Matters",
icon: "bwi-user",
},
{
id: "4",
listName: "Bob Robertson (brobertson@mail.me)",
labelName: "Bob Robertson",
icon: "bwi-user",
},
{
id: "5",
listName: "Ashley Fletcher (aflectcher@mail.me)",
labelName: "Ashley Fletcher",
icon: "bwi-user",
},
{ id: "6", listName: "Rita Olson (rolson@mail.me)", labelName: "Rita Olson", icon: "bwi-user" },
{
id: "7",
listName: "Final listName (fname@mail.me)",
labelName: "(fname@mail.me)",
icon: "bwi-user",
},
],
};
export const Collections = MultiSelectTemplate.bind({});
Collections.args = {
name: "Select collections",
hint: "Collections will be assigned to the associated member",
baseItems: [
{ id: "1", listName: "Collection 1", labelName: "Collection 1", icon: "bwi-collection" },
{ id: "2", listName: "Collection 2", labelName: "Collection 2", icon: "bwi-collection" },
{ id: "3", listName: "Collection 3", labelName: "Collection 3", icon: "bwi-collection" },
{
id: "3.5",
listName: "Child Collection 1 for Parent 1",
labelName: "Child Collection 1 for Parent 1",
icon: "bwi-collection",
parentGrouping: "Parent 1",
},
{
id: "3.55",
listName: "Child Collection 2 for Parent 1",
labelName: "Child Collection 2 for Parent 1",
icon: "bwi-collection",
parentGrouping: "Parent 1",
},
{
id: "3.59",
listName: "Child Collection 3 for Parent 1",
labelName: "Child Collection 3 for Parent 1",
icon: "bwi-collection",
parentGrouping: "Parent 1",
},
{
id: "3.75",
listName: "Child Collection 1 for Parent 2",
labelName: "Child Collection 1 for Parent 2",
icon: "bwi-collection",
parentGrouping: "Parent 2",
},
{ id: "4", listName: "Collection 4", labelName: "Collection 4", icon: "bwi-collection" },
{ id: "5", listName: "Collection 5", labelName: "Collection 5", icon: "bwi-collection" },
{ id: "6", listName: "Collection 6", labelName: "Collection 6", icon: "bwi-collection" },
{ id: "7", listName: "Collection 7", labelName: "Collection 7", icon: "bwi-collection" },
],
};
export const MembersAndGroups = MultiSelectTemplate.bind({});
MembersAndGroups.args = {
name: "Select groups and members",
hint: "Members/Groups will be assigned to the associated collection",
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Joe Smith (jsmith@mail.me)", labelName: "Joe Smith", icon: "bwi-user" },
{
id: "7",
listName: "Tania Stone (tstone@mail.me)",
labelName: "(tstone@mail.me)",
icon: "bwi-user",
},
],
};
export const RemoveSelected = MultiSelectTemplate.bind({});
RemoveSelected.args = {
name: "Select groups",
hint: "Groups will be removed from the list once the dropdown is closed",
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" },
{ id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
],
removeSelectedItems: "true",
};
const StandaloneTemplate: Story<MultiSelectComponent> = (args: MultiSelectComponent) => ({
props: {
...args,
onItemsConfirmed: actionsData.onItemsConfirmed,
export const Disabled: Story = {
...Loading,
args: {
name: "Disabled",
disabled: true,
hint: "This is what a disabled multi-select looks like",
},
};
export const Groups: Story = {
...Loading,
args: {
name: "Select groups",
hint: "Groups will be assigned to the associated member",
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" },
{ id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
],
},
};
export const Members: Story = {
...Loading,
args: {
name: "Select members",
hint: "Members will be assigned to the associated group/collection",
baseItems: [
{ id: "1", listName: "Joe Smith (jsmith@mail.me)", labelName: "Joe Smith", icon: "bwi-user" },
{
id: "2",
listName: "Tania Stone (tstone@mail.me)",
labelName: "Tania Stone",
icon: "bwi-user",
},
{
id: "3",
listName: "Matt Matters (mmatters@mail.me)",
labelName: "Matt Matters",
icon: "bwi-user",
},
{
id: "4",
listName: "Bob Robertson (brobertson@mail.me)",
labelName: "Bob Robertson",
icon: "bwi-user",
},
{
id: "5",
listName: "Ashley Fletcher (aflectcher@mail.me)",
labelName: "Ashley Fletcher",
icon: "bwi-user",
},
{
id: "6",
listName: "Rita Olson (rolson@mail.me)",
labelName: "Rita Olson",
icon: "bwi-user",
},
{
id: "7",
listName: "Final listName (fname@mail.me)",
labelName: "(fname@mail.me)",
icon: "bwi-user",
},
],
},
};
export const Collections: Story = {
...Loading,
args: {
name: "Select collections",
hint: "Collections will be assigned to the associated member",
baseItems: [
{ id: "1", listName: "Collection 1", labelName: "Collection 1", icon: "bwi-collection" },
{ id: "2", listName: "Collection 2", labelName: "Collection 2", icon: "bwi-collection" },
{ id: "3", listName: "Collection 3", labelName: "Collection 3", icon: "bwi-collection" },
{
id: "3.5",
listName: "Child Collection 1 for Parent 1",
labelName: "Child Collection 1 for Parent 1",
icon: "bwi-collection",
parentGrouping: "Parent 1",
},
{
id: "3.55",
listName: "Child Collection 2 for Parent 1",
labelName: "Child Collection 2 for Parent 1",
icon: "bwi-collection",
parentGrouping: "Parent 1",
},
{
id: "3.59",
listName: "Child Collection 3 for Parent 1",
labelName: "Child Collection 3 for Parent 1",
icon: "bwi-collection",
parentGrouping: "Parent 1",
},
{
id: "3.75",
listName: "Child Collection 1 for Parent 2",
labelName: "Child Collection 1 for Parent 2",
icon: "bwi-collection",
parentGrouping: "Parent 2",
},
{ id: "4", listName: "Collection 4", labelName: "Collection 4", icon: "bwi-collection" },
{ id: "5", listName: "Collection 5", labelName: "Collection 5", icon: "bwi-collection" },
{ id: "6", listName: "Collection 6", labelName: "Collection 6", icon: "bwi-collection" },
{ id: "7", listName: "Collection 7", labelName: "Collection 7", icon: "bwi-collection" },
],
},
};
export const MembersAndGroups: Story = {
...Loading,
args: {
name: "Select groups and members",
hint: "Members/Groups will be assigned to the associated collection",
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Joe Smith (jsmith@mail.me)", labelName: "Joe Smith", icon: "bwi-user" },
{
id: "7",
listName: "Tania Stone (tstone@mail.me)",
labelName: "(tstone@mail.me)",
icon: "bwi-user",
},
],
},
};
export const RemoveSelected: Story = {
...Loading,
args: {
name: "Select groups",
hint: "Groups will be removed from the list once the dropdown is closed",
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" },
{ id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
],
removeSelectedItems: true,
},
};
export const Standalone: Story = {
render: (args) => ({
props: {
...args,
onItemsConfirmed: actionsData.onItemsConfirmed,
},
template: `
<bit-multi-select
class="tw-w-full"
[baseItems]="baseItems"
[removeSelectedItems]="removeSelectedItems"
[loading]="loading"
[disabled]="disabled"
(onItemsConfirmed)="onItemsConfirmed($event)">
</bit-multi-select>
`,
}),
args: {
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" },
{ id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
],
removeSelectedItems: true,
},
template: `
<bit-multi-select
class="tw-w-full"
[baseItems]="baseItems"
[removeSelectedItems]="removeSelectedItems"
[loading]="loading"
[disabled]="disabled"
(onItemsConfirmed)="onItemsConfirmed($event)">
</bit-multi-select>
`,
});
export const Standalone = StandaloneTemplate.bind({});
Standalone.args = {
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" },
{ id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
],
removeSelectedItems: "true",
};

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -38,49 +38,42 @@ export default {
},
} as Meta;
const Template: Story<BitPasswordInputToggleDirective> = (
args: BitPasswordInputToggleDirective
) => ({
props: {
...args,
},
template: `
<form>
<bit-form-field>
<bit-label>Password</bit-label>
<input bitInput type="password" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
</bit-form-field>
</form>
`,
});
type Story = StoryObj<BitPasswordInputToggleDirective>;
export const Default = Template.bind({});
Default.props = {};
const TemplateBinding: Story<BitPasswordInputToggleDirective> = (
args: BitPasswordInputToggleDirective
) => ({
props: {
...args,
},
template: `
<form>
<bit-form-field>
<bit-label>Password</bit-label>
<input bitInput type="password" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle [(toggled)]="toggled"></button>
</bit-form-field>
<label class="tw-text-main">
Checked:
<input type="checkbox" [(ngModel)]="toggled" [ngModelOptions]="{standalone: true}" />
</label>
</form>
`,
});
export const Binding = TemplateBinding.bind({});
Binding.props = {
toggled: false,
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<form>
<bit-form-field>
<bit-label>Password</bit-label>
<input bitInput type="password" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
</bit-form-field>
</form>
`,
}),
};
export const Binding: Story = {
render: (args) => ({
props: args,
template: `
<form>
<bit-form-field>
<bit-label>Password</bit-label>
<input bitInput type="password" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle [(toggled)]="toggled"></button>
</bit-form-field>
<label class="tw-text-main">
Checked:
<input type="checkbox" [(ngModel)]="toggled" [ngModelOptions]="{standalone: true}" />
</label>
</form>
`,
}),
args: {
toggled: false,
},
};

View File

@ -7,7 +7,7 @@ import {
ValidatorFn,
Validators,
} from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -81,67 +81,70 @@ function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
};
}
const FullExampleTemplate: Story = (args) => ({
props: {
formObj: exampleFormObj,
submit: () => exampleFormObj.markAllAsTouched(),
...args,
type Story = StoryObj;
export const FullExample: Story = {
render: (args) => ({
props: {
formObj: exampleFormObj,
submit: () => exampleFormObj.markAllAsTouched(),
...args,
},
template: `
<form [formGroup]="formObj" (ngSubmit)="submit()">
<bit-form-field>
<bit-label>Name</bit-label>
<input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" />
</bit-form-field>
<bit-form-field>
<bit-label>Country</bit-label>
<bit-select formControlName="country">
<bit-option *ngFor="let country of countries" [value]="country.value" [label]="country.name"></bit-option>
</bit-select>
</bit-form-field>
<bit-form-field>
<bit-label>Age</bit-label>
<input
bitInput
type="number"
formControlName="age"
min="0"
max="150"
/>
</bit-form-field>
<bit-form-control>
<bit-label>Agree to terms</bit-label>
<input type="checkbox" bitCheckbox formControlName="terms">
<bit-hint>Required for the service to work properly</bit-hint>
</bit-form-control>
<bit-radio-group formControlName="updates">
<bit-label>Subscribe to updates?</bit-label>
<bit-radio-button value="yes">
<bit-label>Yes</bit-label>
</bit-radio-button>
<bit-radio-button value="no">
<bit-label>No</bit-label>
</bit-radio-button>
<bit-radio-button value="later">
<bit-label>Decide later</bit-label>
</bit-radio-button>
</bit-radio-group>
<button type="submit" bitButton buttonType="primary">Submit</button>
</form>
`,
}),
args: {
countries,
},
template: `
<form [formGroup]="formObj" (ngSubmit)="submit()">
<bit-form-field>
<bit-label>Name</bit-label>
<input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" />
</bit-form-field>
<bit-form-field>
<bit-label>Country</bit-label>
<bit-select formControlName="country">
<bit-option *ngFor="let country of countries" [value]="country.value" [label]="country.name"></bit-option>
</bit-select>
</bit-form-field>
<bit-form-field>
<bit-label>Age</bit-label>
<input
bitInput
type="number"
formControlName="age"
min="0"
max="150"
/>
</bit-form-field>
<bit-form-control>
<bit-label>Agree to terms</bit-label>
<input type="checkbox" bitCheckbox formControlName="terms">
<bit-hint>Required for the service to work properly</bit-hint>
</bit-form-control>
<bit-radio-group formControlName="updates">
<bit-label>Subscribe to updates?</bit-label>
<bit-radio-button value="yes">
<bit-label>Yes</bit-label>
</bit-radio-button>
<bit-radio-button value="no">
<bit-label>No</bit-label>
</bit-radio-button>
<bit-radio-button value="later">
<bit-label>Decide later</bit-label>
</bit-radio-button>
</bit-radio-group>
<button type="submit" bitButton buttonType="primary">Submit</button>
</form>
`,
});
export const FullExample = FullExampleTemplate.bind({});
FullExample.args = {
countries,
};

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, StoryObj } from "@storybook/angular";
import { BitIconButtonComponent, IconButtonType } from "./icon-button.component";
@ -31,68 +31,72 @@ export default {
},
} as Meta;
const Template: Story<BitIconButtonComponent> = (args: BitIconButtonComponent) => ({
props: { ...args, buttonTypes },
template: `
<table class="tw-border-spacing-2 tw-text-center tw-text-main">
<thead>
<tr>
<td></td>
<td *ngFor="let buttonType of buttonTypes" class="tw-capitalize tw-font-bold tw-p-4"
[class.tw-text-contrast]="['contrast', 'light'].includes(buttonType)"
[class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">{{buttonType}}</td>
</tr>
</thead>
type Story = StoryObj<BitIconButtonComponent>;
<tbody>
<tr>
<td class="tw-font-bold tw-p-4 tw-text-left">Default</td>
<td *ngFor="let buttonType of buttonTypes" class="tw-p-2" [class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">
<button
[bitIconButton]="bitIconButton"
[buttonType]="buttonType"
[size]="size"
title="Example icon button"
aria-label="Example icon button"></button>
</td>
</tr>
<tr>
<td class="tw-font-bold tw-p-4 tw-text-left">Disabled</td>
<td *ngFor="let buttonType of buttonTypes" class="tw-p-2" [class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">
<button
[bitIconButton]="bitIconButton"
[buttonType]="buttonType"
[size]="size"
disabled
title="Example icon button"
aria-label="Example icon button"></button>
</td>
</tr>
<tr>
<td class="tw-font-bold tw-p-4 tw-text-left">Loading</td>
<td *ngFor="let buttonType of buttonTypes" class="tw-p-2" [class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">
<button
[bitIconButton]="bitIconButton"
[buttonType]="buttonType"
[size]="size"
loading="true"
title="Example icon button"
aria-label="Example icon button"></button>
</td>
</tr>
</tbody>
</table>
`,
});
export const Default = Template.bind({});
Default.args = {
size: "default",
export const Default: Story = {
render: (args: BitIconButtonComponent) => ({
props: { ...args, buttonTypes },
template: `
<table class="tw-border-spacing-2 tw-text-center tw-text-main">
<thead>
<tr>
<td></td>
<td *ngFor="let buttonType of buttonTypes" class="tw-capitalize tw-font-bold tw-p-4"
[class.tw-text-contrast]="['contrast', 'light'].includes(buttonType)"
[class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">{{buttonType}}</td>
</tr>
</thead>
<tbody>
<tr>
<td class="tw-font-bold tw-p-4 tw-text-left">Default</td>
<td *ngFor="let buttonType of buttonTypes" class="tw-p-2" [class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">
<button
[bitIconButton]="bitIconButton"
[buttonType]="buttonType"
[size]="size"
title="Example icon button"
aria-label="Example icon button"></button>
</td>
</tr>
<tr>
<td class="tw-font-bold tw-p-4 tw-text-left">Disabled</td>
<td *ngFor="let buttonType of buttonTypes" class="tw-p-2" [class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">
<button
[bitIconButton]="bitIconButton"
[buttonType]="buttonType"
[size]="size"
disabled
title="Example icon button"
aria-label="Example icon button"></button>
</td>
</tr>
<tr>
<td class="tw-font-bold tw-p-4 tw-text-left">Loading</td>
<td *ngFor="let buttonType of buttonTypes" class="tw-p-2" [class.tw-bg-primary-500]="['contrast', 'light'].includes(buttonType)">
<button
[bitIconButton]="bitIconButton"
[buttonType]="buttonType"
[size]="size"
loading="true"
title="Example icon button"
aria-label="Example icon button"></button>
</td>
</tr>
</tbody>
</table>
`,
}),
args: {
size: "default",
},
};
export const Small = Template.bind({});
Small.args = {
size: "small",
export const Small: Story = {
...Default,
args: {
size: "small",
},
};

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, StoryObj } from "@storybook/angular";
import { BitIconComponent } from "./icon.component";
@ -10,18 +10,22 @@ export default {
},
} as Meta;
const Template: Story<BitIconComponent> = (args: BitIconComponent) => ({
props: args,
template: `
<div class="tw-bg-primary-500 tw-p-5">
<bit-icon [icon]="icon" class="tw-text-primary-300"></bit-icon>
</div>
`,
});
type Story = StoryObj<BitIconComponent>;
export const ReportExposedPasswords = Template.bind({});
export const UnknownIcon = Template.bind({});
UnknownIcon.args = {
icon: "unknown",
export const ReportExposedPasswords: Story = {
render: (args) => ({
props: args,
template: `
<div class="tw-bg-primary-500 tw-p-5">
<bit-icon [icon]="icon" class="tw-text-primary-300"></bit-icon>
</div>
`,
}),
};
export const UnknownIcon: Story = {
...ReportExposedPasswords,
args: {
icon: "unknown" as any,
},
};

View File

@ -12,6 +12,7 @@ export * from "./dialog";
export * from "./form-field";
export * from "./icon-button";
export * from "./icon";
export * from "./input";
export * from "./link";
export * from "./menu";
export * from "./multi-select";

View File

@ -0,0 +1 @@
export * from "./input.module";

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive";
import { LinkModule } from "./link.module";
@ -24,97 +24,99 @@ export default {
},
} as Meta;
const ButtonTemplate: Story<ButtonLinkDirective> = (args: ButtonLinkDirective) => ({
props: args,
template: `
<div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }">
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType">Button</button>
</div>
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType">
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
Add Icon Button
</button>
</div>
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType">
<i class="bwi bwi-fw bwi-sm bwi-angle-right" aria-hidden="true"></i>
Chevron Icon Button
</button>
</div>
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType" class="tw-text-sm">Small Button</button>
</div>
</div>
`,
});
type Story = StoryObj<ButtonLinkDirective>;
const AnchorTemplate: Story<AnchorLinkDirective> = (args: AnchorLinkDirective) => ({
props: args,
template: `
<div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }">
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" href="#">Anchor</a>
export const Buttons: Story = {
render: (args) => ({
props: args,
template: `
<div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }">
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType">Button</button>
</div>
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType">
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
Add Icon Button
</button>
</div>
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType">
<i class="bwi bwi-fw bwi-sm bwi-angle-right" aria-hidden="true"></i>
Chevron Icon Button
</button>
</div>
<div class="tw-block tw-p-2">
<button bitLink [linkType]="linkType" class="tw-text-sm">Small Button</button>
</div>
</div>
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" href="#">
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
Add Icon Anchor
</a>
</div>
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" href="#">
<i class="bwi bwi-fw bwi-sm bwi-angle-right" aria-hidden="true"></i>
Chevron Icon Anchor
</a>
</div>
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" class="tw-text-sm" href="#">Small Anchor</a>
</div>
</div>
`,
});
export const Buttons = ButtonTemplate.bind({});
Buttons.args = {
linkType: "primary",
};
export const Anchors = AnchorTemplate.bind({});
Anchors.args = {
linkType: "primary",
};
const InlineTemplate: Story = (args) => ({
props: args,
template: `
<span class="tw-text-main">
On the internet paragraphs often contain <a bitLink href="#">inline links</a>, but few know that <button bitLink>buttons</button> can be used for similar purposes.
</span>
`,
});
export const Inline = InlineTemplate.bind({});
Inline.args = {
linkType: "primary",
};
const DisabledTemplate: Story = (args) => ({
props: args,
template: `
<button bitLink disabled linkType="primary" class="tw-mr-2">Primary</button>
<button bitLink disabled linkType="secondary" class="tw-mr-2">Secondary</button>
<div class="tw-bg-primary-500 tw-p-2 tw-inline-block">
<button bitLink disabled linkType="contrast" class="tw-mr-2">Contrast</button>
</div>
`,
});
export const Disabled = DisabledTemplate.bind({});
Disabled.parameters = {
controls: {
exclude: ["linkType"],
hideNoControlsWarning: true,
`,
}),
args: {
linkType: "primary",
},
};
export const Anchors: StoryObj<AnchorLinkDirective> = {
render: (args) => ({
props: args,
template: `
<div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }">
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" href="#">Anchor</a>
</div>
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" href="#">
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
Add Icon Anchor
</a>
</div>
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" href="#">
<i class="bwi bwi-fw bwi-sm bwi-angle-right" aria-hidden="true"></i>
Chevron Icon Anchor
</a>
</div>
<div class="tw-block tw-p-2">
<a bitLink [linkType]="linkType" class="tw-text-sm" href="#">Small Anchor</a>
</div>
</div>
`,
}),
args: {
linkType: "primary",
},
};
export const Inline: Story = {
render: (args) => ({
props: args,
template: `
<span class="tw-text-main">
On the internet paragraphs often contain <a bitLink href="#">inline links</a>, but few know that <button bitLink>buttons</button> can be used for similar purposes.
</span>
`,
}),
args: {
linkType: "primary",
},
};
export const Disabled: Story = {
render: (args) => ({
props: args,
template: `
<button bitLink disabled linkType="primary" class="tw-mr-2">Primary</button>
<button bitLink disabled linkType="secondary" class="tw-mr-2">Secondary</button>
<div class="tw-bg-primary-500 tw-p-2 tw-inline-block">
<button bitLink disabled linkType="contrast" class="tw-mr-2">Contrast</button>
</div>
`,
}),
parameters: {
controls: {
exclude: ["linkType"],
hideNoControlsWarning: true,
},
},
};

View File

@ -1,5 +1,5 @@
import { OverlayModule } from "@angular/cdk/overlay";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { ButtonModule } from "../button/button.module";
@ -30,40 +30,42 @@ export default {
},
} as Meta;
const Template: Story<MenuTriggerForDirective> = (args: MenuTriggerForDirective) => ({
props: args,
template: `
<bit-menu #myMenu="menuComponent">
<a href="#" bitMenuItem>Anchor link</a>
<a href="#" bitMenuItem>Another link</a>
<button type="button" bitMenuItem>Button</button>
<bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem>Button after divider</button>
</bit-menu>
type Story = StoryObj<MenuTriggerForDirective>;
<div class="tw-h-40">
<div class="cdk-overlay-pane bit-menu-panel">
<ng-container *ngTemplateOutlet="myMenu.templateRef"></ng-container>
export const OpenMenu: Story = {
render: (args) => ({
props: args,
template: `
<bit-menu #myMenu="menuComponent">
<a href="#" bitMenuItem>Anchor link</a>
<a href="#" bitMenuItem>Another link</a>
<button type="button" bitMenuItem>Button</button>
<bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem>Button after divider</button>
</bit-menu>
<div class="tw-h-40">
<div class="cdk-overlay-pane bit-menu-panel">
<ng-container *ngTemplateOutlet="myMenu.templateRef"></ng-container>
</div>
</div>
</div>
`,
});
const TemplateWithButton: Story<MenuTriggerForDirective> = (args: MenuTriggerForDirective) => ({
props: args,
template: `
<div class="tw-h-40">
<button bitButton buttonType="secondary" [bitMenuTriggerFor]="myMenu">Open menu</button>
</div>
<bit-menu #myMenu>
<a href="#" bitMenuItem>Anchor link</a>
<a href="#" bitMenuItem>Another link</a>
<button type="button" bitMenuItem>Button</button>
<bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem>Button after divider</button>
</bit-menu>`,
});
export const OpenMenu = Template.bind({});
export const ClosedMenu = TemplateWithButton.bind({});
`,
}),
};
export const ClosedMenu: Story = {
render: (args) => ({
props: args,
template: `
<div class="tw-h-40">
<button bitButton buttonType="secondary" [bitMenuTriggerFor]="myMenu">Open menu</button>
</div>
<bit-menu #myMenu>
<a href="#" bitMenuItem>Anchor link</a>
<a href="#" bitMenuItem>Another link</a>
<button type="button" bitMenuItem>Button</button>
<bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem>Button after divider</button>
</bit-menu>`,
}),
};

View File

@ -1,5 +1,5 @@
import { RouterTestingModule } from "@angular/router/testing";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { StoryObj, Meta, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -36,9 +36,10 @@ export default {
},
} as Meta;
export const Default: Story<NavGroupComponent> = (args) => ({
props: args,
template: `
export const Default: StoryObj<NavGroupComponent> = {
render: (args) => ({
props: args,
template: `
<bit-nav-group text="Hello World (Anchor)" [route]="['']" icon="bwi-filter" [open]="true">
<bit-nav-item text="Child A" route="#" icon="bwi-filter"></bit-nav-item>
<bit-nav-item text="Child B" route="#"></bit-nav-item>
@ -50,25 +51,28 @@ export const Default: Story<NavGroupComponent> = (args) => ({
<bit-nav-item text="Child C" icon="bwi-filter"></bit-nav-item>
</bit-nav-group>
`,
});
}),
};
export const Tree: Story<NavGroupComponent> = (args) => ({
props: args,
template: `
<bit-nav-group text="Tree example" icon="bwi-collection" [open]="true">
<bit-nav-group text="Level 1 - with children (empty)" route="#" icon="bwi-collection" variant="tree"></bit-nav-group>
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
<bit-nav-group text="Level 1 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-group text="Level 2 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 3 - no children, no icon" route="#" variant="tree"></bit-nav-item>
<bit-nav-group text="Level 3 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 4 - no children, no icon" route="#" variant="tree"></bit-nav-item>
export const Tree: StoryObj<NavGroupComponent> = {
render: (args) => ({
props: args,
template: `
<bit-nav-group text="Tree example" icon="bwi-collection" [open]="true">
<bit-nav-group text="Level 1 - with children (empty)" route="#" icon="bwi-collection" variant="tree"></bit-nav-group>
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
<bit-nav-group text="Level 1 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-group text="Level 2 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 3 - no children, no icon" route="#" variant="tree"></bit-nav-item>
<bit-nav-group text="Level 3 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 4 - no children, no icon" route="#" variant="tree"></bit-nav-item>
</bit-nav-group>
</bit-nav-group>
<bit-nav-group text="Level 2 - with children (empty)" route="#" icon="bwi-collection" variant="tree" [open]="true"></bit-nav-group>
<bit-nav-item text="Level 2 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
</bit-nav-group>
<bit-nav-group text="Level 2 - with children (empty)" route="#" icon="bwi-collection" variant="tree" [open]="true"></bit-nav-group>
<bit-nav-item text="Level 2 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
</bit-nav-group>
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
</bit-nav-group>
`,
});
`,
}),
};

View File

@ -1,5 +1,5 @@
import { RouterTestingModule } from "@angular/router/testing";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { StoryObj, Meta, moduleMetadata } from "@storybook/angular";
import { IconButtonModule } from "../icon-button";
@ -23,35 +23,42 @@ export default {
},
} as Meta;
const Template: Story<NavItemComponent> = (args: NavItemComponent) => ({
props: args,
template: `
<bit-nav-item text="${args.text}" [route]="['']" icon="${args.icon}"></bit-nav-item>
`,
});
type Story = StoryObj<NavItemComponent>;
export const Default = Template.bind({});
Default.args = {
text: "Hello World",
icon: "bwi-filter",
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-nav-item text="${args.text}" [route]="['']" icon="${args.icon}"></bit-nav-item>
`,
}),
args: {
text: "Hello World",
icon: "bwi-filter",
},
};
export const WithoutIcon = Template.bind({});
WithoutIcon.args = {
text: "Hello World",
icon: "",
export const WithoutIcon: Story = {
...Default,
args: {
text: "Hello World",
icon: "",
},
};
export const WithoutRoute: Story<NavItemComponent> = (args: NavItemComponent) => ({
props: args,
template: `
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
`,
});
export const WithoutRoute: Story = {
render: (args: NavItemComponent) => ({
props: args,
template: `
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
`,
}),
};
export const WithChildButtons: Story<NavItemComponent> = (args: NavItemComponent) => ({
props: args,
template: `
export const WithChildButtons: Story = {
render: (args: NavItemComponent) => ({
props: args,
template: `
<bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection">
<button
slot-start
@ -79,15 +86,18 @@ export const WithChildButtons: Story<NavItemComponent> = (args: NavItemComponent
></button>
</bit-nav-item>
`,
});
}),
};
export const MultipleItemsWithDivider: Story<NavItemComponent> = (args: NavItemComponent) => ({
props: args,
template: `
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-divider></bit-nav-divider>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
`,
});
export const MultipleItemsWithDivider: Story = {
render: (args: NavItemComponent) => ({
props: args,
template: `
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-divider></bit-nav-divider>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
`,
}),
};

View File

@ -1,11 +1,13 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { ButtonModule } from "../button";
import { NoItemsComponent } from "./no-items.component";
import { NoItemsModule } from "./no-items.module";
export default {
title: "Component Library/No Items",
component: NoItemsComponent,
decorators: [
moduleMetadata({
imports: [ButtonModule, NoItemsModule],
@ -13,23 +15,25 @@ export default {
],
} as Meta;
const Template: Story = (args) => ({
props: args,
template: `
<bit-no-items class="tw-text-main">
<ng-container slot="title">No items found</ng-container>
<ng-container slot="description">Your description here.</ng-container>
<button
slot="button"
type="button"
bitButton
buttonType="secondary"
>
<i class="bwi bwi-plus" aria-hidden="true"></i>
New item
</button>
</bit-no-items>
`,
});
type Story = StoryObj<NoItemsComponent>;
export const Default = Template.bind({});
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-no-items class="tw-text-main">
<ng-container slot="title">No items found</ng-container>
<ng-container slot="description">Your description here.</ng-container>
<button
slot="button"
type="button"
bitButton
buttonType="secondary"
>
<i class="bwi bwi-plus" aria-hidden="true"></i>
New item
</button>
</bit-no-items>
`,
}),
};

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, StoryObj } from "@storybook/angular";
import { ProgressComponent } from "./progress.component";
@ -18,22 +18,23 @@ export default {
},
} as Meta;
const Template: Story<ProgressComponent> = (args: ProgressComponent) => ({
props: args,
});
type Story = StoryObj<ProgressComponent>;
export const Empty = Template.bind({});
Empty.args = {
barWidth: 0,
export const Empty: Story = {
args: {
barWidth: 0,
},
};
export const Full = Template.bind({});
Full.args = {
barWidth: 100,
export const Full: Story = {
args: {
barWidth: 100,
},
};
export const CustomText = Template.bind({});
CustomText.args = {
barWidth: 25,
text: "Loading...",
export const CustomText: Story = {
args: {
barWidth: 25,
text: "Loading...",
},
};

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule, FormControl, FormGroup } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -34,65 +34,67 @@ export default {
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=3930%3A16850&t=xXPx6GJYsJfuMQPE-4",
},
},
} as Meta;
} as Meta<RadioGroupComponent>;
const InlineTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) => ({
props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
type Story = StoryObj<RadioGroupComponent>;
<bit-radio-button id="radio-first" [value]="0">
<bit-label>First</bit-label>
</bit-radio-button>
export const Inline: Story = {
render: () => ({
props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" [value]="0">
<bit-label>First</bit-label>
</bit-radio-button>
<bit-radio-button id="radio-second" [value]="1">
<bit-label>Second</bit-label>
</bit-radio-button>
<bit-radio-button id="radio-third" [value]="2">
<bit-label>Third</bit-label>
</bit-radio-button>
</bit-radio-group>
</form>
`,
}),
};
<bit-radio-button id="radio-second" [value]="1">
<bit-label>Second</bit-label>
</bit-radio-button>
<bit-radio-button id="radio-third" [value]="2">
<bit-label>Third</bit-label>
</bit-radio-button>
</bit-radio-group>
</form>
`,
});
export const Inline = InlineTemplate.bind({});
const BlockTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) => ({
props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" class="tw-block" [value]="0">
<bit-label>First</bit-label>
<bit-hint>This is a hint for the first option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-second" class="tw-block" [value]="1">
<bit-label>Second</bit-label>
<bit-hint>This is a hint for the second option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-third" class="tw-block" [value]="2">
<bit-label>Third</bit-label>
<bit-hint>This is a hint for the third option</bit-hint>
</bit-radio-button>
</bit-radio-group>
</form>
`,
});
export const Block = BlockTemplate.bind({});
export const Block: Story = {
render: () => ({
props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" class="tw-block" [value]="0">
<bit-label>First</bit-label>
<bit-hint>This is a hint for the first option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-second" class="tw-block" [value]="1">
<bit-label>Second</bit-label>
<bit-hint>This is a hint for the second option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-third" class="tw-block" [value]="2">
<bit-label>Third</bit-label>
<bit-hint>This is a hint for the third option</bit-hint>
</bit-radio-button>
</bit-radio-group>
</form>
`,
}),
};

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -29,12 +29,14 @@ export default {
],
} as Meta;
const Template: Story<SearchComponent> = (args: SearchComponent) => ({
props: args,
template: `
<bit-search [(ngModel)]="searchText" [placeholder]="placeholder" [disabled]="disabled"></bit-search>
`,
});
type Story = StoryObj<SearchComponent>;
export const Default = Template.bind({});
Default.args = {};
export const Default: Story = {
render: (args: SearchComponent) => ({
props: args,
template: `
<bit-search [(ngModel)]="searchText" [placeholder]="placeholder" [disabled]="disabled"></bit-search>
`,
}),
args: {},
};

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -37,22 +37,26 @@ export default {
},
} as Meta;
const DefaultTemplate: Story<MultiSelectComponent> = (args: MultiSelectComponent) => ({
props: {
...args,
},
template: `<bit-select [disabled]="disabled">
<bit-option value="value1" label="Value 1" icon="bwi-collection"></bit-option>
<bit-option value="value2" label="Value 2" icon="bwi-collection"></bit-option>
<bit-option value="value3" label="Value 3" icon="bwi-collection"></bit-option>
<bit-option value="value4" label="Value 4" icon="bwi-collection" disabled></bit-option>
</bit-select>`,
});
type Story = StoryObj<MultiSelectComponent>;
export const Default = DefaultTemplate.bind({});
Default.args = {};
export const Disabled = DefaultTemplate.bind({});
Disabled.args = {
disabled: true,
export const Default: Story = {
render: (args) => ({
props: {
...args,
},
template: `<bit-select [disabled]="disabled">
<bit-option value="value1" label="Value 1" icon="bwi-collection"></bit-option>
<bit-option value="value2" label="Value 2" icon="bwi-collection"></bit-option>
<bit-option value="value3" label="Value 3" icon="bwi-collection"></bit-option>
<bit-option value="value4" label="Value 4" icon="bwi-collection" disabled></bit-option>
</bit-select>`,
}),
args: {},
};
export const Disabled: Story = {
...Default,
args: {
disabled: true,
},
};

View File

@ -67,7 +67,8 @@ export const Table = (args) => (
</table>
);
<style>{`
<style>
{`
table {
border-spacing: 0.5rem;
border-collapse: separate !important;
@ -85,7 +86,8 @@ td, th {
th {
border: none !important;
}
`}</style>
`}
</style>
# Colors

View File

@ -1,6 +1,6 @@
import { Meta, Story, Source } from "@storybook/addon-docs";
<Meta title="Documentation/Forms" />
<Meta title="Component Library/Form" />
# Forms

View File

@ -1,6 +1,6 @@
<!-- Iconography.stories.mdx -->
{/* Iconography.stories.mdx */}
import { Meta } from "@storybook/addon-docs/";
import { Meta } from "@storybook/addon-docs";
<Meta title="Documentation/Icons" />

View File

@ -1,8 +1,8 @@
import { Meta } from "@storybook/addon-docs";
<Meta title="Documentation/bitInput" />
<Meta title="Component Library/Form/Input" />
# `bitInput`
# Input
`bitInput` is an Angular directive to be used on `<input>`, `<select>`, and `<textarea>` tags in
order to provide standardized TailwindCss styling, error handling, and more. It is meant to be used

View File

@ -2,7 +2,8 @@ import { Meta } from "@storybook/addon-docs";
<Meta title="Documentation/Introduction" />
<style>{`
<style>
{`
.subheading {
--mediumdark: '#999999';
font-weight: 900;
@ -77,7 +78,8 @@ import { Meta } from "@storybook/addon-docs";
font-size: 14px;
line-height: 20px;
}
`}</style>
`}
</style>
# Bitwarden Component Library

View File

@ -1,59 +0,0 @@
import { Meta, Story, Source } from "@storybook/addon-docs";
<Meta title="Documentation/Typography" />
# Typography
<Story id="component-library-typography--h-1" />
```html
<h1 bitTypography="h1">H1</h1>
```
<Story id="component-library-typography--h-2" />
```html
<h2 bitTypography="h2">H2</h2>
```
<Story id="component-library-typography--h-3" />
```html
<h3 bitTypography="h3">H3</h3>
```
<Story id="component-library-typography--h-4" />
```html
<h4 bitTypography="h4">H4</h4>
```
<Story id="component-library-typography--h-5" />
```html
<h5 bitTypography="h5">H5</h5>
```
<Story id="component-library-typography--h-6" />
```html
<h6 bitTypography="h6">H6</h6>
```
<Story id="component-library-typography--body-1" />
```html
<p bitTypography="body1">Body 1</p>
```
<Story id="component-library-typography--body-2" />
```html
<p bitTypography="body2">Body 2</h1>
```
<Story id="component-library-typography--helper" />
```html
<p bitTypography="helper">Helper Text</h1>
```

View File

@ -1,15 +1,19 @@
import { Meta, Story, Source } from "@storybook/addon-docs";
import { Meta, Story, Source, Primary, Controls } from "@storybook/addon-docs";
<Meta title="Documentation/Table" />
import * as stories from "./table.stories";
<Meta of={stories} />
# Table
## Overview
The table component provides a comprehensive way to display, sort and filter data. It consists of
two portions, a UI component called `bit-table` and the underlying data source `TableDataSource`.
This documentation will initially focus on the UI portion before covering the data source.
<Primary />
<Controls />
## UI Component
The UI component consists of a couple of elements.
@ -29,10 +33,6 @@ The UI component consists of a couple of elements.
Netflix” for an edit option for a Netflix item.
- Use [Virtual Scrolling](#virtual-scrolling) for large data sets.
### Example
<Story id="component-library-table--default" />
### Usage
The below code is the minimum required to create a table. However we strongly advise you to use the

View File

@ -1,5 +1,5 @@
import { ScrollingModule } from "@angular/cdk/scrolling";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { countries } from "../form/countries";
@ -27,41 +27,43 @@ export default {
},
} as Meta;
const Template: Story = (args) => ({
props: args,
template: `
<bit-table>
<ng-container header>
<tr>
<th bitCell>Header 1</th>
<th bitCell>Header 2</th>
<th bitCell>Header 3</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 1</td>
<td bitCell>Cell 2 <br> Multiline Cell</td>
<td bitCell>Cell 3</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 4</td>
<td bitCell>Cell 5</td>
<td bitCell>Cell 6</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 7 <br> Multiline Cell</td>
<td bitCell>Cell 8</td>
<td bitCell>Cell 9</td>
</tr>
</ng-template>
</bit-table>
`,
});
type Story = StoryObj;
export const Default = Template.bind({});
Default.args = {
alignRowContent: "baseline",
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-table>
<ng-container header>
<tr>
<th bitCell>Header 1</th>
<th bitCell>Header 2</th>
<th bitCell>Header 3</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 1</td>
<td bitCell>Cell 2 <br> Multiline Cell</td>
<td bitCell>Cell 3</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 4</td>
<td bitCell>Cell 5</td>
<td bitCell>Cell 6</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 7 <br> Multiline Cell</td>
<td bitCell>Cell 8</td>
<td bitCell>Cell 9</td>
</tr>
</ng-template>
</bit-table>
`,
}),
args: {
alignRowContent: "baseline",
},
};
const data = new TableDataSource<{ id: number; name: string; other: string }>();
@ -72,48 +74,13 @@ data.data = [...Array(5).keys()].map((i) => ({
other: `other-${i}`,
}));
const DataSourceTemplate: Story = (args) => ({
props: {
dataSource: data,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="id" default>Id</th>
<th bitCell bitSortable="name">Name</th>
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
`,
});
export const DataSource = DataSourceTemplate.bind({});
const data2 = new TableDataSource<{ id: number; name: string; other: string }>();
data2.data = [...Array(100).keys()].map((i) => ({
id: i,
name: `name-${i}`,
other: `other-${i}`,
}));
const ScrollableTemplate: Story = (args) => ({
props: {
dataSource: data2,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
export const DataSource: Story = {
render: (args) => ({
props: {
dataSource: data,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
@ -123,51 +90,86 @@ const ScrollableTemplate: Story = (args) => ({
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
`,
});
}),
};
export const Scrollable = ScrollableTemplate.bind({});
const data2 = new TableDataSource<{ id: number; name: string; other: string }>();
data2.data = [...Array(100).keys()].map((i) => ({
id: i,
name: `name-${i}`,
other: `other-${i}`,
}));
export const Scrollable: Story = {
render: (args) => ({
props: {
dataSource: data2,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="id" default>Id</th>
<th bitCell bitSortable="name">Name</th>
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
`,
}),
};
const data3 = new TableDataSource<{ value: string; name: string }>();
// Chromatic has a max page size, lowering the number of entries to ensure we don't hit it
data3.data = countries.slice(0, 100);
const FilterableTemplate: Story = (args) => ({
props: {
dataSource: data3,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" />
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
<th bitCell bitSortable="value" width="120px">Value</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.value }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
export const Filterable: Story = {
render: (args) => ({
props: {
dataSource: data3,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" />
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
<th bitCell bitSortable="value" width="120px">Value</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.value }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
`,
});
export const Filterable = FilterableTemplate.bind({});
}),
};
const data4 = new TableDataSource<{ name: string }>();
@ -175,24 +177,24 @@ data4.data = [...Array(5).keys()].map((i) => ({
name: i % 2 == 0 ? `name-${i}`.toUpperCase() : `name-${i}`.toLowerCase(),
}));
const VariableCaseTemplate: Story = (args) => ({
props: {
dataSource: data4,
},
template: `
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.name }}</td>
</tr>
</ng-template>
</bit-table>
export const VariableCase: Story = {
render: (args) => ({
props: {
dataSource: data4,
},
template: `
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.name }}</td>
</tr>
</ng-template>
</bit-table>
`,
});
export const VariableCase = VariableCaseTemplate.bind({});
}),
};

View File

@ -1,7 +1,7 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { Component, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { ButtonModule } from "../button";
import { FormFieldModule } from "../form-field";
@ -44,20 +44,21 @@ export default {
ItemThreeDummyComponent,
DisabledDummyComponent,
],
imports: [
CommonModule,
TabsModule,
ButtonModule,
FormFieldModule,
RouterModule.forRoot(
[
{ path: "", redirectTo: "active", pathMatch: "full" },
{ path: "active", component: ActiveDummyComponent },
{ path: "item-2", component: ItemTwoDummyComponent },
{ path: "item-3", component: ItemThreeDummyComponent },
{ path: "disabled", component: DisabledDummyComponent },
],
{ useHash: true }
imports: [CommonModule, TabsModule, ButtonModule, FormFieldModule, RouterModule],
}),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot(
[
{ path: "", redirectTo: "active", pathMatch: "full" },
{ path: "active", component: ActiveDummyComponent },
{ path: "item-2", component: ItemTwoDummyComponent },
{ path: "item-3", component: ItemThreeDummyComponent },
{ path: "disabled", component: DisabledDummyComponent },
],
{ useHash: true }
)
),
],
}),
@ -70,10 +71,13 @@ export default {
},
} as Meta;
const ContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
props: args,
template: `
<bit-tab-group label="Main Content Tabs" class="tw-text-main">
type Story = StoryObj<TabGroupComponent>;
export const ContentTabs: Story = {
render: (args: any) => ({
props: args,
template: `
<bit-tab-group label="Main Content Tabs" class="tw-text-main">
<bit-tab label="First Tab">First Tab Content</bit-tab>
<bit-tab label="Second Tab">Second Tab Content</bit-tab>
<bit-tab>
@ -85,56 +89,54 @@ const ContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
<bit-tab [disabled]="true" label="Disabled">
Disabled Content
</bit-tab>
</bit-tab-group>
`,
});
</bit-tab-group>
`,
}),
};
export const ContentTabs = ContentTabGroupTemplate.bind({});
export const NavigationTabs: Story = {
render: (args: TabGroupComponent) => ({
props: args,
template: `
<bit-tab-nav-bar label="Main">
<bit-tab-link [route]="['active']">Active</bit-tab-link>
<bit-tab-link [route]="['item-2']">Item 2</bit-tab-link>
<bit-tab-link [route]="['item-3']">Item 3</bit-tab-link>
<bit-tab-link [route]="['disable']" [disabled]="true">Disabled</bit-tab-link>
</bit-tab-nav-bar>
<div class="tw-bg-transparent tw-text-semibold tw-text-center tw-text-main tw-py-10">
<router-outlet></router-outlet>
</div>
`,
}),
};
const NavTabGroupTemplate: Story<TabGroupComponent> = (args: TabGroupComponent) => ({
props: args,
template: `
<bit-tab-nav-bar label="Main">
<bit-tab-link [route]="['active']">Active</bit-tab-link>
<bit-tab-link [route]="['item-2']">Item 2</bit-tab-link>
<bit-tab-link [route]="['item-3']">Item 3</bit-tab-link>
<bit-tab-link [route]="['disable']" [disabled]="true">Disabled</bit-tab-link>
</bit-tab-nav-bar>
<div class="tw-bg-transparent tw-text-semibold tw-text-center tw-text-main tw-py-10">
<router-outlet></router-outlet>
</div>
`,
});
export const NavigationTabs = NavTabGroupTemplate.bind({});
const PreserveContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
props: args,
template: `
<bit-tab-group label="Preserve Content Tabs" [preserveContent]="true" class="tw-text-main">
export const PreserveContentTabs: Story = {
render: (args: any) => ({
props: args,
template: `
<bit-tab-group label="Preserve Content Tabs" [preserveContent]="true" class="tw-text-main">
<bit-tab label="Text Tab">
<p>
Play the video in the other tab and switch back to hear the video is still playing.
</p>
<p>Play the video in the other tab and switch back to hear the video is still playing.</p>
</bit-tab>
<bit-tab label="Video Tab">
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/H0-yWbe5XG4"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
width="560"
height="315"
src="https://www.youtube.com/embed/H0-yWbe5XG4"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</bit-tab>
</bit-tab-group>
`,
});
</bit-tab-group>
`,
}),
};
export const PreserveContentTabs = PreserveContentTabGroupTemplate.bind({});
const KeyboardNavTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
props: args,
template: `
<bit-tab-group label="Keyboard Navigation Tabs" class="tw-text-main">
export const KeyboardNavigation: Story = {
render: (args: any) => ({
props: args,
template: `
<bit-tab-group label="Keyboard Navigation Tabs" class="tw-text-main">
<bit-tab label="Form Tab">
<p>
You can navigate through all tab labels, form inputs, and the button that is outside the tab group via
@ -153,9 +155,8 @@ const KeyboardNavTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
<bit-tab label="No Focusable Content Tab" [contentTabIndex]="0">
<p>This tab has no focusable content, but the panel should still be focusable</p>
</bit-tab>
</bit-tab-group>
<button bitButton buttonType="primary" class="tw-mt-5">External Button</button>
`,
});
export const KeyboardNavigation = KeyboardNavTabGroupTemplate.bind({});
</bit-tab-group>
<button bitButton buttonType="primary" class="tw-mt-5">External Button</button>
`,
}),
};

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { BadgeModule } from "../badge";
@ -25,30 +25,32 @@ export default {
},
} as Meta;
const Template: Story<ToggleGroupComponent> = (args: ToggleGroupComponent) => ({
props: args,
template: `
<bit-toggle-group [(selected)]="selected" aria-label="People list filter">
<bit-toggle value="all">
All <span bitBadge badgeType="info">3</span>
</bit-toggle>
type Story = StoryObj<ToggleGroupComponent>;
<bit-toggle value="invited">
Invited
</bit-toggle>
<bit-toggle value="accepted">
Accepted <span bitBadge badgeType="info">2</span>
</bit-toggle>
<bit-toggle value="deactivated">
Deactivated
</bit-toggle>
</bit-toggle-group>
`,
});
export const Default = Template.bind({});
Default.args = {
selected: "all",
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-toggle-group [(selected)]="selected" aria-label="People list filter">
<bit-toggle value="all">
All <span bitBadge badgeType="info">3</span>
</bit-toggle>
<bit-toggle value="invited">
Invited
</bit-toggle>
<bit-toggle value="accepted">
Accepted <span bitBadge badgeType="info">2</span>
</bit-toggle>
<bit-toggle value="deactivated">
Deactivated
</bit-toggle>
</bit-toggle-group>
`,
}),
args: {
selected: "all",
},
};

View File

@ -0,0 +1,67 @@
import { Meta, Story, Source, Primary, Controls } from "@storybook/addon-docs";
import * as stories from "./typography.stories";
<Meta of={stories} />
# Typography
<Primary />
<Controls />
## Stories
<Story of={stories.H1} />
```html
<h1 bitTypography="h1">H1</h1>
```
<Story of={stories.H2} />
```html
<h2 bitTypography="h2">H2</h2>
```
<Story of={stories.H3} />
```html
<h3 bitTypography="h3">H3</h3>
```
<Story of={stories.H4} />
```html
<h4 bitTypography="h4">H4</h4>
```
<Story of={stories.H5} />
```html
<h5 bitTypography="h5">H5</h5>
```
<Story of={stories.H6} />
```html
<h6 bitTypography="h6">H6</h6>
```
<Story of={stories.Body1} />
```html
<p bitTypography="body1">Body 1</p>
```
<Story of={stories.Body2} />
```html
<p bitTypography="body2">Body 2</h1>
```
<Story of={stories.Helper} />
```html
<p bitTypography="helper">Helper Text</h1>
```

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, StoryObj } from "@storybook/angular";
import { TypographyDirective } from "./typography.directive";
@ -10,61 +10,81 @@ export default {
},
} as Meta;
const Template: Story = (args) => ({
props: args,
template: `<span [bitTypography]="bitTypography" class="tw-text-main">{{text}}</span>`,
});
type Story = StoryObj<TypographyDirective & { text: string }>;
export const H1 = Template.bind({});
H1.args = {
bitTypography: "h1",
text: "h1. Page Title",
export const H1: Story = {
render: (args) => ({
props: args,
template: `<span [bitTypography]="bitTypography" class="tw-text-main">{{text}}</span>`,
}),
args: {
bitTypography: "h1",
text: "h1. Page Title",
},
};
export const H2 = Template.bind({});
H2.args = {
bitTypography: "h2",
text: "h2. Page Section",
export const H2: Story = {
...H1,
args: {
bitTypography: "h2",
text: "h2. Page Section",
},
};
export const H3 = Template.bind({});
H3.args = {
bitTypography: "h3",
text: "h3. Page Section",
export const H3: Story = {
...H1,
args: {
bitTypography: "h3",
text: "h3. Page Section",
},
};
export const H4 = Template.bind({});
H4.args = {
bitTypography: "h4",
text: "h4. Page Section",
export const H4: Story = {
...H1,
args: {
bitTypography: "h4",
text: "h4. Page Section",
},
};
export const H5 = Template.bind({});
H5.args = {
bitTypography: "h5",
text: "h5. Page Section",
export const H5: Story = {
...H1,
args: {
bitTypography: "h5",
text: "h5. Page Section",
},
};
export const H6 = Template.bind({});
H6.args = {
bitTypography: "h6",
text: "h6. Page Section",
export const H6: Story = {
...H1,
args: {
bitTypography: "h6",
text: "h6. Page Section",
},
};
export const Body1 = Template.bind({});
Body1.args = {
bitTypography: "body1",
text: "Body 1",
export const Body1: Story = {
...H1,
args: {
bitTypography: "body1",
text: "Body 1",
},
};
export const Body2 = Template.bind({});
Body2.args = {
bitTypography: "body2",
text: "Body 2",
export const Body2: Story = {
...H1,
args: {
bitTypography: "body2",
text: "Body 2",
},
};
export const Helper = Template.bind({});
Helper.args = {
bitTypography: "helper",
text: "Helper Text",
export const Helper: Story = {
...H1,
args: {
bitTypography: "helper",
text: "Helper Text",
},
};

View File

@ -1,7 +1,12 @@
/* eslint-disable */
const config = require("./tailwind.config.base");
config.content = ["./libs/components/src/**/*.{html,ts,mdx}", "./.storybook/preview.js"];
config.content = [
"libs/components/src/**/*.{html,ts,mdx}",
"apps/web/src/**/*.{html,ts,mdx}",
"bitwarden_license/bit-web/src/**/*.{html,ts,mdx}",
".storybook/preview.tsx",
];
config.safelist = [
{
pattern: /tw-bg-(.*)/,

18634
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,9 +23,9 @@
"test:watch:all": "jest --watchAll",
"test:types": "node ./scripts/test-types.js",
"docs:json": "compodoc -p ./tsconfig.json -e json -d .",
"storybook": "npm run docs:json && start-storybook -p 6006",
"build-storybook": "npm run docs:json && build-storybook",
"chromatic": "chromatic --exit-zero-on-changes"
"storybook": "ng run components:storybook",
"build-storybook": "ng run components:build-storybook",
"build-storybook:ci": "ng run components:build-storybook --webpack-stats-json"
},
"workspaces": [
"apps/*",
@ -45,13 +45,11 @@
"@electron/rebuild": "3.2.13",
"@fluffy-spoon/substitute": "1.208.0",
"@ngtools/webpack": "15.2.8",
"@storybook/addon-a11y": "6.5.16",
"@storybook/addon-actions": "6.5.16",
"@storybook/addon-essentials": "6.5.16",
"@storybook/addon-links": "6.5.16",
"@storybook/angular": "6.5.16",
"@storybook/builder-webpack5": "6.5.16",
"@storybook/manager-webpack5": "6.5.16",
"@storybook/addon-a11y": "7.0.11",
"@storybook/addon-actions": "7.0.11",
"@storybook/addon-essentials": "7.0.11",
"@storybook/addon-links": "7.0.11",
"@storybook/angular": "7.0.11",
"@types/argon2-browser": "1.18.1",
"@types/chrome": "0.0.236",
"@types/duo_web_sdk": "2.7.1",
@ -73,6 +71,7 @@
"@types/node-ipc": "9.2.0",
"@types/papaparse": "5.3.7",
"@types/proper-lockfile": "4.1.2",
"@types/react": "16.14.41",
"@types/retry": "0.12.2",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.59.7",
@ -122,11 +121,14 @@
"prettier": "2.8.8",
"prettier-plugin-tailwindcss": "0.3.0",
"process": "0.11.10",
"react": "18.2.0",
"react-dom": "18.2.0",
"regedit": "^3.0.3",
"remark-gfm": "^3.0.1",
"rimraf": "5.0.1",
"sass": "1.62.1",
"sass-loader": "13.3.0",
"storybook-addon-designs": "6.3.1",
"storybook": "7.0.11",
"style-loader": "3.3.3",
"tailwindcss": "3.3.2",
"ts-jest": "29.1.0",
@ -199,11 +201,13 @@
},
"overrides": {
"tailwindcss": "$tailwindcss",
"react": "18.2.0",
"@storybook/angular": {
"zone.js": "0.12.0"
}
},
"resolutions": {
"@types/react": "18.2.0"
},
"lint-staged": {
"*": "prettier --cache --ignore-unknown --write",
"*.ts": "eslint --cache --cache-strategy content --fix"