Skip to content

Instantly share code, notes, and snippets.

@lighth7015
Last active April 29, 2020 22:57
Show Gist options
  • Save lighth7015/3de9493b335a3179b4edfdf3a5cd8708 to your computer and use it in GitHub Desktop.
Save lighth7015/3de9493b335a3179b4edfdf3a5cd8708 to your computer and use it in GitHub Desktop.
Menu component
import * as React from "preact";
import { h, Fragment } from "preact";
import Match from "preact-router/match";
import { forwardRef } from "preact/compat";
import { Link } from "preact-router/match";
import Slide from "@material-ui/core/Slide";
import Drawer from "@material-ui/core/Drawer";
import Divider from "@material-ui/core/Divider";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import KeyboardArrowLeft from "@material-ui/icons/KeyboardArrowLeft";
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight";
import Typography from "@material-ui/core/Typography";
import SvgIconType from "@material-ui/core/SvgIcon";
import { TransitionProps } from "@material-ui/core/transitions";
import { useDrawerStyles } from "style/styles";
type SvgIcon = typeof SvgIconType;
function len(t: any[]): number {
return t && t.length || 0;
}
/*
const Transition = forwardRef(function Transition(
props: TransitionProps & { children?: React.ReactElement<any, any> },
ref: React.Ref<unknown>
) {
return <Slide direction="up" ref={ref} {...props} />;
});
*/
interface AppDrawerProps {
opened?: boolean;
shouldCollapse: boolean;
}
interface AppDrawerState {
current: Array<number>;
}
enum MenuTypes { Category, MenuItem, Divider }
interface RouteProps {
matches: boolean;
path: string;
url: string;
}
interface MenuItem {
type: MenuTypes;
title?: string;
route?: string;
image?: SvgIcon;
items?: Array<number>;
}
interface NavItem {
type: MenuTypes;
title: string;
index: number;
route?: string;
image?: SvgIcon;
}
type MenuEntries = Array<NavItem>;
type ReducerIterator<T = any> = ( items: Array<T>, child: T, indexOf: number ) => Array<T>;
type SetStateFunc = (state: AppDrawerState) => void;
interface MenuEntryProps {
current: Array<number>;
children: MenuEntries;
onUpdate: SetStateFunc;
selection: NavItem;
}
interface InlineStyle {
[prop: string]: any;
}
interface StyleContainer {
[prop: string]: InlineStyle;
}
class MenuEntry extends React.Component<MenuEntryProps> {
private styles: StyleContainer = {
active: {
backgroundColor: '#3a6ea5',
color: 'white'
}
};
// Getter for menu instance.
private get selection(): NavItem {
return this.props.selection;
}
// Getter for menu caption
private get type(): MenuTypes {
return this.selection.type;
}
// Getter for menu caption
private get caption(): string {
return this.selection.title && this.selection.title || '';
}
// Getter for menu caption
private get children(): MenuEntries {
return this.props.children;
}
// Getter for the item's index
private get index(): number {
return this.selection.index;
}
private get current(): Array<number> {
return this.props.current;
}
private get parent(): number | undefined {
const { current, selection } = this;
return len(current) > 1 && current[current.length - 2] || undefined;
}
private get depth(): number {
return len(this.current);
}
// I hope this works? haha.
private activate(index: number) {
const { current } = this;
if (this.type === MenuTypes.MenuItem) {
console.log(this.children);
console.log("Activate", this.index===0? "menu item (special)": "menu item");
}
else if (this.type === MenuTypes.Category) {
current.push(index);
}
this.props.onUpdate({ current } as AppDrawerState);
}
// Render a category
private get asCategory() {
const activate = this.activate.bind(this, this.index);
if (this.index === 0 && this.depth === 1) {
return (
<ListItem selected>
<ListItemText primary='Hi 1' />
</ListItem>
);
} else {
return (
<ListItem button onClick={activate}>
<ListItemText primary='Hi 2' />
<ListItemSecondaryAction>
<KeyboardArrowRight />
</ListItemSecondaryAction>
</ListItem>
);
}
}
// Render a menu item
private get asMenuItem() {
const isActive: boolean = this.index === 0 && this.depth == 1;
const { selection, caption } = this;
const activate = this.activate.bind(this, this.index);
if (this.parent && this.index === 0) {
return (
<ListItem selected>
<ListItemText primary="Go back" />
</ListItem>
);
}
else {
if (this.parent) {
return (
<ListItem button onClick={activate}>
<ListItemIcon>
<KeyboardArrowLeft />
</ListItemIcon>
<ListItemText primary="B1" />
</ListItem>
);
}
else {
return (
<ListItem button onClick={activate}>
<ListItemText primary="B2" />
</ListItem>
);
}
}
}
// Render a divider.. lol.
private get asDivider() {
return <ListItem divider />;
}
// TODO: I'm not entirely sure what this will do, vis-a-vi performance, but hey
// it's as good a time as any to test the behavior of this out... haha.
render() {
const render =
[() => this.asCategory, () => this.asMenuItem, () => this.asDivider][this.type];
return render();
}
}
export default class Menu extends React.Component<AppDrawerProps, AppDrawerState> {
state: AppDrawerState = {
current: [0]
};
private classes = useDrawerStyles();
private entries: Array<MenuItem> = [
{
type: MenuTypes.Category,
title: "Navigation",
items: [ 2, 3, 1, 4 ]
},
{
type: MenuTypes.Divider
},
{
type: MenuTypes.MenuItem,
title: "Welcome to Firehouse",
route: "/"
},
{
type: MenuTypes.MenuItem,
title: "Second Item",
route: "second"
},
{
type: MenuTypes.Category,
title: "Submenu",
items: [ ]
}
];
private get children(): MenuEntries {
const {
entries,
state: {
current = [ 0 ]
}, entries: {
[current[ current.length - 1 ]]: menu = {
title: '',
items: []
}
}
} = this;
const items: Array<MenuItem> = [
{ title: entries[current[current.length - 1]].title, type: MenuTypes.MenuItem, items: [] } as MenuItem
];
return (menu.items || items).reduce(( children: Array<NavItem>, entry: number, index: number ) => {
const { [entry]: { type, title, route } = { title: 'N/A', route: undefined, type: MenuTypes.MenuItem }
} = entries;
if (index === 0 && len(current) > 1
&& (type === MenuTypes.Category))
{
const { [entries.length - 2]: parent = { title: 'N/A', route: undefined, type: MenuTypes.MenuItem }} = entries;
const prevIndex: number = current[current.length - 2];
children.push({ index: prevIndex, title: parent.title || 'N/A', type: MenuTypes.MenuItem, route: parent.route });
}
children.push({ index, title: title || '', type, route });
return children;
}, []) as Array<NavItem>;
}
render(props: AppDrawerProps, { current }: AppDrawerState) {
const callback: SetStateFunc = this.setState.bind(this);
const children: MenuEntries = this.children;
return (
<Slide direction="left" in={true}>
<Drawer
className={this.classes.drawer}
variant="permanent"
elevation={24}
classes={{
paper: this.classes.paper
}}
>
<List>{
children.map((menu: NavItem, index: number) => (
<MenuEntry key={index} onUpdate={callback} current={current} selection={menu} children={children} />
))
}
<Divider />
</List>
</Drawer>
</Slide>
);
}
}
@lighth7015
Copy link
Author

lighth7015 commented Apr 29, 2020

Current compile "errors"

ERROR in /home/HMNC/robert.butler/Web/parket/examples/firehouse/src/components/drawer.tsx(272,32):
TS2349: This expression is not callable.
  Each member of the union type '{ (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; <U>(callbackfn: (previousValue: U, currentValue: number, ...' has signatures, but none of those signatures are compatible with each other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment