Instantly share code, notes, and snippets.
Last active
January 12, 2021 01:48
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save objectfoo/1e2964680d9fda068f3133ee294bc137 to your computer and use it in GitHub Desktop.
Greedy nav
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | |
| ); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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