Skip to content

Commit

Permalink
feat(business): implement responsive menus for header (#224)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyubisation authored Nov 22, 2019
1 parent f634391 commit 688bcf4
Show file tree
Hide file tree
Showing 27 changed files with 1,383 additions and 438 deletions.
3 changes: 1 addition & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib",
"editor.defaultFormatter": "esbenp.prettier-vscode"
"typescript.tsdk": "node_modules\\typescript\\lib"
}
83 changes: 26 additions & 57 deletions projects/angular-showcase/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,58 +1,27 @@
<nav class="menu" [class.menu-open]="showMenu">
<ul>
<li>
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }"
>Introduction</a
>
</li>
<li class="title">Libraries</li>
<li>
<a routerLink="/public" routerLinkActive="active">@sbb-esta/angular-public</a>
</li>
<li>
<a routerLink="/business" routerLinkActive="active">@sbb-esta/angular-business</a>
</li>
<li>
<a routerLink="/core" routerLinkActive="active">@sbb-esta/angular-core</a>
</li>
<li>
<a routerLink="/icons" routerLinkActive="active">@sbb-esta/angular-icons</a>
</li>
<li>
<a routerLink="/keycloak" routerLinkActive="active">@sbb-esta/angular-keycloak</a>
</li>
<li class="title">About</li>
<li>
<a href="https://angular.io/">Angular {{ angularVersion }}</a>
</li>
<li>
<a routerLink="/">Showcase {{ showcaseVersion }}</a>
</li>
</ul>
</nav>
<sbb-header label="SBB Angular" [subtitle]="'Version ' + showcaseVersion">
<sbb-app-chooser-section label="Links">
<a target="_blank" href="https://github.com/SchweizerischeBundesbahnen/sbb-angular">GitHub</a>
<a target="_blank" href="https://angular.io/">Angular {{ angularVersion }}</a>
</sbb-app-chooser-section>
<a routerLink="/">Introduction</a>
<button [sbbHeaderMenu]="packages">Packages</button>
<a routerLink="/">Introduction</a>
<sbb-header-menu #packages="sbbHeaderMenu">
Packages
<a sbbHeaderMenuItem routerLink="/public" routerLinkActive="sbb-active"
>@sbb-esta/angular-public</a
>
<a sbbHeaderMenuItem routerLink="/business" routerLinkActive="sbb-active"
>@sbb-esta/angular-business</a
>
<a sbbHeaderMenuItem routerLink="/core" routerLinkActive="sbb-active">@sbb-esta/angular-core</a>
<a sbbHeaderMenuItem routerLink="/icons" routerLinkActive="sbb-active"
>@sbb-esta/angular-icons</a
>
<a sbbHeaderMenuItem routerLink="/keycloak" routerLinkActive="sbb-active"
>@sbb-esta/angular-keycloak</a
>
</sbb-header-menu>
</sbb-header>

