mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
[CL-220] item components (#8870)
This commit is contained in:
1
libs/components/src/item/index.ts
Normal file
1
libs/components/src/item/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./item.module";
|
||||
12
libs/components/src/item/item-action.component.ts
Normal file
12
libs/components/src/item/item-action.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { A11yCellDirective } from "../a11y/a11y-cell.directive";
|
||||
|
||||
@Component({
|
||||
selector: "bit-item-action",
|
||||
standalone: true,
|
||||
imports: [],
|
||||
template: `<ng-content></ng-content>`,
|
||||
providers: [{ provide: A11yCellDirective, useExisting: ItemActionComponent }],
|
||||
})
|
||||
export class ItemActionComponent extends A11yCellDirective {}
|
||||
16
libs/components/src/item/item-content.component.html
Normal file
16
libs/components/src/item/item-content.component.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="tw-flex tw-gap-2 tw-items-center">
|
||||
<ng-content select="[slot=start]"></ng-content>
|
||||
|
||||
<div class="tw-flex tw-flex-col tw-items-start tw-text-start tw-w-full [&_p]:tw-mb-0">
|
||||
<div class="tw-text-main tw-text-base">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div class="tw-text-muted tw-text-sm">
|
||||
<ng-content select="[slot=secondary]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-gap-2 tw-items-center">
|
||||
<ng-content select="[slot=end]"></ng-content>
|
||||
</div>
|
||||
15
libs/components/src/item/item-content.component.ts
Normal file
15
libs/components/src/item/item-content.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "bit-item-content, [bit-item-content]",
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: `item-content.component.html`,
|
||||
host: {
|
||||
class:
|
||||
"fvw-target tw-outline-none tw-text-main hover:tw-text-main hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between",
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ItemContentComponent {}
|
||||
13
libs/components/src/item/item-group.component.ts
Normal file
13
libs/components/src/item/item-group.component.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "bit-item-group",
|
||||
standalone: true,
|
||||
imports: [],
|
||||
template: `<ng-content></ng-content>`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
class: "tw-block",
|
||||
},
|
||||
})
|
||||
export class ItemGroupComponent {}
|
||||
21
libs/components/src/item/item.component.html
Normal file
21
libs/components/src/item/item.component.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!-- TODO: Colors will be finalized in the extension refresh feature branch -->
|
||||
<div
|
||||
class="tw-box-border tw-overflow-auto tw-flex tw-bg-background [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-bg-primary-300/20 tw-text-main tw-border-solid tw-border-b tw-border-0 tw-rounded-lg tw-mb-1.5"
|
||||
[ngClass]="
|
||||
focusVisibleWithin()
|
||||
? 'tw-z-10 tw-rounded tw-outline-none tw-ring tw-ring-primary-600 tw-border-transparent'
|
||||
: 'tw-border-b-secondary-300 [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-border-b-transparent'
|
||||
"
|
||||
>
|
||||
<bit-item-action class="item-main-content tw-block tw-w-full">
|
||||
<ng-content></ng-content>
|
||||
</bit-item-action>
|
||||
|
||||
<div
|
||||
#endSlot
|
||||
class="tw-p-2 tw-flex tw-gap-1 tw-items-center"
|
||||
[hidden]="endSlot.childElementCount === 0"
|
||||
>
|
||||
<ng-content select="[slot=end]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
29
libs/components/src/item/item.component.ts
Normal file
29
libs/components/src/item/item.component.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component, HostListener, signal } from "@angular/core";
|
||||
|
||||
import { A11yRowDirective } from "../a11y/a11y-row.directive";
|
||||
|
||||
import { ItemActionComponent } from "./item-action.component";
|
||||
|
||||
@Component({
|
||||
selector: "bit-item",
|
||||
standalone: true,
|
||||
imports: [CommonModule, ItemActionComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: "item.component.html",
|
||||
providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }],
|
||||
})
|
||||
export class ItemComponent extends A11yRowDirective {
|
||||
/**
|
||||
* We have `:focus-within` and `:focus-visible` but no `:focus-visible-within`
|
||||
*/
|
||||
protected focusVisibleWithin = signal(false);
|
||||
@HostListener("focusin", ["$event.target"])
|
||||
onFocusIn(target: HTMLElement) {
|
||||
this.focusVisibleWithin.set(target.matches(".fvw-target:focus-visible"));
|
||||
}
|
||||
@HostListener("focusout")
|
||||
onFocusOut() {
|
||||
this.focusVisibleWithin.set(false);
|
||||
}
|
||||
}
|
||||
141
libs/components/src/item/item.mdx
Normal file
141
libs/components/src/item/item.mdx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./item.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
```ts
|
||||
import { ItemModule } from "@bitwarden/components";
|
||||
```
|
||||
|
||||
# Item
|
||||
|
||||
`<bit-item>` is a horizontal card that contains one or more interactive actions.
|
||||
|
||||
It is a generic container that can be used for either standalone content, an alternative to tables,
|
||||
or to list nav links.
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.Default} />
|
||||
</Canvas>
|
||||
|
||||
## Primary Content
|
||||
|
||||
The primary content of an item is supplied by `bit-item-content`.
|
||||
|
||||
### Content Types
|
||||
|
||||
The content can be a button, anchor, or static container.
|
||||
|
||||
```html
|
||||
<bit-item>
|
||||
<a bit-item-content routerLink="..."> Hi, I am a link. </a>
|
||||
</bit-item>
|
||||
|
||||
<bit-item>
|
||||
<button bit-item-content (click)="...">And I am a button.</button>
|
||||
</bit-item>
|
||||
|
||||
<bit-item>
|
||||
<bit-item-content> I'm just static :( </bit-item-content>
|
||||
</bit-item>
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.ContentTypes} />
|
||||
</Canvas>
|
||||
|
||||
### Content Slots
|
||||
|
||||
`bit-item-content` contains the following slots to help position the content:
|
||||
|
||||
| Slot | Description |
|
||||
| ------------------ | --------------------------------------------------- |
|
||||
| default | primary text or arbitrary content; fan favorite |
|
||||
| `slot="secondary"` | supporting text; under the default slot |
|
||||
| `slot="start"` | commonly an icon or avatar; before the default slot |
|
||||
| `slot="end"` | commonly an icon; after the default slot |
|
||||
|
||||
- Note: There is also an `end` slot within `bit-item` itself. Place
|
||||
[interactive secondary actions](#secondary-actions) there, and place non-interactive content (such
|
||||
as icons) in `bit-item-content`
|
||||
|
||||
```html
|
||||
<bit-item>
|
||||
<button bit-item-content type="button">
|
||||
<bit-avatar slot="start" text="Foo"></bit-avatar>
|
||||
foo@bitwarden.com
|
||||
<ng-container slot="secondary">
|
||||
<div>Bitwarden.com</div>
|
||||
<div><em>locked</em></div>
|
||||
</ng-container>
|
||||
<i slot="end" class="bwi bwi-lock" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-item>
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.ContentSlots} />
|
||||
</Canvas>
|
||||
|
||||
## Secondary Actions
|
||||
|
||||
Secondary interactive actions can be placed in the item through the `"end"` slot, outside of
|
||||
`bit-item-content`.
|
||||
|
||||
Each action must be wrapped by `<bit-item-action>`.
|
||||
|
||||
Actions are commonly icon buttons or badge buttons.
|
||||
|
||||
```html
|
||||
<bit-item>
|
||||
<button bit-item-content>...</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" aria-label="Copy"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" aria-label="Options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
```
|
||||
|
||||
## Item Groups
|
||||
|
||||
Groups of items can be associated by wrapping them in the `<bit-item-group>`.
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.MultipleActionList} />
|
||||
</Canvas>
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.SingleActionList} />
|
||||
</Canvas>
|
||||
|
||||
### A11y
|
||||
|
||||
Keyboard nav is currently disabled due to a bug when used within a virtual scroll viewport.
|
||||
|
||||
Item groups utilize arrow-based keyboard navigation
|
||||
([further reading here](https://www.w3.org/WAI/ARIA/apg/patterns/grid/examples/layout-grids/#kbd_label)).
|
||||
|
||||
Use `aria-label` or `aria-labelledby` to give groups an accessible name.
|
||||
|
||||
```html
|
||||
<bit-item-group aria-label="My Items">
|
||||
<bit-item>...</bit-item>
|
||||
<bit-item>...</bit-item>
|
||||
<bit-item>...</bit-item>
|
||||
</bit-item-group>
|
||||
```
|
||||
|
||||
### Virtual Scrolling
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.VirtualScrolling} />
|
||||
</Canvas>
|
||||
12
libs/components/src/item/item.module.ts
Normal file
12
libs/components/src/item/item.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ItemActionComponent } from "./item-action.component";
|
||||
import { ItemContentComponent } from "./item-content.component";
|
||||
import { ItemGroupComponent } from "./item-group.component";
|
||||
import { ItemComponent } from "./item.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [ItemComponent, ItemContentComponent, ItemActionComponent, ItemGroupComponent],
|
||||
exports: [ItemComponent, ItemContentComponent, ItemActionComponent, ItemGroupComponent],
|
||||
})
|
||||
export class ItemModule {}
|
||||
326
libs/components/src/item/item.stories.ts
Normal file
326
libs/components/src/item/item.stories.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular";
|
||||
|
||||
import { A11yGridDirective } from "../a11y/a11y-grid.directive";
|
||||
import { AvatarModule } from "../avatar";
|
||||
import { BadgeModule } from "../badge";
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
import { TypographyModule } from "../typography";
|
||||
|
||||
import { ItemActionComponent } from "./item-action.component";
|
||||
import { ItemContentComponent } from "./item-content.component";
|
||||
import { ItemGroupComponent } from "./item-group.component";
|
||||
import { ItemComponent } from "./item.component";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Item",
|
||||
component: ItemComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [
|
||||
CommonModule,
|
||||
ItemGroupComponent,
|
||||
AvatarModule,
|
||||
IconButtonModule,
|
||||
BadgeModule,
|
||||
TypographyModule,
|
||||
ItemActionComponent,
|
||||
ItemContentComponent,
|
||||
A11yGridDirective,
|
||||
ScrollingModule,
|
||||
],
|
||||
}),
|
||||
componentWrapperDecorator((story) => `<div class="tw-bg-background-alt tw-p-2">${story}</div>`),
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
type Story = StoryObj<ItemGroupComponent>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const ContentSlots: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<bit-item>
|
||||
<button bit-item-content type="button">
|
||||
<bit-avatar
|
||||
slot="start"
|
||||
[text]="'Foo'"
|
||||
></bit-avatar>
|
||||
foo@bitwarden.com
|
||||
<ng-container slot="secondary">
|
||||
<div>Bitwarden.com</div>
|
||||
<div><em>locked</em></div>
|
||||
</ng-container>
|
||||
<i slot="end" class="bwi bwi-lock" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-item>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const ContentTypes: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Hi, I am a link.
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content href="#">
|
||||
And I am a button.
|
||||
</button>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<bit-item-content>
|
||||
I'm just static :(
|
||||
</bit-item-content>
|
||||
</bit-item>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const TextOverflow: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<div class="tw-text-main tw-mb-4">TODO: Fix truncation</div>
|
||||
<bit-item>
|
||||
<bit-item-content>
|
||||
Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||
</bit-item-content>
|
||||
</bit-item>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const MultipleActionList: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<bit-item-group aria-label="Multiple Action List">
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const SingleActionList: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<bit-item-group aria-label="Single Action List">
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const VirtualScrolling: Story = {
|
||||
render: (_args) => ({
|
||||
props: {
|
||||
data: Array.from(Array(100000).keys()),
|
||||
},
|
||||
template: /*html*/ `
|
||||
<cdk-virtual-scroll-viewport [itemSize]="46" class="tw-h-[500px]">
|
||||
<bit-item-group aria-label="Single Action List">
|
||||
<bit-item *cdkVirtualFor="let item of data">
|
||||
<button bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
{{ item }}
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
Reference in New Issue
Block a user