mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
[EC-16] Implement new Groups Tab (#3563)
* [EC-16] Cleanup RxJS linting problems * [EC-16] Update Group tab to use table component and show collections. * [EC-16] Extract interface from GroupResponse and use it in the view * [EC-16] Remove heading underline * [EC-16] Cleanup i18n * [EC-16] More i18n cleanup * [EC-16] Fix bulk group request type name * [EC-16] Rename group details type * [EC-86] Clear collectionMap before populating it with new collections * [EC-86] Update initialization/loading logic to make better use of the Observable pattern * [EC-86] Make table cells use a pointer cursor * [EC-86] Use bitIconButton for row menu triggers * [EC-86] Refactor GroupDetailsRow interface to wrap GroupDetailsResponse. Remove response model interfaces. Cleanup GroupsComponent. * [EC-86] Add bit-badge-list component and tweak BadgeModule to support both the component and directive. Update mockI18nService to support templated strings. * [EC-86] Cleanup badge color and bitIconButton classes * [EC-86] Cleanup more styles * [EC-86] Add GroupApiService Add a new GroupApiService to replace Group Api calls in the ApiService. * [EC-86] Revisions for badge-list implementation. - Remove `| null` for maxItems according to ADR-0014 - Remove custom setter for items - Use ngOnChanges to update filteredItems - Fix sr-only tailwind class and show screen reader comma after last item if truncated. * [EC-86] Refactor badge-list module/component - Move the badge list component to its own module. - Extract badge list stories from badge stories. - Cleanup bade stories and module after refactor. * [EC-86] Refactor/rename GroupApiService - Re-name GroupApiService to GroupService as there is no need for a separate Api service (no sync or local data for admin services) - Add GroupView for use in the GroupService instead of raw API models - Update views to use GroupView instead of raw GroupResponse models * [EC-86] Refactor group API request models - Move organizationGroupBulkRequest to group requests folder - Fix relative imports in GroupService * [EC-86] Fix linting errors * Fix tab item text color Tab item text color broke after a merge from master and needs a fix to account for bootstrap styles in Web. * [EC-86] Rename new files using kebab-case * [EC-86] Fix group view file name * [EC-86] Fix group request/response file names * [EC-86] Cleanup badge stories per review suggestions * [EC-86] Use inline-flex for badge list container * [EC-86] Move GroupService and Views to Web org module - Move GroupService and GroupServiceAbstraction to Organization Module - Add GroupService provider to Organization Module - Move collection-add-edit.component, user-groups.component, group-add-edit.component, and groups.component into Organization Module as they now depend on GroupService - Remove moved components from Loose Component module * [EC-86] Fix Group table search Adds the id and name properties to GroupDetailsRow to support using the searchPipe (which cannot access nested values such as details.name for filtering). * [EC-86] Fix badge story controls * [EC-87] Edit Group Dialog (#3651) * [EC-87] Update the edit dialog to use content tabs * [EC-87] WIP FormListSelection abstract controller * [EC-87] WIP FormListSelection for members and collections * [EC-87] More WIP on FormListSelection * [EC-87] WIP Working FormSelectionList with initial value support * [EC-87] WIP SelectionList without FormControls and with i18n support for sorting * [EC-87] Final sorted SelectionList with FormArray support * [EC-87] Extract and document FormSelectionList * [EC-87] Functional edit group modal * [EC-87] Remove button icon padding for bitButton directives * [EC-87] Use new disablePadding attribute for Dialog component * [EC-87] Some more cleanup and finetuning * [EC-87] Move enum declaration to top * [EC-87] Remove inline style from access selector * [EC-87] Move Group components into Organization Module * [EC-87] Add MultiSelectModule to Shared Web module * [EC-87] Integrate AccessSelector component in GroupAddEdit modal - Remove duplicate permission / selection readonly helpers from GroupAddEdit component - Use access item views/values for collection and member lists - Replace access selector HTMl with the AccessSelector component * [EC-87] Update Group collections column to open Collection tab * [EC-87] Remove old FormSelectionList file * [EC-87] Fix missed file import changes after merge * [EC-87] Remove GroupAddEditComponent modal service registration Groups component is now using the DialogService which does not require explicit registration for lazy loaded components. * [EC-87] Use injected DIALOG_DATA for GroupAddEdit component - Add types for the GroupAddEdit dialog params, result, and tab indices - Add strongly typed helper method to open GroupAddEdit dialogs - Remove @Input()/@Output() properties. Replaced with the injected DIALOG_DATA params instead - Use dialogRef.close() and result type instead of event emitters * [EC-87] Rename collection tab type to collections * [EC-87] Refactor postGroup() and putGroup() from ApiService - Move postGroup() and putGroup() methods to GroupService - Remove postGroup() and putGroup() from ApiService - Move GroupResponse and GroupRequest into Web (from lib/common) * [EC-87] Remove required attribute * [EC-87] Use PascalCase for template Enums * [EC-87] Use group modal tab enum in template * [EC-87] Convert dialog result to promise * [EC-87] Refactor dialog positionStrategy - Add .top() to position strategy to allow clicking the backdrop to close the dialog - Move the positionStrategy option into the openGroupAddEditDialog helper * [EC-87] Remove [preserveContent] from tab group * [EC-87] Use new CL async actions - Update handlers to be arrow-functions - Remove old form and delete promises - Use [bitSubmit] directive on form - Use bitFormButton directive and [bitAction] for submit and delete buttons - Remove delete/spinner bwi icons as they are handled by the new async directives * [EC-87] Introduce CollectionAccessSelectionView Use a new view to replace the SelectionReadonlyResponse/Request classes. * [EC-87] Use new access selection view in GroupView - Change the collections type - Add members list to make the view more complete - Update the static fromResponse helper to properly map the GroupDetailsResponse to the new access selection view - Update access selector helpers to use new access selection view instead of response/request models * [EC-87] Update GroupService to have a single save() method that accepts a GroupView - Add save() method that checks for existing group id to determine which API method to use - Make post/put group methods private * [EC-87] Utilize the new save() method in the group modal * [EC-87] Use observables for fetching data - Introduce 3 observables for collections, members, and group details - Combine and subscribe to those observables in ngOnInit - Add destroy$ subject - Inject changeDetectorRef to handle quirk of patching the AccessSelector value before available items are set
This commit is contained in:
9
libs/components/src/badge-list/badge-list.component.html
Normal file
9
libs/components/src/badge-list/badge-list.component.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="tw-inline-flex tw-gap-2">
|
||||
<span *ngFor="let item of filteredItems; let last = last" bitBadge [badgeType]="badgeType">
|
||||
{{ item }}
|
||||
<span class="tw-sr-only" *ngIf="!last || isFiltered">, </span>
|
||||
</span>
|
||||
<span *ngIf="isFiltered" bitBadge [badgeType]="badgeType">
|
||||
{{ "plusNMore" | i18n: (items.length - filteredItems.length).toString() }}
|
||||
</span>
|
||||
</div>
|
||||
35
libs/components/src/badge-list/badge-list.component.ts
Normal file
35
libs/components/src/badge-list/badge-list.component.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Component, Input, OnChanges } from "@angular/core";
|
||||
|
||||
import { BadgeTypes } from "../badge";
|
||||
|
||||
@Component({
|
||||
selector: "bit-badge-list",
|
||||
templateUrl: "badge-list.component.html",
|
||||
})
|
||||
export class BadgeListComponent implements OnChanges {
|
||||
private _maxItems: number;
|
||||
|
||||
protected filteredItems: string[] = [];
|
||||
protected isFiltered = false;
|
||||
|
||||
@Input() badgeType: BadgeTypes = "primary";
|
||||
@Input() items: string[] = [];
|
||||
|
||||
@Input()
|
||||
get maxItems(): number | undefined {
|
||||
return this._maxItems;
|
||||
}
|
||||
|
||||
set maxItems(value: number | undefined) {
|
||||
this._maxItems = value == undefined ? undefined : Math.max(1, value);
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.maxItems == undefined) {
|
||||
this.filteredItems = this.items;
|
||||
} else {
|
||||
this.filteredItems = this.items.slice(0, this.maxItems);
|
||||
}
|
||||
this.isFiltered = this.items.length > this.filteredItems.length;
|
||||
}
|
||||
}
|
||||
13
libs/components/src/badge-list/badge-list.module.ts
Normal file
13
libs/components/src/badge-list/badge-list.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { BadgeModule } from "../badge";
|
||||
import { SharedModule } from "../shared";
|
||||
|
||||
import { BadgeListComponent } from "./badge-list.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, BadgeModule],
|
||||
exports: [BadgeListComponent],
|
||||
declarations: [BadgeListComponent],
|
||||
})
|
||||
export class BadgeListModule {}
|
||||
53
libs/components/src/badge-list/badge-list.stories.ts
Normal file
53
libs/components/src/badge-list/badge-list.stories.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Meta, moduleMetadata, Story } from "@storybook/angular";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
|
||||
import { BadgeModule } from "../badge";
|
||||
import { SharedModule } from "../shared";
|
||||
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||
|
||||
import { BadgeListComponent } from "./badge-list.component";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Badge/List",
|
||||
component: BadgeListComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [SharedModule, BadgeModule],
|
||||
declarations: [BadgeListComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: I18nService,
|
||||
useFactory: () => {
|
||||
return new I18nMockService({
|
||||
plusNMore: (n) => `+ ${n} more`,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
args: {
|
||||
badgeType: "primary",
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A16956",
|
||||
},
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
const ListTemplate: Story<BadgeListComponent> = (args: BadgeListComponent) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>
|
||||
`,
|
||||
});
|
||||
|
||||
export const Default = ListTemplate.bind({});
|
||||
Default.args = {
|
||||
badgeType: "info",
|
||||
maxItems: 3,
|
||||
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"],
|
||||
};
|
||||
1
libs/components/src/badge-list/index.ts
Normal file
1
libs/components/src/badge-list/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./badge-list.module";
|
||||
@@ -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,10 +1,17 @@
|
||||
import { Meta, Story } from "@storybook/angular";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Meta, moduleMetadata, Story } from "@storybook/angular";
|
||||
|
||||
import { BadgeDirective } from "./badge.directive";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Badge",
|
||||
component: BadgeDirective,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [CommonModule],
|
||||
declarations: [BadgeDirective],
|
||||
}),
|
||||
],
|
||||
args: {
|
||||
badgeType: "primary",
|
||||
},
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./badge.directive";
|
||||
export { BadgeDirective, BadgeTypes } from "./badge.directive";
|
||||
export * from "./badge.module";
|
||||
|
||||
@@ -18,7 +18,7 @@ import { SimpleDialogComponent } from "./simple-dialog/simple-dialog.component";
|
||||
DialogComponent,
|
||||
SimpleDialogComponent,
|
||||
],
|
||||
exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent],
|
||||
exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent, DialogCloseDirective],
|
||||
providers: [DialogService],
|
||||
})
|
||||
export class DialogModule {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./async-actions";
|
||||
export * from "./avatar";
|
||||
export * from "./badge";
|
||||
export * from "./badge-list";
|
||||
export * from "./banner";
|
||||
export * from "./button";
|
||||
export * from "./callout";
|
||||
|
||||
@@ -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