1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

[PM-2276] Upgrade Storybook to v7 (#5258)

This commit is contained in:
Oscar Hinton
2023-05-26 15:58:06 +02:00
committed by GitHub
parent 1638a1d6f5
commit f7b372a0b0
72 changed files with 6340 additions and 16409 deletions

View File

@@ -0,0 +1,149 @@
import { Meta, Story, Source, Primary, Controls } from "@storybook/addon-docs";
import * as stories from "./table.stories";
<Meta of={stories} />
# Table
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.
<Primary />
<Controls />
## 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.
### 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>
<ng-container header>
<tr>
<th bitCell>Header 1</th>
<th bitCell>Header 2</th>
<th bitCell>Header 3</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow>
<td bitCell>Cell 1</td>
<td bitCell>Cell 2</td>
<td bitCell>Cell 3</td>
</tr>
</ng-template>
</bit-table>
```
## 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 filtering capabilities. This allows the `bitTable`
component to focus on rendering while offloading the data management to the data source.
```ts
// External data source
const data: T[];
const dataSource = new TableDataSource<T>();
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$`.
```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>
```
### 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`.
```html
<th bitCell bitSortable="id" default>Id</th>
<th bitCell bitSortable="name" default>Name</th>
```
It's also possible to define a custom sorting function by setting the `fn` input.
```ts
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
of data. This is easily done by wrapping the table in the `cdk-virtual-scroll-viewport` component,
specify a `itemSize`, set `scrollWindow` to `true` and replace `*ngFor` with `*cdkVirtualFor`.
```html
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="id" default>Id</th>
<th bitCell bitSortable="name">Name</th>
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
</tr>
</ng-container>
<ng-template let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
```

View File

@@ -1,5 +1,5 @@
import { ScrollingModule } from "@angular/cdk/scrolling";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { countries } from "../form/countries";
@@ -27,41 +27,43 @@ export default {
},
} as Meta;
const Template: Story = (args) => ({
props: args,
template: `
<bit-table>
<ng-container header>
<tr>
<th bitCell>Header 1</th>
<th bitCell>Header 2</th>
<th bitCell>Header 3</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 1</td>
<td bitCell>Cell 2 <br> Multiline Cell</td>
<td bitCell>Cell 3</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 4</td>
<td bitCell>Cell 5</td>
<td bitCell>Cell 6</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 7 <br> Multiline Cell</td>
<td bitCell>Cell 8</td>
<td bitCell>Cell 9</td>
</tr>
</ng-template>
</bit-table>
`,
});
type Story = StoryObj;
export const Default = Template.bind({});
Default.args = {
alignRowContent: "baseline",
export const Default: Story = {
render: (args) => ({
props: args,
template: `
<bit-table>
<ng-container header>
<tr>
<th bitCell>Header 1</th>
<th bitCell>Header 2</th>
<th bitCell>Header 3</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 1</td>
<td bitCell>Cell 2 <br> Multiline Cell</td>
<td bitCell>Cell 3</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 4</td>
<td bitCell>Cell 5</td>
<td bitCell>Cell 6</td>
</tr>
<tr bitRow [alignContent]="alignRowContent">
<td bitCell>Cell 7 <br> Multiline Cell</td>
<td bitCell>Cell 8</td>
<td bitCell>Cell 9</td>
</tr>
</ng-template>
</bit-table>
`,
}),
args: {
alignRowContent: "baseline",
},
};
const data = new TableDataSource<{ id: number; name: string; other: string }>();
@@ -72,48 +74,13 @@ data.data = [...Array(5).keys()].map((i) => ({
other: `other-${i}`,
}));
const DataSourceTemplate: Story = (args) => ({
props: {
dataSource: data,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<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>
`,
});
export const DataSource = DataSourceTemplate.bind({});
const data2 = new TableDataSource<{ id: number; name: string; other: string }>();
data2.data = [...Array(100).keys()].map((i) => ({
id: i,
name: `name-${i}`,
other: `other-${i}`,
}));
const ScrollableTemplate: Story = (args) => ({
props: {
dataSource: data2,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
export const DataSource: Story = {
render: (args) => ({
props: {
dataSource: data,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
@@ -123,51 +90,86 @@ const ScrollableTemplate: Story = (args) => ({
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
`,
});
}),
};
export const Scrollable = ScrollableTemplate.bind({});
const data2 = new TableDataSource<{ id: number; name: string; other: string }>();
data2.data = [...Array(100).keys()].map((i) => ({
id: i,
name: `name-${i}`,
other: `other-${i}`,
}));
export const Scrollable: Story = {
render: (args) => ({
props: {
dataSource: data2,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="id" default>Id</th>
<th bitCell bitSortable="name">Name</th>
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.id }}</td>
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.other }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
`,
}),
};
const data3 = new TableDataSource<{ value: string; name: string }>();
// Chromatic has a max page size, lowering the number of entries to ensure we don't hit it
data3.data = countries.slice(0, 100);
const FilterableTemplate: Story = (args) => ({
props: {
dataSource: data3,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" />
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
<th bitCell bitSortable="value" width="120px">Value</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.value }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
export const Filterable: Story = {
render: (args) => ({
props: {
dataSource: data3,
sortFn: (a: any, b: any) => a.id - b.id,
},
template: `
<input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" />
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
<th bitCell bitSortable="value" width="120px">Value</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *cdkVirtualFor="let r of rows$">
<td bitCell>{{ r.name }}</td>
<td bitCell>{{ r.value }}</td>
</tr>
</ng-template>
</bit-table>
</cdk-virtual-scroll-viewport>
`,
});
export const Filterable = FilterableTemplate.bind({});
}),
};
const data4 = new TableDataSource<{ name: string }>();
@@ -175,24 +177,24 @@ data4.data = [...Array(5).keys()].map((i) => ({
name: i % 2 == 0 ? `name-${i}`.toUpperCase() : `name-${i}`.toLowerCase(),
}));
const VariableCaseTemplate: Story = (args) => ({
props: {
dataSource: data4,
},
template: `
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.name }}</td>
</tr>
</ng-template>
</bit-table>
export const VariableCase: Story = {
render: (args) => ({
props: {
dataSource: data4,
},
template: `
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>Name</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>{{ r.name }}</td>
</tr>
</ng-template>
</bit-table>
`,
});
export const VariableCase = VariableCaseTemplate.bind({});
}),
};