<div class="container">
<button type="button" (click)="toggleMenu()" class="burger-icon" [class.active]="showMenu">
<span></span>
<span></span>
<span></span>
</button>
<div class="brand">
<a class="home" href="/en">
<svg viewBox="0 0 187 21" xmlns="http://www.w3.org/2000/svg">
<path
d="M73.06 8.227c-.165-1.49-.993-2.17-2.667-2.17-1.427 0-2.314.658-2.314 1.71 0 .878.556 1.404 1.693 1.622l2.48.46c2.646.48 4.073 1.97 4.073 4.252 0 2.87-2.193 4.71-5.665 4.71-3.678 0-5.785-1.885-5.847-5.195h2.935c.186 1.907 1.073 2.72 3.037 2.72 1.51 0 2.584-.835 2.584-1.995 0-.92-.58-1.49-1.696-1.71l-2.25-.437c-2.875-.57-4.2-1.887-4.2-4.23 0-2.673 2.027-4.382 5.334-4.382 3.245 0 5.25 1.73 5.33 4.644H73.06zM84.594 3.89c3.015 0 4.73 1.36 4.73 3.724 0 1.294-.577 2.257-1.92 3.003 1.714.678 2.44 1.82 2.44 3.704 0 2.563-1.904 4.185-4.92 4.185h-6.49V3.89h6.16zm-.416 5.85c1.468 0 2.317-.613 2.317-1.644 0-1.182-.767-1.732-2.358-1.732h-2.81V9.74h2.85zm.124 6.29c1.716 0 2.564-.637 2.564-1.95 0-1.38-.828-1.997-2.708-1.997h-2.83v3.947h2.974zM98.194 3.89c3.015 0 4.73 1.36 4.73 3.724 0 1.294-.577 2.257-1.92 3.003 1.714.678 2.44 1.82 2.44 3.704 0 2.563-1.903 4.185-4.92 4.185H91.97V3.89h6.224zm-.416 5.85c1.468 0 2.316-.613 2.316-1.644 0-1.182-.766-1.732-2.357-1.732h-2.87V9.74h2.91zm.124 6.29c1.715 0 2.564-.637 2.564-1.95 0-1.38-.828-1.997-2.71-1.997h-2.89v3.947h3.036zM120.96 13.355c-.205 3.353-2.505 5.456-5.956 5.456-3.864 0-6.41-3-6.41-7.58 0-4.56 2.61-7.647 6.47-7.647 3.31 0 5.527 1.95 5.772 5.04h-2.853c-.33-1.665-1.295-2.477-2.897-2.477-2.177 0-3.595 1.97-3.595 5.062 0 3.047 1.398 5.063 3.534 5.063 1.767 0 2.75-.92 3.062-2.915h2.874zm4.89-6.837v3.44h6.08v2.544h-6.08v6.003h-2.878V3.89h9.553v2.628h-6.676zm12.04 0v3.44h6.158v2.544h-6.158v6.003h-2.877V3.89h9.635v2.628h-6.758zM154.414 6.518v3.44h6.22v2.544h-6.22v6.003h-2.898V3.89h9.717v2.628h-6.82zm12.293 0v3.44h6.18v2.544h-6.18v6.003h-2.902V3.89h9.68v2.628h-6.778zm16.73 1.71c-.166-1.492-.993-2.17-2.676-2.17-1.426 0-2.317.657-2.317 1.71 0 .877.56 1.403 1.698 1.62l2.49.46c2.654.482 4.082 1.973 4.082 4.254 0 2.87-2.2 4.71-5.682 4.71-3.69 0-5.8-1.885-5.862-5.195h2.94c.185 1.907 1.08 2.72 3.047 2.72 1.516 0 2.593-.835 2.593-1.995 0-.92-.58-1.49-1.698-1.71l-2.26-.437c-2.883-.57-4.208-1.887-4.208-4.23 0-2.673 2.028-4.382 5.345-4.382 3.255 0 5.268 1.73 5.35 4.644h-2.84z"
fill="#000"
></path>
<path d="M0 0h59.233v20.603H0V0z" fill="#EC0000"></path>
<path
d="M35.186 17.02h3.75l-5.047-5.163h6.265v5.163h2.96v-5.163h6.267l-5.05 5.163h3.752l6.427-6.708-6.426-6.73h-3.752l5.05 5.185h-6.266V3.583h-2.96v5.184h-6.267l5.047-5.184h-3.75l-6.43 6.73 6.43 6.707"
fill="#FFF"
></path>
</svg>
</a>
</div>
<div class="content">
<router-outlet></router-outlet>
</div>
</div>
<router-outlet></router-outlet>
2 changes: 2 additions & 0 deletions projects/angular-showcase/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HeaderModule } from '@sbb-esta/angular-business/header';
import { ScrollingModule } from '@sbb-esta/angular-core/scrolling';
import { ICON_COMPONENT_LIST, IconCollectionModule } from '@sbb-esta/angular-icons';
import { MonacoEditorModule } from 'ngx-monaco-editor';
Expand All @@ -25,6 +26,7 @@ import { PublicModule } from './public/public.module';
IconCollectionModule,
ReactiveFormsModule.withConfig({ warnOnNgModelWithFormControl: 'never' }),
ScrollingModule,
HeaderModule,
HttpClientModule,
BrowserAnimationsModule,
PublicModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@import '../../../../sbb-esta/angular-core/styles/common/variables';
@import '../../../../sbb-esta/angular-core/styles/common/functions';

