Browse Source

feat(admin-ui): Implement mobile layout

Michael Bromley 2 years ago
parent
commit
2cf8f094a9
22 changed files with 178 additions and 51 deletions
  1. 17 3
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  2. 15 7
      packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.html
  3. 45 4
      packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.scss
  4. 12 0
      packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.ts
  5. 8 4
      packages/admin-ui/src/lib/core/src/components/breadcrumb/breadcrumb.component.scss
  6. 2 2
      packages/admin-ui/src/lib/core/src/components/channel-switcher/channel-switcher.component.html
  7. 0 6
      packages/admin-ui/src/lib/core/src/components/channel-switcher/channel-switcher.component.scss
  8. 3 3
      packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html
  9. 4 7
      packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.scss
  10. 8 2
      packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.ts
  11. 5 1
      packages/admin-ui/src/lib/core/src/components/user-menu/user-menu.component.scss
  12. 1 0
      packages/admin-ui/src/lib/core/src/data/client-state/client-defaults.ts
  13. 7 0
      packages/admin-ui/src/lib/core/src/data/client-state/client-resolvers.ts
  14. 2 0
      packages/admin-ui/src/lib/core/src/data/client-state/client-types.graphql
  15. 8 0
      packages/admin-ui/src/lib/core/src/data/definitions/client-definitions.ts
  16. 1 1
      packages/admin-ui/src/lib/core/src/data/providers/base-data.service.ts
  17. 7 1
      packages/admin-ui/src/lib/core/src/data/providers/client-data.service.ts
  18. 5 1
      packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.scss
  19. 8 0
      packages/admin-ui/src/lib/core/src/shared/components/page-title/page-title.component.scss
  20. 15 4
      packages/admin-ui/src/lib/core/src/shared/components/split-view/split-view.component.ts
  21. 1 1
      packages/admin-ui/src/lib/static/styles/global/_buttons.scss
  22. 4 4
      packages/admin-ui/src/lib/static/styles/theme/default.scss

File diff suppressed because it is too large
+ 17 - 3
packages/admin-ui/src/lib/core/src/common/generated-types.ts


+ 15 - 7
packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.html

@@ -1,19 +1,22 @@
 <div class="app-container">
-    <div class="left-nav">
-        <div class="branding py-4 px-4">
-            <a [routerLink]="['/']"
-                ><img src="assets/logo.svg" class="logo" style="max-width: 100px" /></a
-            >
+    <div class="left-nav" [class.expanded]="mainNavExpanded$ | async">
+        <div class="branding">
+            <a [routerLink]="['/']"><img src="assets/logo.svg" class="logo" style="max-width: 100px" /></a>
+            <div class="collapse-menu">
+                <button class="" (click)="collapseNav()">
+                    <clr-icon shape="window-close" size="16"></clr-icon>
+                </button>
+            </div>
         </div>
         <div class="mx-4">
             <vdr-channel-switcher *vdrIfMultichannel></vdr-channel-switcher>
         </div>
         <div class="main-nav-container">
-            <vdr-main-nav></vdr-main-nav>
+            <vdr-main-nav (itemClick)="collapseNav()"></vdr-main-nav>
         </div>
         <div class="settings-nav-container">
             <hr />
-            <vdr-main-nav displayMode="settings"></vdr-main-nav>
+            <vdr-main-nav displayMode="settings" (itemClick)="collapseNav()"></vdr-main-nav>
         </div>
         <div class="mx-2"></div>
     </div>
@@ -21,6 +24,11 @@
     <div class="surface">
         <div class="content-container">
             <div class="top-bar">
+                <div class="expand-menu mr-1">
+                    <button class="" (click)="expandNav()">
+                        <clr-icon shape="menu" size="16"></clr-icon>
+                    </button>
+                </div>
                 <div>
                     <vdr-breadcrumb></vdr-breadcrumb>
                 </div>

+ 45 - 4
packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.scss

@@ -11,12 +11,45 @@
     color: var(--color-left-nav-text);
     display: flex;
     flex-direction: column;
+    overflow: hidden;
     height: 100%;
     border-right: 1px solid var(--color-weight-150);
     //box-shadow: -3px 1px 10px 0px rgb(0 0 0 / 18%);
     z-index: 1;
     width: var(--left-nav-width);
     max-width: var(--left-nav-width);
