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

@ -31,12 +31,17 @@ 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": {
}, "builder": "@storybook/angular:start-storybook",
"storybook": {
"projectType": "application",
"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,36 +49,7 @@ export default {
StoryLayoutComponent, StoryLayoutComponent,
StoryContentComponent, StoryContentComponent,
], ],
imports: [ imports: [JslibModule, MenuModule, IconButtonModule, LinkModule, RouterModule],
JslibModule,
MenuModule,
IconButtonModule,
LinkModule,
RouterModule.forRoot(
[
{
path: "",
component: StoryLayoutComponent,
children: [
{
path: "",
redirectTo: "vault",
pathMatch: "full",
},
{
path: "sm/:organizationId",
component: StoryContentComponent,
},
{
path: "vault",
component: StoryContentComponent,
},
],
},
],
{ useHash: true }
),
],
providers: [ providers: [
{ provide: OrganizationService, useClass: MockOrganizationService }, { provide: OrganizationService, useClass: MockOrganizationService },
MockOrganizationService, MockOrganizationService,
@ -93,6 +64,36 @@ export default {
}, },
], ],
}), }),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot(
[
{
path: "",
component: StoryLayoutComponent,
children: [
{
path: "",
redirectTo: "vault",
pathMatch: "full",
},
{
path: "sm/:organizationId",
component: StoryContentComponent,
},
{
path: "vault",
component: StoryContentComponent,
},
],
},
],
{ useHash: true }
)
),
],
}),
], ],
} as Meta; } 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,43 +28,46 @@ export default {
component: LayoutComponent, component: LayoutComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [ imports: [RouterModule, LayoutModule, IconModule],
RouterModule.forRoot(
[
{
path: "",
component: LayoutComponent,
children: [
{
path: "",
redirectTo: "secrets",
pathMatch: "full",
},
{
path: "secrets",
component: StoryContentComponent,
data: {
title: "secrets",
searchTitle: "searchSecrets",
},
},
{
outlet: "sidebar",
path: "",
component: NavigationComponent,
},
],
},
],
{ useHash: true }
),
LayoutModule,
IconModule,
PreloadedEnglishI18nModule,
],
declarations: [StoryContentComponent], declarations: [StoryContentComponent],
providers: [{ provide: OrganizationService, useClass: MockOrganizationService }], providers: [{ provide: OrganizationService, useClass: MockOrganizationService }],
}), }),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot(
[
{
path: "",
component: LayoutComponent,
children: [
{
path: "",
redirectTo: "secrets",
pathMatch: "full",
},
{
path: "secrets",
component: StoryContentComponent,
data: {
title: "secrets",
searchTitle: "searchSecrets",
},
},
{
outlet: "sidebar",
path: "",
component: NavigationComponent,
},
],
},
],
{ useHash: true }
)
),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
], ],
} as Meta; } 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>;
props: args,
template: `<app-promise-example></app-promise-example>`,
});
export const UsingPromise = PromiseTemplate.bind({}); export const UsingPromise: Story = {
render: (args) => ({
props: args,
template: `<app-promise-example></app-promise-example>`,
}),
};
const ObservableTemplate: Story<PromiseExampleComponent> = (args: PromiseExampleComponent) => ({ export const UsingObservable: Story = {
props: args, render: (args) => ({
template: `<app-observable-example></app-observable-example>`, props: args,
}); 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>;
props: args, type ObservableStory = StoryObj<ObservableExampleComponent>;
template: `<app-promise-example></app-promise-example>`,
});
export const UsingPromise = PromiseTemplate.bind({}); export const UsingPromise: PromiseStory = {
render: (args) => ({
props: args,
template: `<app-promise-example></app-promise-example>`,
}),
};
const ObservableTemplate: Story<ObservableExampleComponent> = ( export const UsingObservable: ObservableStory = {
args: ObservableExampleComponent render: (args) => ({
) => ({ 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> = ( template: `<app-rejected-promise-example></app-rejected-promise-example>`,
args: ObservableExampleComponent }),
) => ({ };
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>;
props: args,
template: `
<bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>
`,
});
export const Default = ListTemplate.bind({}); export const Default: Story = {
Default.args = { render: (args) => ({
badgeType: "info", props: args,
maxItems: 3, template: `
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"], <bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>
`,
}),
args: {
badgeType: "info",
maxItems: 3,
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"],
},
}; };

View File

@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common"; import { 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,43 +21,54 @@ 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>;
props: args,
template: `
<span class="tw-text-main">Span </span><span bitBadge [badgeType]="badgeType">Badge</span>
<br><br>
<span class="tw-text-main">Link </span><a href="#" bitBadge [badgeType]="badgeType">Badge</a>
<br><br>
<span class="tw-text-main">Button </span><button bitBadge [badgeType]="badgeType">Badge</button>
`,
});
export const Primary = Template.bind({}); export const Primary: Story = {
Primary.args = {}; render: (args) => ({
props: args,
export const Secondary = Template.bind({}); template: `
Secondary.args = { <span class="tw-text-main">Span </span><span bitBadge [badgeType]="badgeType">Badge</span>
badgeType: "secondary", <br><br>
<span class="tw-text-main">Link </span><a href="#" bitBadge [badgeType]="badgeType">Badge</a>
<br><br>
<span class="tw-text-main">Button </span><button bitBadge [badgeType]="badgeType">Badge</button>
`,
}),
}; };
export const Success = Template.bind({}); export const Secondary: Story = {
Success.args = { ...Primary,
badgeType: "success", args: {
badgeType: "secondary",
},
}; };
export const Danger = Template.bind({}); export const Success: Story = {
Danger.args = { ...Primary,
badgeType: "danger", args: {
badgeType: "success",
},
}; };
export const Warning = Template.bind({}); export const Danger: Story = {
Warning.args = { ...Primary,
badgeType: "warning", args: {
badgeType: "danger",
},
}; };
export const Info = Template.bind({}); export const Warning: Story = {
Info.args = { ...Primary,
badgeType: "info", args: {
badgeType: "warning",
},
};
export const Info: Story = {
...Primary,
args: {
badgeType: "info",
},
}; };

View File

@ -1,6 +1,8 @@
import { Meta, Story } from "@storybook/addon-docs"; import { Meta, Story, Controls, Canvas, Primary } from "@storybook/addon-docs";
<Meta title="Documentation/Banner" /> import * as stories from "./banner.stories";
<Meta of={stories} />
# Banner # 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,34 +39,46 @@ 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>;
props: args,
template: ` export const Premium: Story = {
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)"> args: {
bannerType: "premium",
},
render: (args: BannerComponent) => ({
props: args,
template: `
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)">
Content Really Long Text Lorem Ipsum Ipsum Ipsum Content Really Long Text Lorem Ipsum Ipsum Ipsum
<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,
bannerType: "info", args: {
bannerType: "info",
},
}; };
export const Warning = Template.bind({}); export const Warning: Story = {
Warning.args = { ...Premium,
bannerType: "warning", args: {
bannerType: "warning",
},
}; };
export const Danger = Template.bind({}); export const Danger: Story = {
Danger.args = { ...Premium,
bannerType: "danger", args: {
bannerType: "danger",
},
}; };

View File