:host {
display: block;
height: calc(100vh - 48px);
Expand All @@ -20,6 +23,11 @@ p {
max-width: 700px;
}

a {
line-height: pxToEm(23px);
font-size: pxToRem($sizeFontDefault);
}

nav {
padding: 0;
margin: 50px 0 50px 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
border-right: solid 1px #cccccc;
transition: all 0.3s ease;
z-index: 998;
margin-top: 54px;
}

:host ::ng-deep *:not(li) > ul {
Expand Down
5 changes: 2 additions & 3 deletions projects/angular-showcase/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,10 @@ sbb-business {

body {
transition: all 0.3s ease;
overflow-y: auto;
overflow-x: hidden;
overflow: hidden;
position: relative;
left: 0;
top: 48px;
top: 54px;

&.home {
top: 0;
Expand Down
29 changes: 29 additions & 0 deletions projects/sbb-esta/angular-business/header/src/_header.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
$sbbHeaderMenuWidth: 300px;

// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="#000" d="M5 12.491h12.49M13.5 16.5l3.99-4.008L13.5 8.5"/></svg>
$sbbHeaderLinkArrow: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBkPSJNNSAxMi40OTFoMTIuNDlNMTMuNSAxNi41bDMuOTktNC4wMDhMMTMuNSA4LjUiLz48L3N2Zz4=';
// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="#c60018" d="M5 12.491h12.49M13.5 16.5l3.99-4.008L13.5 8.5"/></svg>
$sbbHeaderLinkArrowRed: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzYwMDE4IiBkPSJNNSAxMi40OTFoMTIuNDlNMTMuNSAxNi41bDMuOTktNC4wMDhMMTMuNSA4LjUiLz48L3N2Zz4=';

$sbbHeaderBaseZIndex: 1000;
$sbbHeaderSideMenuZIndex: 800;
$sbbHeaderMenuMobileZIndex: 1100;
$sbbHeaderMenuTabletZIndex: 700;

@mixin headerRightArrow() {
position: relative;

&::after {
content: url($sbbHeaderLinkArrow);
display: inline-block;
width: 24px;
height: 24px;
position: absolute;
right: 0;
vertical-align: middle;
}

&:hover::after {
content: url($sbbHeaderLinkArrowRed);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
$sbbBusiness: true;
@import '../../../../angular-core/styles/common/colors';
@import '../../../../angular-core/styles/common/variables';

// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="#000" d="M5 12.491h12.49M13.5 16.5l3.99-4.008L13.5 8.5"/></svg>
$sbbHeaderAppChooserLinkArrow: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBkPSJNNSAxMi40OTFoMTIuNDlNMTMuNSAxNi41bDMuOTktNC4wMDhMMTMuNSA4LjUiLz48L3N2Zz4=';
// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="#c60018" d="M5 12.491h12.49M13.5 16.5l3.99-4.008L13.5 8.5"/></svg>
$sbbHeaderAppChooserLinkArrowRed: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzYwMDE4IiBkPSJNNSAxMi40OTFoMTIuNDlNMTMuNSAxNi41bDMuOTktNC4wMDhMMTMuNSA4LjUiLz48L3N2Zz4=';
@import '../header';

:host {
display: flex;
Expand All @@ -26,30 +22,16 @@ $sbbHeaderAppChooserLinkArrowRed: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR
line-height: 20px;
padding: 8px 32px 10px 0;
border-top: 1px solid $sbbColorCloud;
position: relative;
text-decoration: none;
outline: none;
@include headerRightArrow();

&:last-child {
border-bottom: 1px solid $sbbColorCloud;
}

&::after {
content: url($sbbHeaderAppChooserLinkArrow);
display: inline-block;
width: 24px;
height: 24px;
position: absolute;
right: 0;
vertical-align: middle;
}

&:hover::after {
content: url($sbbHeaderAppChooserLinkArrowRed);
}
}

&::last-of-type {
&:last-of-type {
margin-bottom: 1rem;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostListener, Inject, Input } from '@angular/core';

import { Header, SBB_HEADER } from '../header/header';

@Component({
selector: 'sbb-app-chooser-section',
Expand All @@ -8,4 +10,15 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
})
export class AppChooserSectionComponent {
@Input() label: string;

constructor(@Inject(SBB_HEADER) private _header: Header) {}

/** Close the header menu when any a or button child element is clicked. */
@HostListener('click', ['$event'])
_handleChildClick(event: Event) {
const target = event.target as HTMLElement;
if (target && target.tagName && (target.tagName === 'A' || target.tagName === 'BUTTON')) {
this._header.opened = false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { FocusableOption, FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';
import { DOCUMENT } from '@angular/common';
import {
Directive,
ElementRef,
EventEmitter,
HostBinding,
HostListener,
Inject,
Input,
OnDestroy,
Output
} from '@angular/core';
import { CanDisableCtor, mixinDisabled } from '@sbb-esta/angular-core/common-behaviors';

// Boilerplate for applying mixins to HeaderMenuItemDirective.
/** @docs-private */
class HeaderMenuItemBase {}
// tslint:disable-next-line: naming-convention
const _HeaderMenuItemBase: CanDisableCtor & typeof HeaderMenuItemBase = mixinDisabled(
HeaderMenuItemBase
);

@Directive({
selector: '[sbbHeaderMenuItem]'
})
export class HeaderMenuItemDirective extends _HeaderMenuItemBase
implements FocusableOption, OnDestroy {
/** ARIA role for the menu item. */
@Input() @HostBinding('attr.role') role: 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' =
'menuitem';

private _document: Document;

/** Whether the menu item is highlighted. */
_highlighted: boolean = false;

/** Whether the menu item acts as a trigger for a sub-menu. */
_triggersSubmenu: boolean = false;

/** Emits whenever a this item is clicked when enabled. */
@Output() click: EventEmitter<Event> = new EventEmitter<Event>();

constructor(
private _elementRef: ElementRef<HTMLElement>,
private _focusMonitor?: FocusMonitor,
@Inject(DOCUMENT) document?: any
) {
super();
if (_focusMonitor) {
// Start monitoring the element so it gets the appropriate focused classes. We want
// to show the focus style for menu items only when the focus was not caused by a
// mouse or touch interaction.
_focusMonitor.monitor(this._elementRef, false);
}

this._document = document;
}

/** Focuses the menu item. */
focus(origin: FocusOrigin = 'program', options?: FocusOptions): void {
if (this._focusMonitor) {
this._focusMonitor.focusVia(this._getHostElement(), origin, options);
} else {
this._getHostElement().focus(options);
}
}

ngOnDestroy() {
if (this._focusMonitor) {
this._focusMonitor.stopMonitoring(this._elementRef);
}
}

/** Used to set the `tabindex`. */
_getTabIndex(): string {
return this.disabled ? '-1' : '0';
}

/** Returns the host DOM element. */
_getHostElement(): HTMLElement {
return this._elementRef.nativeElement;
}

/** Prevents the default element actions if it is disabled. */
@HostListener('click', ['$event'])
_checkDisabled(event: Event): void {
if (this.disabled) {
event.preventDefault();
event.stopPropagation();
}
}

/** Gets the label to be used when determining whether the option should be focused. */
getLabel(): string {
const element: HTMLElement = this._elementRef.nativeElement;
const textNodeType = this._document ? this._document.TEXT_NODE : 3;
let output = '';

if (element.childNodes) {
const length = element.childNodes.length;

// Go through all the top-level text nodes and extract their text.
// We skip anything that's not a text node to prevent the text from
// being thrown off by something like an icon.
for (let i = 0; i < length; i++) {
if (element.childNodes[i].nodeType === textNodeType) {
output += element.childNodes[i].textContent;
}
}
}

return output.trim();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<span #panelTarget>
<ng-content></ng-content>
<sbb-icon-chevron-small-down></sbb-icon-chevron-small-down>
</span>
Loading

0 comments on commit 688bcf4

Please sign in to comment.