Skip to content

Instantly share code, notes, and snippets.

@objectfoo
Last active January 12, 2021 01:48
Show Gist options
  • Select an option

  • Save objectfoo/1e2964680d9fda068f3133ee294bc137 to your computer and use it in GitHub Desktop.

Select an option

Save objectfoo/1e2964680d9fda068f3133ee294bc137 to your computer and use it in GitHub Desktop.
Greedy nav
import * as React from "react";
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import Toolbar from "@material-ui/core/Toolbar";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import { Theme } from "@material-ui/core";
import clsx from "clsx";
import useGreedyNav, { IMenuItem } from "./use-greedy-nav";
const useStyles = makeStyles((theme: Theme) => createStyles({
horizontal: {
"& .horiz-nav-item": {
minWidth: "120px",
borderBottom: `2px solid transparent`,
"&.selected": {
borderBottom: `2px solid ${theme.palette.primary.main}`,
},
},
},
vertButton: {
minWidth: "120px",
borderBottom: `2px solid transparent`,
"&.selected": {
borderBottom: `2px solid ${theme.palette.primary.main}`,
},
},
}));
export const menuItems: IMenuItem[] = [
{ name: "Menu One" },
{ name: "Menu Two" },
{ name: "Menu Three" },
{ name: "Menu Four" },
{ name: "Menu Five" },
{ name: "Menu Six" },
];
export function SiteNav() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [selected, setSelected] = React.useState<IMenuItem>(menuItems[0]);
const nav = useGreedyNav(menuItems);
return (
<Toolbar key="toolbar" ref={nav.container as React.MutableRefObject<HTMLDivElement>} className={classes.horizontal}>
{nav.horizontal.map((item) => {
const cn =`horiz-nav-item${item === selected ? " selected" : ""}`;
return (
<Button variant="text" color="primary" key={item.name} className={cn} onClick={() => setSelected(item)} onKeyDown={() => console.log("trap arrow keys")}>
{item.name}
</Button>
);
})}
<Box flex="1 1 100%" />
{nav.vertical.current.length > 0 ? (
<Box display="flex" alignItems="stretch" justifyContent="center" id="drop-down-toggle">
<Button aria-controls="nav-drop-down" aria-haspopup="true" onClick={(e) => setAnchorEl(e.currentTarget)}>
Menu
</Button>
<Menu id="nav-drop-down" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={() => setAnchorEl(null)}>
{nav.vertical.current.map((item) => {
return (
<MenuItem
key={item.name}
className={clsx(classes.vertButton, {"selected":item === selected})}
onClick={() => {
setSelected(item);
setAnchorEl(null);
}
}>
{item.name}
</MenuItem>
);
})}
</Menu>
</Box>
) : (
null
)}
</Toolbar>
);
}
import * as React from "react";
export interface IMenuItem {
name: string;
}
type InitialNavItems = IMenuItem & {[key: string]: any };
/**
* @description accept list of objects and a container ref, return horizontal items
* that will fit inside of container div, other items returned in vertical
*/
export default function useGreedyNav(initialNavItems: InitialNavItems[]) {
const container = React.useRef<HTMLDivElement>();
const breakpoints = React.useRef<number[]>([]);
const [available, setAvailable] = React.useState<number>(0);
const [horizontal, setHorizontal] = React.useState<IMenuItem[]>([...initialNavItems]);
const vertical = React.useRef<IMenuItem[]>([]);
React.useEffect(() => {
const onResize = () => {
const clientWidth = container.current ? container.current.clientWidth : 0;
setAvailable(clientWidth);
};
window.addEventListener("resize", onResize);
setAvailable(container.current ? container.current.clientWidth : 0);
return () => window.removeEventListener("resize", onResize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useLayoutEffect(() => {
const used = container.current ? container.current.scrollWidth : 0;
if (available !== 0 && used > available) {
breakpoints.current.push(used);
const moveItem = horizontal.pop();
if (moveItem) {
vertical.current.unshift(moveItem);
}
setHorizontal(horizontal.slice(0));
} else {
if (available > breakpoints.current[breakpoints.current.length -1]) {
const moveItem = vertical.current.shift();
if (moveItem) {
setHorizontal(horizontal.concat(moveItem));
}
}
}
}, [available, horizontal])
return {
container,
horizontal,
vertical,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment