Skip to content

Commit

Permalink
fix: start working on the Navigator component
Browse files Browse the repository at this point in the history
  • Loading branch information
spaenleh committed Jan 10, 2025
1 parent b805cfb commit 202e29c
Show file tree
Hide file tree
Showing 11 changed files with 695 additions and 0 deletions.
56 changes: 56 additions & 0 deletions src/components/ui/Navigator/CurrentItemNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Typography } from '@mui/material';

import { DiscriminatedItem, ItemType } from '@graasp/sdk';

import ItemMenu, { ItemMenuProps } from './ItemMenu.js';
import CenterAlignWrapper from './common/CenterAlignWrapper.js';
import NavigationLink from './common/NavigationLink.js';
import { ITEM_NAME_MAX_LENGTH } from './common/constants.js';

export type CurrentItemProps = {
item: DiscriminatedItem;
buildBreadcrumbsItemLinkId?: (id: string) => string;
buildIconId?: (id: string) => string;
buildMenuId?: (id: string) => string;
buildMenuItemId?: (id: string) => string;
useChildren: ItemMenuProps['useChildren'];
buildToItemPath: (id: string) => LinkProps['to'];
showArrow: boolean;
};
const CurrentItemNavigation = ({
item,
buildBreadcrumbsItemLinkId,
buildToItemPath,
useChildren,
buildIconId,
buildMenuId,
buildMenuItemId,
showArrow,
}: CurrentItemProps): JSX.Element | null => {
return (
<CenterAlignWrapper>
<NavigationLink
id={buildBreadcrumbsItemLinkId?.(item.id)}
key={item.id}
to={buildToItemPath(item?.id)}
>
<Typography>
{truncate(item.name, { length: ITEM_NAME_MAX_LENGTH })}
</Typography>
</NavigationLink>
{(item.type === ItemType.FOLDER || showArrow) && (
<ItemMenu
useChildren={useChildren}
itemId={item.id}
buildToItemPath={buildToItemPath}
buildIconId={buildIconId}
buildMenuItemId={buildMenuItemId}
buildMenuId={buildMenuId}
renderArrow={showArrow}
/>
)}
</CenterAlignWrapper>
);
};

export default CurrentItemNavigation;
78 changes: 78 additions & 0 deletions src/components/ui/Navigator/ExtraItemsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';

import {
IconButton,
IconButtonProps,
Menu,
MenuItem,
Typography,
} from '@mui/material';

import { ChevronRightIcon } from 'lucide-react';

import { MenuItemType } from './Navigator.js';

export type ExtraItemsMenuProps = {
icon?: JSX.Element;
menuItems: MenuItemType[];
buildIconId?: (id: string) => string;
buildMenuId?: (itemId: string) => string;
name: string;
};

const Separator = <ChevronRightIcon data-testid="NavigateNextIcon" />;

const ExtraItemsMenu = ({
icon = Separator,
menuItems,
buildIconId,
buildMenuId,
name,
}: ExtraItemsMenuProps): JSX.Element => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick: IconButtonProps['onClick'] = (event) => {
setAnchorEl(event.currentTarget);
};

const handleClose = (): void => {
setAnchorEl(null);
};

return (
<>
<IconButton
onClick={handleClick}
aria-haspopup="true"
id={buildIconId?.(name)}
aria-expanded={open ? true : undefined}
>
{icon}
</IconButton>
<Menu
anchorEl={anchorEl}
open={open}
id={buildMenuId?.(name)}
onClose={handleClose}
onClick={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
{menuItems?.map(({ name, path }) => (
<MenuItem key={name} component={Link} to={path}>
<Typography>{name}</Typography>
</MenuItem>
))}
</Menu>
</>
);
};

export default ExtraItemsMenu;
40 changes: 40 additions & 0 deletions src/components/ui/Navigator/ExtraItemsNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Box, Typography } from '@mui/material';

import truncate from 'lodash.truncate';

import ExtraItemsMenu from './ExtraItemsMenu.js';
import { MenuItemType } from './Navigator.js';
import CenterAlignWrapper from './common/CenterAlignWrapper.js';
import NavigationLink from './common/NavigationLink.js';
import { ITEM_NAME_MAX_LENGTH } from './common/constants.js';

export interface ExtraItem {
name: string;
path: string;
icon?: JSX.Element;
menuItems?: MenuItemType[];
}

