1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-21 11:35:34 +01:00

[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/validators/notAllowedValueAsync.validator.ts
./libs/angular/src/services/theming/themeBuilder.ts ./libs/angular/src/services/theming/themeBuilder.ts
./libs/angular/src/interfaces/selectOptions.ts ./libs/angular/src/interfaces/selectOptions.ts
./libs/components/src/stories/Introduction.stories.mdx
./libs/common/src/misc/nodeUtils.ts ./libs/common/src/misc/nodeUtils.ts
./libs/common/src/misc/linkedFieldOption.decorator.ts ./libs/common/src/misc/linkedFieldOption.decorator.ts
./libs/common/src/misc/serviceUtils.ts ./libs/common/src/misc/serviceUtils.ts

View File

@ -32,11 +32,16 @@ jobs:
- name: Install Node dependencies - name: Install Node dependencies
run: npm ci 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 - name: Publish to Chromatic
uses: chromaui/action@a89b674adf766dbde41ad9ea2b2b60b91188a0f0 uses: chromaui/action@a89b674adf766dbde41ad9ea2b2b60b91188a0f0
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
storybookBuildDir: ./storybook-static
exitOnceUploaded: true exitOnceUploaded: true
onlyChanged: true onlyChanged: true
externals: "[\"libs/components/**/*.scss\", \"libs/components/tailwind.config*.js\"]" 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"], "exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"],
"files": [ "files": [
"./typings.d.ts", "./typings.d.ts",
"./preview.tsx",
"../libs/components/src/main.ts", "../libs/components/src/main.ts",
"../libs/components/src/polyfills.ts" "../libs/components/src/polyfills.ts"
] ]

View File

@ -135,20 +135,25 @@
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
}
}
}, },
"storybook": { "storybook": {
"projectType": "application", "builder": "@storybook/angular:start-storybook",
"root": "libs/components",
"sourceRoot": "libs/components/src",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"tsConfig": ".storybook/tsconfig.json", "configDir": ".storybook",
"styles": ["libs/components/src/styles.scss", "libs/components/src/styles.css"], "browserTarget": "components:build",
"scripts": [] "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 { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { action } from "@storybook/addon-actions"; 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 { JslibModule } from "@bitwarden/angular/jslib.module";
import { import {
@ -39,12 +40,14 @@ export default {
FormsModule, FormsModule,
TabsModule, TabsModule,
TableModule, TableModule,
PreloadedEnglishI18nModule,
JslibModule, JslibModule,
IconButtonModule, IconButtonModule,
], ],
providers: [], providers: [],
}), }),
applicationConfig({
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
}),
], ],
parameters: {}, parameters: {},
argTypes: { 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 { RouterModule } from "@angular/router";
import { Meta, Story, moduleMetadata } from "@storybook/angular"; import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -49,11 +49,24 @@ export default {
StoryLayoutComponent, StoryLayoutComponent,
StoryContentComponent, StoryContentComponent,
], ],
imports: [ imports: [JslibModule, MenuModule, IconButtonModule, LinkModule, RouterModule],
JslibModule, providers: [
MenuModule, { provide: OrganizationService, useClass: MockOrganizationService },
IconButtonModule, MockOrganizationService,
LinkModule, {
provide: I18nService,
useFactory: () => {
return new I18nMockService({
moreFromBitwarden: "More from Bitwarden",
switchProducts: "Switch Products",
});
},
},
],
}),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot( RouterModule.forRoot(
[ [
{ {
@ -77,21 +90,9 @@ export default {
}, },
], ],
{ useHash: true } { useHash: true }
)
), ),
], ],
providers: [
{ provide: OrganizationService, useClass: MockOrganizationService },
MockOrganizationService,
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
moreFromBitwarden: "More from Bitwarden",
switchProducts: "Switch Products",
});
},
},
],
}), }),
], ],
} as Meta; } as Meta;

View File

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

View File

@ -1,5 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterTestingModule } from "@angular/router/testing"; 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 { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components"; import { BadgeModule, IconModule } from "@bitwarden/components";
@ -17,15 +18,12 @@ export default {
component: ReportListComponent, component: ReportListComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [ imports: [JslibModule, BadgeModule, RouterTestingModule, IconModule],
JslibModule,
BadgeModule,
RouterTestingModule,
PreloadedEnglishI18nModule,
IconModule,
],
declarations: [PremiumBadgeComponent, ReportCardComponent], declarations: [PremiumBadgeComponent, ReportCardComponent],
}), }),
applicationConfig({
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
}),
], ],
args: { args: {
reports: Object.values(reports).map((report) => ({ 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 { RouterModule } from "@angular/router";
import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { applicationConfig, Meta, moduleMetadata, Story } from "@storybook/angular";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service"; 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 { VaultItemsComponent } from "./vault-items.component";
import { VaultItemsModule } from "./vault-items.module"; import { VaultItemsModule } from "./vault-items.module";
@Component({
template: "",
})
class EmptyComponent {}
const organizations = [...new Array(3).keys()].map(createOrganization); const organizations = [...new Array(3).keys()].map(createOrganization);
const groups = [...Array(3).keys()].map(createGroupView); const groups = [...Array(3).keys()].map(createGroupView);
const collections = [...Array(5).keys()].map(createCollectionView); const collections = [...Array(5).keys()].map(createCollectionView);
@ -46,11 +41,7 @@ export default {
component: VaultItemsComponent, component: VaultItemsComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [ imports: [VaultItemsModule, RouterModule],
VaultItemsModule,
PreloadedEnglishI18nModule,
RouterModule.forRoot([{ path: "**", component: EmptyComponent }], { useHash: true }),
],
providers: [ providers: [
{ {
provide: EnvironmentService, provide: EnvironmentService,
@ -103,6 +94,12 @@ export default {
}, },
], ],
}), }),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
], ],
args: { args: {
disabled: false, 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 { RouterModule } from "@angular/router";
import { Meta, Story, moduleMetadata } from "@storybook/angular"; import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@ -28,7 +28,13 @@ export default {
component: LayoutComponent, component: LayoutComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [ imports: [RouterModule, LayoutModule, IconModule],
declarations: [StoryContentComponent],
providers: [{ provide: OrganizationService, useClass: MockOrganizationService }],
}),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot( RouterModule.forRoot(
[ [
{ {
@ -57,13 +63,10 @@ export default {
}, },
], ],
{ useHash: true } { useHash: true }
)
), ),
LayoutModule, importProvidersFrom(PreloadedEnglishI18nModule),
IconModule,
PreloadedEnglishI18nModule,
], ],
declarations: [StoryContentComponent],
providers: [{ provide: OrganizationService, useClass: MockOrganizationService }],
}), }),
], ],
} as Meta; } as Meta;

View File

