Skip to content

Commit

Permalink
Update CL documentation (#5379)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton authored May 8, 2023
1 parent f51ed10 commit d53f79e
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 220 deletions.
10 changes: 9 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
{
"printWidth": 100
"printWidth": 100,
"overrides": [
{
"files": "*.mdx",
"options": {
"proseWrap": "always"
}
}
]
}
49 changes: 28 additions & 21 deletions libs/components/src/async-actions/in-forms.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,32 @@ 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

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({...})
Expand All @@ -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.

Expand All @@ -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({...})
Expand Down Expand Up @@ -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.

Expand All @@ -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>
Expand Down
12 changes: 6 additions & 6 deletions libs/components/src/async-actions/overview.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down
22 changes: 12 additions & 10 deletions libs/components/src/async-actions/standalone.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ 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

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

Expand Down Expand Up @@ -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.

Expand Down
12 changes: 3 additions & 9 deletions libs/components/src/form-field/form-field.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
`,
});
Expand Down
161 changes: 56 additions & 105 deletions libs/components/src/radio-button/radio-button.stories.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,18 @@
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";

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: [
{
Expand All @@ -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<ExampleComponent> = (args: ExampleComponent) => ({
props: args,
template: storyTemplate,
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>
<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",
};
Loading

0 comments on commit d53f79e

Please sign in to comment.