+
+    @media screen and (max-width: $breakpoint-medium) {
+        &.expanded {
+            position: fixed;
+            inset: 0;
+            width: 100%;
+            max-width: 100%;
+            animation: fade-in 0.2s ease-in-out forwards;
+        }
+    }
+}
+
+// fade in animation
+@keyframes fade-in {
+    0% {
+        opacity: 0;
+    }
+    100% {
+        opacity: 1;
+    }
+}
+.expand-menu,
+.collapse-menu {
+    @media screen and (min-width: $breakpoint-medium) {
+        display: none;
+    }
+    button {
+        border: none;
+        padding: calc(var(--space-unit) * 1) calc(var(--space-unit) * 2);
+        background-color: var(--color-weight-125);
+        border-radius: var(--border-radius-lg);
+    }
 }
 
 .top-bar {
@@ -24,9 +57,12 @@
     display: flex;
     align-items: center;
     justify-content: space-between;
-    padding: calc(var(--space-unit) * 4);
-    padding-left: var(--surface-margin-left);
+    padding: calc(var(--space-unit) * 2) calc(var(--space-unit) * 1);
     background-color: var(--page-header-color);
+    @media screen and (min-width: $breakpoint-medium) {
+        padding: calc(var(--space-unit) * 4);
+        padding-left: var(--surface-margin-left);
+    }
 }
 
 .main-nav-container {
@@ -39,8 +75,8 @@
     hr {
         margin: 0 calc(var(--space-unit) * 4);
         margin-bottom: calc(var(--space-unit) * 4);
-        border: 1px solid #D9D9D9;
-        box-shadow: 0px 1px 0px #FFFFFF;
+        border: 1px solid #d9d9d9;
+        box-shadow: 0px 1px 0px #ffffff;
     }
 }
 