@ -1,6 +1,6 @@
import { Component } from "@angular/core"; import { Component, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router"; import { 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,47 +48,54 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<BreadcrumbsComponent> = (args: BreadcrumbsComponent) => ({ type Story = StoryObj<BreadcrumbsComponent & { items: Breadcrumb[] }>;
props: args,
template: `
<h3 class="tw-text-main">Router links</h3>
<p>
<bit-breadcrumbs [show]="show">
<bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" [route]="[item.route]">{{item.name}}</bit-breadcrumb>
</bit-breadcrumbs>
</p>
<h3 class="tw-text-main">Click emit</h3> export const TopLevel: Story = {
<p> render: (args) => ({
<bit-breadcrumbs [show]="show"> props: args,
<bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" (click)="click($event)">{{item.name}}</bit-breadcrumb> template: `
</bit-breadcrumbs> <h3 class="tw-text-main">Router links</h3>
</p> <p>
`, <bit-breadcrumbs [show]="show">
}); <bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" [route]="[item.route]">{{item.name}}</bit-breadcrumb>
</bit-breadcrumbs>
</p>
<h3 class="tw-text-main">Click emit</h3>
<p>
<bit-breadcrumbs [show]="show">
<bit-breadcrumb *ngFor="let item of items" [icon]="item.icon" (click)="click($event)">{{item.name}}</bit-breadcrumb>
</bit-breadcrumbs>
</p>
`,
}),
export const TopLevel = Template.bind({}); 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,
items: [ args: {
{ name: "Acme Vault", route: "/" }, items: [
{ icon: "bwi-collection", name: "Collection", route: "collection" }, { name: "Acme Vault", route: "/" },
] as Breadcrumb[], { icon: "bwi-collection", name: "Collection", route: "collection" },
] as Breadcrumb[],
},
}; };
export const Overflow = Template.bind({}); export const Overflow: Story = {
Overflow.args = { ...TopLevel,
items: [ args: {
{ name: "Acme Vault", route: "" }, items: [
{ icon: "bwi-collection", name: "Collection", route: "collection" }, { name: "Acme Vault", route: "" },
{ icon: "bwi-collection", name: "Middle-Collection 1", route: "middle-collection-1" }, { icon: "bwi-collection", name: "Collection", route: "collection" },
{ icon: "bwi-collection", name: "Middle-Collection 2", route: "middle-collection-2" }, { icon: "bwi-collection", name: "Middle-Collection 1", route: "middle-collection-1" },
{ icon: "bwi-collection", name: "Middle-Collection 3", route: "middle-collection-3" }, { icon: "bwi-collection", name: "Middle-Collection 2", route: "middle-collection-2" },
{ icon: "bwi-collection", name: "Middle-Collection 4", route: "middle-collection-4" }, { icon: "bwi-collection", name: "Middle-Collection 3", route: "middle-collection-3" },
{ icon: "bwi-collection", name: "End Collection", route: "end-collection" }, { icon: "bwi-collection", name: "Middle-Collection 4", route: "middle-collection-4" },
] as Breadcrumb[], { icon: "bwi-collection", name: "End Collection", route: "end-collection" },
] as Breadcrumb[],
},
}; };

View File

