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

Update CL documentation (#5379)

This commit is contained in:
Oscar Hinton 2023-05-08 14:46:59 +02:00 committed by GitHub
parent f51ed1092d
commit d53f79e325
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 313 additions and 218 deletions

View File

@ -1,3 +1,11 @@
{ {
"printWidth": 100 "printWidth": 100,
"overrides": [
{
"files": "*.mdx",
"options": {
"proseWrap": "always"
}
}
]
} }

View File

@ -4,10 +4,12 @@ import { Meta } from "@storybook/addon-docs";
# Async Actions In Forms # Async Actions In Forms
These directives should be used when building forms with buttons that trigger long running tasks in the background, These directives should be used when building forms with buttons that trigger long running tasks in
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). 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 ## 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` ### 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 A handler is a function that returns a promise or an observable. Functions that return `void` are
useful because `return;` can be used to abort an action. 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 **NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler
component using the variable `this`. 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 **NOTE:** `formGroup.invalid` will always return `true` after the first `await` operation, event if
invalid. This is due to the form getting disabled by the `bitSubmit` directive while waiting for the async action to complete. 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 **NOTE:** Handlers do not need to check if any previous requests have finished because the
users attempting to trigger new actions before the previous ones have finished. directives have built in protection against users attempting to trigger new actions before the
previous ones have finished.
```ts ```ts
@Component({...}) @Component({...})
@ -51,8 +55,8 @@ class Component {
Add the `bitSubmit` directive and supply the handler defined in step 1. Add the `bitSubmit` directive and supply the handler defined in step 1.
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. **NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. This is different
This is different from how submit handlers are usually defined with the output syntax `(ngSubmit)="handler()"`. 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. **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` ### 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 A handler is a function that returns a promise or an observable. Functions that return `void` are
useful for aborting an action. 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 **NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler
component using the variable `this`. 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 **NOTE:** Handlers do not need to check if any previous requests have finished because the
users attempting to trigger new actions before the previous ones have finished. directives have built in protection against users attempting to trigger new actions before the
previous ones have finished.
```ts ```ts
@Component({...}) @Component({...})
@ -113,7 +118,8 @@ The `bitSubmit` directive is required because of its coordinating role inside of
### 3. Add directives to the `button` element ### 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. **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 ## `[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 ```html
<form [formGroup]="formGroup" [bitSubmit]="submit" [allowDisabledFormSubmit]="true">...</form> <form [formGroup]="formGroup" [bitSubmit]="submit" [allowDisabledFormSubmit]="true">...</form>

View File

@ -4,14 +4,14 @@ import { Meta } from "@storybook/addon-docs";
# Async Actions # Async Actions
The directives in this module makes it easier for developers to reflect the progress of async actions in the UI when using The directives in this module makes it easier for developers to reflect the progress of async
buttons, while also providing robust and standardized error handling. 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 These buttons can either be standalone (such as Refresh buttons), submit buttons for forms or as
that are part of a form (such as Delete buttons). 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 These directives are meant to replace the older `appApiAction` directive, providing the option to
clutter inside our view `components`. use `observables` and reduce clutter inside our view `components`.
## When to use? ## When to use?

View File

@ -4,8 +4,9 @@ import { Meta } from "@storybook/addon-docs";
# Standalone Async Actions # Standalone Async Actions
These directives should be used when building a standalone button that triggers a long running task in the background, These directives should be used when building a standalone button that triggers a long running task
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). 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 ## Usage
@ -13,14 +14,15 @@ Adding async actions to standalone buttons requires the following 2 steps
### 1. Add a handler to your `Component` ### 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 A handler is a function that returns a promise or an observable. Functions that return `void` are
useful because `return;` can be used to abort an action. 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 **NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler
component using the variable `this`. 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 **NOTE:** Handlers do not need to check if any previous requests have finished because the
users attempting to trigger new actions before the previous ones have finished. directives have built in protection against users attempting to trigger new actions before the
previous ones have finished.
#### Example using promises #### Example using promises
@ -48,8 +50,8 @@ class Component {
Add the `bitAction` directive and supply the handler defined in step 1. Add the `bitAction` directive and supply the handler defined in step 1.
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. **NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`. This is different
This is different from how click handlers are usually defined with the output syntax `(click)="handler()"`. 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. **NOTE:** `[bitAction]` is used instead of `(click)`. Using both is not supported.

View File

@ -95,18 +95,12 @@ const Template: Story<BitFormFieldComponent> = (args: BitFormFieldComponent) =>
...args, ...args,
}, },
template: ` template: `
<form [formGroup]="formObj" (ngSubmit)="submit()"> <form [formGroup]="formObj">
<bit-form-field> <bit-form-field>
<bit-label>Name</bit-label> <bit-label>Label</bit-label>
<input bitInput formControlName="name" /> <input bitInput formControlName="name" />
<bit-hint>Optional Hint</bit-hint>
</bit-form-field> </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> </form>
`, `,
}); });