@@ -58,7 +94,12 @@
 .branding {
     display: flex;
     align-items: center;
+    justify-content: space-between;
     min-width: 0;
+    padding: calc(var(--space-unit) * 2) calc(var(--space-unit) * 4);
+    @media screen and (min-width: $breakpoint-medium) {
+        padding: calc(var(--space-unit) * 4);
+    }
 }
 
 .logo {

+ 12 - 0
packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.ts

@@ -24,6 +24,7 @@ export class AppShellComponent implements OnInit {
     availableLanguages: LanguageCode[] = [];
     hideVendureBranding = getAppConfig().hideVendureBranding;
     pageTitle$: Observable<string>;
+    mainNavExpanded$: Observable<boolean>;
 
     constructor(
         private authService: AuthService,
@@ -46,6 +47,9 @@ export class AppShellComponent implements OnInit {
         this.pageTitle$ = this.breadcrumbService.breadcrumbs$.pipe(
             map(breadcrumbs => breadcrumbs[breadcrumbs.length - 1].label),
         );
+        this.mainNavExpanded$ = this.dataService.client
+            .uiState()
+            .stream$.pipe(map(({ uiState }) => uiState.mainNavExpanded));
     }
 
     selectUiLanguage() {
@@ -76,6 +80,14 @@ export class AppShellComponent implements OnInit {
             });
     }
 
+    expandNav() {
+        this.dataService.client.setMainNavExpanded(true).subscribe();
+    }
+
+    collapseNav() {
+        this.dataService.client.setMainNavExpanded(false).subscribe();
+    }
+
     logOut() {
         this.authService.logOut().subscribe(() => {
             const { loginUrl } = getAppConfig();

+ 8 - 4
packages/admin-ui/src/lib/core/src/components/breadcrumb/breadcrumb.component.scss

@@ -1,4 +1,4 @@
-@import "variables";
+@import 'variables';
 
 :host {
     display: block;
@@ -8,23 +8,27 @@
 .breadcrumbs {
     list-style-type: none;
     display: flex;
+    align-items: center;
     overflow-x: auto;
     max-width: 100vw;
     line-height: 100%;
     height: 40px;
     padding: calc(var(--space-unit) * 1.5) calc(var(--space-unit) * 2.5);
-    font-size: var(--font-size-sm);
+    font-size: var(--font-size-xs);
     background-color: var(--color-weight-125);
     border-radius: var(--border-radius-lg);
+    @media screen and (min-width: $breakpoint-small) {
+        font-size: var(--font-size-sm);
+    }
     li {
         display: inline-block;
         white-space: nowrap;
-        a:link, a:visited {
+        a:link,
+        a:visited {
             color: var(--color-weight-700);
         }
         &:last-child {
             font-weight: 600;
         }
     }
-
 }

+ 2 - 2
packages/admin-ui/src/lib/core/src/components/channel-switcher/channel-switcher.component.html

@@ -2,10 +2,10 @@
     <vdr-dropdown>
         <button class="active-channel m-auto" vdrDropdownTrigger>
             <vdr-channel-badge [channelCode]="activeChannelCode$ | async"></vdr-channel-badge>
-            <span class="md:hidden channel-label">{{
+            <span class="channel-label">{{
                 activeChannelCode$ | async | channelCodeToLabel | translate
             }}</span>
-            <span class="trigger md:hidden"><clr-icon shape="ellipsis-vertical"></clr-icon></span>
+            <span class="trigger"><clr-icon shape="ellipsis-vertical"></clr-icon></span>
         </button>
         <vdr-dropdown-menu vdrPosition="bottom-right">
             <input

+ 0 - 6
packages/admin-ui/src/lib/core/src/components/channel-switcher/channel-switcher.component.scss

@@ -27,9 +27,6 @@
     clr-icon {
         color: var(--color-left-nav-text);
     }
-    @media screen and (max-width: $breakpoint-medium) {
-        justify-content: center;
-    }
 }
 
 .channel-label {
@@ -38,7 +35,4 @@
     flex: 1;
     max-width: 100px;
     text-overflow: ellipsis;
-    @media screen and (max-width: $breakpoint-medium) {
-        display: none;
-    }
 }

+ 3 - 3
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html

@@ -26,7 +26,7 @@
                                 [type]="sectionBadge"
                             ></vdr-status-badge>
                         </ng-container>
-                        <label class="nav-group-header md:hidden mx-4" [for]="section.id">{{
+                        <label class="nav-group-header mx-4" [for]="section.id">{{
                             section.label | translate
                         }}</label>
                         <button *ngIf="section.collapsible" class="button-small bg-weight-150" (click)="toggleExpand(section)">
@@ -47,7 +47,7 @@
                                 <a
                                     [attr.data-item-id]="section.id"
                                     [routerLink]="getRouterLink(item)"
-                                    (click)="item.onClick && item.onClick($event)"
+                                    (click)="onItemClick(item, $event)"
                                 >
                                     <ng-container *ngIf="item.statusBadge | async as itemBadge">
                                         <vdr-status-badge
@@ -60,7 +60,7 @@
                                         size="16"
                                         [title]="item.label | translate"
                                     ></clr-icon>
-                                    <span class="md:hidden">{{ item.label | translate }}</span>
+                                    <span class="">{{ item.label | translate }}</span>
                                 </a>
                             </div>
                         </ng-container>

+ 4 - 7
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.scss

@@ -58,24 +58,21 @@ nav.main-nav {
             border-right-color: var(--color-weight-300);
         }
 
-        &.active, &.active a:link, &.active a:visited {
+        &.active,
+        &.active a:link,
+        &.active a:visited {
             color: var(--color-primary-800);
             border-right-color: var(--color-primary-500);
             clr-icon {
                 color: var(--color-primary-500);
             }
         }
-        @media screen and (max-width: $breakpoint-medium) {
-            justify-content: center;
-        }
     }
 }
 
 .nav-list clr-icon {
     flex-shrink: 0;
-    @media screen and (min-width: $breakpoint-medium) {
-        margin-right: var(--space-unit);
-    }
+    margin-right: var(--space-unit);
 }
 
 .nav-group {

+ 8 - 2
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.ts

@@ -1,7 +1,7 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
-import { NavMenuSection } from '../../providers/nav-builder/nav-builder-types';
+import { NavMenuItem, NavMenuSection } from '../../providers/nav-builder/nav-builder-types';
 import { BaseNavComponent } from '../base-nav/base-nav.component';
 
 @Component({
@@ -11,6 +11,7 @@ import { BaseNavComponent } from '../base-nav/base-nav.component';
 })
 export class MainNavComponent extends BaseNavComponent implements OnInit {
     @Input() displayMode: string | undefined;
+    @Output() itemClick = new EventEmitter<NavMenuItem>();
     mainMenuConfig$: Observable<NavMenuSection[]>;
     expandedSections: string[] = [];
 
@@ -51,4 +52,9 @@ export class MainNavComponent extends BaseNavComponent implements OnInit {
             }
         }
     }
+
+    onItemClick(item: NavMenuItem, event: MouseEvent) {
+        item.onClick?.(event);
+        this.itemClick.emit(item);
+    }
 }

+ 5 - 1
packages/admin-ui/src/lib/core/src/components/user-menu/user-menu.component.scss

@@ -28,11 +28,15 @@
     height: 40px;
     background-color: var(--color-weight-125);
     border-radius: var(--border-radius-lg);
-    padding: calc(var(--space-unit) * 1.5) calc(var(--space-unit) * 2.5) calc(var(--space-unit) * 1.5) calc(var(--space-unit) * 1.5) ;
+
     cursor: pointer;
     &:hover {
         color: var(--color-text-300);
     }
+    @media screen and (min-width: $breakpoint-medium) {
+        padding: calc(var(--space-unit) * 1.5) calc(var(--space-unit) * 2) calc(var(--space-unit) * 1.5)
+            calc(var(--space-unit) * 1.5);
+    }
 }
 
 .user-circle {

+ 1 - 0
packages/admin-ui/src/lib/core/src/data/client-state/client-defaults.ts

@@ -29,6 +29,7 @@ export function getClientDefaults(localStorageService: LocalStorageService) {
             contentLanguage: currentContentLanguage,
             theme: activeTheme,
             displayUiExtensionPoints: false,
+            mainNavExpanded: false,
             __typename: 'UiState',
         } as GetUiStateQuery['uiState'],
     };

+ 7 - 0
packages/admin-ui/src/lib/core/src/data/client-state/client-resolvers.ts

@@ -100,6 +100,13 @@ export const clientResolvers: ResolverDefinition = {
             cache.writeQuery({ query: GET_UI_STATE, data });
             return args.display;
         },
+        setMainNavExpanded: (_, args: Codegen.SetMainNavExpandedMutationVariables, { cache }): boolean => {
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            const previous = cache.readQuery<Codegen.GetUiStateQuery>({ query: GET_UI_STATE })!;
+            const data = updateUiState(previous, 'mainNavExpanded', args.expanded);
+            cache.writeQuery({ query: GET_UI_STATE, data });
+            return args.expanded;
+        },
         setActiveChannel: (_, args: Codegen.SetActiveChannelMutationVariables, { cache }): UserStatus => {
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             const previous = cache.readQuery<GetUserStatusQuery>({ query: GET_USER_STATUS })!;

+ 2 - 0
packages/admin-ui/src/lib/core/src/data/client-state/client-types.graphql

@@ -15,6 +15,7 @@ type Mutation {
     setUiTheme(theme: String!): String!
     setActiveChannel(channelId: ID!): UserStatus!
     setDisplayUiExtensionPoints(display: Boolean!): Boolean!
+    setMainNavExpanded(expanded: Boolean!): Boolean!
     updateUserChannels(channels: [CurrentUserChannelInput!]!): UserStatus!
 }
 
@@ -38,6 +39,7 @@ type UiState {
     contentLanguage: LanguageCode!
     theme: String!
     displayUiExtensionPoints: Boolean!
+    mainNavExpanded: Boolean!
 }
 
 input CurrentUserChannelInput {

+ 8 - 0
packages/admin-ui/src/lib/core/src/data/definitions/client-definitions.ts

@@ -66,6 +66,12 @@ export const SET_DISPLAY_UI_EXTENSION_POINTS = gql`
     }
 `;
 
+export const SET_MAIN_NAV_EXPANDED = gql`
+    mutation SetMainNavExpanded($expanded: Boolean!) {
+        setMainNavExpanded(expanded: $expanded) @client
+    }
+`;
+
 export const SET_CONTENT_LANGUAGE = gql`
     mutation SetContentLanguage($languageCode: LanguageCode!) {
         setContentLanguage(languageCode: $languageCode) @client
@@ -103,6 +109,7 @@ export const GET_UI_STATE = gql`
             contentLanguage
             theme
             displayUiExtensionPoints
+            mainNavExpanded
         }
     }
 `;
@@ -121,6 +128,7 @@ export const GET_CLIENT_STATE = gql`
             contentLanguage
             theme
             displayUiExtensionPoints
+            mainNavExpanded
         }
     }
     ${USER_STATUS_FRAGMENT}

+ 1 - 1
packages/admin-ui/src/lib/core/src/data/providers/base-data.service.ts

@@ -52,7 +52,7 @@ export class BaseDataService {
      * Performs a GraphQL mutation
      */
     mutate<T, V extends Record<string, any> = Record<string, any>>(
-        mutation: DocumentNode,
+        mutation: DocumentNode | TypedDocumentNode<T, V>,
         variables?: V,
         update?: MutationUpdaterFn<T>,
     ): Observable<T> {

+ 7 - 1
packages/admin-ui/src/lib/core/src/data/providers/client-data.service.ts

@@ -1,5 +1,5 @@
 import * as Codegen from '../../common/generated-types';
-import { CurrentUserChannel, LanguageCode } from '../../common/generated-types';
+import { CurrentUserChannel, LanguageCode, SetMainNavExpandedDocument } from '../../common/generated-types';
 import {
     GET_NEWTORK_STATUS,
     GET_UI_STATE,
@@ -120,6 +120,12 @@ export class ClientDataService {
         });
     }
 
+    setMainNavExpanded(expanded: boolean) {
+        return this.baseDataService.mutate(SetMainNavExpandedDocument, {
+            expanded,
+        });
+    }
+
     setActiveChannel(channelId: string) {
         return this.baseDataService.mutate<
             Codegen.SetActiveChannelMutation,

+ 5 - 1
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.scss

@@ -134,7 +134,11 @@ td {
 
 tr td:first-of-type,
 tr th:first-of-type {
-    padding-left: var(--surface-margin-left);
+    text-align: center;
+    @media screen and (min-width: $breakpoint-medium) {
+        padding-left: var(--surface-margin-left);
+        text-align: left;
+    }
 }
 
 th:last-of-type,

+ 8 - 0
packages/admin-ui/src/lib/core/src/shared/components/page-title/page-title.component.scss

@@ -1,3 +1,5 @@
+@import "variables";
+
 :host {
     display: block;
 }
@@ -10,6 +12,12 @@
         margin-top: 0;
         color: var(--color-weight-900);
         font-weight: 600;
+        @media screen and (max-width: $breakpoint-small) {
+            font-size: var(--font-size-xl);
+        }
+        @media screen and (max-width: $breakpoint-medium) {
+            font-size: 24px
+        }
     }
 }
 

+ 15 - 4
packages/admin-ui/src/lib/core/src/shared/components/split-view/split-view.component.ts

@@ -51,15 +51,26 @@ export class SplitViewComponent implements AfterContentInit, AfterViewInit {
         const hostElement = this.viewContainerRef.element.nativeElement;
         const hostElementWidth = hostElement.getBoundingClientRect()?.width;
 
-        const mouseDown$ = fromEvent<MouseEvent>(this.resizeHandle.nativeElement, 'mousedown');
-        const mouseMove$ = fromEvent<MouseEvent>(document, 'mousemove');
-        const mouseUp$ = fromEvent<MouseEvent>(document, 'mouseup');
+        const mouseDown$ = merge(
+            fromEvent<MouseEvent>(this.resizeHandle.nativeElement, 'mousedown'),
+            fromEvent<MouseEvent>(this.resizeHandle.nativeElement, 'touchstart'),
+        );
+        const mouseMove$ = merge(
+            fromEvent<MouseEvent>(document, 'mousemove'),
+            fromEvent<TouchEvent>(document, 'touchmove'),
+        );
+        const mouseUp$ = merge(
+            fromEvent<MouseEvent>(document, 'mouseup'),
+            fromEvent<TouchEvent>(document, 'touchend'),
+        );
 
         // update right panel width when resize handle is dragged
         this.rightPanelWidth$ = mouseDown$.pipe(
             switchMap(() => mouseMove$.pipe(takeUntil(mouseUp$))),
             map(event => {
-                const width = hostElement.getBoundingClientRect().right - event.clientX;
+                const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
+                console.log(`clientX: ${clientX}`);
+                const width = hostElement.getBoundingClientRect().right - clientX;
                 return Math.max(100, Math.min(width, hostElementWidth - 100));
             }),
             startWith(hostElementWidth / 2),

+ 1 - 1
packages/admin-ui/src/lib/static/styles/global/_buttons.scss

@@ -5,7 +5,7 @@
     justify-content: flex-end;
     white-space: nowrap;
     align-items: center;
-    padding: var(--space-unit);
+    padding: var(--space-unit) calc(var(--space-unit) * 1.5);
     font-size: var(--font-size-sm);
     gap: var(--space-unit);
     border: none;

+ 4 - 4
packages/admin-ui/src/lib/static/styles/theme/default.scss

@@ -114,12 +114,12 @@
     --layout-content-max-width: 1400px;
     --left-nav-width: 0px;
     --surface-width: 100vw;
-    --surface-margin-left: 4px;
+    --surface-margin-left: 8px;
 
     @media screen and (min-width: $breakpoint-small) {
-        --left-nav-width: 100px;
-        --surface-width: calc(100vw - 100px);
-        --surface-margin-left: 4px;
+        //--left-nav-width: 100px;
+        //--surface-width: calc(100vw - 100px);
+        //--surface-margin-left: 4px;
     }
 
     @media screen and (min-width: $breakpoint-medium) {

Some files were not shown because too many files changed in this diff