362 lines
8.6 KiB
TypeScript
362 lines
8.6 KiB
TypeScript
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import "bootstrap/dist/css/bootstrap.min.css";
|
|
import * as React from "react";
|
|
import { Nav, Navbar, NavDropdown } from "react-bootstrap";
|
|
|
|
export interface ToolbarProps<D> {
|
|
toolbar: IToolbar<D>;
|
|
generateData(): D;
|
|
position?: "top" | "bottom";
|
|
sticky?: boolean;
|
|
bg?: "light" | "dark";
|
|
brand?: ILink;
|
|
}
|
|
|
|
/**
|
|
* Definition of a Toolbar.
|
|
*
|
|
* @export
|
|
* @interface IToolbar
|
|
* @template D
|
|
*/
|
|
export interface IToolbar<D> {
|
|
/**
|
|
* Items of the toolbar.
|
|
*
|
|
* @type {(Array<IAction<D> | IMenu<D> | ILink>)}
|
|
* @memberof IToolbar
|
|
*/
|
|
items: Array<IAction<D> | IMenu<D> | ILink>;
|
|
}
|
|
|
|
export interface IAction<D> {
|
|
type: "action";
|
|
/**
|
|
* Action which should be performed if the element
|
|
* has been clicked.
|
|
*
|
|
* @param {D} data
|
|
* @memberof IAction
|
|
*/
|
|
onClick(data: D): void;
|
|
/**
|
|
* Label of the Menu.
|
|
*
|
|
* @type {string}
|
|
* @memberof IAction
|
|
*/
|
|
label: string;
|
|
/**
|
|
* Additional Icon.
|
|
*/
|
|
icon?: IconDefinition;
|
|
}
|
|
|
|
/**
|
|
* A Menu of the Toolbar
|
|
*
|
|
* @export
|
|
* @interface IMenu
|
|
* @template D
|
|
*/
|
|
export interface IMenu<D> {
|
|
type: "menu";
|
|
/**
|
|
* The Menu ID.
|
|
*
|
|
* @type {string}
|
|
* @memberof IMenu
|
|
*/
|
|
id: string;
|
|
/**
|
|
* Label of the Menu
|
|
*
|
|
* @type {string}
|
|
* @memberof IMenu
|
|
*/
|
|
label: string;
|
|
/**
|
|
* Items of the Menu.
|
|
*
|
|
* @type {(Array<IAction<D> | ILink | IDivider>)}
|
|
* @memberof IMenu
|
|
*/
|
|
items: Array<IAction<D> | IMenu<D> | ILink | IDivider>;
|
|
}
|
|
|
|
/**
|
|
* A Classical Link.
|
|
*
|
|
* @export
|
|
* @interface ILink
|
|
*/
|
|
export interface ILink {
|
|
/**
|
|
* Type of the Element.
|
|
*
|
|
* @type {'link'}
|
|
* @memberof ILink
|
|
*/
|
|
type: "link";
|
|
/**
|
|
* Label
|
|
*
|
|
* @type {string}
|
|
* @memberof ILink
|
|
*/
|
|
label: string;
|
|
/**
|
|
* Referene, where the client should be navigated on click
|
|
*
|
|
* @type {string}
|
|
* @memberof ILink
|
|
*/
|
|
ref: string;
|
|
/**
|
|
* Additional Icon.
|
|
*/
|
|
icon?: IconDefinition | string;
|
|
}
|
|
|
|
/**
|
|
* A Divider in a Menu.
|
|
*
|
|
* @export
|
|
* @interface IDivider
|
|
*/
|
|
export interface IDivider {
|
|
/**
|
|
* Type Element
|
|
*
|
|
* @type {'divider'}
|
|
* @memberof IDivider
|
|
*/
|
|
type: "divider";
|
|
}
|
|
|
|
export type ToolbarState<D> = ToolbarProps<D>;
|
|
|
|
class Toolbar<D> extends React.Component<ToolbarProps<D>, ToolbarState<D>> {
|
|
private _currentId = 0;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = Object.assign(
|
|
{
|
|
bg: "dark",
|
|
position: "top",
|
|
sticky: true
|
|
},
|
|
this.props
|
|
);
|
|
}
|
|
|
|
_generateId() {
|
|
const id = this._currentId;
|
|
this._currentId += 1;
|
|
return id;
|
|
}
|
|
|
|
_renderMenuEntry(entry: IAction<D> | IMenu<D> | ILink) {
|
|
const _this = this;
|
|
|
|
switch (entry.type) {
|
|
case "action":
|
|
if (entry.icon) {
|
|
return (
|
|
<Nav.Link
|
|
key={this._generateId()}
|
|
onClick={(e) => {
|
|
entry.onClick(this.state.generateData());
|
|
}}
|
|
>
|
|
<FontAwesomeIcon icon={entry.icon} /> {entry.label}
|
|
</Nav.Link>
|
|
);
|
|
}
|
|
return (
|
|
<Nav.Link
|
|
key={this._generateId()}
|
|
onClick={(e) => {
|
|
entry.onClick(this.state.generateData());
|
|
}}
|
|
>
|
|
{entry.label}
|
|
</Nav.Link>
|
|
);
|
|
case "menu":
|
|
return this._renderDropdown(entry);
|
|
case "link":
|
|
if (entry.icon && typeof entry.icon === "object") {
|
|
return (
|
|
<Nav.Link key={this._generateId()} href={entry.ref}>
|
|
<FontAwesomeIcon icon={entry.icon} /> {entry.label}
|
|
</Nav.Link>
|
|
);
|
|
} else if (entry.icon && typeof entry.icon === "string") {
|
|
return (
|
|
<Nav.Link key={this._generateId()} href={entry.ref}>
|
|
<img
|
|
src={entry.icon}
|
|
width="30"
|
|
height="30"
|
|
className="d-inline-block align-top"
|
|
/>
|
|
</Nav.Link>
|
|
);
|
|
}
|
|
return (
|
|
<Nav.Link key={this._generateId()} href={entry.ref}>
|
|
{entry.label}
|
|
</Nav.Link>
|
|
);
|
|
}
|
|
}
|
|
|
|
_renderDropdown(item: IMenu<D>) {
|
|
// Issue: Drop-Down wont display the Dropdowns after opening a modal:
|
|
// Issue: https://github.com/react-bootstrap/react-bootstrap/issues/5409
|
|
// Dirty Fix: onClick={e => e.stopPropagation() in the "NavDropdown"
|
|
return (
|
|
<NavDropdown
|
|
key={this._generateId()}
|
|
id={item.id}
|
|
title={item.label}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Iterate over the deteced Modules. */}
|
|
{item.items.map((entry) => {
|
|
switch (entry.type) {
|
|
case "action":
|
|
if (entry.icon) {
|
|
return (
|
|
<NavDropdown.Item
|
|
key={this._generateId()}
|
|
onClick={(e) => {
|
|
entry.onClick(this.state.generateData());
|
|
}}
|
|
>
|
|
<FontAwesomeIcon icon={entry.icon} /> {entry.label}
|
|
</NavDropdown.Item>
|
|
);
|
|
}
|
|
return (
|
|
<Nav.Link
|
|
key={this._generateId()}
|
|
onClick={(e) => {
|
|
entry.onClick(this.state.generateData());
|
|
}}
|
|
>
|
|
{entry.label}
|
|
</Nav.Link>
|
|
);
|
|
case "divider":
|
|
return <NavDropdown.Divider key={this._generateId()} />;
|
|
case "link":
|
|
if (entry.icon && typeof entry.icon === "object") {
|
|
return (
|
|
<NavDropdown.Item key={this._generateId()} href={entry.ref}>
|
|
<FontAwesomeIcon icon={entry.icon} /> {entry.label}
|
|
</NavDropdown.Item>
|
|
);
|
|
}
|
|
return (
|
|
<NavDropdown.Item key={this._generateId()} href={entry.ref}>
|
|
{entry.label}
|
|
</NavDropdown.Item>
|
|
);
|
|
case "menu":
|
|
return this._renderDropdown(entry);
|
|
}
|
|
})}
|
|
</NavDropdown>
|
|
);
|
|
}
|
|
|
|
_renderBrand() {
|
|
switch (typeof this.state.brand.icon) {
|
|
case "string":
|
|
return (
|
|
<img
|
|
src={this.state.brand.icon}
|
|
height="30"
|
|
className="d-inline-block align-top"
|
|
/>
|
|
);
|
|
case "object":
|
|
return <FontAwesomeIcon icon={this.state.brand.icon as any} />;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main Function to Render the Layout
|
|
*/
|
|
render() {
|
|
this._currentId = 0;
|
|
|
|
return (
|
|
<>
|
|
{this.state.sticky ? (
|
|
// Render a collapsable Navbar containing the defined Menu structure.
|
|
<Navbar
|
|
collapseOnSelect
|
|
sticky={this.state.position}
|
|
expand="lg"
|
|
bg={this.state.bg}
|
|
variant={this.state.bg}
|
|
>
|
|
{this.state.brand ? (
|
|
<>
|
|
<Navbar.Brand href={this.state.brand.ref}>
|
|
{this._renderBrand()}
|
|
{this.state.brand.label}
|
|
</Navbar.Brand>
|
|
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
|
</>
|
|
) : (
|
|
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
|
)}
|
|
<Navbar.Collapse id="responsive-navbar-nav">
|
|
<Nav>
|
|
{this.state.toolbar.items.map((item) =>
|
|
this._renderMenuEntry(item)
|
|
)}
|
|
</Nav>
|
|
</Navbar.Collapse>
|
|
</Navbar>
|
|
) : (
|
|
<Navbar
|
|
collapseOnSelect
|
|
fixed={this.state.position}
|
|
expand="lg"
|
|
bg={this.state.bg}
|
|
variant={this.state.bg}
|
|
>
|
|
{this.state.brand ? (
|
|
<>
|
|
<Navbar.Brand href={this.state.brand.ref}>
|
|
{this._renderBrand()}
|
|
{this.state.brand.label}
|
|
</Navbar.Brand>
|
|
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
|
</>
|
|
) : (
|
|
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
|
)}
|
|
<Navbar.Collapse id="responsive-navbar-nav">
|
|
<Nav>
|
|
{this.state.toolbar.items.map((item) =>
|
|
this._renderMenuEntry(item)
|
|
)}
|
|
</Nav>
|
|
</Navbar.Collapse>
|
|
</Navbar>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default Toolbar;
|