const ExtraItemsNavigation = ({
extraItems,
}: {
extraItems: ExtraItem[];
}): JSX.Element[] | null => {
return extraItems.map(({ icon, name, path, menuItems }) => (
<CenterAlignWrapper>
<Box display="flex" gap={1}>
{icon}
<NavigationLink to={path}>
<Typography>
{truncate(name, { length: ITEM_NAME_MAX_LENGTH })}
</Typography>
</NavigationLink>
</Box>
{menuItems && menuItems.length > 0 && (
<ExtraItemsMenu menuItems={menuItems} name={name} />
)}
</CenterAlignWrapper>
));
};

export default ExtraItemsNavigation;
98 changes: 98 additions & 0 deletions src/components/ui/Navigator/HomeMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { ChevronRightIcon, HomeIcon } from 'lucide-react';

import {
IconButton,
IconButtonProps,
Menu,
MenuItem,
MenuProps,
Typography,
} from '@mui/material';

import React from 'react';
import { Link } from 'react-router-dom';

import NavigationLink from './common/NavigationLink.js';

const Separator = <ChevronRightIcon data-testid='NavigateNextIcon' />;

type Props = {
selected: { name: string; id: string; to: string };
elements: {
name: string;
id: string;
to: string;
}[];
menuId?: string;
buildMenuItemId?: (itemId: string) => string;
homeDropdownId?: string;
};

const HomeMenu = ({
buildMenuItemId,
elements,
homeDropdownId,
menuId,
selected,
}: Props): JSX.Element => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);

const handleClick: IconButtonProps['onClick'] = (event) => {
setAnchorEl(event.currentTarget);
};

const handleClose = (): void => {
setAnchorEl(null);
};

const onClick: MenuProps['onClick'] = (): void => {
handleClose();
};

return (
<>
<HomeIcon />
<IconButton
onClick={handleClick}
id={homeDropdownId}
aria-controls={open ? 'root' : undefined}
aria-haspopup='true'
aria-expanded={open ? true : undefined}
>
{Separator}
</IconButton>
<Menu
anchorEl={anchorEl}
open={open}
id={menuId}
onClose={handleClose}
onClick={onClick}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
{elements.map(({ name, id, to }) => (
<MenuItem
key={id}
component={Link}
to={to}
id={buildMenuItemId?.(id)}
>
<Typography>{name}</Typography>
</MenuItem>
))}
</Menu>
<NavigationLink to={selected.to} key={selected.id}>
<Typography>{selected.name}</Typography>
</NavigationLink>
</>
);
};

export default HomeMenu;
101 changes: 101 additions & 0 deletions src/components/ui/Navigator/ItemMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { UseQueryResult } from '@tanstack/react-query';
import { ChevronRightIcon } from 'lucide-react';

import {
IconButton,
IconButtonProps,
Menu,
MenuItem,
Typography,
} from '@mui/material';

import { useState } from 'react';
import { Link, LinkProps } from 'react-router-dom';

import { DiscriminatedItem } from '@graasp/sdk';

export const Separator = <ChevronRightIcon data-testid='NavigateNextIcon' />;

export type ItemMenuProps = {
buildIconId?: (id: string) => string;
buildMenuId?: (itemId: string) => string;
buildMenuItemId?: (itemId: string) => string;
buildToItemPath: (itemId: string) => LinkProps['to'];
icon?: JSX.Element;
itemId: string;
useChildren: (...args: unknown[]) => UseQueryResult<DiscriminatedItem[]>;
renderArrow?: boolean;
};

const ItemMenu = ({
buildIconId,
buildMenuId,
buildMenuItemId,
buildToItemPath,
icon = Separator,
itemId,
useChildren,
renderArrow,
}: ItemMenuProps): JSX.Element | null => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);

const { data: items } = useChildren(itemId);

const handleClick: IconButtonProps['onClick'] = (event) => {
setAnchorEl(event.currentTarget);
};

const handleClose = (): void => {
setAnchorEl(null);
};

if (!items?.length && renderArrow) {
// to display icon as a separator specially if there's an extra items after items menu
return icon;
}
if (!items?.length) {
return null;
}
return (
<>
<IconButton
onClick={handleClick}
id={buildIconId?.(itemId)}
aria-controls={open ? buildMenuId?.(itemId) : undefined}
aria-haspopup='true'
aria-expanded={open ? true : undefined}
>
{icon}
</IconButton>
<Menu
anchorEl={anchorEl}
id={buildMenuId?.(itemId)}
open={open}
onClose={handleClose}
onClick={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
{items?.map(({ name, id }) => (
<MenuItem
id={buildMenuItemId?.(id)}
key={id}
component={Link}
to={buildToItemPath(id)}
>
<Typography>{name}</Typography>
</MenuItem>
))}
</Menu>
</>
);
};

export default ItemMenu;
Loading

0 comments on commit 202e29c

Please sign in to comment.