Skip to content

Commit

Permalink
feat(admin-ui): Add support for custom action bar dropdown menus
Browse files Browse the repository at this point in the history
Closes #2678
  • Loading branch information
michaelbromley committed Feb 16, 2024
1 parent 81419e4 commit 4d8bc74
Show file tree
Hide file tree
Showing 50 changed files with 473 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<vdr-ab-left></vdr-ab-left>

<vdr-ab-right>
<vdr-action-bar-items locationId="asset-detail"></vdr-action-bar-items>
<vdr-action-bar-items locationId="asset-detail" />
<button
*vdrIfPermissions="['UpdateCatalog', 'UpdateAsset']"
class="btn btn-primary"
Expand All @@ -12,6 +12,7 @@
>
{{ 'common.update' | translate }}
</button>
<vdr-action-bar-dropdown-menu locationId="asset-detail" />
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
></vdr-language-selector>
</vdr-ab-left>
<vdr-ab-right>
<vdr-action-bar-items locationId="collection-detail"></vdr-action-bar-items>
<vdr-action-bar-items locationId="collection-detail" />
<button
class="btn btn-primary"
*ngIf="isNew$ | async; else updateButton"
Expand All @@ -27,8 +27,9 @@
>
{{ 'common.update' | translate }}
</button>
</ng-template></vdr-ab-right
>
</ng-template>
<vdr-action-bar-dropdown-menu locationId="collection-detail" />
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
<form class="form" [formGroup]="detailForm">
Expand Down Expand Up @@ -138,10 +139,7 @@
></vdr-assets>
</vdr-card>
<vdr-card [title]="'catalog.filters' | translate">
<vdr-form-field
[label]="'catalog.filter-inheritance' | translate"
for="inheritFilters"
>
<vdr-form-field [label]="'catalog.filter-inheritance' | translate" for="inheritFilters">
<clr-toggle-wrapper>
<input
type="checkbox"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
></vdr-language-selector>
</vdr-ab-left>
<vdr-ab-right>
<vdr-action-bar-items locationId="collection-list"></vdr-action-bar-items>
<vdr-action-bar-items locationId="collection-list" />
<a
class="btn btn-primary"
*vdrIfPermissions="['CreateCatalog', 'CreateCollection']"
Expand All @@ -17,6 +17,7 @@
<clr-icon shape="plus"></clr-icon>
{{ 'catalog.create-new-collection' | translate }}
</a>
<vdr-action-bar-dropdown-menu locationId="collection-list" />
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</vdr-ab-left>

<vdr-ab-right>
<vdr-action-bar-items locationId="facet-detail"></vdr-action-bar-items>
<vdr-action-bar-items locationId="facet-detail" />
<button
class="btn btn-primary"
*ngIf="isNew$ | async; else updateButton"
Expand All @@ -29,6 +29,7 @@
{{ 'common.update' | translate }}
</button>
</ng-template>
<vdr-action-bar-dropdown-menu locationId="facet-detail" />
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
></vdr-language-selector>
</vdr-ab-left>
<vdr-ab-right>
<vdr-action-bar-items locationId="facet-list"></vdr-action-bar-items>
<vdr-action-bar-items locationId="facet-list" />
<a
class="btn btn-primary"
[routerLink]="['./create']"
Expand All @@ -17,6 +17,7 @@
<clr-icon shape="plus"></clr-icon>
{{ 'catalog.create-new-facet' | translate }}
</a>
<vdr-action-bar-dropdown-menu locationId="facet-list" />
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
{{ 'common.update' | translate }}
</button>
</ng-template>
<vdr-action-bar-dropdown-menu locationId="product-detail"></vdr-action-bar-dropdown-menu>
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,12 @@
<clr-icon shape="plus"></clr-icon>
{{ 'catalog.create-new-product' | translate }}
</a>
<vdr-dropdown>
<button class="icon-button" vdrDropdownTrigger>
<clr-icon shape="ellipsis-vertical"></clr-icon>
<vdr-action-bar-dropdown-menu [alwaysShow]="true" locationId="product-list">
<button type="button" vdrDropdownItem (click)="rebuildSearchIndex()">
<clr-icon shape="refresh" class=""></clr-icon>
{{ 'catalog.rebuild-search-index' | translate }}
</button>
<vdr-dropdown-menu vdrPosition="bottom-right">
<button type="button" vdrDropdownItem (click)="rebuildSearchIndex()">
<clr-icon shape="refresh" class=""></clr-icon>
{{ 'catalog.rebuild-search-index' | translate }}
</button>
</vdr-dropdown-menu>
</vdr-dropdown>
</vdr-action-bar-dropdown-menu>
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
>
{{ 'common.update' | translate }}
</button>
<vdr-action-bar-dropdown-menu locationId="product-variant-detail"></vdr-action-bar-dropdown-menu>
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { APP_INITIALIZER, Provider } from '@angular/core';
import { ActionBarDropdownMenuItem } from '../providers/nav-builder/nav-builder-types';
import { NavBuilderService } from '../providers/nav-builder/nav-builder.service';

