mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +00:00
Merge branch 'master' into PM-2135-beeep-refactor-and-refresh-web-user-verification-components
This commit is contained in:
@@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
|
||||
const { compilerOptions } = require("./tsconfig");
|
||||
|
||||
const sharedConfig = require("../../libs/shared/jest.config.base");
|
||||
const sharedConfig = require("../../libs/shared/jest.config.angular");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
displayName: "libs/components tests",
|
||||
|
||||
@@ -4,10 +4,12 @@ import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
# Async Actions In Forms
|
||||
|
||||
These directives should be used when building forms with buttons that trigger long running tasks in the background,
|
||||
eg. Submit or Delete buttons. For buttons that are not associated with a form see [Standalone Async Actions](?path=/story/component-library-async-actions-standalone-documentation--page).
|
||||
These directives should be used when building forms with buttons that trigger long running tasks in
|
||||
the background, eg. Submit or Delete buttons. For buttons that are not associated with a form see
|
||||
[Standalone Async Actions](?path=/story/component-library-async-actions-standalone-documentation--page).
|
||||
|
||||
There are two separately supported use-cases: Submit buttons and standalone form buttons (eg. Delete buttons).
|
||||
There are two separately supported use-cases: Submit buttons and standalone form buttons (eg. Delete
|
||||
buttons).
|
||||
|
||||
## Usage: Submit buttons
|
||||
|
||||
@@ -15,17 +17,19 @@ Adding async actions to submit buttons requires the following 3 steps
|
||||
|
||||
### 1. Add a handler to your `Component`
|
||||
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is
|
||||
useful because `return;` can be used to abort an action.
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are
|
||||
also supported which is useful because `return;` can be used to abort an action.
|
||||
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler
|
||||
needs access to the parent component using the variable `this`.
|
||||
|
||||
**NOTE:** `formGroup.invalid` will always return `true` after the first `await` operation, event if the form is not actually
|
||||
invalid. This is due to the form getting disabled by the `bitSubmit` directive while waiting for the async action to complete.
|
||||
**NOTE:** `formGroup.invalid` will always return `true` after the first `await` operation, event if
|
||||
the form is not actually invalid. This is due to the form getting disabled by the `bitSubmit`
|
||||
directive while waiting for the async action to complete.
|
||||
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against
|
||||
users attempting to trigger new actions before the previous ones have finished.
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the
|
||||
directives have built in protection against users attempting to trigger new actions before the
|
||||
previous ones have finished.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
@@ -51,8 +55,8 @@ class Component {
|
||||
|
||||
Add the `bitSubmit` directive and supply the handler defined in step 1.
|
||||
|
||||
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`.
|
||||
This is different from how submit handlers are usually defined with the output syntax `(ngSubmit)="handler()"`.
|
||||
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. This is different
|
||||
from how submit handlers are usually defined with the output syntax `(ngSubmit)="handler()"`.
|
||||
|
||||
**NOTE:** `[bitSubmit]` is used instead of `(ngSubmit)`. Using both is not supported.
|
||||
|
||||
@@ -76,14 +80,15 @@ Adding async actions to standalone form buttons requires the following 3 steps.
|
||||
|
||||
### 1. Add a handler to your `Component`
|
||||
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is
|
||||
useful for aborting an action.
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are
|
||||
also supported which is useful for aborting an action.
|
||||
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler
|
||||
needs access to the parent component using the variable `this`.
|
||||
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against
|
||||
users attempting to trigger new actions before the previous ones have finished.
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the
|
||||
directives have built in protection against users attempting to trigger new actions before the
|
||||
previous ones have finished.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
@@ -113,7 +118,8 @@ The `bitSubmit` directive is required because of its coordinating role inside of
|
||||
|
||||
### 3. Add directives to the `button` element
|
||||
|
||||
Add `bitButton`, `bitFormButton`, `bitAction` directives to the button. Make sure to supply a handler.
|
||||
Add `bitButton`, `bitFormButton`, `bitAction` directives to the button. Make sure to supply a
|
||||
handler.
|
||||
|
||||
**NOTE:** A summary of what each directive does can be found inside the source code.
|
||||
|
||||
@@ -124,7 +130,8 @@ Add `bitButton`, `bitFormButton`, `bitAction` directives to the button. Make sur
|
||||
|
||||
## `[bitSubmit]` Disabled Form Submit
|
||||
|
||||
If you need your form to be able to submit even when the form is disabled, then add `[allowDisabledFormSubmit]="true"` to your `<form>`
|
||||
If you need your form to be able to submit even when the form is disabled, then add
|
||||
`[allowDisabledFormSubmit]="true"` to your `<form>`
|
||||
|
||||
```html
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit" [allowDisabledFormSubmit]="true">...</form>
|
||||
|
||||
@@ -4,14 +4,14 @@ import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
# Async Actions
|
||||
|
||||
The directives in this module makes it easier for developers to reflect the progress of async actions in the UI when using
|
||||
buttons, while also providing robust and standardized error handling.
|
||||
The directives in this module makes it easier for developers to reflect the progress of async
|
||||
actions in the UI when using buttons, while also providing robust and standardized error handling.
|
||||
|
||||
These buttons can either be standalone (such as Refresh buttons), submit buttons for forms or as standalone buttons
|
||||
that are part of a form (such as Delete buttons).
|
||||
These buttons can either be standalone (such as Refresh buttons), submit buttons for forms or as
|
||||
standalone buttons that are part of a form (such as Delete buttons).
|
||||
|
||||
These directives are meant to replace the older `appApiAction` directive, providing the option to use `observables` and reduce
|
||||
clutter inside our view `components`.
|
||||
These directives are meant to replace the older `appApiAction` directive, providing the option to
|
||||
use `observables` and reduce clutter inside our view `components`.
|
||||
|
||||
## When to use?
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
# Standalone Async Actions
|
||||
|
||||
These directives should be used when building a standalone button that triggers a long running task in the background,
|
||||
eg. Refresh buttons. For non-submit buttons that are associated with forms see [Async Actions In Forms](?path=/story/component-library-async-actions-in-forms-documentation--page).
|
||||
These directives should be used when building a standalone button that triggers a long running task
|
||||
in the background, eg. Refresh buttons. For non-submit buttons that are associated with forms see
|
||||
[Async Actions In Forms](?path=/story/component-library-async-actions-in-forms-documentation--page).
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -13,14 +14,15 @@ Adding async actions to standalone buttons requires the following 2 steps
|
||||
|
||||
### 1. Add a handler to your `Component`
|
||||
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is
|
||||
useful because `return;` can be used to abort an action.
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are
|
||||
also supported which is useful because `return;` can be used to abort an action.
|
||||
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler
|
||||
needs access to the parent component using the variable `this`.
|
||||
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against
|
||||
users attempting to trigger new actions before the previous ones have finished.
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the
|
||||
directives have built in protection against users attempting to trigger new actions before the
|
||||
previous ones have finished.
|
||||
|
||||
#### Example using promises
|
||||
|
||||
@@ -48,8 +50,8 @@ class Component {
|
||||
|
||||
Add the `bitAction` directive and supply the handler defined in step 1.
|
||||
|
||||
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`.
|
||||
This is different from how click handlers are usually defined with the output syntax `(click)="handler()"`.
|
||||
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. This is different
|
||||
from how click handlers are usually defined with the output syntax `(click)="handler()"`.
|
||||
|
||||
**NOTE:** `[bitAction]` is used instead of `(click)`. Using both is not supported.
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Meta, moduleMetadata, Story } from "@storybook/angular";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
import { LinkModule } from "../link";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||
|
||||
@@ -13,7 +14,7 @@ export default {
|
||||
component: BannerComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [SharedModule, IconButtonModule],
|
||||
imports: [SharedModule, IconButtonModule, LinkModule],
|
||||
providers: [
|
||||
{
|
||||
provide: I18nService,
|
||||
@@ -45,7 +46,7 @@ const Template: Story<BannerComponent> = (args: BannerComponent) => ({
|
||||
template: `
|
||||
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)">
|
||||
Content Really Long Text Lorem Ipsum Ipsum Ipsum
|
||||
<button>Button</button>
|
||||
<button bitLink linkType="contrast">Button</button>
|
||||
</bit-banner>
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -20,8 +20,23 @@
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="tw-overflow-y-auto tw-pb-8" [ngClass]="{ 'tw-p-4': !disablePadding }">
|
||||
<ng-content select="[bitDialogContent]"></ng-content>
|
||||
<div class="tw-relative tw-flex tw-flex-col tw-overflow-hidden">
|
||||
<div
|
||||
*ngIf="loading"
|
||||
class="tw-absolute tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin bwi-3x"></i>
|
||||
</div>
|
||||
<div
|
||||
class="tw-pb-8"
|
||||
[ngClass]="{
|
||||
'tw-p-4': !disablePadding,
|
||||
'tw-overflow-y-auto': !loading,
|
||||
'tw-invisible tw-overflow-y-hidden': loading
|
||||
}"
|
||||
>
|
||||
<ng-content select="[bitDialogContent]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -9,9 +9,15 @@ import { fadeIn } from "../animations";
|
||||
animations: [fadeIn],
|
||||
})
|
||||
export class DialogComponent {
|
||||
/**
|
||||
* Dialog size, more complex dialogs should use large, otherwise default is fine.
|
||||
*/
|
||||
@Input() dialogSize: "small" | "default" | "large" = "default";
|
||||
|
||||
private _disablePadding = false;
|
||||
/**
|
||||
* Disable the built-in padding on the dialog, for use with tabbed dialogs.
|
||||
*/
|
||||
@Input() set disablePadding(value: boolean | "") {
|
||||
this._disablePadding = coerceBooleanProperty(value);
|
||||
}
|
||||
@@ -19,6 +25,11 @@ export class DialogComponent {
|
||||
return this._disablePadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the dialog as loading which replaces the content with a spinner.
|
||||
*/
|
||||
@Input() loading = false;
|
||||
|
||||
@HostBinding("class") get classes() {
|
||||
return ["tw-flex", "tw-flex-col", "tw-max-h-screen", "tw-w-screen", "tw-p-4"].concat(
|
||||
this.width
|
||||
|
||||
@@ -32,6 +32,7 @@ export default {
|
||||
}),
|
||||
],
|
||||
args: {
|
||||
loading: false,
|
||||
dialogSize: "small",
|
||||
},
|
||||
argTypes: {
|
||||
@@ -52,21 +53,22 @@ export default {
|
||||
const Template: Story<DialogComponent> = (args: DialogComponent) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
|
||||
<span bitDialogTitle>{{title}}</span>
|
||||
<span bitDialogContent>Dialog body text goes here.</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Save</button>
|
||||
<button bitButton buttonType="secondary">Cancel</button>
|
||||
<button
|
||||
class="tw-ml-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
size="default"
|
||||
title="Delete"
|
||||
aria-label="Delete"></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
<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>
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -94,23 +96,30 @@ Large.args = {
|
||||
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" [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>
|
||||
<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>
|
||||
end of sequence!
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Save</button>
|
||||
<button bitButton buttonType="secondary">Cancel</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</bit-dialog>
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -122,20 +131,20 @@ ScrollingContent.args = {
|
||||
const TemplateTabbed: Story<DialogComponent> = (args: DialogComponent) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
|
||||
<span bitDialogTitle>Tab Content Example</span>
|
||||
<span bitDialogContent>
|
||||
<bit-tab-group>
|
||||
<bit-tab label="First Tab">First Tab Content</bit-tab>
|
||||
<bit-tab label="Second Tab">Second Tab Content</bit-tab>
|
||||
<bit-tab label="Third Tab">Third Tab Content</bit-tab>
|
||||
</bit-tab-group>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Save</button>
|
||||
<button bitButton buttonType="secondary">Cancel</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
<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>
|
||||
`,
|
||||
});
|
||||
|
||||
|
||||
@@ -95,18 +95,12 @@ const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) =>
|
||||
...args,
|
||||
},
|
||||
template: `
|
||||
<form [formGroup]="formObj" (ngSubmit)="submit()">
|
||||
<form [formGroup]="formObj">
|
||||
<bit-form-field>
|
||||
<bit-label>Name</bit-label>
|
||||
<bit-label>Label</bit-label>
|
||||
<input bitInput formControlName="name" />
|
||||
<bit-hint>Optional Hint</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>Email</bit-label>
|
||||
<input bitInput formControlName="email" />
|
||||
</bit-form-field>
|
||||
|
||||
<button type="submit" bitButton buttonType="primary">Submit</button>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -78,15 +78,9 @@ export class BitInputDirective implements BitFormFieldControl {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
private isActive = true;
|
||||
@HostListener("blur")
|
||||
onBlur() {
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
@HostListener("input")
|
||||
onInput() {
|
||||
this.isActive = false;
|
||||
this.ngControl?.control?.markAsUntouched();
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
@@ -97,7 +91,7 @@ export class BitInputDirective implements BitFormFieldControl {
|
||||
this.ngControl?.errors != null
|
||||
);
|
||||
} else {
|
||||
return this.ngControl?.status === "INVALID" && this.ngControl?.touched && this.isActive;
|
||||
return this.ngControl?.status === "INVALID" && this.ngControl?.touched;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule, FormBuilder } from "@angular/forms";
|
||||
import { FormsModule, ReactiveFormsModule, FormControl, FormGroup } from "@angular/forms";
|
||||
import { Meta, moduleMetadata, Story } from "@storybook/angular";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@@ -7,70 +6,13 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||
|
||||
import { RadioButtonModule } from "./radio-button.module";
|
||||
|
||||
const template = `
|
||||
<form [formGroup]="formObj">
|
||||
<bit-radio-group formControlName="radio" aria-label="Example radio group">
|
||||
<bit-label *ngIf="label">Group of radio buttons</bit-label>
|
||||
<bit-radio-button *ngFor="let option of TestValue | keyvalue" [ngClass]="{ 'tw-block': blockLayout }"
|
||||
[value]="option.value" id="radio-{{option.key}}" [disabled]="optionDisabled?.includes(option.value)">
|
||||
<bit-label>{{ option.key }}</bit-label>
|
||||
<bit-hint *ngIf="blockLayout">This is a hint for the {{option.key}} option</bit-hint>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
</form>`;
|
||||
|
||||
const TestValue = {
|
||||
First: 0,
|
||||
Second: 1,
|
||||
Third: 2,
|
||||
};
|
||||
|
||||
const reverseObject = (obj: Record<any, any>) =>
|
||||
Object.fromEntries(Object.entries(obj).map(([key, value]) => [value, key]));
|
||||
|
||||
@Component({
|
||||
selector: "app-example",
|
||||
template: template,
|
||||
})
|
||||
class ExampleComponent {
|
||||
protected TestValue = TestValue;
|
||||
|
||||
protected formObj = this.formBuilder.group({
|
||||
radio: TestValue.First,
|
||||
});
|
||||
|
||||
@Input() layout: "block" | "inline" = "inline";
|
||||
|
||||
@Input() label: boolean;
|
||||
|
||||
@Input() set selected(value: number) {
|
||||
this.formObj.patchValue({ radio: value });
|
||||
}
|
||||
|
||||
@Input() set groupDisabled(disable: boolean) {
|
||||
if (disable) {
|
||||
this.formObj.disable();
|
||||
} else {
|
||||
this.formObj.enable();
|
||||
}
|
||||
}
|
||||
|
||||
@Input() optionDisabled: number[] = [];
|
||||
|
||||
get blockLayout() {
|
||||
return this.layout === "block";
|
||||
}
|
||||
|
||||
constructor(private formBuilder: FormBuilder) {}
|
||||
}
|
||||
import { RadioGroupComponent } from "./radio-group.component";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Form/Radio Button",
|
||||
component: ExampleComponent,
|
||||
component: RadioGroupComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
declarations: [ExampleComponent],
|
||||
imports: [FormsModule, ReactiveFormsModule, RadioButtonModule],
|
||||
providers: [
|
||||
{
|
||||
@@ -92,56 +34,65 @@ export default {
|
||||
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=3930%3A16850&t=xXPx6GJYsJfuMQPE-4",
|
||||
},
|
||||
},
|
||||
args: {
|
||||
selected: TestValue.First,
|
||||
groupDisabled: false,
|
||||
optionDisabled: null,
|
||||
label: true,
|
||||
layout: "inline",
|
||||
},
|
||||
argTypes: {
|
||||
selected: {
|
||||
options: Object.values(TestValue),
|
||||
control: {
|
||||
type: "inline-radio",
|
||||
labels: reverseObject(TestValue),
|
||||
},
|
||||
},
|
||||
optionDisabled: {
|
||||
options: Object.values(TestValue),
|
||||
control: {
|
||||
type: "check",
|
||||
labels: reverseObject(TestValue),
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
options: ["inline", "block"],
|
||||
control: {
|
||||
type: "inline-radio",
|
||||
labels: ["inline", "block"],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
const storyTemplate = `<app-example [selected]="selected" [groupDisabled]="groupDisabled" [optionDisabled]="optionDisabled" [label]="label" [layout]="layout"></app-example>`;
|
||||
const InlineTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) => ({
|
||||
props: {
|
||||
formObj: new FormGroup({
|
||||
radio: new FormControl(0),
|
||||
}),
|
||||
},
|
||||
template: `
|
||||
<form [formGroup]="formObj">
|
||||
<bit-radio-group formControlName="radio" aria-label="Example radio group">
|
||||
<bit-label>Group of radio buttons</bit-label>
|
||||
|
||||
const InlineTemplate: Story<ExampleComponent> = (args: ExampleComponent) => ({
|
||||
props: args,
|
||||
template: storyTemplate,
|
||||
<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>
|
||||
`,
|
||||
});
|
||||
|
||||
export const Inline = InlineTemplate.bind({});
|
||||
Inline.args = {
|
||||
layout: "inline",
|
||||
};
|
||||
|
||||
const BlockTemplate: Story<ExampleComponent> = (args: ExampleComponent) => ({
|
||||
props: args,
|
||||
template: storyTemplate,
|
||||
const BlockTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) => ({
|
||||
props: {
|
||||
formObj: new FormGroup({
|
||||
radio: new FormControl(0),
|
||||
}),
|
||||
},
|
||||
template: `
|
||||
<form [formGroup]="formObj">
|
||||
<bit-radio-group formControlName="radio" aria-label="Example radio group">
|
||||
<bit-label>Group of radio buttons</bit-label>
|
||||
|
||||
<bit-radio-button id="radio-first" class="tw-block" [value]="0">
|
||||
<bit-label>First</bit-label>
|
||||
<bit-hint>This is a hint for the first option</bit-hint>
|
||||
</bit-radio-button>
|
||||
|
||||
<bit-radio-button id="radio-second" class="tw-block" [value]="1">
|
||||
<bit-label>Second</bit-label>
|
||||
<bit-hint>This is a hint for the second option</bit-hint>
|
||||
</bit-radio-button>
|
||||
|
||||
<bit-radio-button id="radio-third" class="tw-block" [value]="2">
|
||||
<bit-label>Third</bit-label>
|
||||
<bit-hint>This is a hint for the third option</bit-hint>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
export const Block = BlockTemplate.bind({});
|
||||
Block.args = {
|
||||
layout: "block",
|
||||
};
|
||||
|
||||
3
libs/components/src/search/close-button-white.svg
Normal file
3
libs/components/src/search/close-button-white.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512">
|
||||
<path fill="#fff" d="m381.1 343.16-87.47-87.76 87.67-86.62a9.24 9.24 0 0 0 0-13.06l-24.93-25.06a9.18 9.18 0 0 0-6.54-2.66c-2.46 0-4.8 1-6.53 2.66l-87.27 86.36-87.4-86.29a9.18 9.18 0 0 0-6.53-2.66c-2.47 0-4.8 1-6.53 2.66L130.7 155.8a9.24 9.24 0 0 0 0 13.06l87.67 86.62-87.4 87.69a9.22 9.22 0 0 0-2.74 6.53c0 2.46.94 4.8 2.74 6.53l24.93 25.05a9.19 9.19 0 0 0 13.07 0l87.06-87.42 87.14 87.36a9.19 9.19 0 0 0 13.06 0l24.94-25.06a9.23 9.23 0 0 0 2.73-6.53 9.55 9.55 0 0 0-2.8-6.46Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 573 B |
19
libs/components/src/search/search.component.css
Normal file
19
libs/components/src/search/search.component.css
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Tailwind doesn't have a good way to style search-cancel-button.
|
||||
*/
|
||||
bit-search input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 21px;
|
||||
width: 21px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
background-repeat: no-repeat;
|
||||
mask-image: url("./close-button-white.svg");
|
||||
-webkit-mask-image: url("./close-button-white.svg");
|
||||
background-color: rgba(var(--color-text-muted));
|
||||
}
|
||||
|
||||
bit-search input[type="search"]::-webkit-search-cancel-button:hover {
|
||||
background-color: rgba(var(--color-text-main));
|
||||
}
|
||||
@@ -81,13 +81,13 @@ import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
# Bitwarden Component Library
|
||||
|
||||
The Bitwarden Component Library is a collection of reusable low level components which empowers designers and
|
||||
developers to work more efficiently. The primary goal is to ensure a consistent design and behavior across the
|
||||
different clients and platforms. Currently the primary focus is the web based clients, namely _web_, _browser_ and
|
||||
_desktop_.
|
||||
The Bitwarden Component Library is a collection of reusable low level components which empowers
|
||||
designers and developers to work more efficiently. The primary goal is to ensure a consistent design
|
||||
and behavior across the different clients and platforms. Currently the primary focus is the web
|
||||
based clients, namely _web_, _browser_ and _desktop_.
|
||||
|
||||
**Roll out status:** we are currently in the process of transitioning the Web Vault to utilize the component library
|
||||
and the other clients will follow once this work is completed.
|
||||
**Roll out status:** we are currently in the process of transitioning the Web Vault to utilize the
|
||||
component library and the other clients will follow once this work is completed.
|
||||
|
||||
<div className="subheading">Configure</div>
|
||||
|
||||
|
||||
@@ -4,19 +4,21 @@ import { Meta, Story } from "@storybook/addon-docs";
|
||||
|
||||
# Banner
|
||||
|
||||
Banners are used for important communication with the user that needs to be seen right away, but has little effect on the experience. Banners appear at the top of the user's screen on page load and persist across all pages a user navigates to.
|
||||
|
||||
- They should always be dismissable and never use a timeout. If a user dismisses a banner, it should not reappear during that same active session.
|
||||
|
||||
- Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their effectiveness may decrease if too many are used.
|
||||
Banners are used for important communication with the user that needs to be seen right away, but has
|
||||
little effect on the experience. Banners appear at the top of the user's screen on page load and
|
||||
persist across all pages a user navigates to.
|
||||
|
||||
- They should always be dismissable and never use a timeout. If a user dismisses a banner, it should
|
||||
not reappear during that same active session.
|
||||
- Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their
|
||||
effectiveness may decrease if too many are used.
|
||||
- Avoid stacking multiple banners.
|
||||
|
||||
- Banners support a button link (text button).
|
||||
|
||||
## Types
|
||||
|
||||
Icons should remain consistent across these types. Do not change the icon without consulting designers.
|
||||
Icons should remain consistent across these types. Do not change the icon without consulting
|
||||
designers.
|
||||
|
||||
Use the following guidelines to help choose the correct type of banner.
|
||||
|
||||
@@ -46,4 +48,6 @@ Rarely used, but may be used to alert users over critical messages or very outda
|
||||
|
||||
## Accessibility
|
||||
|
||||
Include `role="status" aria-live="polite"` attributes to ensure screen readers announce the content prior to the test of the page.
|
||||
Banners sets the `role="status"` and `aria-live="polite"` attributes to ensure screen readers
|
||||
announce the content prior to the test of the page. This behaviour can be disabled by setting
|
||||
`[useAlertRole]="false"`.
|
||||
|
||||
@@ -4,30 +4,54 @@ import { Meta, Story } from "@storybook/addon-docs";
|
||||
|
||||
# Button
|
||||
|
||||
Use buttons for actions in forms, dialogs, and more with support for style, block, icon, and state.
|
||||
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.
|
||||
|
||||
For pairings in the bottom left corner of a page or component, the `primary` call to action will go on the left side of a button group with the `secondary` call to action option on the left.
|
||||
## Guidelines
|
||||
|
||||
Pairings in the top right corner of a page, should have the `primary` call to action on the right.
|
||||
### Choosing the `<a>` or `<button>`
|
||||
|
||||
Groups of buttons should have 1rem of spacing between them.
|
||||
|
||||
## Choosing the `<a>` or `<button>`
|
||||
|
||||
Buttons can use either the `<a>` or `<button>` tags. Choose which based on the action the button takes:
|
||||
Buttons can use either the `<a>` or `<button>` tags. Choose which based on the action the button
|
||||
takes:
|
||||
|
||||
- If navigating to a new page, use `<a>`
|
||||
- If taking an action on the current page use `<button>`
|
||||
- If the button launches a dialog, use `<button>`
|
||||
|
||||
## Submit and async actions
|
||||
### Groups
|
||||
|
||||
Both submit and async action buttons use a loading button state while an action is taken.
|
||||
Groups of buttons should be seperated by a `0.5` rem gap. Usually acomplished by using the
|
||||
`tw-gap-2` class in the button group container.
|
||||
|
||||
Groups within page content, dialog footers or forms should have the `primary` call to action placed
|
||||
to left. Groups in headers and navigational areas should have the `primary` call to action on the
|
||||
right.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Please follow these guidelines to ensure that buttons are accessible to all users.
|
||||
|
||||
### Color contrast
|
||||
|
||||
All button styles are WCAG compliant when displayed on `background` and `background-alt` colors. To
|
||||
use a button on a different background, double check that the color contrast is sufficient in both
|
||||
the light and dark themes.
|
||||
|
||||
### Loading Buttons
|
||||
|
||||
Include an `aria-label` attribute that defaults to “loading” but can be configurable per
|
||||
implementation. On click, the screen reader should announce the `aria-label`. Once the action is
|
||||
compelted, use another messaging pattern to alert the user that the action is complete (example:
|
||||
success toast).
|
||||
|
||||
### Submit and async actions
|
||||
|
||||
Both submit and async action buttons use a loading button state while an action is taken. If your
|
||||
button is preforming a long running task in the background like a server API call, be sure to review
|
||||
the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page).
|
||||
|
||||
<Story id="component-library-button--loading" />
|
||||
|
||||
If your button is preforming a long running task in the background like a server API call, be sure to review the [Async Actions Directive](https://components.bitwarden.com/?path=/story/component-library-async-actions-overview--page).
|
||||
|
||||
## Styles
|
||||
|
||||
There are 3 main styles for the button: Primary, Secondary, and Danger.
|
||||
@@ -36,13 +60,16 @@ There are 3 main styles for the button: Primary, Secondary, and Danger.
|
||||
|
||||
<Story id="component-library-button--primary" />
|
||||
|
||||
Use the primary button styling for all Primary call to actions. An action is "primary" if it relates to the main purpose of a page. There should never be 2 primary styled buttons next to each other.
|
||||
Use the primary button styling for all Primary call to actions. An action is "primary" if it relates
|
||||
to the main purpose of a page. There should never be 2 primary styled buttons next to each other.
|
||||
|
||||
### Secondary
|
||||
|
||||
<Story id="component-library-button--secondary" />
|
||||
|
||||
The secondary styling should be used for secondary calls to action. An action is "secondary" if it relates indirectly to the purpose of a page. There may be multiple secondary buttons next to each other; however, generally there should only be 1 or 2 calls to action per page.
|
||||
The secondary styling should be used for secondary calls to action. An action is "secondary" if it
|
||||
relates indirectly to the purpose of a page. There may be multiple secondary buttons next to each
|
||||
other; however, generally there should only be 1 or 2 calls to action per page.
|
||||
|
||||
### Danger
|
||||
|
||||
@@ -52,22 +79,14 @@ Use the danger styling only in settings when the user may preform a permanent ac
|
||||
|
||||
## Disabled UI
|
||||
|
||||
Both the disabled and loading states use the default state’s color with a 60% opacity or `tw-opacity-60`.
|
||||
Both the disabled and loading states use the default state’s color with a 60% opacity or
|
||||
`tw-opacity-60`.
|
||||
|
||||
<Story id="component-library-button--disabled" />
|
||||
|
||||
## Block
|
||||
|
||||
Typically button widths expand with their text. In some causes though buttons may need to be block where the width is fixed and the text wraps to 2 lines if exceeding the button’s width.
|
||||
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 button’s width.
|
||||
|
||||
<Story id="component-library-button--block" />
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Color contrast
|
||||
|
||||
All button styles are WCAG compliant when displayed on `background` and `background-alt` colors. To use a button on a different background, double check that the color contrast is sufficient in both the light and dark themes.
|
||||
|
||||
### Loading Buttons
|
||||
|
||||
Include an `aria-label` attribute that defaults to “loading” but can be configurable per implementation. On click, the screen reader should announce the `aria-label`. Once the action is compelted, use another messaging pattern to alert the user that the action is complete (example: success toast).
|
||||
|
||||
@@ -89,6 +89,17 @@ th {
|
||||
|
||||
# Colors
|
||||
|
||||
Tailwind traditionally has a very large color palette. Bitwarden has their own more limited color
|
||||
palette instead.
|
||||
|
||||
This has a couple of advantages:
|
||||
|
||||
- Promotes consistency across the application.
|
||||
- Easier to maintain and make adjustments.
|
||||
- Allows us to support more than two themes light & dark, should it be needed.
|
||||
|
||||
Below are all the permited colors. Please consult design before considering adding a new color.
|
||||
|
||||
<div class="tw-flex tw-space-x-4">
|
||||
<Table />
|
||||
<Table class="theme_dark tw-bg-background" />
|
||||
|
||||
51
libs/components/src/stories/forms-docs.stories.mdx
Normal file
51
libs/components/src/stories/forms-docs.stories.mdx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Documentation/Forms" />
|
||||
|
||||
# Forms
|
||||
|
||||
Examples and usage guidelines for form control styles, layout options, and custom components for
|
||||
creating a wide variety of forms.
|
||||
|
||||
## Overview
|
||||
|
||||
Component Library forms should always be built using [Angular Reactive Forms][reactive]. Please read
|
||||
[ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should
|
||||
always use the native `form` element and bind a `formGroup`.
|
||||
|
||||
Forms consists of one or multiple sections, and ends with one or multiple buttons.
|
||||
|
||||
### Form Field
|
||||
|
||||
A form field is the most common section in a form. It consists of a label, control and a optional
|
||||
hint.
|
||||
|
||||
<Story id="component-library-form-field--default" />
|
||||
|
||||
<Source id="component-library-form-field--default" />
|
||||
|
||||
### Radio group
|
||||
|
||||
A radio group is a form field that consists of a main label and multiple radio groups. Each group
|
||||
consists of a label and a radio input.
|
||||
|
||||
#### Block
|
||||
|
||||
<Story id="component-library-form-radio-button--block" />
|
||||
|
||||
<Source id="component-library-form-radio-button--block" />
|
||||
|
||||
#### Inline
|
||||
|
||||
<Story id="component-library-form-radio-button--inline" />
|
||||
|
||||
<Source id="component-library-form-radio-button--inline" />
|
||||
|
||||
## Full Example
|
||||
|
||||
<Story id="component-library-form--full-example" />
|
||||
|
||||
<Source id="component-library-form--full-example" />
|
||||
|
||||
[reactive]: https://angular.io/guide/reactive-forms
|
||||
[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms
|
||||
@@ -6,7 +6,11 @@ import { Meta } from "@storybook/addon-docs/";
|
||||
|
||||
# Iconography
|
||||
|
||||
Avoid using icons to convey information unless paired with meaningful, clear text. If an icon must be used and text cannot be displayed visually along with the icon, use an `aria-label` to provide the text to screen readers, and a `title` attribute to provide the text visually through a tool tip. Note: this pattern should only be followed for very common iconography such as, a settings cog icon or an options menu icon.
|
||||
Avoid using icons to convey information unless paired with meaningful, clear text. If an icon must
|
||||
be used and text cannot be displayed visually along with the icon, use an `aria-label` to provide
|
||||
the text to screen readers, and a `title` attribute to provide the text visually through a tool tip.
|
||||
Note: this pattern should only be followed for very common iconography such as, a settings cog icon
|
||||
or an options menu icon.
|
||||
|
||||
## Status Indicators
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
# `bitInput`
|
||||
|
||||
`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 within a `<bit-form-field>` custom component.
|
||||
`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
|
||||
within a `<bit-form-field>` custom component.
|
||||
|
||||
## Basic Usage Example
|
||||
|
||||
@@ -20,8 +20,8 @@ It is meant to be used within a `<bit-form-field>` custom component.
|
||||
|
||||
## Disabled `bitInput` and Error Handling
|
||||
|
||||
If you would like to be able to still show errors when an input is disabled for
|
||||
specific validation scenarios, then set `[showErrorsWhenDisabled]="true"`
|
||||
If you would like to be able to still show errors when an input is disabled for specific validation
|
||||
scenarios, then set `[showErrorsWhenDisabled]="true"`
|
||||
|
||||
```html
|
||||
<bit-form-field>
|
||||
@@ -31,7 +31,8 @@ specific validation scenarios, then set `[showErrorsWhenDisabled]="true"`
|
||||
</bit-form-field>
|
||||
```
|
||||
|
||||
**NOTE:** Disabling a FormControl removes validation errors so you must manually set the errors after disabling:
|
||||
**NOTE:** Disabling a FormControl removes validation errors so you must manually set the errors
|
||||
after disabling:
|
||||
|
||||
```ts
|
||||
get exampleFormCtrl(): FormControl {
|
||||
|
||||
@@ -6,13 +6,37 @@ import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||
|
||||
## Overview
|
||||
|
||||
All tables should have a visible horizontal header and label for each column.
|
||||
The table component provides a comprehensive way to display, sort and filter data. It consists of
|
||||
two portions, a UI component called `bit-table` and the underlying data source `TableDataSource`.
|
||||
This documentation will initially focus on the UI portion before covering the data source.
|
||||
|
||||
## UI Component
|
||||
|
||||
The UI component consists of a couple of elements.
|
||||
|
||||
- `bit-table`: The main component that creates a native table element and applies the table styling.
|
||||
- `header`: A container for the table header.
|
||||
- `body`: A container for the table body.
|
||||
- `bitCell`: A cell within the table. Used for both headers and content.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Always include a row or column header with your table; this allows screen readers to better
|
||||
contextualize the data
|
||||
- Avoid spanning data across cells.
|
||||
- Be sure to make repeating actions unique by associating them with the object they relate to.
|
||||
Example: if there are multiple “Edit” buttons on a table, a screen reader should read “Edit,
|
||||
Netflix” for an edit option for a Netflix item.
|
||||
- Use [Virtual Scrolling](#virtual-scrolling) for large data sets.
|
||||
|
||||
### Example
|
||||
|
||||
<Story id="component-library-table--default" />
|
||||
|
||||
The below code is the absolute minimum required to create a table. However we strongly advise you to
|
||||
use the `dataSource` input to provide a data source for your table. This allows you to easily sort
|
||||
data.
|
||||
### Usage
|
||||
|
||||
The below code is the minimum required to create a table. However we strongly advise you to use the
|
||||
`dataSource` input to provide a data source for your table. This allows you to easily sort data.
|
||||
|
||||
```html
|
||||
<bit-table>
|
||||
@@ -36,9 +60,8 @@ data.
|
||||
## Data Source
|
||||
|
||||
Bitwarden provides a data source for tables that can be used in place of a traditional data array.
|
||||
The `TableDataSource` implements sorting and will in the future also support filtering. This allows
|
||||
the `bitTable` component to focus on rendering while offloading the data management to the data
|
||||
source.
|
||||
The `TableDataSource` implements sorting and filtering capabilities. This allows the `bitTable`
|
||||
component to focus on rendering while offloading the data management to the data source.
|
||||
|
||||
```ts
|
||||
// External data source
|
||||
@@ -51,14 +74,31 @@ dataSource.data = data;
|
||||
We use the `dataSource` as an input to the `bit-table` component, and access the rows to render
|
||||
within the `ng-template`which provides access to the rows using `let-rows$`.
|
||||
|
||||
<Source id="component-library-table--data-source" />
|
||||
```html
|
||||
<bit-table [dataSource]="dataSource">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell bitSortable="id" default>Id</th>
|
||||
<th bitCell bitSortable="name">Name</th>
|
||||
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let r of rows$ | async">
|
||||
<td bitCell>{{ r.id }}</td>
|
||||
<td bitCell>{{ r.name }}</td>
|
||||
<td bitCell>{{ r.other }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
```
|
||||
|
||||
### Sortable
|
||||
### Sorting
|
||||
|
||||
We provide a simple component for displaying sortable column headers. The `bitSortable` component
|
||||
wires up to the `TableDataSource` and will automatically sort the data when clicked and display
|
||||
an indicator for which column is currently sorted. The dafault sorting can be specified by setting
|
||||
the `default`.
|
||||
wires up to the `TableDataSource` and will automatically sort the data when clicked and display an
|
||||
indicator for which column is currently sorted. The dafault sorting can be specified by setting the
|
||||
`default`.
|
||||
|
||||
```html
|
||||
<th bitCell bitSortable="id" default>Id</th>
|
||||
@@ -71,6 +111,16 @@ It's also possible to define a custom sorting function by setting the `fn` input
|
||||
const sortFn = (a: T, b: T) => (a.id > b.id ? 1 : -1);
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
The `TableDataSource` supports a rudimentary filtering capability most commonly used to implement a
|
||||
search function. It works by converting each entry into a string of it's properties. The string is
|
||||
then compared against the filter value using a simple `indexOf`check.
|
||||
|
||||
```ts
|
||||
dataSource.filter = "search value";
|
||||
```
|
||||
|
||||
### Virtual Scrolling
|
||||
|
||||
It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount
|
||||
@@ -97,8 +147,3 @@ specify a `itemSize`, set `scrollWindow` to `true` and replace `*ngFor` with `*c
|
||||
</bit-table>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Always incude a row or column header with your table; this allows screen readers to better contextualize the data
|
||||
- Avoid spanning data across cells
|
||||
|
||||
@@ -79,3 +79,5 @@
|
||||
|
||||
--tw-ring-offset-color: #1f242e;
|
||||
}
|
||||
|
||||
@import "./search/search.component.css";
|
||||
|
||||
Reference in New Issue
Block a user