Skip to content

Commit

Permalink
Merge pull request #49 from cal-smith/master
Browse files Browse the repository at this point in the history
feat: add overflow menu and associated components
  • Loading branch information
zvonimirfras authored Sep 10, 2018
2 parents ca134a1 + be07152 commit cc5a776
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 56 deletions.
41 changes: 20 additions & 21 deletions src/dialog/dialog.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,31 @@ import { Dialog } from "./dialog.component";
import { DialogDirective } from "./dialog.directive";
import { DialogPlaceholderComponent } from "./dialog-placeholder.component";

import { Popover } from "./popover/popover.component";
import { PopoverDirective } from "./popover/popover.directive";
import { PopoverMenu } from "./popover/popover-menu.component";
import { PopoverMenuDirective } from "./popover/popover-menu.directive";

import { Tooltip } from "./tooltip/tooltip.component";
import { TooltipDirective } from "./tooltip/tooltip.directive";
import { EllipsisTooltipDirective } from "./tooltip/ellipsis-tooltip.directive";

import { OverflowMenu } from "./overflow-menu/overflow-menu.component";
import { OverflowMenuPane } from "./overflow-menu/overflow-menu-pane.component";
import { OverflowMenuDirective } from "./overflow-menu/overflow-menu.directive";
import { OverflowMenuOption } from "./overflow-menu/overflow-menu-option.component";

// exports
export { DialogService } from "./dialog.service";
export { DialogPlaceholderService } from "./dialog-placeholder.service";
export { Dialog } from "./dialog.component";
export { DialogDirective } from "./dialog.directive";
export { DialogPlaceholderComponent } from "./dialog-placeholder.component";

export { Popover } from "./popover/popover.component";
export { PopoverDirective } from "./popover/popover.directive";
export { PopoverMenu } from "./popover/popover-menu.component";
export { PopoverMenuDirective } from "./popover/popover-menu.directive";

export { Tooltip } from "./tooltip/tooltip.component";
export { TooltipDirective } from "./tooltip/tooltip.directive";
export { EllipsisTooltipDirective } from "./tooltip/ellipsis-tooltip.directive";

export { OverflowMenu } from "./overflow-menu/overflow-menu.component";
export { OverflowMenuPane } from "./overflow-menu/overflow-menu-pane.component";
export { OverflowMenuDirective } from "./overflow-menu/overflow-menu.directive";
export { OverflowMenuOption } from "./overflow-menu/overflow-menu-option.component";