@ -1,5 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router"; 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 { delay, of, startWith } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -14,24 +15,15 @@ export default {
component: OnboardingComponent, component: OnboardingComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [ imports: [JslibModule, RouterModule, LinkModule, IconModule, ProgressModule],
JslibModule,
RouterModule.forRoot(
[
{
path: "",
component: OnboardingComponent,
},
],
{ useHash: true }
),
LinkModule,
IconModule,
ProgressModule,
PreloadedEnglishI18nModule,
],
declarations: [OnboardingTaskComponent], declarations: [OnboardingTaskComponent],
}), }),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
], ],
} as Meta; } 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 { 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 { BehaviorSubject, combineLatest, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -16,8 +22,8 @@ import {
NavigationModule, NavigationModule,
TabsModule, TabsModule,
TypographyModule, TypographyModule,
InputModule,
} from "@bitwarden/components"; } 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 { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/app/tests/preloaded-english-i18n.module";
import { HeaderComponent } from "./header.component"; import { HeaderComponent } from "./header.component";
@ -68,15 +74,7 @@ export default {
moduleMetadata({ moduleMetadata({
imports: [ imports: [
JslibModule, JslibModule,
RouterModule.forRoot( RouterModule,
[
{
path: "",
component: HeaderComponent,
},
],
{ useHash: true }
),
AvatarModule, AvatarModule,
BreadcrumbsModule, BreadcrumbsModule,
ButtonModule, ButtonModule,
@ -87,7 +85,6 @@ export default {
TabsModule, TabsModule,
TypographyModule, TypographyModule,
NavigationModule, NavigationModule,
PreloadedEnglishI18nModule,
], ],
declarations: [HeaderComponent, MockProductSwitcher, MockDynamicAvatar], declarations: [HeaderComponent, MockProductSwitcher, MockDynamicAvatar],
providers: [ providers: [
@ -100,6 +97,12 @@ export default {
}, },
], ],
}), }),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
], ],
} as Meta; } as Meta;

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular"; import { Meta, StoryObj } from "@storybook/angular";
import { AvatarComponent } from "./avatar.component"; import { AvatarComponent } from "./avatar.component";
@ -18,41 +18,46 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<AvatarComponent> = (args: AvatarComponent) => ({ type Story = StoryObj<AvatarComponent>;
props: args,
});
export const Default = Template.bind({}); export const Default: Story = {
Default.args = { args: {
color: "#175ddc", color: "#175ddc",
},
}; };
export const Large = Template.bind({}); export const Large: Story = {
Large.args = { args: {
size: "large", size: "large",
},
}; };
export const Small = Template.bind({}); export const Small: Story = {
Small.args = { args: {
size: "small", size: "small",
},
}; };
export const LightBackground = Template.bind({}); export const LightBackground: Story = {
LightBackground.args = { args: {
color: "#d2ffcf", color: "#d2ffcf",
},
}; };
export const Border = Template.bind({}); export const Border: Story = {
Border.args = { args: {
border: true, border: true,
},
}; };
export const ColorByID = Template.bind({}); export const ColorByID: Story = {
ColorByID.args = { args: {
id: 236478, id: "236478",
},
}; };
export const ColorByText = Template.bind({}); export const ColorByText: Story = {
ColorByText.args = { args: {
text: "Jason Doe", 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -38,16 +38,19 @@ export default {
}, },
} as Meta; } as Meta;
const ListTemplate: Story<BadgeListComponent> = (args: BadgeListComponent) => ({ type Story = StoryObj<BadgeListComponent>;
export const Default: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list> <bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>
`, `,
}); }),
export const Default = ListTemplate.bind({}); args: {
Default.args = {
badgeType: "info", badgeType: "info",
maxItems: 3, maxItems: 3,
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"], items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"],
},
}; };

View File

@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { BadgeDirective } from "./badge.directive"; import { BadgeDirective } from "./badge.directive";
@ -21,9 +21,12 @@ export default {
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16956", 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) => ({ type Story = StoryObj<BadgeDirective>;
export const Primary: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<span class="tw-text-main">Span </span><span bitBadge [badgeType]="badgeType">Badge</span> <span class="tw-text-main">Span </span><span bitBadge [badgeType]="badgeType">Badge</span>
@ -32,32 +35,40 @@ const Template: Story<BadgeDirective> = (args: BadgeDirective) => ({
<br><br> <br><br>
<span class="tw-text-main">Button </span><button bitBadge [badgeType]="badgeType">Badge</button> <span class="tw-text-main">Button </span><button bitBadge [badgeType]="badgeType">Badge</button>
`, `,
}); }),
};
export const Primary = Template.bind({}); export const Secondary: Story = {
Primary.args = {}; ...Primary,
args: {
export const Secondary = Template.bind({});
Secondary.args = {
badgeType: "secondary", badgeType: "secondary",
},
}; };
export const Success = Template.bind({}); export const Success: Story = {
Success.args = { ...Primary,
args: {
badgeType: "success", badgeType: "success",
},
}; };
export const Danger = Template.bind({}); export const Danger: Story = {
Danger.args = { ...Primary,
args: {
badgeType: "danger", badgeType: "danger",
},
}; };
export const Warning = Template.bind({}); export const Warning: Story = {
Warning.args = { ...Primary,
args: {
badgeType: "warning", badgeType: "warning",
},
}; };
export const Info = Template.bind({}); export const Info: Story = {
Info.args = { ...Primary,
args: {
badgeType: "info", 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 # Banner
@ -15,6 +17,10 @@ persist across all pages a user navigates to.
- Avoid stacking multiple banners. - Avoid stacking multiple banners.
- Banners support a button link (text button). - Banners support a button link (text button).
<Primary />
<Controls />
## Types ## Types
Icons should remain consistent across these types. Do not change the icon without consulting 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 ### Premium
<Story id="component-library-banner--premium" /> <Story of={stories.Premium} />
Used primarily to encourage user to upgrade to premium. Used primarily to encourage user to upgrade to premium.
### Info ### Info
<Story id="component-library-banner--info" /> <Story of={stories.Info} />
Used to communicate release notes, server maintenance or other informative event. Used to communicate release notes, server maintenance or other informative event.
### Warning ### Warning
<Story id="component-library-banner--warning" /> <Story of={stories.Warning} />
Used to alert the user of outdated info or versions. Used to alert the user of outdated info or versions.
### Danger ### 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. 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -39,9 +39,15 @@ export default {
argTypes: { argTypes: {
onClose: { action: "onClose" }, onClose: { action: "onClose" },
}, },
} as Meta; } as Meta<BannerComponent>;
const Template: Story<BannerComponent> = (args: BannerComponent) => ({ type Story = StoryObj<BannerComponent>;
export const Premium: Story = {
args: {
bannerType: "premium",
},
render: (args: BannerComponent) => ({
props: args, props: args,
template: ` template: `
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)"> <bit-banner [bannerType]="bannerType" (onClose)="onClose($event)">
@ -49,24 +55,30 @@ const Template: Story<BannerComponent> = (args: BannerComponent) => ({
<button bitLink linkType="contrast">Button</button> <button bitLink linkType="contrast">Button</button>
</bit-banner> </bit-banner>
`, `,
}); }),
};
export const Premium = Template.bind({});
Premium.args = { Premium.args = {
bannerType: "premium", bannerType: "premium",
}; };
export const Info = Template.bind({}); export const Info: Story = {
Info.args = { ...Premium,
args: {
bannerType: "info", bannerType: "info",
},
}; };
export const Warning = Template.bind({}); export const Warning: Story = {
Warning.args = { ...Premium,
args: {
bannerType: "warning", bannerType: "warning",
},
}; };
export const Danger = Template.bind({}); export const Danger: Story = {
Danger.args = { ...Premium,
args: {
bannerType: "danger", 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 { 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 { IconButtonModule } from "../icon-button";
import { LinkModule } from "../link"; import { LinkModule } from "../link";
@ -26,16 +26,19 @@ export default {
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
declarations: [BreadcrumbComponent], declarations: [BreadcrumbComponent],
imports: [ imports: [LinkModule, MenuModule, IconButtonModule, RouterModule],
LinkModule, }),
MenuModule, applicationConfig({
IconButtonModule, providers: [
RouterModule.forRoot([{ path: "**", component: EmptyComponent }], { useHash: true }), importProvidersFrom(
RouterModule.forRoot([{ path: "**", component: EmptyComponent }], { useHash: true })
),
], ],
}), }),
], ],
args: { args: {
items: [], items: [],
show: 3,
}, },
argTypes: { argTypes: {
breadcrumbs: { breadcrumbs: {
@ -45,7 +48,10 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<BreadcrumbsComponent> = (args: BreadcrumbsComponent) => ({ type Story = StoryObj<BreadcrumbsComponent & { items: Breadcrumb[] }>;
export const TopLevel: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<h3 class="tw-text-main">Router links</h3> <h3 class="tw-text-main">Router links</h3>
@ -62,23 +68,26 @@ const Template: Story<BreadcrumbsComponent> = (args: BreadcrumbsComponent) => ({
</bit-breadcrumbs> </bit-breadcrumbs>
</p> </p>
`, `,
}); }),
export const TopLevel = Template.bind({}); args: {
TopLevel.args = {
items: [{ icon: "bwi-star", name: "Top Level" }] as Breadcrumb[], items: [{ icon: "bwi-star", name: "Top Level" }] as Breadcrumb[],
},
}; };
export const SecondLevel = Template.bind({}); export const SecondLevel: Story = {
SecondLevel.args = { ...TopLevel,
args: {
items: [ items: [
{ name: "Acme Vault", route: "/" }, { name: "Acme Vault", route: "/" },
{ icon: "bwi-collection", name: "Collection", route: "collection" }, { icon: "bwi-collection", name: "Collection", route: "collection" },
] as Breadcrumb[], ] as Breadcrumb[],
},
}; };
export const Overflow = Template.bind({}); export const Overflow: Story = {
Overflow.args = { ...TopLevel,
args: {
items: [ items: [
{ name: "Acme Vault", route: "" }, { name: "Acme Vault", route: "" },
{ icon: "bwi-collection", name: "Collection", route: "collection" }, { icon: "bwi-collection", name: "Collection", route: "collection" },
@ -88,4 +97,5 @@ Overflow.args = {
{ icon: "bwi-collection", name: "Middle-Collection 4", route: "middle-collection-4" }, { icon: "bwi-collection", name: "Middle-Collection 4", route: "middle-collection-4" },
{ icon: "bwi-collection", name: "End Collection", route: "end-collection" }, { icon: "bwi-collection", name: "End Collection", route: "end-collection" },
] as Breadcrumb[], ] 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 # Button
Buttons are interactive elements that can be triggered using a mouse, keyboard, or touch. They are 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. used to indicate actions that can be performed by a user such as submitting a form.
<Primary />
<Controls />
## Guidelines ## Guidelines
### Choosing the `<a>` or `<button>` ### 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 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). the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page).
<Story id="component-library-button--loading" /> <Story of={stories.Loading} />
## Styles ## Styles
@ -58,14 +64,14 @@ There are 3 main styles for the button: Primary, Secondary, and Danger.
### Primary ### 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 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. to the main purpose of a page. There should never be 2 primary styled buttons next to each other.
### Secondary ### 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 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 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 ### 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. 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 Both the disabled and loading states use the default states color with a 60% opacity or
`tw-opacity-60`. `tw-opacity-60`.
<Story id="component-library-button--disabled" /> <Story of={stories.Disabled} />
## Block ## Block
Typically button widths expand with their text. In some causes though buttons may need to be 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. 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"; import { ButtonComponent } from "./button.component";
@ -16,53 +16,62 @@ export default {
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=5115%3A26950", 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) => ({ type Story = StoryObj<ButtonComponent>;
export const Primary: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block">Button</button> <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> <a bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">Link</a>
`, `,
}); }),
args: {
export const Primary = Template.bind({});
Primary.args = {
buttonType: "primary", buttonType: "primary",
},
}; };
export const Secondary = Template.bind({}); export const Secondary: Story = {
Secondary.args = { ...Primary,
args: {
buttonType: "secondary", buttonType: "secondary",
},
}; };
export const Danger = Template.bind({}); export const Danger: Story = {
Danger.args = { ...Primary,
args: {
buttonType: "danger", buttonType: "danger",
},
}; };
const AllStylesTemplate: Story = (args) => ({ export const Loading: Story = {
render: (args) => ({
props: args, props: args,
template: ` 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="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="secondary" class="tw-mr-2">Secondary</button>
<button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
`, `,
}); }),
args: {
export const Loading = AllStylesTemplate.bind({});
Loading.args = {
disabled: false, disabled: false,
loading: true, loading: true,
},
}; };
export const Disabled = AllStylesTemplate.bind({}); export const Disabled: Story = {
Disabled.args = { ...Loading,
args: {
disabled: true, disabled: true,
loading: false, loading: false,
},
}; };
const DisabledWithAttributeTemplate: Story = (args) => ({ export const DisabledWithAttribute: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<ng-container *ngIf="disabled"> <ng-container *ngIf="disabled">
@ -76,15 +85,15 @@ const DisabledWithAttributeTemplate: Story = (args) => ({
<button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> <button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
</ng-container> </ng-container>
`, `,
}); }),
args: {
export const DisabledWithAttribute = DisabledWithAttributeTemplate.bind({});
DisabledWithAttribute.args = {
disabled: true, disabled: true,
loading: false, loading: false,
},
}; };
const BlockTemplate: Story<ButtonComponent> = (args: ButtonComponent) => ({ export const Block: Story = {
render: (args: ButtonComponent) => ({
props: args, props: args,
template: ` template: `
<span class="tw-flex"> <span class="tw-flex">
@ -95,9 +104,8 @@ const BlockTemplate: Story<ButtonComponent> = (args: ButtonComponent) => ({
<a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a> <a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a>
</span> </span>
`, `,
}); }),
args: {
export const Block = BlockTemplate.bind({});
Block.args = {
block: true, 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -35,31 +35,39 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<CalloutComponent> = (args: CalloutComponent) => ({ type Story = StoryObj<CalloutComponent>;
export const Success: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-callout [type]="type" [title]="title">Content</bit-callout> <bit-callout [type]="type" [title]="title">Content</bit-callout>
`, `,
}); }),
args: {
export const Success = Template.bind({});
Success.args = {
type: "success", type: "success",
title: "Success", title: "Success",
},
}; };
export const Info = Template.bind({}); export const Info: Story = {
Info.args = { ...Success,
args: {
type: "info", type: "info",
title: "Info", title: "Info",
},
}; };
export const Warning = Template.bind({}); export const Warning: Story = {
Warning.args = { ...Success,
args: {
type: "warning", type: "warning",
},
}; };
export const Danger = Template.bind({}); export const Danger: Story = {
Danger.args = { ...Success,
args: {
type: "danger", type: "danger",
},
}; };

View File

@ -1,6 +1,6 @@
import { Component, Input } from "@angular/core"; import { Component, Input } from "@angular/core";
import { FormsModule, ReactiveFormsModule, FormBuilder, Validators } from "@angular/forms"; 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"; import { I18nService } from "@bitwarden/common/src/abstractions/i18n.service";
@ -69,25 +69,28 @@ export default {
}, },
} as Meta; } as Meta;
const DefaultTemplate: Story<ExampleComponent> = (args: ExampleComponent) => ({ type Story = StoryObj<ExampleComponent>;
export const Default: Story = {
render: (args) => ({
props: args, props: args,
template: `<app-example [checked]="checked" [disabled]="disabled"></app-example>`, template: `<app-example [checked]="checked" [disabled]="disabled"></app-example>`,
}); }),
parameters: {
export const Default = DefaultTemplate.bind({});
Default.parameters = {
docs: { docs: {
source: { source: {
code: template, code: template,
}, },
}, },
}; },
Default.args = { args: {
checked: false, checked: false,
disabled: false, disabled: false,
},
}; };
const CustomTemplate: Story = (args) => ({ export const Custom: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<div class="tw-flex tw-flex-col tw-w-32"> <div class="tw-flex tw-flex-col tw-w-32">
@ -105,7 +108,5 @@ const CustomTemplate: Story = (args) => ({
</label> </label>
</div> </div>
`, `,
}); }),
CustomTemplate.args = {}; };
export const Custom = CustomTemplate.bind({});

View File

@ -23,7 +23,7 @@ enum CharacterType {
preserveWhitespaces: false, preserveWhitespaces: false,
}) })
export class ColorPasswordComponent { export class ColorPasswordComponent {
@Input() private password: string = null; @Input() password: string = null;
@Input() showCount = false; @Input() showCount = false;
characterStyles: Record<CharacterType, string[]> = { 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"; import { ColorPasswordComponent } from "./color-password.component";
@ -19,34 +19,40 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<ColorPasswordComponent> = (args: ColorPasswordComponent) => ({ type Story = StoryObj<ColorPasswordComponent>;
export const ColorPassword: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password> <bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
`, `,
}); }),
};
const WrappedTemplate: Story<ColorPasswordComponent> = (args: ColorPasswordComponent) => ({ export const WrappedColorPassword: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<div class="tw-max-w-32"> <div class="tw-max-w-32">
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password> <bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
</div> </div>
`, `,
}); }),
export const ColorPassword = Template.bind({});
export const WrappedColorPassword = WrappedTemplate.bind({});
export const ColorPasswordCount = Template.bind({});
ColorPasswordCount.args = {
password: examplePassword,
showCount: true,
}; };
export const WrappedColorPasswordCount = WrappedTemplate.bind({}); export const ColorPasswordCount: Story = {
WrappedColorPasswordCount.args = { ...ColorPassword,
args: {
password: examplePassword, password: examplePassword,
showCount: true, 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 { DIALOG_DATA, DialogModule, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core"; 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -90,8 +90,6 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<StoryDialogComponent> = (args: StoryDialogComponent) => ({ type Story = StoryObj<StoryDialogComponent>;
props: args,
});
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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -50,7 +50,10 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<DialogComponent> = (args: DialogComponent) => ({ type Story = StoryObj<DialogComponent & { title: string }>;
export const Default: Story = {
render: (args: DialogComponent) => ({
props: args, props: args,
template: ` template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding"> <bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
@ -70,40 +73,48 @@ const Template: Story<DialogComponent> = (args: DialogComponent) => ({
</ng-container> </ng-container>
</bit-dialog> </bit-dialog>
`, `,
}); }),
args: {
export const Default = Template.bind({});
Default.args = {
dialogSize: "default", dialogSize: "default",
title: "Default", title: "Default",
},
}; };
export const Small = Template.bind({}); export const Small: Story = {
Small.args = { ...Default,
args: {
dialogSize: "small", dialogSize: "small",
title: "Small", title: "Small",
},
}; };
export const LongTitle = Template.bind({}); export const LongTitle: Story = {
LongTitle.args = { ...Default,
args: {
dialogSize: "small", dialogSize: "small",
title: "Long_Title_That_Should_Be_Truncated", title: "Long_Title_That_Should_Be_Truncated",
},
}; };
export const Large = Template.bind({}); export const Large: Story = {
Large.args = { ...Default,
args: {
dialogSize: "large", dialogSize: "large",
title: "Large", title: "Large",
},
}; };
export const Loading = Template.bind({}); export const Loading: Story = {
Loading.args = { ...Default,
args: {
dialogSize: "large", dialogSize: "large",
loading: true, loading: true,
title: "Loading", title: "Loading",
},
}; };
const TemplateScrolling: Story<DialogComponent> = (args: DialogComponent) => ({ export const ScrollingContent: Story = {
render: (args: DialogComponent) => ({
props: args, props: args,
template: ` template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding"> <bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
@ -121,14 +132,14 @@ const TemplateScrolling: Story<DialogComponent> = (args: DialogComponent) => ({
</ng-container> </ng-container>
</bit-dialog> </bit-dialog>
`, `,
}); }),
args: {
export const ScrollingContent = TemplateScrolling.bind({});
ScrollingContent.args = {
dialogSize: "small", dialogSize: "small",
},
}; };
const TemplateTabbed: Story<DialogComponent> = (args: DialogComponent) => ({ export const TabContent: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding"> <bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
@ -146,14 +157,11 @@ const TemplateTabbed: Story<DialogComponent> = (args: DialogComponent) => ({
</ng-container> </ng-container>
</bit-dialog> </bit-dialog>
`, `,
}); }),
args: {
export const TabContent = TemplateTabbed.bind({});
TabContent.args = {
dialogSize: "large", dialogSize: "large",
disablePadding: true, disablePadding: true,
}; },
TabContent.story = {
parameters: { parameters: {
docs: { docs: {
storyDescription: `An example of using the \`bitTabGroup\` component within the Dialog. The content padding should be 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 { DialogModule } from "@angular/cdk/dialog";
import { Component } from "@angular/core"; 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 { SimpleDialogType, SimpleDialogOptions } from "@bitwarden/angular/services/dialog";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -239,8 +239,6 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<StoryDialogComponent> = (args: StoryDialogComponent) => ({ type Story = StoryObj<StoryDialogComponent>;
props: args,
});
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 { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core"; 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -90,8 +90,6 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<StoryDialogComponent> = (args: StoryDialogComponent) => ({ type Story = StoryObj<StoryDialogComponent>;
props: args,
});
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 { ButtonModule } from "../../button";
import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive";
@ -22,7 +22,10 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({ type Story = StoryObj<SimpleDialogComponent & { useDefaultIcon: boolean }>;
export const Default: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-simple-dialog> <bit-simple-dialog>
@ -34,11 +37,11 @@ const Template: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) =>
</ng-container> </ng-container>
</bit-simple-dialog> </bit-simple-dialog>
`, `,
}); }),
};
export const Default = Template.bind({}); export const CustomIcon: Story = {
render: (args) => ({
const TemplateWithIcon: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args, props: args,
template: ` template: `
<bit-simple-dialog> <bit-simple-dialog>
@ -51,11 +54,11 @@ const TemplateWithIcon: Story<SimpleDialogComponent> = (args: SimpleDialogCompon
</ng-container> </ng-container>
</bit-simple-dialog> </bit-simple-dialog>
`, `,
}); }),
};
export const CustomIcon = TemplateWithIcon.bind({}); export const ScrollingContent: Story = {
render: (args: SimpleDialogComponent) => ({
const TemplateScroll: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args, props: args,
template: ` template: `
<bit-simple-dialog> <bit-simple-dialog>
@ -74,9 +77,8 @@ const TemplateScroll: Story<SimpleDialogComponent> = (args: SimpleDialogComponen
</ng-container> </ng-container>
</bit-simple-dialog> </bit-simple-dialog>
`, `,
}); }),
args: {
export const ScrollingContent = TemplateScroll.bind({});
ScrollingContent.args = {
useDefaultIcon: true, useDefaultIcon: true,
},
}; };

View File

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

View File

@ -1,5 +1,5 @@
import { UntypedFormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -50,7 +50,8 @@ function submit() {
formObj.markAllAsTouched(); formObj.markAllAsTouched();
} }
const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({ export const Default: StoryObj<BitFormFieldComponent> = {
render: (args) => ({
props: { props: {
formObj: formObj, formObj: formObj,
submit: submit, submit: submit,
@ -72,7 +73,5 @@ const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) =>
<bit-error-summary [formGroup]="formObj"></bit-error-summary> <bit-error-summary [formGroup]="formObj"></bit-error-summary>
</form> </form>
`, `,
}); }),
};
export const Default = Template.bind({});
Default.props = {};

View File

@ -7,7 +7,7 @@ import {
ValidatorFn, ValidatorFn,
Validators, Validators,
} from "@angular/forms"; } 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -87,8 +87,10 @@ function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
function submit() { function submit() {
defaultFormObj.markAllAsTouched(); defaultFormObj.markAllAsTouched();
} }
type Story = StoryObj<BitFormFieldComponent>;
const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({ export const Default: Story = {
render: (args) => ({
props: { props: {
formObj: defaultFormObj, formObj: defaultFormObj,
submit: submit, submit: submit,
@ -103,12 +105,11 @@ const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) =>
</bit-form-field> </bit-form-field>
</form> </form>
`, `,
}); }),
};
export const Default = Template.bind({}); export const Required: Story = {
Default.props = {}; render: (args) => ({
const RequiredTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: { props: {
formObj: formObj, formObj: formObj,
...args, ...args,
@ -124,12 +125,11 @@ const RequiredTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldCompon
<input bitInput formControlName="required" placeholder="Placeholder" /> <input bitInput formControlName="required" placeholder="Placeholder" />
</bit-form-field> </bit-form-field>
`, `,
}); }),
};
export const Required = RequiredTemplate.bind({}); export const Hint: Story = {
Required.props = {}; render: (args: BitFormFieldComponent) => ({
const HintTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: { props: {
formObj: formObj, formObj: formObj,
...args, ...args,
@ -141,12 +141,11 @@ const HintTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent)
<bit-hint>Long hint text</bit-hint> <bit-hint>Long hint text</bit-hint>
</bit-form-field> </bit-form-field>
`, `,
}); }),
};
export const Hint = HintTemplate.bind({}); export const Disabled: Story = {
Required.props = {}; render: (args) => ({
const DisabledTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args, props: args,
template: ` template: `
<bit-form-field> <bit-form-field>
@ -154,12 +153,12 @@ const DisabledTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldCompon
<input bitInput placeholder="Placeholder" disabled /> <input bitInput placeholder="Placeholder" disabled />
</bit-form-field> </bit-form-field>
`, `,
}); }),
args: {},
};
export const Disabled = DisabledTemplate.bind({}); export const InputGroup: Story = {
Disabled.args = {}; render: (args) => ({
const GroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args, props: args,
template: ` template: `
<bit-form-field> <bit-form-field>
@ -169,12 +168,12 @@ const GroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent
<span bitSuffix>USD</span> <span bitSuffix>USD</span>
</bit-form-field> </bit-form-field>
`, `,
}); }),
args: {},
};
export const InputGroup = GroupTemplate.bind({}); export const ButtonInputGroup: Story = {
InputGroup.args = {}; render: (args) => ({
const ButtonGroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args, props: args,
template: ` template: `
<bit-form-field> <bit-form-field>
@ -187,14 +186,12 @@ const ButtonGroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldCom
</button> </button>
</bit-form-field> </bit-form-field>
`, `,
}); }),
args: {},
};
export const ButtonInputGroup = ButtonGroupTemplate.bind({}); export const DisabledButtonInputGroup: Story = {
ButtonInputGroup.args = {}; render: (args) => ({
const DisabledButtonInputGroupTemplate: Story<BitFormFieldComponent> = (
args: BitFormFieldComponent
) => ({
props: args, props: args,
template: ` template: `
<bit-form-field> <bit-form-field>
@ -208,12 +205,12 @@ const DisabledButtonInputGroupTemplate: Story<BitFormFieldComponent> = (
</button> </button>
</bit-form-field> </bit-form-field>
`, `,
}); }),
args: {},
};
export const DisabledButtonInputGroup = DisabledButtonInputGroupTemplate.bind({}); export const Select: Story = {
DisabledButtonInputGroup.args = {}; render: (args: BitFormFieldComponent) => ({
const SelectTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args, props: args,
template: ` template: `
<bit-form-field> <bit-form-field>
@ -224,12 +221,12 @@ const SelectTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponen
</select> </select>
</bit-form-field> </bit-form-field>
`, `,
}); }),
args: {},
};
export const Select = SelectTemplate.bind({}); export const AdvancedSelect: Story = {
Select.args = {}; render: (args: BitFormFieldComponent) => ({
const AdvancedSelectTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args, props: args,
template: ` template: `
<bit-form-field> <bit-form-field>
@ -240,12 +237,11 @@ const AdvancedSelectTemplate: Story<BitFormFieldComponent> = (args: BitFormField
</bit-select> </bit-select>
</bit-form-field> </bit-form-field>
`, `,
}); }),
};
export const AdvancedSelect = AdvancedSelectTemplate.bind({}); export const Textarea: Story = {
AdvancedSelectTemplate.args = {}; render: (args: BitFormFieldComponent) => ({
const TextareaTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args, props: args,
template: ` template: `
<bit-form-field> <bit-form-field>
@ -253,7 +249,6 @@ const TextareaTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldCompon
<textarea bitInput rows="4"></textarea> <textarea bitInput rows="4"></textarea>
</bit-form-field> </bit-form-field>
`, `,
}); }),
args: {},
export const Textarea = TextareaTemplate.bind({}); };
Textarea.args = {};

View File

@ -7,7 +7,7 @@ import {
} from "@angular/forms"; } from "@angular/forms";
import { NgSelectModule } from "@ng-select/ng-select"; import { NgSelectModule } from "@ng-select/ng-select";
import { action } from "@storybook/addon-actions"; 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -75,7 +75,10 @@ function submit(formObj: FormGroup) {
formObj.markAllAsTouched(); formObj.markAllAsTouched();
} }
const MultiSelectTemplate: Story<MultiSelectComponent> = (args: MultiSelectComponent) => ({ type Story = StoryObj<MultiSelectComponent & { name: string; hint: string }>;
export const Loading: Story = {
render: (args) => ({
props: { props: {
formObj: formObjFactory(), formObj: formObjFactory(),
submit: submit, submit: submit,
@ -100,25 +103,27 @@ const MultiSelectTemplate: Story<MultiSelectComponent> = (args: MultiSelectCompo
<button type="submit" bitButton buttonType="primary">Submit</button> <button type="submit" bitButton buttonType="primary">Submit</button>
</form> </form>
`, `,
}); }),
args: {
export const Loading = MultiSelectTemplate.bind({}); baseItems: [] as any,
Loading.args = {
baseItems: [],
name: "Loading", name: "Loading",
hint: "This is what a loading multi-select looks like", hint: "This is what a loading multi-select looks like",
loading: "true", loading: true,
},
}; };
export const Disabled = MultiSelectTemplate.bind({}); export const Disabled: Story = {
Disabled.args = { ...Loading,
args: {
name: "Disabled", name: "Disabled",
disabled: "true", disabled: true,
hint: "This is what a disabled multi-select looks like", hint: "This is what a disabled multi-select looks like",
},
}; };
export const Groups = MultiSelectTemplate.bind({}); export const Groups: Story = {
Groups.args = { ...Loading,
args: {
name: "Select groups", name: "Select groups",
hint: "Groups will be assigned to the associated member", hint: "Groups will be assigned to the associated member",
baseItems: [ baseItems: [
@ -130,10 +135,12 @@ Groups.args = {
{ id: "6", listName: "Group 6", labelName: "Group 6", 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" }, { id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
], ],
},
}; };
export const Members = MultiSelectTemplate.bind({}); export const Members: Story = {
Members.args = { ...Loading,
args: {
name: "Select members", name: "Select members",
hint: "Members will be assigned to the associated group/collection", hint: "Members will be assigned to the associated group/collection",
baseItems: [ baseItems: [
@ -162,7 +169,12 @@ Members.args = {
labelName: "Ashley Fletcher", labelName: "Ashley Fletcher",
icon: "bwi-user", icon: "bwi-user",
}, },
{ id: "6", listName: "Rita Olson (rolson@mail.me)", labelName: "Rita Olson", icon: "bwi-user" }, {
id: "6",
listName: "Rita Olson (rolson@mail.me)",
labelName: "Rita Olson",
icon: "bwi-user",
},
{ {
id: "7", id: "7",
listName: "Final listName (fname@mail.me)", listName: "Final listName (fname@mail.me)",
@ -170,10 +182,12 @@ Members.args = {
icon: "bwi-user", icon: "bwi-user",
}, },
], ],
},
}; };
export const Collections = MultiSelectTemplate.bind({}); export const Collections: Story = {
Collections.args = { ...Loading,
args: {
name: "Select collections", name: "Select collections",
hint: "Collections will be assigned to the associated member", hint: "Collections will be assigned to the associated member",
baseItems: [ baseItems: [
@ -213,10 +227,12 @@ Collections.args = {
{ id: "6", listName: "Collection 6", labelName: "Collection 6", 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" }, { id: "7", listName: "Collection 7", labelName: "Collection 7", icon: "bwi-collection" },
], ],
},
}; };
export const MembersAndGroups = MultiSelectTemplate.bind({}); export const MembersAndGroups: Story = {
MembersAndGroups.args = { ...Loading,
args: {
name: "Select groups and members", name: "Select groups and members",
hint: "Members/Groups will be assigned to the associated collection", hint: "Members/Groups will be assigned to the associated collection",
baseItems: [ baseItems: [
@ -233,10 +249,12 @@ MembersAndGroups.args = {
icon: "bwi-user", icon: "bwi-user",
}, },
], ],
},
}; };
export const RemoveSelected = MultiSelectTemplate.bind({}); export const RemoveSelected: Story = {
RemoveSelected.args = { ...Loading,
args: {
name: "Select groups", name: "Select groups",
hint: "Groups will be removed from the list once the dropdown is closed", hint: "Groups will be removed from the list once the dropdown is closed",
baseItems: [ baseItems: [
@ -248,10 +266,12 @@ RemoveSelected.args = {
{ id: "6", listName: "Group 6", labelName: "Group 6", 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" }, { id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
], ],
removeSelectedItems: "true", removeSelectedItems: true,
},
}; };
const StandaloneTemplate: Story<MultiSelectComponent> = (args: MultiSelectComponent) => ({ export const Standalone: Story = {
render: (args) => ({
props: { props: {
...args, ...args,
onItemsConfirmed: actionsData.onItemsConfirmed, onItemsConfirmed: actionsData.onItemsConfirmed,
@ -266,10 +286,8 @@ const StandaloneTemplate: Story<MultiSelectComponent> = (args: MultiSelectCompon
(onItemsConfirmed)="onItemsConfirmed($event)"> (onItemsConfirmed)="onItemsConfirmed($event)">
</bit-multi-select> </bit-multi-select>
`, `,
}); }),
args: {
export const Standalone = StandaloneTemplate.bind({});
Standalone.args = {
baseItems: [ baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" }, { id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" }, { id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
@ -279,5 +297,6 @@ Standalone.args = {
{ id: "6", listName: "Group 6", labelName: "Group 6", 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" }, { id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
], ],
removeSelectedItems: "true", removeSelectedItems: true,
},
}; };

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -38,12 +38,11 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<BitPasswordInputToggleDirective> = ( type Story = StoryObj<BitPasswordInputToggleDirective>;
args: BitPasswordInputToggleDirective
) => ({ export const Default: Story = {
props: { render: (args) => ({
...args, props: args,
},
template: ` template: `
<form> <form>
<bit-form-field> <bit-form-field>
@ -53,17 +52,12 @@ const Template: Story<BitPasswordInputToggleDirective> = (
</bit-form-field> </bit-form-field>
</form> </form>
`, `,
}); }),
};
export const Default = Template.bind({}); export const Binding: Story = {
Default.props = {}; render: (args) => ({
props: args,
const TemplateBinding: Story<BitPasswordInputToggleDirective> = (
args: BitPasswordInputToggleDirective
) => ({
props: {
...args,
},
template: ` template: `
<form> <form>
<bit-form-field> <bit-form-field>
@ -78,9 +72,8 @@ const TemplateBinding: Story<BitPasswordInputToggleDirective> = (
</label> </label>
</form> </form>
`, `,
}); }),
args: {
export const Binding = TemplateBinding.bind({});
Binding.props = {
toggled: false, toggled: false,
},
}; };

View File

@ -7,7 +7,7 @@ import {
ValidatorFn, ValidatorFn,
Validators, Validators,
} from "@angular/forms"; } 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -81,7 +81,10 @@ function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
}; };
} }
const FullExampleTemplate: Story = (args) => ({ type Story = StoryObj;
export const FullExample: Story = {
render: (args) => ({
props: { props: {
formObj: exampleFormObj, formObj: exampleFormObj,
submit: () => exampleFormObj.markAllAsTouched(), submit: () => exampleFormObj.markAllAsTouched(),
@ -139,9 +142,9 @@ const FullExampleTemplate: Story = (args) => ({
<button type="submit" bitButton buttonType="primary">Submit</button> <button type="submit" bitButton buttonType="primary">Submit</button>
</form> </form>
`, `,
}); }),
export const FullExample = FullExampleTemplate.bind({}); args: {
FullExample.args = {
countries, 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"; import { BitIconButtonComponent, IconButtonType } from "./icon-button.component";
@ -31,7 +31,10 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<BitIconButtonComponent> = (args: BitIconButtonComponent) => ({ type Story = StoryObj<BitIconButtonComponent>;
export const Default: Story = {
render: (args: BitIconButtonComponent) => ({
props: { ...args, buttonTypes }, props: { ...args, buttonTypes },
template: ` template: `
<table class="tw-border-spacing-2 tw-text-center tw-text-main"> <table class="tw-border-spacing-2 tw-text-center tw-text-main">
@ -85,14 +88,15 @@ const Template: Story<BitIconButtonComponent> = (args: BitIconButtonComponent) =
</tbody> </tbody>
</table> </table>
`, `,
}); }),
args: {
export const Default = Template.bind({});
Default.args = {
size: "default", size: "default",
},
}; };
export const Small = Template.bind({}); export const Small: Story = {
Small.args = { ...Default,
args: {
size: "small", 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"; import { BitIconComponent } from "./icon.component";
@ -10,18 +10,22 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<BitIconComponent> = (args: BitIconComponent) => ({ type Story = StoryObj<BitIconComponent>;
export const ReportExposedPasswords: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<div class="tw-bg-primary-500 tw-p-5"> <div class="tw-bg-primary-500 tw-p-5">
<bit-icon [icon]="icon" class="tw-text-primary-300"></bit-icon> <bit-icon [icon]="icon" class="tw-text-primary-300"></bit-icon>
</div> </div>
`, `,
}); }),
};
export const ReportExposedPasswords = Template.bind({});
export const UnknownIcon: Story = {
export const UnknownIcon = Template.bind({}); ...ReportExposedPasswords,
UnknownIcon.args = { args: {
icon: "unknown", icon: "unknown" as any,
},
}; };

View File

@ -12,6 +12,7 @@ export * from "./dialog";
export * from "./form-field"; export * from "./form-field";
export * from "./icon-button"; export * from "./icon-button";
export * from "./icon"; export * from "./icon";
export * from "./input";
export * from "./link"; export * from "./link";
export * from "./menu"; export * from "./menu";
export * from "./multi-select"; 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 { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive";
import { LinkModule } from "./link.module"; import { LinkModule } from "./link.module";
@ -24,7 +24,10 @@ export default {
}, },
} as Meta; } as Meta;
const ButtonTemplate: Story<ButtonLinkDirective> = (args: ButtonLinkDirective) => ({ type Story = StoryObj<ButtonLinkDirective>;
export const Buttons: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }"> <div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }">
@ -48,9 +51,14 @@ const ButtonTemplate: Story<ButtonLinkDirective> = (args: ButtonLinkDirective) =
</div> </div>
</div> </div>
`, `,
}); }),
args: {
linkType: "primary",
},
};
const AnchorTemplate: Story<AnchorLinkDirective> = (args: AnchorLinkDirective) => ({ export const Anchors: StoryObj<AnchorLinkDirective> = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }"> <div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-500': linkType === 'contrast' }">
@ -74,33 +82,28 @@ const AnchorTemplate: Story<AnchorLinkDirective> = (args: AnchorLinkDirective) =
</div> </div>
</div> </div>
`, `,
}); }),
args: {
export const Buttons = ButtonTemplate.bind({});
Buttons.args = {
linkType: "primary", linkType: "primary",
},
}; };
export const Anchors = AnchorTemplate.bind({}); export const Inline: Story = {
Anchors.args = { render: (args) => ({
linkType: "primary",
};
const InlineTemplate: Story = (args) => ({
props: args, props: args,
template: ` template: `
<span class="tw-text-main"> <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. 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> </span>
`, `,
}); }),
args: {
export const Inline = InlineTemplate.bind({});
Inline.args = {
linkType: "primary", linkType: "primary",
},
}; };
const DisabledTemplate: Story = (args) => ({ export const Disabled: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<button bitLink disabled linkType="primary" class="tw-mr-2">Primary</button> <button bitLink disabled linkType="primary" class="tw-mr-2">Primary</button>
@ -109,12 +112,11 @@ const DisabledTemplate: Story = (args) => ({
<button bitLink disabled linkType="contrast" class="tw-mr-2">Contrast</button> <button bitLink disabled linkType="contrast" class="tw-mr-2">Contrast</button>
</div> </div>
`, `,
}); }),
parameters: {
export const Disabled = DisabledTemplate.bind({});
Disabled.parameters = {
controls: { controls: {
exclude: ["linkType"], exclude: ["linkType"],
hideNoControlsWarning: true, hideNoControlsWarning: true,
}, },
},
}; };

View File

@ -1,5 +1,5 @@
import { OverlayModule } from "@angular/cdk/overlay"; 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"; import { ButtonModule } from "../button/button.module";
@ -30,7 +30,10 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<MenuTriggerForDirective> = (args: MenuTriggerForDirective) => ({ type Story = StoryObj<MenuTriggerForDirective>;
export const OpenMenu: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-menu #myMenu="menuComponent"> <bit-menu #myMenu="menuComponent">
@ -47,9 +50,10 @@ const Template: Story<MenuTriggerForDirective> = (args: MenuTriggerForDirective)
</div> </div>
</div> </div>
`, `,
}); }),
};
const TemplateWithButton: Story<MenuTriggerForDirective> = (args: MenuTriggerForDirective) => ({ export const ClosedMenu: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<div class="tw-h-40"> <div class="tw-h-40">
@ -63,7 +67,5 @@ const TemplateWithButton: Story<MenuTriggerForDirective> = (args: MenuTriggerFor
<bit-menu-divider></bit-menu-divider> <bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem>Button after divider</button> <button type="button" bitMenuItem>Button after divider</button>
</bit-menu>`, </bit-menu>`,
}); }),
};
export const OpenMenu = Template.bind({});
export const ClosedMenu = TemplateWithButton.bind({});

View File

@ -1,5 +1,5 @@
import { RouterTestingModule } from "@angular/router/testing"; 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -36,7 +36,8 @@ export default {
}, },
} as Meta; } as Meta;
export const Default: Story<NavGroupComponent> = (args) => ({ export const Default: StoryObj<NavGroupComponent> = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-nav-group text="Hello World (Anchor)" [route]="['']" icon="bwi-filter" [open]="true"> <bit-nav-group text="Hello World (Anchor)" [route]="['']" icon="bwi-filter" [open]="true">
@ -50,9 +51,11 @@ export const Default: Story<NavGroupComponent> = (args) => ({
<bit-nav-item text="Child C" icon="bwi-filter"></bit-nav-item> <bit-nav-item text="Child C" icon="bwi-filter"></bit-nav-item>
</bit-nav-group> </bit-nav-group>
`, `,
}); }),
};
export const Tree: Story<NavGroupComponent> = (args) => ({ export const Tree: StoryObj<NavGroupComponent> = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-nav-group text="Tree example" icon="bwi-collection" [open]="true"> <bit-nav-group text="Tree example" icon="bwi-collection" [open]="true">
@ -71,4 +74,5 @@ export const Tree: Story<NavGroupComponent> = (args) => ({
<bit-nav-item text="Level 1 - 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-group>
`, `,
}); }),
};

View File

@ -1,5 +1,5 @@
import { RouterTestingModule } from "@angular/router/testing"; 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"; import { IconButtonModule } from "../icon-button";
@ -23,33 +23,40 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<NavItemComponent> = (args: NavItemComponent) => ({ type Story = StoryObj<NavItemComponent>;
export const Default: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-nav-item text="${args.text}" [route]="['']" icon="${args.icon}"></bit-nav-item> <bit-nav-item text="${args.text}" [route]="['']" icon="${args.icon}"></bit-nav-item>
`, `,
}); }),
args: {
export const Default = Template.bind({});
Default.args = {
text: "Hello World", text: "Hello World",
icon: "bwi-filter", icon: "bwi-filter",
},
}; };
export const WithoutIcon = Template.bind({}); export const WithoutIcon: Story = {
WithoutIcon.args = { ...Default,
args: {
text: "Hello World", text: "Hello World",
icon: "", icon: "",
},
}; };
export const WithoutRoute: Story<NavItemComponent> = (args: NavItemComponent) => ({ export const WithoutRoute: Story = {
render: (args: NavItemComponent) => ({
props: args, props: args,
template: ` 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>
`, `,
}); }),
};
export const WithChildButtons: Story<NavItemComponent> = (args: NavItemComponent) => ({ export const WithChildButtons: Story = {
render: (args: NavItemComponent) => ({
props: args, props: args,
template: ` template: `
<bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection"> <bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection">
@ -79,9 +86,11 @@ export const WithChildButtons: Story<NavItemComponent> = (args: NavItemComponent
></button> ></button>
</bit-nav-item> </bit-nav-item>
`, `,
}); }),
};
export const MultipleItemsWithDivider: Story<NavItemComponent> = (args: NavItemComponent) => ({ export const MultipleItemsWithDivider: Story = {
render: (args: NavItemComponent) => ({
props: args, props: args,
template: ` 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>
@ -90,4 +99,5 @@ export const MultipleItemsWithDivider: Story<NavItemComponent> = (args: NavItemC
<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-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 { ButtonModule } from "../button";
import { NoItemsComponent } from "./no-items.component";
import { NoItemsModule } from "./no-items.module"; import { NoItemsModule } from "./no-items.module";
export default { export default {
title: "Component Library/No Items", title: "Component Library/No Items",
component: NoItemsComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [ButtonModule, NoItemsModule], imports: [ButtonModule, NoItemsModule],
@ -13,7 +15,10 @@ export default {
], ],
} as Meta; } as Meta;
const Template: Story = (args) => ({ type Story = StoryObj<NoItemsComponent>;
export const Default: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-no-items class="tw-text-main"> <bit-no-items class="tw-text-main">
@ -30,6 +35,5 @@ const Template: Story = (args) => ({
</button> </button>
</bit-no-items> </bit-no-items>
`, `,
}); }),
};
export const Default = Template.bind({});

View File

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

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule, FormControl, FormGroup } from "@angular/forms"; 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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -34,9 +34,12 @@ export default {
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=3930%3A16850&t=xXPx6GJYsJfuMQPE-4", 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) => ({ type Story = StoryObj<RadioGroupComponent>;
export const Inline: Story = {
render: () => ({
props: { props: {
formObj: new FormGroup({ formObj: new FormGroup({
radio: new FormControl(0), radio: new FormControl(0),
@ -61,11 +64,11 @@ const InlineTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) =
</bit-radio-group> </bit-radio-group>
</form> </form>
`, `,
}); }),
};
export const Inline = InlineTemplate.bind({}); export const Block: Story = {
render: () => ({
const BlockTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) => ({
props: { props: {
formObj: new FormGroup({ formObj: new FormGroup({
radio: new FormControl(0), radio: new FormControl(0),
@ -93,6 +96,5 @@ const BlockTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) =>
</bit-radio-group> </bit-radio-group>
</form> </form>
`, `,
}); }),
};
export const Block = BlockTemplate.bind({});

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; 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 { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -29,12 +29,14 @@ export default {
], ],
} as Meta; } as Meta;
const Template: Story<SearchComponent> = (args: SearchComponent) => ({ type Story = StoryObj<SearchComponent>;
export const Default: Story = {
render: (args: SearchComponent) => ({
props: args, props: args,
template: ` template: `
<bit-search [(ngModel)]="searchText" [placeholder]="placeholder" [disabled]="disabled"></bit-search> <bit-search [(ngModel)]="searchText" [placeholder]="placeholder" [disabled]="disabled"></bit-search>
`, `,
}); }),
args: {},
export const Default = Template.bind({}); };
Default.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"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@ -37,7 +37,10 @@ export default {
}, },
} as Meta; } as Meta;
const DefaultTemplate: Story<MultiSelectComponent> = (args: MultiSelectComponent) => ({ type Story = StoryObj<MultiSelectComponent>;
export const Default: Story = {
render: (args) => ({
props: { props: {
...args, ...args,
}, },
@ -47,12 +50,13 @@ const DefaultTemplate: Story<MultiSelectComponent> = (args: MultiSelectComponent
<bit-option value="value3" label="Value 3" 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-option value="value4" label="Value 4" icon="bwi-collection" disabled></bit-option>
</bit-select>`, </bit-select>`,
}); }),
args: {},
export const Default = DefaultTemplate.bind({}); };
Default.args = {};
export const Disabled: Story = {
export const Disabled = DefaultTemplate.bind({}); ...Default,
Disabled.args = { args: {
disabled: true, disabled: true,
},
}; };

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import { Meta } from "@storybook/addon-docs"; 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 `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 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" /> <Meta title="Documentation/Introduction" />
<style>{` <style>
{`
.subheading { .subheading {
--mediumdark: '#999999'; --mediumdark: '#999999';
font-weight: 900; font-weight: 900;
@ -77,7 +78,8 @@ import { Meta } from "@storybook/addon-docs";
font-size: 14px; font-size: 14px;
line-height: 20px; line-height: 20px;
} }
`}</style> `}
</style>
# Bitwarden Component Library # 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 # Table
## Overview
The table component provides a comprehensive way to display, sort and filter data. It consists of 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`. 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. This documentation will initially focus on the UI portion before covering the data source.
<Primary />
<Controls />
## UI Component ## UI Component
The UI component consists of a couple of elements. 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. Netflix” for an edit option for a Netflix item.
- Use [Virtual Scrolling](#virtual-scrolling) for large data sets. - Use [Virtual Scrolling](#virtual-scrolling) for large data sets.
### Example
<Story id="component-library-table--default" />
### Usage ### Usage
The below code is the minimum required to create a table. However we strongly advise you to use the 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 { ScrollingModule } from "@angular/cdk/scrolling";
import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { countries } from "../form/countries"; import { countries } from "../form/countries";
@ -27,7 +27,10 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story = (args) => ({ type Story = StoryObj;
export const Default: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-table> <bit-table>
@ -57,11 +60,10 @@ const Template: Story = (args) => ({
</ng-template> </ng-template>
</bit-table> </bit-table>
`, `,
}); }),
args: {
export const Default = Template.bind({});
Default.args = {
alignRowContent: "baseline", alignRowContent: "baseline",
},
}; };
const data = new TableDataSource<{ id: number; name: string; other: string }>(); const data = new TableDataSource<{ id: number; name: string; other: string }>();
@ -72,7 +74,8 @@ data.data = [...Array(5).keys()].map((i) => ({
other: `other-${i}`, other: `other-${i}`,
})); }));
const DataSourceTemplate: Story = (args) => ({ export const DataSource: Story = {
render: (args) => ({
props: { props: {
dataSource: data, dataSource: data,
sortFn: (a: any, b: any) => a.id - b.id, sortFn: (a: any, b: any) => a.id - b.id,
@ -95,9 +98,8 @@ const DataSourceTemplate: Story = (args) => ({
</ng-template> </ng-template>
</bit-table> </bit-table>
`, `,
}); }),
};
export const DataSource = DataSourceTemplate.bind({});
const data2 = new TableDataSource<{ id: number; name: string; other: string }>(); const data2 = new TableDataSource<{ id: number; name: string; other: string }>();
@ -107,7 +109,8 @@ data2.data = [...Array(100).keys()].map((i) => ({
other: `other-${i}`, other: `other-${i}`,
})); }));
const ScrollableTemplate: Story = (args) => ({ export const Scrollable: Story = {
render: (args) => ({
props: { props: {
dataSource: data2, dataSource: data2,
sortFn: (a: any, b: any) => a.id - b.id, sortFn: (a: any, b: any) => a.id - b.id,
@ -132,16 +135,16 @@ const ScrollableTemplate: Story = (args) => ({
</bit-table> </bit-table>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
`, `,
}); }),
};
export const Scrollable = ScrollableTemplate.bind({});
const data3 = new TableDataSource<{ value: string; name: string }>(); 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 // Chromatic has a max page size, lowering the number of entries to ensure we don't hit it
data3.data = countries.slice(0, 100); data3.data = countries.slice(0, 100);
const FilterableTemplate: Story = (args) => ({ export const Filterable: Story = {
render: (args) => ({
props: { props: {
dataSource: data3, dataSource: data3,
sortFn: (a: any, b: any) => a.id - b.id, sortFn: (a: any, b: any) => a.id - b.id,
@ -165,9 +168,8 @@ const FilterableTemplate: Story = (args) => ({
</bit-table> </bit-table>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
`, `,
}); }),
};
export const Filterable = FilterableTemplate.bind({});
const data4 = new TableDataSource<{ name: string }>(); const data4 = new TableDataSource<{ name: string }>();
@ -175,7 +177,8 @@ data4.data = [...Array(5).keys()].map((i) => ({
name: i % 2 == 0 ? `name-${i}`.toUpperCase() : `name-${i}`.toLowerCase(), name: i % 2 == 0 ? `name-${i}`.toUpperCase() : `name-${i}`.toLowerCase(),
})); }));
const VariableCaseTemplate: Story = (args) => ({ export const VariableCase: Story = {
render: (args) => ({
props: { props: {
dataSource: data4, dataSource: data4,
}, },
@ -193,6 +196,5 @@ const VariableCaseTemplate: Story = (args) => ({
</ng-template> </ng-template>
</bit-table> </bit-table>
`, `,
}); }),
};
export const VariableCase = VariableCaseTemplate.bind({});

View File

@ -1,7 +1,7 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component } from "@angular/core"; import { Component, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router"; 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 { ButtonModule } from "../button";
import { FormFieldModule } from "../form-field"; import { FormFieldModule } from "../form-field";
@ -44,11 +44,11 @@ export default {
ItemThreeDummyComponent, ItemThreeDummyComponent,
DisabledDummyComponent, DisabledDummyComponent,
], ],
imports: [ imports: [CommonModule, TabsModule, ButtonModule, FormFieldModule, RouterModule],
CommonModule, }),
TabsModule, applicationConfig({
ButtonModule, providers: [
FormFieldModule, importProvidersFrom(
RouterModule.forRoot( RouterModule.forRoot(
[ [
{ path: "", redirectTo: "active", pathMatch: "full" }, { path: "", redirectTo: "active", pathMatch: "full" },
@ -58,6 +58,7 @@ export default {
{ path: "disabled", component: DisabledDummyComponent }, { path: "disabled", component: DisabledDummyComponent },
], ],
{ useHash: true } { useHash: true }
)
), ),
], ],
}), }),
@ -70,7 +71,10 @@ export default {
}, },
} as Meta; } as Meta;
const ContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({ type Story = StoryObj<TabGroupComponent>;
export const ContentTabs: Story = {
render: (args: any) => ({
props: args, props: args,
template: ` template: `
<bit-tab-group label="Main Content Tabs" class="tw-text-main"> <bit-tab-group label="Main Content Tabs" class="tw-text-main">
@ -87,11 +91,11 @@ const ContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
</bit-tab> </bit-tab>
</bit-tab-group> </bit-tab-group>
`, `,
}); }),
};
export const ContentTabs = ContentTabGroupTemplate.bind({}); export const NavigationTabs: Story = {
render: (args: TabGroupComponent) => ({
const NavTabGroupTemplate: Story<TabGroupComponent> = (args: TabGroupComponent) => ({
props: args, props: args,
template: ` template: `
<bit-tab-nav-bar label="Main"> <bit-tab-nav-bar label="Main">
@ -104,18 +108,16 @@ const NavTabGroupTemplate: Story<TabGroupComponent> = (args: TabGroupComponent)
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
`, `,
}); }),
};
export const NavigationTabs = NavTabGroupTemplate.bind({}); export const PreserveContentTabs: Story = {
render: (args: any) => ({
const PreserveContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
props: args, props: args,
template: ` template: `
<bit-tab-group label="Preserve Content Tabs" [preserveContent]="true" class="tw-text-main"> <bit-tab-group label="Preserve Content Tabs" [preserveContent]="true" class="tw-text-main">
<bit-tab label="Text Tab"> <bit-tab label="Text Tab">
<p> <p>Play the video in the other tab and switch back to hear the video is still playing.</p>
Play the video in the other tab and switch back to hear the video is still playing.
</p>
</bit-tab> </bit-tab>
<bit-tab label="Video Tab"> <bit-tab label="Video Tab">
<iframe <iframe
@ -127,11 +129,11 @@ const PreserveContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) =>
</bit-tab> </bit-tab>
</bit-tab-group> </bit-tab-group>
`, `,
}); }),
};
export const PreserveContentTabs = PreserveContentTabGroupTemplate.bind({}); export const KeyboardNavigation: Story = {
render: (args: any) => ({
const KeyboardNavTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
props: args, props: args,
template: ` template: `
<bit-tab-group label="Keyboard Navigation Tabs" class="tw-text-main"> <bit-tab-group label="Keyboard Navigation Tabs" class="tw-text-main">
@ -156,6 +158,5 @@ const KeyboardNavTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
</bit-tab-group> </bit-tab-group>
<button bitButton buttonType="primary" class="tw-mt-5">External Button</button> <button bitButton buttonType="primary" class="tw-mt-5">External Button</button>
`, `,
}); }),
};
export const KeyboardNavigation = KeyboardNavTabGroupTemplate.bind({});

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { BadgeModule } from "../badge"; import { BadgeModule } from "../badge";
@ -25,7 +25,10 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<ToggleGroupComponent> = (args: ToggleGroupComponent) => ({ type Story = StoryObj<ToggleGroupComponent>;
export const Default: Story = {
render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-toggle-group [(selected)]="selected" aria-label="People list filter"> <bit-toggle-group [(selected)]="selected" aria-label="People list filter">
@ -46,9 +49,8 @@ const Template: Story<ToggleGroupComponent> = (args: ToggleGroupComponent) => ({
</bit-toggle> </bit-toggle>
</bit-toggle-group> </bit-toggle-group>
`, `,
}); }),
args: {
export const Default = Template.bind({});
Default.args = {
selected: "all", 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"; import { TypographyDirective } from "./typography.directive";
@ -10,61 +10,81 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story = (args) => ({ type Story = StoryObj<TypographyDirective & { text: string }>;
export const H1: Story = {
render: (args) => ({
props: args, props: args,
template: `<span [bitTypography]="bitTypography" class="tw-text-main">{{text}}</span>`, template: `<span [bitTypography]="bitTypography" class="tw-text-main">{{text}}</span>`,
}); }),
args: {
export const H1 = Template.bind({});
H1.args = {
bitTypography: "h1", bitTypography: "h1",
text: "h1. Page Title", text: "h1. Page Title",
},
}; };
export const H2 = Template.bind({}); export const H2: Story = {
H2.args = { ...H1,
args: {
bitTypography: "h2", bitTypography: "h2",
text: "h2. Page Section", text: "h2. Page Section",
},
}; };
export const H3 = Template.bind({}); export const H3: Story = {
H3.args = { ...H1,
args: {
bitTypography: "h3", bitTypography: "h3",
text: "h3. Page Section", text: "h3. Page Section",
},
}; };
export const H4 = Template.bind({}); export const H4: Story = {
H4.args = { ...H1,
args: {
bitTypography: "h4", bitTypography: "h4",
text: "h4. Page Section", text: "h4. Page Section",
},
}; };
export const H5 = Template.bind({}); export const H5: Story = {
H5.args = { ...H1,
args: {
bitTypography: "h5", bitTypography: "h5",
text: "h5. Page Section", text: "h5. Page Section",
},
}; };
export const H6 = Template.bind({}); export const H6: Story = {
H6.args = { ...H1,
args: {
bitTypography: "h6", bitTypography: "h6",
text: "h6. Page Section", text: "h6. Page Section",
},
}; };
export const Body1 = Template.bind({}); export const Body1: Story = {
Body1.args = { ...H1,
args: {
bitTypography: "body1", bitTypography: "body1",
text: "Body 1", text: "Body 1",
},
}; };
export const Body2 = Template.bind({}); export const Body2: Story = {
Body2.args = { ...H1,
args: {
bitTypography: "body2", bitTypography: "body2",
text: "Body 2", text: "Body 2",
},
}; };
export const Helper = Template.bind({}); export const Helper: Story = {
Helper.args = { ...H1,
args: {
bitTypography: "helper", bitTypography: "helper",
text: "Helper Text", text: "Helper Text",
},
}; };

View File

@ -1,7 +1,12 @@
/* eslint-disable */ /* eslint-disable */
const config = require("./tailwind.config.base"); 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 = [ config.safelist = [
{ {
pattern: /tw-bg-(.*)/, pattern: /tw-bg-(.*)/,

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