View File

@ -1,5 +1,4 @@
import { Component, Input } from "@angular/core"; import { FormsModule, ReactiveFormsModule, FormControl, FormGroup } from "@angular/forms";
import { FormsModule, ReactiveFormsModule, FormBuilder } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; 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 { I18nMockService } from "../utils/i18n-mock.service";
import { RadioButtonModule } from "./radio-button.module"; import { RadioButtonModule } from "./radio-button.module";
import { RadioGroupComponent } from "./radio-group.component";
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) {}
}
export default { export default {
title: "Component Library/Form/Radio Button", title: "Component Library/Form/Radio Button",
component: ExampleComponent, component: RadioGroupComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
declarations: [ExampleComponent],
imports: [FormsModule, ReactiveFormsModule, RadioButtonModule], imports: [FormsModule, ReactiveFormsModule, RadioButtonModule],
providers: [ providers: [
{ {
@ -92,56 +34,65 @@ 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",
}, },
}, },
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; } 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) => ({ <bit-radio-button id="radio-first" [value]="0">
props: args, <bit-label>First</bit-label>
template: storyTemplate, </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({}); export const Inline = InlineTemplate.bind({});
Inline.args = {
layout: "inline",
};
const BlockTemplate: Story<ExampleComponent> = (args: ExampleComponent) => ({ const BlockTemplate: Story<RadioGroupComponent> = (args: RadioGroupComponent) => ({
props: args, props: {
template: storyTemplate, formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" class="tw-block" [value]="0">
<bit-label>First</bit-label>
<bit-hint>This is a hint for the first option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-second" class="tw-block" [value]="1">
<bit-label>Second</bit-label>
<bit-hint>This is a hint for the second option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-third" class="tw-block" [value]="2">
<bit-label>Third</bit-label>
<bit-hint>This is a hint for the third option</bit-hint>
</bit-radio-button>
</bit-radio-group>
</form>
`,
}); });
export const Block = BlockTemplate.bind({}); export const Block = BlockTemplate.bind({});
Block.args = {
layout: "block",
};

View File

@ -81,13 +81,13 @@ import { Meta } from "@storybook/addon-docs";
# Bitwarden Component Library # Bitwarden Component Library
The Bitwarden Component Library is a collection of reusable low level components which empowers designers and The Bitwarden Component Library is a collection of reusable low level components which empowers
developers to work more efficiently. The primary goal is to ensure a consistent design and behavior across the designers and developers to work more efficiently. The primary goal is to ensure a consistent design
different clients and platforms. Currently the primary focus is the web based clients, namely _web_, _browser_ and and behavior across the different clients and platforms. Currently the primary focus is the web
_desktop_. 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 **Roll out status:** we are currently in the process of transitioning the Web Vault to utilize the
and the other clients will follow once this work is completed. component library and the other clients will follow once this work is completed.
<div className="subheading">Configure</div> <div className="subheading">Configure</div>

View File

@ -4,20 +4,21 @@ import { Meta, Story } from "@storybook/addon-docs";
# Banner # Banner
Banners are used for important communication with the user that needs to be seen right away, but has little effect on Banners are used for important communication with the user that needs to be seen right away, but has
the experience. Banners appear at the top of the user's screen on page load and persist across all pages a user little effect on the experience. Banners appear at the top of the user's screen on page load and
navigates to. 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 - They should always be dismissable and never use a timeout. If a user dismisses a banner, it should
during that same active session. 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 - Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their
decrease if too many are used. effectiveness may decrease if too many are used.
- Avoid stacking multiple banners. - Avoid stacking multiple banners.
- Banners supports buttons and anchors using [bitLink](?path=/story/component-library-link--anchors). - Banners support a button link (text button).
## Types ## 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. Use the following guidelines to help choose the correct type of banner.
@ -47,5 +48,6 @@ Rarely used, but may be used to alert users over critical messages or very outda
## Accessibility ## Accessibility
Dialogs sets the `role="status"` and `aria-live="polite"` attributes to ensure screen readers announce the content Banners sets the `role="status"` and `aria-live="polite"` attributes to ensure screen readers
prior to the test of the page. This behaviour can be disabled by setting `[useAlertRole]="false"`. announce the content prior to the test of the page. This behaviour can be disabled by setting
`[useAlertRole]="false"`.

View File

@ -4,30 +4,54 @@ import { Meta, Story } from "@storybook/addon-docs";
# Button # 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. Buttons can use either the `<a>` or `<button>` tags. Choose which based on the action the button
takes:
## Choosing the `<a>` or `<button>`
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 navigating to a new page, use `<a>`
- If taking an action on the current page use `<button>` - If taking an action on the current page use `<button>`
- If the button launches a dialog, 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" /> <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 ## Styles
There are 3 main styles for the button: Primary, Secondary, and Danger. 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" /> <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 ### Secondary
<Story id="component-library-button--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 ### Danger
@ -52,22 +79,14 @@ Use the danger styling only in settings when the user may preform a permanent ac
## Disabled UI ## Disabled UI
Both the disabled and loading states use the default states color with a 60% opacity or `tw-opacity-60`. Both the disabled and loading states use the default states color with a 60% opacity or
`tw-opacity-60`.
<Story id="component-library-button--disabled" /> <Story id="component-library-button--disabled" />
## Block ## 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. Typically button widths expand with their text. In some causes though buttons may need to be block
where the width is fixed and the text wraps to 2 lines if exceeding the buttons width.
<Story id="component-library-button--block" /> <Story 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).

View File

@ -89,6 +89,17 @@ th {
# Colors # 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"> <div class="tw-flex tw-space-x-4">
<Table /> <Table />
<Table class="theme_dark tw-bg-background" /> <Table class="theme_dark tw-bg-background" />

View 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

View File

@ -6,7 +6,11 @@ import { Meta } from "@storybook/addon-docs/";
# Iconography # 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 ## Status Indicators

View File

@ -4,9 +4,9 @@ import { Meta } from "@storybook/addon-docs";
# `bitInput` # `bitInput`
`bitInput` is an Angular directive to be used on `<input>`, `<select>`, and `<textarea>` `bitInput` is an Angular directive to be used on `<input>`, `<select>`, and `<textarea>` tags in
tags in order to provide standardized TailwindCss styling, error handling, and more. order to provide standardized TailwindCss styling, error handling, and more. It is meant to be used
It is meant to be used within a `<bit-form-field>` custom component. within a `<bit-form-field>` custom component.
## Basic Usage Example ## 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 ## Disabled `bitInput` and Error Handling
If you would like to be able to still show errors when an input is disabled for If you would like to be able to still show errors when an input is disabled for specific validation
specific validation scenarios, then set `[showErrorsWhenDisabled]="true"` scenarios, then set `[showErrorsWhenDisabled]="true"`
```html ```html
<bit-form-field> <bit-form-field>
@ -31,7 +31,8 @@ specific validation scenarios, then set `[showErrorsWhenDisabled]="true"`
</bit-form-field> </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 ```ts
get exampleFormCtrl(): FormControl { get exampleFormCtrl(): FormControl {

View File

@ -6,13 +6,37 @@ import { Meta, Story, Source } from "@storybook/addon-docs";
## Overview ## 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" /> <Story id="component-library-table--default" />
The below code is the absolute minimum required to create a table. However we strongly advise you to ### Usage
use the `dataSource` input to provide a data source for your table. This allows you to easily sort
data. 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 ```html
<bit-table> <bit-table>
@ -36,9 +60,8 @@ data.
## Data Source ## Data Source
Bitwarden provides a data source for tables that can be used in place of a traditional data array. 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 `TableDataSource` implements sorting and filtering capabilities. This allows the `bitTable`
the `bitTable` component to focus on rendering while offloading the data management to the data component to focus on rendering while offloading the data management to the data source.
source.
```ts ```ts
// External data source // 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 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$`. 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 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 wires up to the `TableDataSource` and will automatically sort the data when clicked and display an
an indicator for which column is currently sorted. The dafault sorting can be specified by setting indicator for which column is currently sorted. The dafault sorting can be specified by setting the
the `default`. `default`.
```html ```html
<th bitCell bitSortable="id" default>Id</th> <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); 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 ### Virtual Scrolling
It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount 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> </bit-table>
</cdk-virtual-scroll-viewport> </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