// either provides a new instance of DialogPlaceholderService, or returns the parent
export function DIALOG_PLACEHOLDER_SERVICE_PROVIDER_FACTORY(parentService: DialogPlaceholderService) {
return parentService || new DialogPlaceholderService();
Expand All @@ -51,26 +51,26 @@ export const DIALOG_PLACEHOLDER_SERVICE_PROVIDER = {
@NgModule({
declarations: [
Dialog,
Popover,
PopoverMenu,
Tooltip,
OverflowMenu,
OverflowMenuPane,
DialogDirective,
PopoverDirective,
PopoverMenuDirective,
TooltipDirective,
EllipsisTooltipDirective,
OverflowMenuDirective,
OverflowMenuOption,
DialogPlaceholderComponent
],
exports: [
Dialog,
Popover,
PopoverMenu,
Tooltip,
OverflowMenu,
OverflowMenuPane,
DialogDirective,
PopoverDirective,
PopoverMenuDirective,
TooltipDirective,
EllipsisTooltipDirective,
OverflowMenuDirective,
OverflowMenuOption,
DialogPlaceholderComponent
],
providers: [
Expand All @@ -79,9 +79,8 @@ export const DIALOG_PLACEHOLDER_SERVICE_PROVIDER = {
],
entryComponents: [
Dialog,
Popover,
PopoverMenu,
Tooltip
Tooltip,
OverflowMenuPane
],
imports: [CommonModule, TranslateModule, StaticIconModule]
})
Expand Down
5 changes: 0 additions & 5 deletions src/dialog/dialog.service.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import {
EventEmitter,
Injector,
Component,
ComponentRef,
ComponentFactory,
ComponentFactoryResolver,
Injectable,
ApplicationRef,
ViewContainerRef,
Host
} from "@angular/core";
import { Subscription } from "rxjs";
import { DialogConfig } from "./dialog-config.interface";
import { DialogPlaceholderService } from "./dialog-placeholder.service";
import { Popover } from "..";


/**
* `Dialog` object to be injected into other components.
Expand Down
69 changes: 69 additions & 0 deletions src/dialog/overflow-menu/overflow-menu-option.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
HostBinding,
Component,
Input,
ElementRef
} from "@angular/core";

/**
* `OverflowMenuOption` represents a single option in an overflow menu
*
* Presently it has three possible states - normal, disabled, and danger:
* ```
* <ibm-overflow-menu-option>Simple option</ibm-overflow-menu-option>
* <ibm-overflow-menu-option disabled="true">Disabled</ibm-overflow-menu-option>
* <ibm-overflow-menu-option type="danger">Danger option</ibm-overflow-menu-option>
* ```
*
* For content that expands beyod the overflow menu `OverflowMenuOption` automatically adds a title attribute.
*/
@Component({
selector: "ibm-overflow-menu-option",
template: `
<button
class="bx--overflow-menu-options__btn"
[ngClass]="{
'bx--overflow-menu-options__option--danger': type === 'danger',
'bx--overflow-menu-options__option--disabled': disabled
}"
[tabindex]="(disabled ? -1 : null)"
[title]="(titleEnabled ? content : '')">
<ng-content></ng-content>
</button>
`
})
export class OverflowMenuOption {
@HostBinding("class") optionClass = "bx--overflow-menu-options__option";
@HostBinding("attr.role") role = "list-item";

/**
* toggles between `normal` and `danger` states
*/
@Input() type: "normal" | "danger" = "normal";
/**
* disable/enable interactions
*/
@Input() disabled = false;

constructor(private elementRef: ElementRef) {}

/**
* Returns true if the content string is longer than the width of the containing button
*
* note: getter ties into the view check cycle so we always get an accurate value
*/
get titleEnabled() {
const button = this.elementRef.nativeElement.querySelector("button");
if (button.scrollWidth > button.offsetWidth) {
return true;
}
return false;
}

/**
* Returns the text content projected into the component
*/
get content(): string {
return this.elementRef.nativeElement.querySelector("button").textContent;
}
}
24 changes: 24 additions & 0 deletions src/dialog/overflow-menu/overflow-menu-pane.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component } from "@angular/core";
import { Dialog } from "../dialog.component";

/**
* Extend the `Dialog` component to create an overflow menu.
*
* Not used directly. See overflow-menu.component and overflow-menu.directive for more
*/
@Component({
selector: "ibm-overflow-menu-pane",
template: `
<div #dialog>
<ul
class="bx--overflow-menu-options bx--overflow-menu-options--open"
tabindex="-1">
<ng-template
[ngTemplateOutlet]="dialogConfig.content"
[ngTemplateOutletContext]="{overflowMenu: this}">
</ng-template>
</ul>
</div>
`
})
export class OverflowMenuPane extends Dialog {}
35 changes: 35 additions & 0 deletions src/dialog/overflow-menu/overflow-menu.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component } from "@angular/core";

/**
* The OverFlow menu component encapsulates the OverFlowMenu directive, and the menu iconography into one convienent component
*
* html:
* ```
* <ibm-overflow-menu [options]="overflowContent"></ibm-overflow-menu>
* <ng-template #overflowContent>
* <ibm-overflow-menu-option>Option 1</ibm-overflow-menu-option>
* <ibm-overflow-menu-option>Option 2</ibm-overflow-menu-option>
* </ng-template>
* ```
*/
@Component({
selector: "ibm-overflow-menu",
template: `
<div
[ibmOverflowMenu]="options"
class="bx--overflow-menu"
style="display: block;">
<svg class="bx--overflow-menu__icon" width="3" height="15" viewBox="0 0 3 15">
<g fill-rule="evenodd">
<circle cx="1.5" cy="1.5" r="1.5" />
<circle cx="1.5" cy="7.5" r="1.5" />
<circle cx="1.5" cy="13.5" r="1.5" />
</g>
</svg>
</div>
<ng-template #options>
<ng-content></ng-content>
</ng-template>
`
})
export class OverflowMenu {}
54 changes: 54 additions & 0 deletions src/dialog/overflow-menu/overflow-menu.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
Directive,
ElementRef,
ViewContainerRef,
Input,
TemplateRef
} from "@angular/core";
import { DialogDirective } from "./../dialog.directive";
import { DialogService } from "./../dialog.service";
import { OverflowMenuPane } from "./overflow-menu-pane.component";


