Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "emphasized" to menu item options #3225

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 43 additions & 20 deletions packages/core/ui/CascadingMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext, useMemo, useCallback } from 'react'
import {
Badge,
Divider,
ListItemIcon,
ListItemText,
Expand All @@ -8,7 +9,13 @@ import {
MenuItem,
PopoverOrigin,
} from '@mui/material'
import { MenuItem as JBMenuItem, MenuItemEndDecoration } from './Menu'
import {
MenuItem as JBMenuItem,
MenuItemEndDecoration,
EmptyIcon,
SubMenuItem,
hasEmphasized,
} from './Menu'
import {
bindHover,
bindFocus,
Expand All @@ -17,7 +24,6 @@ import {
PopupState,
} from 'material-ui-popup-state/hooks'
import HoverMenu from 'material-ui-popup-state/HoverMenu'
import ChevronRight from '@mui/icons-material/ChevronRight'

const CascadingContext = React.createContext({
parentPopupState: null,
Expand Down Expand Up @@ -48,32 +54,47 @@ function CascadingMenuItem({
}

function CascadingSubmenu({
title,
inset,
popupId,
item,
hasIcon,
...props
}: {
children: React.ReactNode
title: string
onMenuItemClick: Function
menuItems: JBMenuItem[]
inset: boolean
popupId: string
item: SubMenuItem
hasIcon: boolean
}) {
const { parentPopupState } = React.useContext(CascadingContext)
const popupState = usePopupState({
popupId,
variant: 'popover',
parentPopupState,
})
const { subMenu: menuItems } = item
const emphasized = item.emphasized || hasEmphasized(menuItems)
return (
<>
<MenuItem {...bindHover(popupState)} {...bindFocus(popupState)}>
<ListItemText primary={title} inset={inset} />
<ChevronRight />
{hasIcon ? (
<ListItemIcon>
<EmptyIcon />
</ListItemIcon>
) : null}
<Badge
color="error"
variant="dot"
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
invisible={!emphasized}
>
<ListItemText primary={item.label} />
</Badge>
<div style={{ flexGrow: 1, minWidth: 10 }} />
<EndDecoration item={item} />
</MenuItem>
<CascadingSubmenuHover
{...props}
menuItems={menuItems}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
popupState={popupState}
Expand Down Expand Up @@ -177,10 +198,9 @@ function CascadingMenuList({
<CascadingSubmenu
key={`subMenu-${item.label}-${idx}`}
popupId={`subMenu-${item.label}`}
title={item.label}
inset={hasIcon}
onMenuItemClick={onMenuItemClick}
menuItems={item.subMenu}
item={item}
hasIcon={hasIcon}
>
<CascadingMenuList
{...props}
Expand All @@ -200,16 +220,19 @@ function CascadingMenuList({
onClick={'onClick' in item ? handleClick(item.onClick) : undefined}
disabled={Boolean(item.disabled)}
>
{item.icon ? (
{item.icon || hasIcon ? (
<ListItemIcon>
<item.icon />
{item.icon ? <item.icon /> : <EmptyIcon />}
</ListItemIcon>
) : null}{' '}
<ListItemText
primary={item.label}
secondary={item.subLabel}
inset={hasIcon && !item.icon}
/>
) : null}
<Badge
color="error"
variant="dot"
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
invisible={!item.emphasized}
>
<ListItemText primary={item.label} secondary={item.subLabel} />
</Badge>
<div style={{ flexGrow: 1, minWidth: 10 }} />
<EndDecoration item={item} />
</CascadingMenuItem>
Expand Down
15 changes: 12 additions & 3 deletions packages/core/ui/DropDownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useRef, useState } from 'react'
import { Button, alpha } from '@mui/material'
import { Button, Badge, alpha } from '@mui/material'
import { makeStyles } from 'tss-react/mui'
import { observer } from 'mobx-react'
import ArrowDropDown from '@mui/icons-material/ArrowDropDown'

import Menu, { MenuItem } from './Menu'
import Menu, { MenuItem, hasEmphasized } from './Menu'

const useStyles = makeStyles()(theme => ({
root: {
Expand Down Expand Up @@ -50,6 +50,8 @@ function DropDownMenu({
setOpen(false)
}

const emphasized = hasEmphasized(menuItems)

return (
<div className={classes.root}>
<Button
Expand All @@ -59,7 +61,14 @@ function DropDownMenu({
data-testid="dropDownMenuButton"
classes={{ root: classes.buttonRoot }}
>
{menuTitle}
<Badge
color="error"
variant="dot"
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
invisible={!emphasized}
>
{menuTitle}
</Badge>
<ArrowDropDown />
</Button>
<Menu
Expand Down
52 changes: 45 additions & 7 deletions packages/core/ui/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'
import {
Badge,
Divider,
Grow,
ListItemIcon,
Expand All @@ -12,11 +13,12 @@ import {
Paper,
Popover,
PopoverProps,
SvgIcon,
SvgIconProps,
} from '@mui/material'
import { makeStyles } from 'tss-react/mui'
// icons
import ArrowRightIcon from '@mui/icons-material/ArrowRight'
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
import CheckBoxIcon from '@mui/icons-material/CheckBox'
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'
import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'
Expand Down Expand Up @@ -71,7 +73,7 @@ export function MenuItemEndDecoration(props: MenuItemEndDecorationProps) {
}
let icon
if (type === 'subMenu') {
icon = <ArrowRightIcon color="action" />
icon = <ChevronRightIcon color="action" />
} else if (type === 'checkbox') {
if (checked) {
const color = disabled ? 'inherit' : 'secondary'
Expand Down Expand Up @@ -107,6 +109,7 @@ export interface BaseMenuItem {
subLabel?: string
icon?: React.ComponentType<SvgIconProps>
disabled?: boolean
emphasized?: boolean
}

export interface NormalMenuItem extends BaseMenuItem {
Expand Down Expand Up @@ -157,6 +160,29 @@ interface MenuPageProps {

type MenuItemStyleProp = MenuItemProps['style']

export function EmptyIcon(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="" />
</SvgIcon>
)
}

export function hasEmphasized(menuItems: MenuItem[]): boolean {
for (const menuItem of menuItems) {
if ('emphasized' in menuItem) {
return true
}
if ('subMenu' in menuItem) {
const emphasized = hasEmphasized(menuItem.subMenu)
if (emphasized) {
return emphasized
}
}
}
return false
}

function findNextValidIdx(menuItems: MenuItem[], currentIdx: number) {
const idx = menuItems
.slice(currentIdx + 1)
Expand Down Expand Up @@ -275,6 +301,7 @@ const MenuPage = React.forwardRef<HTMLDivElement, MenuPageProps>(
}
let icon = null
let endDecoration = null
let { emphasized } = menuItem
if (menuItem.icon) {
const Icon = menuItem.icon
icon = (
Expand All @@ -285,6 +312,7 @@ const MenuPage = React.forwardRef<HTMLDivElement, MenuPageProps>(
}
if ('subMenu' in menuItem) {
endDecoration = <MenuItemEndDecoration type="subMenu" />
emphasized = emphasized || hasEmphasized(menuItem.subMenu)
} else if (
menuItem.type === 'checkbox' ||
menuItem.type === 'radio'
Expand Down Expand Up @@ -343,11 +371,21 @@ const MenuPage = React.forwardRef<HTMLDivElement, MenuPageProps>(
disabled={Boolean(menuItem.disabled)}
>
{icon}
<ListItemText
primary={menuItem.label}
secondary={menuItem.subLabel}
inset={hasIcon && !menuItem.icon}
/>
<Badge
color="error"
variant="dot"
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
invisible={!emphasized}
>
<ListItemText
primary={menuItem.label}
secondary={menuItem.subLabel}
inset={hasIcon && !menuItem.icon}
/>
</Badge>
{endDecoration}
</MUIMenuItem>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import {
getConf,
readConfObject,
} from '@jbrowse/core/configuration'
import { MenuItem, hasEmphasized } from '@jbrowse/core/ui'
import CascadingMenu from '@jbrowse/core/ui/CascadingMenu'
import { getSession, getContainingView } from '@jbrowse/core/util'
import { BaseTrackModel } from '@jbrowse/core/pluggableElementTypes/models'
import { IconButton, Paper, Typography, alpha } from '@mui/material'
import { IconButton, Paper, Typography, Badge, alpha } from '@mui/material'
import { makeStyles } from 'tss-react/mui'

import {
Expand Down Expand Up @@ -100,11 +101,13 @@ const TrackLabel = React.forwardRef<HTMLDivElement, Props>(
}
}

const items = [
const items: MenuItem[] = [
...(session.getTrackActionMenuItems?.(trackConf) || []),
...track.trackMenuItems(),
].sort((a, b) => (b.priority || 0) - (a.priority || 0))

const emphasized = hasEmphasized(items)

return (
<Paper ref={ref} className={cx(className, classes.root)}>
<span
Expand All @@ -131,15 +134,22 @@ const TrackLabel = React.forwardRef<HTMLDivElement, Props>(
>
{trackName}
</Typography>
<IconButton
{...bindTrigger(popupState)}
className={classes.iconButton}
color="secondary"
data-testid="track_menu_icon"
disabled={!items.length}
<Badge
color="error"
overlap="circular"
variant="dot"
invisible={!emphasized}
>
<MoreVertIcon fontSize="small" />
</IconButton>
<IconButton
{...bindTrigger(popupState)}
className={classes.iconButton}
color="secondary"
data-testid="track_menu_icon"
disabled={!items.length}
>
<MoreVertIcon fontSize="small" />
</IconButton>
</Badge>
<CascadingMenu
{...bindPopover(popupState)}
onMenuItemClick={(_: unknown, callback: Function) => callback()}
Expand Down
Loading