@ -1,12 +1,18 @@
import { Meta, Story } from "@storybook/addon-docs"; import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
<Meta title="Documentation/Button" /> import * as stories from "./button.stories";
<Meta of={stories} />
# Button # 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,88 +16,96 @@ 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>;
props: args,
template: `
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block">Button</button>
<a bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">Link</a>
`,
});
export const Primary = Template.bind({}); export const Primary: Story = {
Primary.args = { render: (args) => ({
buttonType: "primary", props: args,
template: `
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block">Button</button>
<a bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">Link</a>
`,
}),
args: {
buttonType: "primary",
},
}; };
export const Secondary = Template.bind({}); export const Secondary: Story = {
Secondary.args = { ...Primary,
buttonType: "secondary", args: {
buttonType: "secondary",
},
}; };
export const Danger = Template.bind({}); export const Danger: Story = {
Danger.args = { ...Primary,
buttonType: "danger", args: {
buttonType: "danger",
},
}; };
const AllStylesTemplate: Story = (args) => ({ export const Loading: Story = {
props: args, render: (args) => ({
template: ` props: args,
<button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button> template: `
<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="primary" class="tw-mr-2">Primary</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="secondary" class="tw-mr-2">Secondary</button>
`, <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
}); `,
}),
export const Loading = AllStylesTemplate.bind({}); args: {
Loading.args = { disabled: false,
disabled: false, loading: true,
loading: true, },
}; };
export const Disabled = AllStylesTemplate.bind({}); export const Disabled: Story = {
Disabled.args = { ...Loading,
disabled: true, args: {
loading: false, disabled: true,
loading: false,
},
}; };
const DisabledWithAttributeTemplate: Story = (args) => ({ export const DisabledWithAttribute: Story = {
props: args, render: (args) => ({
template: ` props: args,
<ng-container *ngIf="disabled"> template: `
<button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button> <ng-container *ngIf="disabled">
<button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button> <button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> <button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
</ng-container> <button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
<ng-container *ngIf="!disabled"> </ng-container>
<button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button> <ng-container *ngIf="!disabled">
<button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button> <button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> <button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
</ng-container> <button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
`, </ng-container>
}); `,
}),
export const DisabledWithAttribute = DisabledWithAttributeTemplate.bind({}); args: {
DisabledWithAttribute.args = { disabled: true,
disabled: true, loading: false,
loading: false, },
}; };
const BlockTemplate: Story<ButtonComponent> = (args: ButtonComponent) => ({ export const Block: Story = {
props: args, render: (args: ButtonComponent) => ({
template: ` props: args,
<span class="tw-flex"> template: `
<button bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button> <span class="tw-flex">
<a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">[block]="true" Link</a> <button bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button>
<a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">[block]="true" Link</a>
<button bitButton [buttonType]="buttonType" block class="tw-ml-2">block Button</button>
<a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a> <button bitButton [buttonType]="buttonType" block class="tw-ml-2">block Button</button>
</span> <a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a>
`, </span>
}); `,
}),
export const Block = BlockTemplate.bind({}); args: {
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>;
props: args,
template: `
<bit-callout [type]="type" [title]="title">Content</bit-callout>
`,
});
export const Success = Template.bind({}); export const Success: Story = {
Success.args = { render: (args) => ({
type: "success", props: args,
title: "Success", template: `
<bit-callout [type]="type" [title]="title">Content</bit-callout>
`,
}),
args: {
type: "success",
title: "Success",
},
}; };
export const Info = Template.bind({}); export const Info: Story = {
Info.args = { ...Success,
type: "info", args: {
title: "Info", type: "info",
title: "Info",
},
}; };
export const Warning = Template.bind({}); export const Warning: Story = {
Warning.args = { ...Success,
type: "warning", args: {
type: "warning",
},
}; };
export const Danger = Template.bind({}); export const Danger: Story = {
Danger.args = { ...Success,
type: "danger", args: {
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,43 +69,44 @@ export default {
}, },
} as Meta; } as Meta;
const DefaultTemplate: Story<ExampleComponent> = (args: ExampleComponent) => ({ type Story = StoryObj<ExampleComponent>;
props: args,
template: `<app-example [checked]="checked" [disabled]="disabled"></app-example>`,
});
export const Default = DefaultTemplate.bind({}); export const Default: Story = {
Default.parameters = { render: (args) => ({
docs: { props: args,
source: { template: `<app-example [checked]="checked" [disabled]="disabled"></app-example>`,
code: template, }),
parameters: {
docs: {
source: {
code: template,
},
}, },
}, },
}; args: {
Default.args = { checked: false,
checked: false, disabled: false,
disabled: false, },
}; };
const CustomTemplate: Story = (args) => ({ export const Custom: Story = {
props: args, render: (args) => ({
template: ` props: args,
<div class="tw-flex tw-flex-col tw-w-32"> template: `
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline"> <div class="tw-flex tw-flex-col tw-w-32">
A-Z <label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox> A-Z
</label> <input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline"> </label>
a-z <label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox> a-z
</label> <input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline"> </label>
0-9 <label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox> 0-9
</label> <input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</div> </label>
`, </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>;
props: args,
template: `
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
`,
});
const WrappedTemplate: Story<ColorPasswordComponent> = (args: ColorPasswordComponent) => ({ export const ColorPassword: Story = {
props: args, render: (args) => ({
template: ` props: args,
<div class="tw-max-w-32"> 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>
</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 WrappedColorPassword: Story = {
WrappedColorPasswordCount.args = { render: (args) => ({
password: examplePassword, props: args,
showCount: true, template: `
<div class="tw-max-w-32">
<bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password>
</div>
`,
}),
};
export const ColorPasswordCount: Story = {
...ColorPassword,
args: {
password: examplePassword,
showCount: true,
},
};
export const WrappedColorPasswordCount: Story = {
...WrappedColorPassword,
args: {
password: examplePassword,
showCount: true,
},
}; };

View File

@ -1,6 +1,6 @@
import { DIALOG_DATA, DialogModule, DialogRef } from "@angular/cdk/dialog"; import { 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,110 +50,118 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<DialogComponent> = (args: DialogComponent) => ({ type Story = StoryObj<DialogComponent & { title: string }>;
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>{{title}}</span>
<ng-container bitDialogContent>Dialog body text goes here.</ng-container>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
<button
[disabled]="loading"
class="tw-ml-auto"
bitIconButton="bwi-trash"
buttonType="danger"
size="default"
title="Delete"
aria-label="Delete"></button>
</ng-container>
</bit-dialog>
`,
});
export const Default = Template.bind({}); export const Default: Story = {
Default.args = { render: (args: DialogComponent) => ({
dialogSize: "default", props: args,
title: "Default", template: `
}; <bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>{{title}}</span>
export const Small = Template.bind({}); <ng-container bitDialogContent>Dialog body text goes here.</ng-container>
Small.args = { <ng-container bitDialogFooter>
dialogSize: "small", <button bitButton buttonType="primary" [disabled]="loading">Save</button>
title: "Small", <button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
}; <button
[disabled]="loading"
export const LongTitle = Template.bind({}); class="tw-ml-auto"
LongTitle.args = { bitIconButton="bwi-trash"
dialogSize: "small", buttonType="danger"
title: "Long_Title_That_Should_Be_Truncated", size="default"
}; title="Delete"
aria-label="Delete"></button>
export const Large = Template.bind({});
Large.args = {
dialogSize: "large",
title: "Large",
};
export const Loading = Template.bind({});
Loading.args = {
dialogSize: "large",
loading: true,
title: "Loading",
};
const TemplateScrolling: Story<DialogComponent> = (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>Scrolling Example</span>
<span bitDialogContent>
Dialog body text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container> </ng-container>
end of sequence! </bit-dialog>
</span> `,
<ng-container bitDialogFooter> }),
<button bitButton buttonType="primary" [disabled]="loading">Save</button> args: {
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button> dialogSize: "default",
</ng-container> title: "Default",
</bit-dialog> },
`,
});
export const ScrollingContent = TemplateScrolling.bind({});
ScrollingContent.args = {
dialogSize: "small",
}; };
const TemplateTabbed: Story<DialogComponent> = (args: DialogComponent) => ({ export const Small: Story = {
props: args, ...Default,
template: ` args: {
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding"> dialogSize: "small",
<span bitDialogTitle>Tab Content Example</span> title: "Small",
<span bitDialogContent> },
<bit-tab-group>
<bit-tab label="First Tab">First Tab Content</bit-tab>
<bit-tab label="Second Tab">Second Tab Content</bit-tab>
<bit-tab label="Third Tab">Third Tab Content</bit-tab>
</bit-tab-group>
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
</ng-container>
</bit-dialog>
`,
});
export const TabContent = TemplateTabbed.bind({});
TabContent.args = {
dialogSize: "large",
disablePadding: true,
}; };
TabContent.story = {
export const LongTitle: Story = {
...Default,
args: {
dialogSize: "small",
title: "Long_Title_That_Should_Be_Truncated",
},
};
export const Large: Story = {
...Default,
args: {
dialogSize: "large",
title: "Large",
},
};
export const Loading: Story = {
...Default,
args: {
dialogSize: "large",
loading: true,
title: "Loading",
},
};
export const ScrollingContent: Story = {
render: (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
<span bitDialogTitle>Scrolling Example</span>
<span bitDialogContent>
Dialog body text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
</ng-container>
</bit-dialog>
`,
}),
args: {
dialogSize: "small",
},
};
export const TabContent: Story = {
render: (args) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
<span bitDialogTitle>Tab Content Example</span>
<span bitDialogContent>
<bit-tab-group>
<bit-tab label="First Tab">First Tab Content</bit-tab>
<bit-tab label="Second Tab">Second Tab Content</bit-tab>
<bit-tab label="Third Tab">Third Tab Content</bit-tab>
</bit-tab-group>
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
</ng-container>
</bit-dialog>
`,
}),
args: {
dialogSize: "large",
disablePadding: true,
},
parameters: { 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,61 +22,63 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({ type Story = StoryObj<SimpleDialogComponent & { useDefaultIcon: boolean }>;
props: args,
template: `
<bit-simple-dialog>
<span bitDialogTitle>Alert Dialog</span>
<span bitDialogContent>Message Content</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
});
export const Default = Template.bind({}); export const Default: Story = {
render: (args) => ({
const TemplateWithIcon: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({ props: args,
props: args, template: `
template: ` <bit-simple-dialog>
<bit-simple-dialog> <span bitDialogTitle>Alert Dialog</span>
<i bitDialogIcon class="bwi bwi-star tw-text-3xl tw-text-success" aria-hidden="true"></i> <span bitDialogContent>Message Content</span>
<span bitDialogTitle>Premium Subscription Available</span> <ng-container bitDialogFooter>
<span bitDialogContent> Message Content</span> <button bitButton buttonType="primary">Yes</button>
<ng-container bitDialogFooter> <button bitButton buttonType="secondary">No</button>
<button bitButton buttonType="primary">Yes</button> </ng-container>
<button bitButton buttonType="secondary">No</button> </bit-simple-dialog>
</ng-container> `,
</bit-simple-dialog> }),
`, };
});
export const CustomIcon: Story = {
export const CustomIcon = TemplateWithIcon.bind({}); render: (args) => ({
props: args,
const TemplateScroll: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({ template: `
props: args, <bit-simple-dialog>
template: ` <i bitDialogIcon class="bwi bwi-star tw-text-3xl tw-text-success" aria-hidden="true"></i>
<bit-simple-dialog> <span bitDialogTitle>Premium Subscription Available</span>
<span bitDialogTitle>Alert Dialog</span> <span bitDialogContent> Message Content</span>
<span bitDialogContent> <ng-container bitDialogFooter>
Message Content <button bitButton buttonType="primary">Yes</button>
Message text goes here.<br> <button bitButton buttonType="secondary">No</button>
<ng-container *ngFor="let _ of [].constructor(100)"> </ng-container>
repeating lines of characters <br> </bit-simple-dialog>
</ng-container> `,
end of sequence! }),
</span> };
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button> export const ScrollingContent: Story = {
<button bitButton buttonType="secondary">No</button> render: (args: SimpleDialogComponent) => ({
</ng-container> props: args,
</bit-simple-dialog> template: `
`, <bit-simple-dialog>
}); <span bitDialogTitle>Alert Dialog</span>
<span bitDialogContent>
export const ScrollingContent = TemplateScroll.bind({}); Message Content
ScrollingContent.args = { Message text goes here.<br>
useDefaultIcon: true, <ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</ng-container>
</bit-simple-dialog>
`,
}),
args: {
useDefaultIcon: true,
},
}; };

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule, FormBuilder } from "@angular/forms"; import { 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> = {
props: { render: (args: BitFormFieldComponent) => ({
formObj: new FormBuilder().group({ props: {
name: ["", forbiddenCharacters(["\\", "/", "@", "#", "$", "%", "^", "&", "*", "(", ")"])], formObj: new FormBuilder().group({
}), 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,29 +50,28 @@ function submit() {
formObj.markAllAsTouched(); formObj.markAllAsTouched();
} }
const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({ export const Default: StoryObj<BitFormFieldComponent> = {
props: { render: (args) => ({
formObj: formObj, props: {
submit: submit, formObj: formObj,
...args, submit: submit,
}, ...args,
template: ` },
<form [formGroup]="formObj" (ngSubmit)="submit()"> template: `
<bit-form-field> <form [formGroup]="formObj" (ngSubmit)="submit()">
<bit-label>Name</bit-label> <bit-form-field>
<input bitInput formControlName="name" /> <bit-label>Name</bit-label>
</bit-form-field> <input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label> <bit-form-field>
<input bitInput formControlName="email" /> <bit-label>Email</bit-label>
</bit-form-field> <input bitInput formControlName="email" />
</bit-form-field>
<button type="submit" bitButton buttonType="primary">Submit</button>
<bit-error-summary [formGroup]="formObj"></bit-error-summary> <button type="submit" bitButton buttonType="primary">Submit</button>
</form> <bit-error-summary [formGroup]="formObj"></bit-error-summary>
`, </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,173 +87,168 @@ 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 = {
props: { render: (args) => ({
formObj: defaultFormObj, props: {
submit: submit, formObj: defaultFormObj,
...args, submit: submit,
}, ...args,
template: ` },
<form [formGroup]="formObj"> template: `
<form [formGroup]="formObj">
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput formControlName="name" />
<bit-hint>Optional Hint</bit-hint>
</bit-form-field>
</form>
`,
}),
};
export const Required: Story = {
render: (args) => ({
props: {
formObj: formObj,
...args,
},
template: `
<bit-form-field> <bit-form-field>
<bit-label>Label</bit-label> <bit-label>Label</bit-label>
<input bitInput formControlName="name" /> <input bitInput required placeholder="Placeholder" />
<bit-hint>Optional Hint</bit-hint>
</bit-form-field> </bit-form-field>
</form>
`, <bit-form-field [formGroup]="formObj">
}); <bit-label>FormControl</bit-label>
<input bitInput formControlName="required" placeholder="Placeholder" />
</bit-form-field>
`,
}),
};
export const Default = Template.bind({}); export const Hint: Story = {
Default.props = {}; render: (args: BitFormFieldComponent) => ({
props: {
formObj: formObj,
...args,
},
template: `
<bit-form-field [formGroup]="formObj">
<bit-label>FormControl</bit-label>
<input bitInput formControlName="required" placeholder="Placeholder" />
<bit-hint>Long hint text</bit-hint>
</bit-form-field>
`,
}),
};
const RequiredTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({ export const Disabled: Story = {
props: { render: (args) => ({
formObj: formObj, props: args,
...args, template: `
}, <bit-form-field>
template: ` <bit-label>Label</bit-label>
<bit-form-field> <input bitInput placeholder="Placeholder" disabled />
<bit-label>Label</bit-label> </bit-form-field>
<input bitInput required placeholder="Placeholder" /> `,
</bit-form-field> }),
args: {},
};
<bit-form-field [formGroup]="formObj"> export const InputGroup: Story = {
<bit-label>FormControl</bit-label> render: (args) => ({
<input bitInput formControlName="required" placeholder="Placeholder" /> props: args,
</bit-form-field> template: `
`, <bit-form-field>
}); <bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" />
<span bitPrefix>$</span>
<span bitSuffix>USD</span>
</bit-form-field>
`,
}),
args: {},
};
export const Required = RequiredTemplate.bind({}); export const ButtonInputGroup: Story = {
Required.props = {}; render: (args) => ({
props: args,
template: `
<bit-form-field>
<button bitPrefix bitIconButton="bwi-star"></button>
<input bitInput placeholder="Placeholder" />
<button bitSuffix bitIconButton="bwi-eye"></button>
<button bitSuffix bitIconButton="bwi-clone"></button>
<button bitSuffix bitButton>
Apply
</button>
</bit-form-field>
`,
}),
args: {},
};
const HintTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({ export const DisabledButtonInputGroup: Story = {
props: { render: (args) => ({
formObj: formObj, props: args,
...args, template: `
}, <bit-form-field>
template: ` <bit-label>Label</bit-label>
<bit-form-field [formGroup]="formObj"> <button bitPrefix bitIconButton="bwi-star" disabled></button>
<bit-label>FormControl</bit-label> <input bitInput placeholder="Placeholder" disabled />
<input bitInput formControlName="required" placeholder="Placeholder" /> <button bitSuffix bitIconButton="bwi-eye" disabled></button>
<bit-hint>Long hint text</bit-hint> <button bitSuffix bitIconButton="bwi-clone" disabled></button>
</bit-form-field> <button bitSuffix bitButton disabled>
`, Apply
}); </button>
</bit-form-field>
`,
}),
args: {},
};
export const Hint = HintTemplate.bind({}); export const Select: Story = {
Required.props = {}; render: (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<select bitInput>
<option>Select</option>
<option>Other</option>
</select>
</bit-form-field>
`,
}),
args: {},
};
const DisabledTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({ export const AdvancedSelect: Story = {
props: args, render: (args: BitFormFieldComponent) => ({
template: ` props: args,
<bit-form-field> template: `
<bit-label>Label</bit-label> <bit-form-field>
<input bitInput placeholder="Placeholder" disabled /> <bit-label>Label</bit-label>
</bit-form-field> <bit-select>
`, <bit-option label="Select"></bit-option>
}); <bit-option label="Other"></bit-option>
</bit-select>
</bit-form-field>
`,
}),
};
export const Disabled = DisabledTemplate.bind({}); export const Textarea: Story = {
Disabled.args = {}; render: (args: BitFormFieldComponent) => ({
props: args,
const GroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({ template: `
props: args, <bit-form-field>
template: ` <bit-label>Textarea</bit-label>
<bit-form-field> <textarea bitInput rows="4"></textarea>
<bit-label>Label</bit-label> </bit-form-field>
<input bitInput placeholder="Placeholder" /> `,
<span bitPrefix>$</span> }),
<span bitSuffix>USD</span> args: {},
</bit-form-field> };
`,
});
export const InputGroup = GroupTemplate.bind({});
InputGroup.args = {};
const ButtonGroupTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<button bitPrefix bitIconButton="bwi-star"></button>
<input bitInput placeholder="Placeholder" />
<button bitSuffix bitIconButton="bwi-eye"></button>
<button bitSuffix bitIconButton="bwi-clone"></button>
<button bitSuffix bitButton>
Apply
</button>
</bit-form-field>
`,
});
export const ButtonInputGroup = ButtonGroupTemplate.bind({});
ButtonInputGroup.args = {};
const DisabledButtonInputGroupTemplate: Story<BitFormFieldComponent> = (
args: BitFormFieldComponent
) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<button bitPrefix bitIconButton="bwi-star" disabled></button>
<input bitInput placeholder="Placeholder" disabled />
<button bitSuffix bitIconButton="bwi-eye" disabled></button>
<button bitSuffix bitIconButton="bwi-clone" disabled></button>
<button bitSuffix bitButton disabled>
Apply
</button>
</bit-form-field>
`,
});
export const DisabledButtonInputGroup = DisabledButtonInputGroupTemplate.bind({});
DisabledButtonInputGroup.args = {};
const SelectTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<select bitInput>
<option>Select</option>
<option>Other</option>
</select>
</bit-form-field>
`,
});
export const Select = SelectTemplate.bind({});
Select.args = {};
const AdvancedSelectTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Label</bit-label>
<bit-select>
<bit-option label="Select"></bit-option>
<bit-option label="Other"></bit-option>
</bit-select>
</bit-form-field>
`,
});
export const AdvancedSelect = AdvancedSelectTemplate.bind({});
AdvancedSelectTemplate.args = {};
const TextareaTemplate: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>Textarea</bit-label>
<textarea bitInput rows="4"></textarea>
</bit-form-field>
`,
});
export const Textarea = TextareaTemplate.bind({});
Textarea.args = {};

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

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { 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,49 +38,42 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<BitPasswordInputToggleDirective> = ( type Story = StoryObj<BitPasswordInputToggleDirective>;
args: BitPasswordInputToggleDirective
) => ({
props: {
...args,
},
template: `
<form>
<bit-form-field>
<bit-label>Password</bit-label>
<input bitInput type="password" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
</bit-form-field>
</form>
`,
});
export const Default = Template.bind({}); export const Default: Story = {
Default.props = {}; render: (args) => ({
props: args,
const TemplateBinding: Story<BitPasswordInputToggleDirective> = ( template: `
args: BitPasswordInputToggleDirective <form>
) => ({ <bit-form-field>
props: { <bit-label>Password</bit-label>
...args, <input bitInput type="password" />
}, <button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
template: ` </bit-form-field>
<form> </form>
<bit-form-field> `,
<bit-label>Password</bit-label> }),
<input bitInput type="password" /> };
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle [(toggled)]="toggled"></button>
</bit-form-field> export const Binding: Story = {
render: (args) => ({
<label class="tw-text-main"> props: args,
Checked: template: `
<input type="checkbox" [(ngModel)]="toggled" [ngModelOptions]="{standalone: true}" /> <form>
</label> <bit-form-field>
</form> <bit-label>Password</bit-label>
`, <input bitInput type="password" />
}); <button type="button" bitIconButton bitSuffix bitPasswordInputToggle [(toggled)]="toggled"></button>
</bit-form-field>
export const Binding = TemplateBinding.bind({});
Binding.props = { <label class="tw-text-main">
toggled: false, Checked:
<input type="checkbox" [(ngModel)]="toggled" [ngModelOptions]="{standalone: true}" />
</label>
</form>
`,
}),
args: {
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,67 +81,70 @@ function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
}; };
} }
const FullExampleTemplate: Story = (args) => ({ type Story = StoryObj;
props: {
formObj: exampleFormObj, export const FullExample: Story = {
submit: () => exampleFormObj.markAllAsTouched(), render: (args) => ({
...args, props: {
formObj: exampleFormObj,
submit: () => exampleFormObj.markAllAsTouched(),
...args,
},
template: `
<form [formGroup]="formObj" (ngSubmit)="submit()">
<bit-form-field>
<bit-label>Name</bit-label>
<input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" />
</bit-form-field>
<bit-form-field>
<bit-label>Country</bit-label>
<bit-select formControlName="country">
<bit-option *ngFor="let country of countries" [value]="country.value" [label]="country.name"></bit-option>
</bit-select>
</bit-form-field>
<bit-form-field>
<bit-label>Age</bit-label>
<input
bitInput
type="number"
formControlName="age"
min="0"
max="150"
/>
</bit-form-field>
<bit-form-control>
<bit-label>Agree to terms</bit-label>
<input type="checkbox" bitCheckbox formControlName="terms">
<bit-hint>Required for the service to work properly</bit-hint>
</bit-form-control>
<bit-radio-group formControlName="updates">
<bit-label>Subscribe to updates?</bit-label>
<bit-radio-button value="yes">
<bit-label>Yes</bit-label>
</bit-radio-button>
<bit-radio-button value="no">
<bit-label>No</bit-label>
</bit-radio-button>
<bit-radio-button value="later">
<bit-label>Decide later</bit-label>
</bit-radio-button>
</bit-radio-group>
<button type="submit" bitButton buttonType="primary">Submit</button>
</form>
`,
}),
args: {
countries,
}, },
template: `
<form [formGroup]="formObj" (ngSubmit)="submit()">
<bit-form-field>
<bit-label>Name</bit-label>
<input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" />
</bit-form-field>
<bit-form-field>
<bit-label>Country</bit-label>
<bit-select formControlName="country">
<bit-option *ngFor="let country of countries" [value]="country.value" [label]="country.name"></bit-option>
</bit-select>
</bit-form-field>
<bit-form-field>
<bit-label>Age</bit-label>
<input
bitInput
type="number"
formControlName="age"
min="0"
max="150"
/>
</bit-form-field>
<bit-form-control>
<bit-label>Agree to terms</bit-label>
<input type="checkbox" bitCheckbox formControlName="terms">
<bit-hint>Required for the service to work properly</bit-hint>
</bit-form-control>
<bit-radio-group formControlName="updates">
<bit-label>Subscribe to updates?</bit-label>
<bit-radio-button value="yes">
<bit-label>Yes</bit-label>
</bit-radio-button>
<bit-radio-button value="no">
<bit-label>No</bit-label>
</bit-radio-button>
<bit-radio-button value="later">
<bit-label>Decide later</bit-label>
</bit-radio-button>
</bit-radio-group>
<button type="submit" bitButton buttonType="primary">Submit</button>
</form>
`,
});
export const FullExample = FullExampleTemplate.bind({});
FullExample.args = {
countries,
}; };

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { OverlayModule } from "@angular/cdk/overlay"; import { 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,40 +30,42 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<MenuTriggerForDirective> = (args: MenuTriggerForDirective) => ({ type Story = StoryObj<MenuTriggerForDirective>;
props: args,
template: `
<bit-menu #myMenu="menuComponent">
<a href="#" bitMenuItem>Anchor link</a>
<a href="#" bitMenuItem>Another link</a>
<button type="button" bitMenuItem>Button</button>
<bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem>Button after divider</button>
</bit-menu>
<div class="tw-h-40"> export const OpenMenu: Story = {
<div class="cdk-overlay-pane bit-menu-panel"> render: (args) => ({
<ng-container *ngTemplateOutlet="myMenu.templateRef"></ng-container> props: args,
template: `
<bit-menu #myMenu="menuComponent">
<a href="#" bitMenuItem>Anchor link</a>
<a href="#" bitMenuItem>Another link</a>
<button type="button" bitMenuItem>Button</button>
<bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem>Button after divider</button>
</bit-menu>
<div class="tw-h-40">
<div class="cdk-overlay-pane bit-menu-panel">
<ng-container *ngTemplateOutlet="myMenu.templateRef"></ng-container>
</div>
</div> </div>
</div> `,
`, }),
}); };
export const ClosedMenu: Story = {
const TemplateWithButton: Story<MenuTriggerForDirective> = (args: MenuTriggerForDirective) => ({ render: (args) => ({
props: args, props: args,
template: ` template: `
<div class="tw-h-40"> <div class="tw-h-40">
<button bitButton buttonType="secondary" [bitMenuTriggerFor]="myMenu">Open menu</button> <button bitButton buttonType="secondary" [bitMenuTriggerFor]="myMenu">Open menu</button>
</div> </div>
<bit-menu #myMenu> <bit-menu #myMenu>
<a href="#" bitMenuItem>Anchor link</a> <a href="#" bitMenuItem>Anchor link</a>
<a href="#" bitMenuItem>Another link</a> <a href="#" bitMenuItem>Another link</a>
<button type="button" bitMenuItem>Button</button> <button type="button" bitMenuItem>Button</button>
<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,9 +36,10 @@ export default {
}, },
} as Meta; } as Meta;
export const Default: Story<NavGroupComponent> = (args) => ({ export const Default: StoryObj<NavGroupComponent> = {
props: args, render: (args) => ({
template: ` props: args,
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">
<bit-nav-item text="Child A" route="#" icon="bwi-filter"></bit-nav-item> <bit-nav-item text="Child A" route="#" icon="bwi-filter"></bit-nav-item>
<bit-nav-item text="Child B" route="#"></bit-nav-item> <bit-nav-item text="Child B" route="#"></bit-nav-item>
@ -50,25 +51,28 @@ export const Default: Story<NavGroupComponent> = (args) => ({
<bit-nav-item text="Child C" icon="bwi-filter"></bit-nav-item> <bit-nav-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> = {
props: args, render: (args) => ({
template: ` props: args,
<bit-nav-group text="Tree example" icon="bwi-collection" [open]="true"> template: `
<bit-nav-group text="Level 1 - with children (empty)" route="#" icon="bwi-collection" variant="tree"></bit-nav-group> <bit-nav-group text="Tree example" icon="bwi-collection" [open]="true">
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item> <bit-nav-group text="Level 1 - with children (empty)" route="#" icon="bwi-collection" variant="tree"></bit-nav-group>
<bit-nav-group text="Level 1 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true"> <bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
<bit-nav-group text="Level 2 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true"> <bit-nav-group text="Level 1 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 3 - no children, no icon" route="#" variant="tree"></bit-nav-item> <bit-nav-group text="Level 2 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-group text="Level 3 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true"> <bit-nav-item text="Level 3 - no children, no icon" route="#" variant="tree"></bit-nav-item>
<bit-nav-item text="Level 4 - no children, no icon" route="#" variant="tree"></bit-nav-item> <bit-nav-group text="Level 3 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 4 - no children, no icon" route="#" variant="tree"></bit-nav-item>
</bit-nav-group>
</bit-nav-group> </bit-nav-group>
<bit-nav-group text="Level 2 - with children (empty)" route="#" icon="bwi-collection" variant="tree" [open]="true"></bit-nav-group>
<bit-nav-item text="Level 2 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
</bit-nav-group> </bit-nav-group>
<bit-nav-group text="Level 2 - with children (empty)" route="#" icon="bwi-collection" variant="tree" [open]="true"></bit-nav-group> <bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
<bit-nav-item text="Level 2 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item>
</bit-nav-group> </bit-nav-group>
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item> `,
</bit-nav-group> }),
`, };
});

View File

@ -1,5 +1,5 @@
import { RouterTestingModule } from "@angular/router/testing"; import { 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,35 +23,42 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<NavItemComponent> = (args: NavItemComponent) => ({ type Story = StoryObj<NavItemComponent>;
props: args,
template: `
<bit-nav-item text="${args.text}" [route]="['']" icon="${args.icon}"></bit-nav-item>
`,
});
export const Default = Template.bind({}); export const Default: Story = {
Default.args = { render: (args) => ({
text: "Hello World", props: args,
icon: "bwi-filter", template: `
<bit-nav-item text="${args.text}" [route]="['']" icon="${args.icon}"></bit-nav-item>
`,
}),
args: {
text: "Hello World",
icon: "bwi-filter",
},
}; };
export const WithoutIcon = Template.bind({}); export const WithoutIcon: Story = {
WithoutIcon.args = { ...Default,
text: "Hello World", args: {
icon: "", text: "Hello World",
icon: "",
},
}; };
export const WithoutRoute: Story<NavItemComponent> = (args: NavItemComponent) => ({ export const WithoutRoute: Story = {
props: args, render: (args: NavItemComponent) => ({
template: ` props: args,
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item> template: `
`, <bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
}); `,
}),
};
export const WithChildButtons: Story<NavItemComponent> = (args: NavItemComponent) => ({ export const WithChildButtons: Story = {
props: args, render: (args: NavItemComponent) => ({
template: ` props: args,
template: `
<bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection"> <bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection">
<button <button
slot-start slot-start
@ -79,15 +86,18 @@ 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 = {
props: args, render: (args: NavItemComponent) => ({
template: ` props: args,
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item> template: `
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item> <bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-divider></bit-nav-divider> <bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item> <bit-nav-divider></bit-nav-divider>
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item> <bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
`, <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,23 +15,25 @@ export default {
], ],
} as Meta; } as Meta;
const Template: Story = (args) => ({ type Story = StoryObj<NoItemsComponent>;
props: args,
template: `
<bit-no-items class="tw-text-main">
<ng-container slot="title">No items found</ng-container>
<ng-container slot="description">Your description here.</ng-container>
<button
slot="button"
type="button"
bitButton
buttonType="secondary"
>
<i class="bwi bwi-plus" aria-hidden="true"></i>
New item
</button>
</bit-no-items>
`,
});
export const Default = Template.bind({}); export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-no-items class="tw-text-main">
<ng-container slot="title">No items found</ng-container>
<ng-container slot="description">Your description here.</ng-container>
<button
slot="button"
type="button"
bitButton
buttonType="secondary"
>
<i class="bwi bwi-plus" aria-hidden="true"></i>
New item
</button>
</bit-no-items>
`,
}),
};

View File

@ -1,4 +1,4 @@
import { Meta, Story } from "@storybook/angular"; import { Meta, StoryObj } from "@storybook/angular";
import { ProgressComponent } from "./progress.component"; 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,65 +34,67 @@ 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>;
props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" [value]="0"> export const Inline: Story = {
<bit-label>First</bit-label> render: () => ({
</bit-radio-button> props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" [value]="0">
<bit-label>First</bit-label>
</bit-radio-button>
<bit-radio-button id="radio-second" [value]="1">
<bit-label>Second</bit-label>
</bit-radio-button>
<bit-radio-button id="radio-third" [value]="2">
<bit-label>Third</bit-label>
</bit-radio-button>
</bit-radio-group>
</form>
`,
}),
};
<bit-radio-button id="radio-second" [value]="1"> export const Block: Story = {
<bit-label>Second</bit-label> render: () => ({
</bit-radio-button> props: {
formObj: new FormGroup({
<bit-radio-button id="radio-third" [value]="2"> radio: new FormControl(0),
<bit-label>Third</bit-label> }),
</bit-radio-button> },
</bit-radio-group> template: `
</form> <form [formGroup]="formObj">
`, <bit-radio-group formControlName="radio" aria-label="Example radio group">
}); <bit-label>Group of radio buttons</bit-label>
export const Inline = InlineTemplate.bind({}); <bit-radio-button id="radio-first" class="tw-block" [value]="0">
<bit-label>First</bit-label>
const BlockTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) => ({ <bit-hint>This is a hint for the first option</bit-hint>
props: { </bit-radio-button>
formObj: new FormGroup({
radio: new FormControl(0), <bit-radio-button id="radio-second" class="tw-block" [value]="1">
}), <bit-label>Second</bit-label>
}, <bit-hint>This is a hint for the second option</bit-hint>
template: ` </bit-radio-button>
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group"> <bit-radio-button id="radio-third" class="tw-block" [value]="2">
<bit-label>Group of radio buttons</bit-label> <bit-label>Third</bit-label>
<bit-hint>This is a hint for the third option</bit-hint>
<bit-radio-button id="radio-first" class="tw-block" [value]="0"> </bit-radio-button>
<bit-label>First</bit-label> </bit-radio-group>
<bit-hint>This is a hint for the first option</bit-hint> </form>
</bit-radio-button> `,
}),
<bit-radio-button id="radio-second" class="tw-block" [value]="1"> };
<bit-label>Second</bit-label>
<bit-hint>This is a hint for the second option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-third" class="tw-block" [value]="2">
<bit-label>Third</bit-label>
<bit-hint>This is a hint for the third option</bit-hint>
</bit-radio-button>
</bit-radio-group>
</form>
`,
});
export const Block = BlockTemplate.bind({});

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

View File

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

View File

@ -67,7 +67,8 @@ export const Table = (args) => (
</table> </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,41 +27,43 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story = (args) => ({ type Story = StoryObj;
props: args,
template: `
<bit-table>
<ng-container header>
<tr>
<th bitCell>Header 1</th>
<th bitCell>Header 2</th>
<th bitCell>Header 3</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 1</td>
<td bitCell>Cell 2 <br> Multiline Cell</td>
<td bitCell>Cell 3</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 4</td>
<td bitCell>Cell 5</td>
<td bitCell>Cell 6</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 7 <br> Multiline Cell</td>
<td bitCell>Cell 8</td>
<td bitCell>Cell 9</td>
</tr>
</ng-template>
</bit-table>
`,
});
export const Default = Template.bind({}); export const Default: Story = {
Default.args = { render: (args) => ({
alignRowContent: "baseline", props: args,
template: `
<bit-table>
<ng-container header>
<tr>
<th bitCell>Header 1</th>
<th bitCell>Header 2</th>
<th bitCell>Header 3</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 1</td>
<td bitCell>Cell 2 <br> Multiline Cell</td>
<td bitCell>Cell 3</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 4</td>
<td bitCell>Cell 5</td>
<td bitCell>Cell 6</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 7 <br> Multiline Cell</td>
<td bitCell>Cell 8</td>
<td bitCell>Cell 9</td>
</tr>
</ng-template>
</bit-table>
`,
}),
args: {
alignRowContent: "baseline",
},
}; };
const data = new TableDataSource<{ id: number; name: string; other: string }>(); const data = new TableDataSource<{ id: number; name: string; other: string }>();
@ -72,48 +74,13 @@ data.data = [...Array(5).keys()].map((i) => ({
other: `other-${i}`, other: `other-${i}`,
})); }));
const DataSourceTemplate: Story = (args) => ({ export const DataSource: Story = {
props: { render: (args) => ({
dataSource: data, props: {
sortFn: (a: any, b: any) => a.id - b.id, dataSource: data,
}, sortFn: (a: any, b: any) => a.id - b.id,
template: ` },
<bit-table [dataSource]="dataSource"> template: `
<ng-container header>
<tr>
<th bitCell bitSortable="id" default>Id</th>
<th bitCell bitSortable="name">Name</th>
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
`,
});
export const DataSource = DataSourceTemplate.bind({});
const data2 = new TableDataSource<{ id: number; name: string; other: string }>();
data2.data = [...Array(100).keys()].map((i) => ({
id: i,
name: `name-${i}`,
other: `other-${i}`,
}));
const ScrollableTemplate: Story = (args) => ({
props: {
dataSource: data2,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource"> <bit-table [dataSource]="dataSource">
<ng-container header> <ng-container header>
<tr> <tr>
@ -123,51 +90,86 @@ const ScrollableTemplate: Story = (args) => ({
</tr> </tr>
</ng-container> </ng-container>
<ng-template body let-rows$> <ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$"> <tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.id }}</td> <td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td> <td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td> <td bitCell>{{ r.other }}</td>
</tr> </tr>
</ng-template> </ng-template>
</bit-table> </bit-table>
</cdk-virtual-scroll-viewport>
`, `,
}); }),
};
export const Scrollable = ScrollableTemplate.bind({}); const data2 = new TableDataSource<{ id: number; name: string; other: string }>();
data2.data = [...Array(100).keys()].map((i) => ({
id: i,
name: `name-${i}`,
other: `other-${i}`,
}));
export const Scrollable: Story = {
render: (args) => ({
props: {
dataSource: data2,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="id" default>Id</th>
<th bitCell bitSortable="name">Name</th>
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
`,
}),
};
const data3 = new TableDataSource<{ value: string; name: string }>(); 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 = {
props: { render: (args) => ({
dataSource: data3, props: {
sortFn: (a: any, b: any) => a.id - b.id, dataSource: data3,
}, sortFn: (a: any, b: any) => a.id - b.id,
template: ` },
<input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" /> template: `
<cdk-virtual-scroll-viewport scrollWindow itemSize="47"> <input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" />
<bit-table [dataSource]="dataSource"> <cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<ng-container header> <bit-table [dataSource]="dataSource">
<tr> <ng-container header>
<th bitCell bitSortable="name" default>Name</th> <tr>
<th bitCell bitSortable="value" width="120px">Value</th> <th bitCell bitSortable="name" default>Name</th>
</tr> <th bitCell bitSortable="value" width="120px">Value</th>
</ng-container> </tr>
<ng-template body let-rows$> </ng-container>
<tr bitRow *cdkVirtualFor="let r of rows$"> <ng-template body let-rows$>
<td bitCell>{{ r.name }}</td> <tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.value }}</td> <td bitCell>{{ r.name }}</td>
</tr> <td bitCell>{{ r.value }}</td>
</ng-template> </tr>
</bit-table> </ng-template>
</cdk-virtual-scroll-viewport> </bit-table>
</cdk-virtual-scroll-viewport>
`, `,
}); }),
};
export const Filterable = FilterableTemplate.bind({});
const data4 = new TableDataSource<{ name: string }>(); const data4 = new TableDataSource<{ name: string }>();
@ -175,24 +177,24 @@ data4.data = [...Array(5).keys()].map((i) => ({
name: i % 2 == 0 ? `name-${i}`.toUpperCase() : `name-${i}`.toLowerCase(), name: i % 2 == 0 ? `name-${i}`.toUpperCase() : `name-${i}`.toLowerCase(),
})); }));
const VariableCaseTemplate: Story = (args) => ({ export const VariableCase: Story = {
props: { render: (args) => ({
dataSource: data4, props: {
}, dataSource: data4,
template: ` },
<bit-table [dataSource]="dataSource"> template: `
<ng-container header> <bit-table [dataSource]="dataSource">
<tr> <ng-container header>
<th bitCell bitSortable="name" default>Name</th> <tr>
</tr> <th bitCell bitSortable="name" default>Name</th>
</ng-container> </tr>
<ng-template body let-rows$> </ng-container>
<tr bitRow *ngFor="let r of rows$ | async"> <ng-template body let-rows$>
<td bitCell>{{ r.name }}</td> <tr bitRow *ngFor="let r of rows$ | async">
</tr> <td bitCell>{{ r.name }}</td>
</ng-template> </tr>
</bit-table> </ng-template>
</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,20 +44,21 @@ 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" },
{ path: "active", component: ActiveDummyComponent }, { path: "active", component: ActiveDummyComponent },
{ path: "item-2", component: ItemTwoDummyComponent }, { path: "item-2", component: ItemTwoDummyComponent },
{ path: "item-3", component: ItemThreeDummyComponent }, { path: "item-3", component: ItemThreeDummyComponent },
{ path: "disabled", component: DisabledDummyComponent }, { path: "disabled", component: DisabledDummyComponent },
], ],
{ useHash: true } { useHash: true }
)
), ),
], ],
}), }),
@ -70,10 +71,13 @@ export default {
}, },
} as Meta; } as Meta;
const ContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({ type Story = StoryObj<TabGroupComponent>;
props: args,
template: ` export const ContentTabs: Story = {
<bit-tab-group label="Main Content Tabs" class="tw-text-main"> render: (args: any) => ({
props: args,
template: `
<bit-tab-group label="Main Content Tabs" class="tw-text-main">
<bit-tab label="First Tab">First Tab Content</bit-tab> <bit-tab label="First Tab">First Tab Content</bit-tab>
<bit-tab label="Second Tab">Second Tab Content</bit-tab> <bit-tab label="Second Tab">Second Tab Content</bit-tab>
<bit-tab> <bit-tab>
@ -85,56 +89,54 @@ const ContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
<bit-tab [disabled]="true" label="Disabled"> <bit-tab [disabled]="true" label="Disabled">
Disabled Content Disabled Content
</bit-tab> </bit-tab>
</bit-tab-group> </bit-tab-group>
`, `,
}); }),
};
export const ContentTabs = ContentTabGroupTemplate.bind({}); export const NavigationTabs: Story = {
render: (args: TabGroupComponent) => ({
props: args,
template: `
<bit-tab-nav-bar label="Main">
<bit-tab-link [route]="['active']">Active</bit-tab-link>
<bit-tab-link [route]="['item-2']">Item 2</bit-tab-link>
<bit-tab-link [route]="['item-3']">Item 3</bit-tab-link>
<bit-tab-link [route]="['disable']" [disabled]="true">Disabled</bit-tab-link>
</bit-tab-nav-bar>
<div class="tw-bg-transparent tw-text-semibold tw-text-center tw-text-main tw-py-10">
<router-outlet></router-outlet>
</div>
`,
}),
};
const NavTabGroupTemplate: Story<TabGroupComponent> = (args: TabGroupComponent) => ({ export const PreserveContentTabs: Story = {
props: args, render: (args: any) => ({
template: ` props: args,
<bit-tab-nav-bar label="Main"> template: `
<bit-tab-link [route]="['active']">Active</bit-tab-link> <bit-tab-group label="Preserve Content Tabs" [preserveContent]="true" class="tw-text-main">
<bit-tab-link [route]="['item-2']">Item 2</bit-tab-link>
<bit-tab-link [route]="['item-3']">Item 3</bit-tab-link>
<bit-tab-link [route]="['disable']" [disabled]="true">Disabled</bit-tab-link>
</bit-tab-nav-bar>
<div class="tw-bg-transparent tw-text-semibold tw-text-center tw-text-main tw-py-10">
<router-outlet></router-outlet>
</div>
`,
});
export const NavigationTabs = NavTabGroupTemplate.bind({});
const PreserveContentTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
props: args,
template: `
<bit-tab-group label="Preserve Content Tabs" [preserveContent]="true" class="tw-text-main">
<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
width="560" width="560"
height="315" height="315"
src="https://www.youtube.com/embed/H0-yWbe5XG4" src="https://www.youtube.com/embed/H0-yWbe5XG4"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe> allowfullscreen></iframe>
</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">
<bit-tab label="Form Tab"> <bit-tab label="Form Tab">
<p> <p>
You can navigate through all tab labels, form inputs, and the button that is outside the tab group via You can navigate through all tab labels, form inputs, and the button that is outside the tab group via
@ -153,9 +155,8 @@ const KeyboardNavTabGroupTemplate: Story<TabGroupComponent> = (args: any) => ({
<bit-tab label="No Focusable Content Tab" [contentTabIndex]="0"> <bit-tab label="No Focusable Content Tab" [contentTabIndex]="0">
<p>This tab has no focusable content, but the panel should still be focusable</p> <p>This tab has no focusable content, but the panel should still be focusable</p>
</bit-tab> </bit-tab>
</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,30 +25,32 @@ export default {
}, },
} as Meta; } as Meta;
const Template: Story<ToggleGroupComponent> = (args: ToggleGroupComponent) => ({ type Story = StoryObj<ToggleGroupComponent>;
props: args,
template: `
<bit-toggle-group [(selected)]="selected" aria-label="People list filter">
<bit-toggle value="all">
All <span bitBadge badgeType="info">3</span>
</bit-toggle>
<bit-toggle value="invited"> export const Default: Story = {
Invited render: (args) => ({
</bit-toggle> props: args,
template: `
<bit-toggle value="accepted"> <bit-toggle-group [(selected)]="selected" aria-label="People list filter">
Accepted <span bitBadge badgeType="info">2</span> <bit-toggle value="all">
</bit-toggle> All <span bitBadge badgeType="info">3</span>
</bit-toggle>
<bit-toggle value="deactivated">
Deactivated <bit-toggle value="invited">
</bit-toggle> Invited
</bit-toggle-group> </bit-toggle>
`,
}); <bit-toggle value="accepted">
Accepted <span bitBadge badgeType="info">2</span>
export const Default = Template.bind({}); </bit-toggle>
Default.args = {
selected: "all", <bit-toggle value="deactivated">
Deactivated
</bit-toggle>
</bit-toggle-group>
`,
}),
args: {
selected: "all",
},
}; };

View File

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

View File

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

18634
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,9 +23,9 @@
"test:watch:all": "jest --watchAll", "test: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"