/**
* @description
* Adds a dropdown menu item to the ActionBar at the top right of each list or detail view. The locationId can
* be determined by pressing `ctrl + u` when running the Admin UI in dev mode.
*
* @example
* ```ts title="providers.ts"
* import { addActionBarDropdownMenuItem } from '\@vendure/admin-ui/core';
*
* export default [
* addActionBarDropdownMenuItem({
* id: 'print-invoice',
* label: 'Print Invoice',
* locationId: 'order-detail',
* routerLink: ['/extensions/invoicing'],
* }),
* ];
* ```
* @docsCategory action-bar
*/
export function addActionBarDropdownMenuItem(config: ActionBarDropdownMenuItem): Provider {
return {
provide: APP_INITIALIZER,
multi: true,
useFactory: (navBuilderService: NavBuilderService) => () => {
navBuilderService.addActionBarDropdownMenuItem(config);
},
deps: [NavBuilderService],
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export interface ActionBarButtonState {
* @docsCategory action-bar
*/
export interface ActionBarItem {
/**
* @description
* A unique identifier for the item.
*/
id: string;
label: string;
locationId: ActionBarLocationId;
Expand Down Expand Up @@ -145,6 +149,53 @@ export interface ActionBarItem {
requiresPermission?: string | string[];
}

/**
* @description
* A dropdown menu item in the ActionBar area at the top of one of the list or detail views.
*
* @docsCategory action-bar
* @since 2.2.0
*/
export interface ActionBarDropdownMenuItem {
/**
* @description
* A unique identifier for the item.
*/
id: string;
label: string;
locationId: ActionBarLocationId;
/**
* @description
* Whether to render a divider above this item.
*/
hasDivider?: boolean;
/**
* @description
* A function which returns an observable of the button state, allowing you to
* dynamically enable/disable or show/hide the button.
*/
buttonState?: (context: ActionBarContext) => Observable<ActionBarButtonState>;
onClick?: (event: MouseEvent, context: ActionBarContext) => void;
routerLink?: RouterLinkDefinition;
icon?: string;
/**
* @description
* Control the display of this item based on the user permissions. Note: if you attempt to pass a
* {@link PermissionDefinition} object, you will get a compilation error. Instead, pass the plain
* string version. For example, if the permission is defined as:
* ```ts
* export const MyPermission = new PermissionDefinition('ProductReview');
* ```
* then the generated permission strings will be:
*
* - `CreateProductReview`
* - `ReadProductReview`
* - `UpdateProductReview`
* - `DeleteProductReview`
*/
requiresPermission?: string | string[];
}

/**
* @description
* A function which returns the router link for an {@link ActionBarItem} or {@link NavMenuItem}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Permission } from '../../common/generated-types';

import {
ActionBarContext,
ActionBarDropdownMenuItem,
ActionBarItem,
NavMenuBadgeType,
NavMenuItem,
Expand All @@ -24,6 +25,7 @@ import {
export class NavBuilderService {
menuConfig$: Observable<NavMenuSection[]>;
actionBarConfig$: Observable<ActionBarItem[]>;
actionBarDropdownConfig$: Observable<ActionBarDropdownMenuItem[]>;
sectionBadges: { [sectionId: string]: Observable<NavMenuBadgeType> } = {};

private initialNavMenuConfig$ = new BehaviorSubject<NavMenuSection[]>([]);
Expand All @@ -34,6 +36,7 @@ export class NavBuilderService {
before?: string;
}> = [];
private addedActionBarItems: ActionBarItem[] = [];
private addedActionBarDropdownMenuItems: ActionBarDropdownMenuItem[] = [];

constructor() {
this.setupStreams();
Expand Down Expand Up @@ -86,6 +89,20 @@ export class NavBuilderService {
}
}

/**
* Adds a dropdown menu to the ActionBar at the top right of each list or detail view. The locationId can
* be determined by inspecting the DOM and finding the `<vdr-action-bar>` element and its
* `data-location-id` attribute.
*/
addActionBarDropdownMenuItem(config: ActionBarDropdownMenuItem) {
if (!this.addedActionBarDropdownMenuItems.find(item => item.id === config.id)) {
this.addedActionBarDropdownMenuItems.push({
requiresPermission: Permission.Authenticated,
...config,
});
}
}

getRouterLink(
config: { routerLink?: RouterLinkDefinition; context: ActionBarContext },
route: ActivatedRoute,
Expand Down Expand Up @@ -185,5 +202,6 @@ export class NavBuilderService {
);

this.actionBarConfig$ = of(this.addedActionBarItems);
this.actionBarDropdownConfig$ = of(this.addedActionBarDropdownMenuItems);
}
}
2 changes: 2 additions & 0 deletions packages/admin-ui/src/lib/core/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export * from './data/utils/add-custom-fields';
export * from './data/utils/get-server-location';
export * from './data/utils/remove-readonly-custom-fields';
export * from './data/utils/transform-relation-custom-field-inputs';
export * from './extension/add-action-bar-dropdown-menu-item';
export * from './extension/add-action-bar-item';
export * from './extension/add-nav-menu-item';
export * from './extension/components/angular-route.component';
Expand Down Expand Up @@ -124,6 +125,7 @@ export * from './providers/overlay-host/overlay-host.service';
export * from './providers/page/page.service';
export * from './providers/permissions/permissions.service';
export * from './shared/components/action-bar/action-bar.component';
export * from './shared/components/action-bar-dropdown-menu/action-bar-dropdown-menu.component';
export * from './shared/components/action-bar-items/action-bar-items.component';
export * from './shared/components/address-form/address-form.component';
export * from './shared/components/affixed-input/affixed-input.component';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<vdr-ui-extension-point [locationId]="locationId" api="actionBarDropdown" [leftPx]="-24">
<vdr-dropdown #dropdownComponent *ngIf="alwaysShow || (items$ | async)?.length">
<button class="icon-button" vdrDropdownTrigger>
<clr-icon shape="ellipsis-vertical"></clr-icon>
</button>
<vdr-dropdown-menu vdrPosition="bottom-right">
<ng-content />
<ng-container *ngFor="let item of items$ | async">
<ng-container *ngIf="buttonStates[item.id] | async as buttonState">
<div class="dropdown-divider" *ngIf="item.hasDivider === true"></div>
<button
type="button"
vdrDropdownItem
*vdrIfPermissions="item.requiresPermission"
[routerLink]="getRouterLink(item)"
[class.hidden]="buttonState.visible === false"
[disabled]="buttonState.disabled"
(click)="handleClick($event, item)"
class="mr-2"
>
<clr-icon *ngIf="item.icon" [attr.shape]="item.icon"></clr-icon>
{{ item.label | translate }}
</button>
</ng-container>
</ng-container>
</vdr-dropdown-menu>
</vdr-dropdown>
</vdr-ui-extension-point>
Loading

0 comments on commit 4d8bc74

Please sign in to comment.