Skip to content

Instantly share code, notes, and snippets.

@lighth7015
Last active April 28, 2020 17:07
Show Gist options
  • Save lighth7015/008299f0042df5bdc6a637ffa0aa5c28 to your computer and use it in GitHub Desktop.
Save lighth7015/008299f0042df5bdc6a637ffa0aa5c28 to your computer and use it in GitHub Desktop.
Stack Menu Implementation
import React from "react";
import { h } from "react";
export default
class AppDrawer extends React.Component<AppDrawerProps, AppDrawerState> {
state: AppDrawerState = {
current: [0]
};
private entries: Array<MenuItem> = [
{
title: "Navigation",
items: [
{ type: MenuTypes.Category, index: 0 },
{ type: MenuTypes.MenuItem, index: 1 },
{ type: MenuTypes.MenuItem, index: 2 },
{ type: MenuTypes.Divider },
{ type: MenuTypes.Category, index: 3 }
]
},
{
title: "Welcome to Firehouse",
route: "/"
},
{
title: "Second Item",
route: "second"
},
{
title: "Submenu",
items: []
}
];
private get items(): MenuEntries {
const { entries, state: { current = [ 0 ] }} = this;
const [ index ] = current;
const { items: children = [] }: MenuItem = entries[index];
const items: MenuEntries = [];
const iterator: ReducerIterator<NavEntry> = ( items: MenuEntries, child: NavEntry, index: number ) => {
const menu: MenuItem = { title: '', type: child.type, items: [] };
if (child.type !== MenuTypes.Divider) {
const { type: _unused, ...props } = child;
Object.assign(menu, props);
}
items.push(menu);
return items;
};
if (len(current) > 1) {
console.log( 'Prepend parent.' );
}
return children.reduce( iterator, [] ) as MenuEntries;
}
render(props: AppDrawerProps, { current }: AppDrawerState) {
const callback: SetStateFunc = this.setState.bind(this);
console.log(this.items);
return (
<ul>{
this.items.map((menu: MenuItem, index: number) => (
<MenuEntry
key={index} items={this.entries}
scope={current} onUpdate={callback}
menu={menu} index={index} />
))
}</ul>
);
}
}
const len: (t: any[]) => number = (t: any[]) => t.length;
enum MenuTypes { Category, MenuItem, Divider }
interface InlineStyle {
[prop: string]: any;
}
interface StyleContainer {
[prop: string]: InlineStyle;
}
interface AppDrawerProps {
opened?: boolean;
shouldCollapse: boolean;
}
interface AppDrawerState {
current: Array<number>;
}
interface NavEntry {
type: MenuTypes;
index?: number;
}
interface MenuItem {
type?: MenuTypes;
title: string;
route?: string | undefined;
items?: Array<NavEntry>;
}
type MenuEntries = Array<MenuItem>;
type NavEntries = Array<NavEntry>;
type ReducerIterator<T = any, Container = Array<T>> = ( items: Container | any, child: T, index: number ) => Container;
type SetStateFunc = (state: AppDrawerState) => void;
interface MenuEntryProps {
menu: MenuItem;
index: number;
scope: Array<number>;
items: Array<MenuItem>;
onUpdate: SetStateFunc;
}
import React from "react";
import { h } from "react";
class MenuEntry extends React.Component<MenuEntryProps> {
/**
* Getter for menu instance.
*/
private get menu(): MenuItem {
return this.props.menu;
}
/**
* Getter for menu caption
*/
private get type(): MenuTypes {
const index: number = this.props.scope[this.depth - 1];
const entry: MenuItem = this.props.items[index];
if (entry) {
return entry.items[this.index].type;
}
else {
console.log({ index });
}
}
/**
* Getter for menu caption
*/
private get caption(): string {
return this.menu.title && this.menu.title || '';
}
/**
* Getter for the item's index
*/
private get index(): number {
return this.props.index;
}
/**
* Getter for the current menu depth
*/
private get depth(): number {
return len(this.props.scope);
}
/**
* Handler for any onClick callback target during rendering of this control.
*
* FIXME This is currently broken, because I'm working out how to push state back
* and forth, allowing you to traverse however deep this navigation menu is
*/
private activate() {
const { depth, index, menu, props: { items: list, scope: current }} = this;
const items: Array<number> = current.slice( current.indexOf( index ) >= 0?
current.indexOf( index ) + 1: len(current));
if (this.type === MenuTypes.MenuItem) {
if ( depth == current.indexOf( index ) + 1) {
current.pop();
} else {
console.log("TODO: Attempt to activate menu item.", [ depth, index ], menu);
}
} else if (this.type === MenuTypes.Category) {
//current.push(index);
console.log( 'New scope:', current, index);
}
else {
console.log( "C" );
}
this.props.onUpdate({ current } as AppDrawerState);
}
// Render a category
private get asCategory() {
const activate = this.activate.bind(this);
if (this.index === 0 && this.depth === 1) {
return (<li>[back] {this.caption}</li>);
}
else {
return (<li onClick={activate}>[{this.caption}]</li>);
}
}
// Render a menu item
private get asMenuItem() {
const isActive: boolean = this.index === 0 && this.depth == 1;
const { menu, caption, activate } = this;
if (this.index === 0) {
if (this.depth == 1) {
return (<li>A1</li>);
}
else {
return (<li>A2</li>);
}
}
else {
if (this.depth > 1) {
return (<li onClick={activate.bind(this)}>B1</li>);
}
else {
return (<li onClick={activate.bind(this)}>B2</li>);
}
}
}
// Render a divider.. lol.
private get asDivider() {
return (<hr />);
}
/**
* FIXME I'm not entirely sure how this will handle (performance-wise), but hey it's as good
* a time as any to test its performance impact out. That being said, however, I don't
* don't know any better ways of handling this kind of thing.
*/
render() {
const render: Array<() => JSX.Element> = [
() => this.asCategory,
() => this.asMenuItem,
() => this.asDivider
] [this.type] || this.asMenuItem;
return render();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment