mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 06:23:38 +00:00
[EC-86] Add bit-badge-list component and tweak BadgeModule to support both the component and directive.
Update mockI18nService to support templated strings.
This commit is contained in:
@@ -81,22 +81,11 @@
|
||||
</button>
|
||||
</td>
|
||||
<td bitCell (click)="edit(g)" class="tw-cursor-pointer">
|
||||
<span
|
||||
*ngFor="let cName of g.collectionNames.slice(0, maxCollections); let last = last"
|
||||
bitBadge
|
||||
badgeType="secondary"
|
||||
class="tw-mx-1"
|
||||
>
|
||||
{{ cName }} <span class="sr-only" *ngIf="!last">, </span>
|
||||
</span>
|
||||
<span
|
||||
*ngIf="g.collectionNames.length > maxCollections"
|
||||
bitBadge
|
||||
badgeType="secondary"
|
||||
class="tw-mx-1"
|
||||
>
|
||||
{{ "plusNMore" | i18n: (g.collectionNames.length - maxCollections).toString() }}
|
||||
</span>
|
||||
<bit-badge-list
|
||||
[items]="g.collectionNames"
|
||||
[maxItems]="2"
|
||||
badgeType="warning"
|
||||
></bit-badge-list>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
|
||||
@@ -71,7 +71,6 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected didScroll = false;
|
||||
protected pageSize = 100;
|
||||
protected maxCollections = 2;
|
||||
|
||||
private pagedGroupsCount = 0;
|
||||
private pagedGroups: GroupDetailsRow[];
|
||||
|
||||
12
libs/components/src/badge/badge-list.component.html
Normal file
12
libs/components/src/badge/badge-list.component.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<span
|
||||
*ngFor="let item of filteredItems; let last = last"
|
||||
bitBadge
|
||||
[badgeType]="badgeType"
|
||||
class="tw-mx-1"
|
||||
>
|
||||
{{ item }}
|
||||
<span class="sr-only" *ngIf="!last">, </span>
|
||||
</span>
|
||||
<span *ngIf="isFiltered" bitBadge [badgeType]="badgeType" class="tw-mx-1">
|
||||
{{ "plusNMore" | i18n: (items.length - filteredItems.length).toString() }}
|
||||
</span>
|
||||
48
libs/components/src/badge/badge-list.component.ts
Normal file
48
libs/components/src/badge/badge-list.component.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { BadgeTypes } from "./badge.directive";
|
||||
|
||||
@Component({
|
||||
selector: "bit-badge-list",
|
||||
templateUrl: "badge-list.component.html",
|
||||
})
|
||||
export class BadgeListComponent {
|
||||
private _maxItems: number | null = null;
|
||||
private _items: string[] = [];
|
||||
|
||||
protected filteredItems: string[] = [];
|
||||
|
||||
@Input() badgeType: BadgeTypes = "primary";
|
||||
|
||||
@Input()
|
||||
get maxItems(): number | null {
|
||||
return this._maxItems;
|
||||
}
|
||||
|
||||
set maxItems(value: number | null) {
|
||||
this._maxItems = value == null ? null : Math.max(1, value);
|
||||
this.updateFilteredItems();
|
||||
}
|
||||
|
||||
@Input()
|
||||
get items(): string[] {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
set items(value: string[]) {
|
||||
this._items = value;
|
||||
this.updateFilteredItems();
|
||||
}
|
||||
|
||||
protected get isFiltered(): boolean {
|
||||
return this._items.length > this.filteredItems.length;
|
||||
}
|
||||
|
||||
private updateFilteredItems() {
|
||||
if (this.maxItems == null) {
|
||||
this.filteredItems = this._items;
|
||||
} else {
|
||||
this.filteredItems = this._items.slice(0, this.maxItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Directive, ElementRef, HostBinding, Input } from "@angular/core";
|
||||
|
||||
type BadgeTypes = "primary" | "secondary" | "success" | "danger" | "warning" | "info";
|
||||
export type BadgeTypes = "primary" | "secondary" | "success" | "danger" | "warning" | "info";
|
||||
|
||||
const styles: Record<BadgeTypes, string[]> = {
|
||||
primary: ["tw-bg-primary-500"],
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../shared";
|
||||
|
||||
import { BadgeListComponent } from "./badge-list.component";
|
||||
import { BadgeDirective } from "./badge.directive";
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [BadgeDirective],
|
||||
declarations: [BadgeDirective],
|
||||
imports: [SharedModule],
|
||||
exports: [BadgeDirective, BadgeListComponent],
|
||||
declarations: [BadgeDirective, BadgeListComponent],
|
||||
})
|
||||
export class BadgeModule {}
|
||||
|
||||
@@ -1,13 +1,40 @@
|
||||
import { Meta, Story } from "@storybook/angular";
|
||||
import { Meta, moduleMetadata, Story } from "@storybook/angular";
|
||||
|
||||
import { BadgeDirective } from "./badge.directive";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
|
||||
import { SharedModule } from "../shared";
|
||||
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||
|
||||
import { BadgeListComponent } from "./badge-list.component";
|
||||
import { BadgeDirective, BadgeTypes } from "./badge.directive";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Badge",
|
||||
component: BadgeDirective,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [SharedModule],
|
||||
declarations: [BadgeDirective, BadgeListComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: I18nService,
|
||||
useFactory: () => {
|
||||
return new I18nMockService({
|
||||
plusNMore: (n) => `+ ${n} more`,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
args: {
|
||||
badgeType: "primary",
|
||||
},
|
||||
argTypes: {
|
||||
badgeType: {
|
||||
options: ["primary", "secondary", "success", "danger", "warning", "info"] as BadgeTypes[],
|
||||
control: { type: "inline-radio" },
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
@@ -54,3 +81,16 @@ export const Info = Template.bind({});
|
||||
Info.args = {
|
||||
badgeType: "info",
|
||||
};
|
||||
|
||||
const ListTemplate: Story<BadgeListComponent> = (args: BadgeListComponent) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>`,
|
||||
});
|
||||
|
||||
export const BadgeList = ListTemplate.bind({});
|
||||
BadgeList.args = {
|
||||
badgeType: "info",
|
||||
maxItems: 3,
|
||||
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"],
|
||||
};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./badge.directive";
|
||||
export { BadgeDirective } from "./badge.directive";
|
||||
export * from "./badge.module";
|
||||
|
||||
@@ -9,10 +9,14 @@ export class I18nMockService implements I18nService {
|
||||
collator: Intl.Collator;
|
||||
localeNames: Map<string, string>;
|
||||
|
||||
constructor(private lookupTable: Record<string, string>) {}
|
||||
constructor(private lookupTable: Record<string, string | ((...args: string[]) => string)>) {}
|
||||
|
||||
t(id: string, p1?: string, p2?: string, p3?: string) {
|
||||
return this.lookupTable[id];
|
||||
const value = this.lookupTable[id];
|
||||
if (typeof value == "string") {
|
||||
return value;
|
||||
}
|
||||
return value(p1, p2, p3);
|
||||
}
|
||||
|
||||
translate(id: string, p1?: string, p2?: string, p3?: string) {
|
||||
|
||||
Reference in New Issue
Block a user