291 lines
8.7 KiB
TypeScript
291 lines
8.7 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;
|
|
}
|
|
|
|
/**
|
|
* A Divider in a Menu.
|
|
*
|
|
* @export
|
|
* @interface IDivider
|
|
*/
|
|
export interface IDivider {
|
|
/**
|
|
* Type Element
|
|
*
|
|
* @type {'divider'}
|
|
* @memberof IDivider
|
|
*/
|
|
type: 'divider'
|
|
}
|
|
|
|
export interface ToolbarState<D> extends 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){
|
|
return (
|
|
<Nav.Link key={this._generateId()} href={entry.ref}>
|
|
<FontAwesomeIcon icon={entry.icon}/> {entry.label}
|
|
</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){
|
|
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>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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.state.brand.icon ? <FontAwesomeIcon icon = {this.state.brand.icon} />: ''}
|
|
{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.state.brand.icon ? <FontAwesomeIcon icon = {this.state.brand.icon} />: ''}
|
|
{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; |