-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
save button collapse and smaller logo on small screens
- Loading branch information
Showing
5 changed files
with
337 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* FF Typescript Foundation Library | ||
* Copyright 2019 Ralph Wiedemeier, Frame Factory GmbH | ||
* | ||
* License: MIT | ||
*/ | ||
|
||
import { customElement, property, html, PropertyValues } from "./CustomElement"; | ||
|
||
import Button from "./Button"; | ||
import "./Menu"; | ||
import { IMenuItem } from "./Menu"; | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
|
||
export type DropdownDirection = "up" | "down"; | ||
export type DropdownAlign = "left" | "right"; | ||
|
||
@customElement("ff-dropdown") | ||
export default class Dropdown extends Button | ||
{ | ||
/** Direction of the dropdown menu. Possible values: "down" (default), "up". */ | ||
@property({ type: String }) | ||
direction: DropdownDirection = "down"; | ||
|
||
@property({ type: String }) | ||
align: DropdownAlign = "left"; | ||
|
||
/** Items to be displayed in the dropdown menu. */ | ||
@property({ attribute: false }) | ||
items: Array<IMenuItem | string> = []; | ||
|
||
@property({ type: Number }) | ||
itemIndex = -1; | ||
|
||
|
||
constructor() | ||
{ | ||
super(); | ||
this.caret = true; | ||
|
||
this.onKeyOrPointer = this.onKeyOrPointer.bind(this); | ||
this.onKeyDown = this.onKeyDown.bind(this); | ||
} | ||
|
||
protected firstConnected() | ||
{ | ||
super.firstConnected(); | ||
this.classList.add("ff-dropdown"); | ||
} | ||
|
||
protected connected() | ||
{ | ||
super.connected(); | ||
document.addEventListener("pointerdown", this.onKeyOrPointer, { capture: true, passive: true }); | ||
document.addEventListener("keyup", this.onKeyOrPointer, { capture: true, passive: true }); | ||
} | ||
|
||
protected disconnected() | ||
{ | ||
super.disconnected(); | ||
document.removeEventListener("pointerdown", this.onKeyOrPointer); | ||
document.removeEventListener("keyup", this.onKeyOrPointer); | ||
} | ||
|
||
protected render() | ||
{ | ||
const classes = (this.direction === "up" ? "ff-position-above " : "ff-position-below ") | ||
+ (this.align === "right" ? "ff-align-right" : "ff-align-left"); | ||
|
||
const menu = this.selected ? html`<ff-menu class=${classes} .items=${this.items} itemIndex=${this.itemIndex} setFocus></ff-menu>` : null; | ||
return html`${super.render()}${menu}`; | ||
} | ||
|
||
protected onClick(event: MouseEvent) | ||
{ | ||
this.selected = !this.selected; | ||
if (!this.selected) { | ||
setTimeout(() => this.focus(), 0); | ||
} | ||
} | ||
|
||
|
||
|
||
protected onKeyDown(event: KeyboardEvent) | ||
{ | ||
super.onKeyDown(event); | ||
|
||
// on escape key close the dropdown menu | ||
if (event.code === "Escape" && this.selected) { | ||
this.selected = false; | ||
} | ||
} | ||
|
||
protected onKeyOrPointer(event: UIEvent) | ||
{ | ||
// if pointer goes down outside this close the dropdown menu | ||
if (this.selected && !(event.target instanceof Node && this.contains(event.target))) { | ||
this.selected = false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/** | ||
* FF Typescript Foundation Library | ||
* Copyright 2019 Ralph Wiedemeier, Frame Factory GmbH | ||
* | ||
* License: MIT | ||
*/ | ||
|
||
import "./Button"; | ||
import { IButtonClickEvent, IButtonKeyboardEvent } from "./Button"; | ||
|
||
import CustomElement, { customElement, property, html } from "./CustomElement"; | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
|
||
export interface IMenuItem | ||
{ | ||
index?: number; | ||
name?: string; | ||
text?: string; | ||
icon?: string; | ||
checked?: boolean; | ||
disabled?: boolean; | ||
divider?: boolean; | ||
selectedIndex?: number; | ||
selected?: boolean; | ||
} | ||
|
||
export interface IMenuSelectEvent extends CustomEvent | ||
{ | ||
type: "select"; | ||
target: Menu; | ||
detail: { | ||
item: IMenuItem; | ||
} | ||
} | ||
|
||
@customElement("ff-menu") | ||
export default class Menu extends CustomElement | ||
{ | ||
static readonly iconChecked = "fas fa-check"; | ||
|
||
/** Optional name to identify the dropdown. */ | ||
@property({ type: String }) | ||
name = ""; | ||
|
||
/** Optional index to identify the dropdown. */ | ||
@property({ type: Number }) | ||
index = 0; | ||
|
||
/** Entries to be displayed in the dropdown menu. */ | ||
@property({ attribute: false }) | ||
items: Array<IMenuItem | string> = null; | ||
|
||
@property({ type: Number }) | ||
itemIndex = -1; | ||
|
||
@property({ type: Boolean }) | ||
setFocus = false; | ||
|
||
|
||
protected firstConnected() | ||
{ | ||
this.setAttribute("role", "menu"); | ||
this.classList.add("ff-menu"); | ||
} | ||
|
||
protected render() | ||
{ | ||
if (!this.items) { | ||
return html``; | ||
} | ||
|
||
return html`${this.items.map((item, index) => this.renderItem(item, index))}`; | ||
} | ||
|
||
protected renderItem(item: IMenuItem | string, index: number) | ||
{ | ||
let text, icon; | ||
|
||
if (typeof item === "string") { | ||
text = item; | ||
icon = "empty"; | ||
} | ||
else if (item.divider) { | ||
return html`<div class="ff-divider"></div>`; | ||
} | ||
else { | ||
text = item.text; | ||
icon = item.icon || (item.checked ? "check" : "empty"); | ||
} | ||
|
||
return html`<ff-button index=${index} selectedIndex=${this.itemIndex} | ||
icon=${icon} text=${text} @click=${this.onClick} @keydown=${this.onKeyDown}></ff-button>`; | ||
} | ||
|
||
updated() | ||
{ | ||
if (this.setFocus) { | ||
const index = this.itemIndex >= 0 ? this.itemIndex : 0; | ||
this.focusItem(index); | ||
} | ||
} | ||
|
||
protected focusItem(index: number) | ||
{ | ||
const child = this.children.item(index); | ||
|
||
if (child instanceof HTMLElement) { | ||
child.focus(); | ||
} | ||
} | ||
|
||
protected onClick(event: IButtonClickEvent) | ||
{ | ||
const item = this.items[event.target.index]; | ||
|
||
if (!item) { | ||
return; | ||
} | ||
|
||
this.dispatchEvent(new CustomEvent("select", { | ||
detail: { item }, | ||
bubbles: true | ||
}) as IMenuSelectEvent); | ||
} | ||
|
||
protected onKeyDown(event: IButtonKeyboardEvent) | ||
{ | ||
const items = this.items; | ||
|
||
if (event.code === "ArrowDown") { | ||
let index = event.target.index; | ||
do { index = (index + 1) % items.length } while (items[index]["divider"]); | ||
this.focusItem(index); | ||
} | ||
else if (event.code === "ArrowUp") { | ||
let index = event.target.index; | ||
do { index = (index + items.length - 1) % items.length } while (items[index]["divider"]); | ||
this.focusItem(index); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.