/**
* Directive for extending `Dialog` to create overflow menus.
*
* class: OverflowMenuDirective (extends DialogDirective)
*
*
* selector: `ibmOverflowMenu`
*
*
* ```html
* <div [ibmOverflowMenu]="templateRef"></div>
* <ng-template #templateRef>
* <!-- overflow menu options here -->
* </ng-template>
* ```
*/
@Directive({
selector: "[ibmOverflowMenu]",
exportAs: "ibmOverflowMenu",
providers: [
DialogService
]
})
export class OverflowMenuDirective extends DialogDirective {
@Input() ibmOverflowMenu: TemplateRef<any>;

/**
* Creates an instance of `OverflowMenuDirective`.
*/
constructor(
protected elementRef: ElementRef,
protected viewContainerRef: ViewContainerRef,
protected dialogService: DialogService
) {
super(elementRef, viewContainerRef, dialogService);
dialogService.create(OverflowMenuPane);
}

onDialogInit() {
this.dialogConfig.content = this.ibmOverflowMenu;
}
}
32 changes: 32 additions & 0 deletions src/dialog/overflow-menu/overflow-menu.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { storiesOf, moduleMetadata } from "@storybook/angular";

import { TranslateModule } from "@ngx-translate/core";

import { DialogModule } from "../../";

storiesOf("Overflow Menu", module)
.addDecorator(
moduleMetadata({
imports: [
DialogModule,
TranslateModule.forRoot()
]
})
)
.add("Basic", () => ({
template: `
<ibm-overflow-menu>
<ibm-overflow-menu-option>
An example option that is really long to show what should be done to handle long text
</ibm-overflow-menu-option>
<ibm-overflow-menu-option>Option 2</ibm-overflow-menu-option>
<li class="bx--overflow-menu-options__option">
<button class="bx--overflow-menu-options__btn">A fully custom option</button>
</li>
<ibm-overflow-menu-option>Option 4</ibm-overflow-menu-option>
<ibm-overflow-menu-option disabled="true">Disabled</ibm-overflow-menu-option>
<ibm-overflow-menu-option type="danger">Danger option</ibm-overflow-menu-option>
</ibm-overflow-menu>
<ibm-dialog-placeholder></ibm-dialog-placeholder>
`
}));
27 changes: 0 additions & 27 deletions src/dialog/tooltip/tooltip.component.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
import {
Component,
Input,
Output,
EventEmitter,
OnInit,
AfterViewInit,
Injector,
ElementRef,
TemplateRef,
HostListener,
ViewChild,
HostBinding
} from "@angular/core";
import { Dialog } from "./../dialog.component";

/**
* Extend the `Dialog` component to create a tooltip for exposing content.
* @export
* @class Tooltip
* @extends {Dialog}
*/
@Component({
selector: "ibm-tooltip",
Expand Down Expand Up @@ -46,28 +34,13 @@ export class Tooltip extends Dialog {
@HostBinding("style.display") style = "inline-block";
/**
* Value is set to `true` if the `Tooltip` is to display a `TemplateRef` instead of a string.
* @type {boolean}
* @memberof Tooltip
*/
public hasContentTemplate = false;

/**
* Check whether there is a template for the `Tooltip` content.
* @memberof Tooltip
*/
onDialogInit() {
this.hasContentTemplate = this.dialogConfig.content instanceof TemplateRef;
}

/**
* Set the class of the `Tooltip`.
* @returns null
* @memberof Tooltip
*/
public getClass() {
if (this.dialogConfig.type) {
return `tooltip--${this.dialogConfig.type}-${this.placement}`;
}
return `tooltip--${this.placement}`;
}
}
3 changes: 2 additions & 1 deletion src/index.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ storiesOf("Welcome", module).add("to Carbon Angular", () => ({
template: `
<h1>Carbon Components Angular</h1>
<h2>An Angular implementation of the Carbon Design System</h2>
<a href="https://angular.carbondesignsystem.com/documentation">Documentation</a>
`,
props: {},
props: {}
}));
Loading

0 comments on commit cc5a776

Please sign in to comment.