Change icon on active menu item (#903)

* add icons

* add logic to change the icon

* make the active menu bold

* Encapsulate the dynamic icon behaviour into a self-contained component

Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Ruchi Kushwaha 2021-04-17 10:10:07 +05:30 committed by GitHub
parent 16a5ac323b
commit b441260186
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 21 deletions

View File

@ -1,40 +1,80 @@
import React from 'react'
import ShuffleIcon from '@material-ui/icons/Shuffle'
import LibraryAddIcon from '@material-ui/icons/LibraryAdd'
import VideoLibraryIcon from '@material-ui/icons/VideoLibrary'
import RepeatIcon from '@material-ui/icons/Repeat'
import AlbumIcon from '@material-ui/icons/Album'
import FavoriteIcon from '@material-ui/icons/Favorite'
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'
import StarIcon from '@material-ui/icons/Star'
import StarBorderIcon from '@material-ui/icons/StarBorder'
import AlbumOutlinedIcon from '@material-ui/icons/AlbumOutlined'
import LibraryAddOutlinedIcon from '@material-ui/icons/LibraryAddOutlined'
import VideoLibraryOutlinedIcon from '@material-ui/icons/VideoLibraryOutlined'
import config from '../config'
import DynamicMenuIcon from '../layout/DynamicMenuIcon'
export default {
all: {
icon: AlbumIcon,
icon: (
<DynamicMenuIcon
path={'album/all'}
icon={AlbumOutlinedIcon}
activeIcon={AlbumIcon}
/>
),
params: 'sort=name&order=ASC',
},
random: { icon: ShuffleIcon, params: 'sort=random&order=ASC' },
random: {
icon: <ShuffleIcon />,
params: 'sort=random&order=ASC',
},
...(config.enableFavourites && {
starred: {
icon: FavoriteIcon,
icon: (
<DynamicMenuIcon
path={'album/starred'}
icon={FavoriteBorderIcon}
activeIcon={FavoriteIcon}
/>
),
params: 'sort=starred_at&order=DESC&filter={"starred":true}',
},
}),
...(config.enableStarRating && {
topRated: {
icon: StarIcon,
icon: (
<DynamicMenuIcon
path={'album/topRated'}
icon={StarBorderIcon}
activeIcon={StarIcon}
/>
),
params: 'sort=rating&order=DESC&filter={"has_rating":true}',
},
}),
recentlyAdded: {
icon: LibraryAddIcon,
icon: (
<DynamicMenuIcon
path={'album/recentlyAdded'}
icon={LibraryAddOutlinedIcon}
activeIcon={LibraryAddIcon}
/>
),
params: 'sort=recently_added&order=DESC',
},
recentlyPlayed: {
icon: VideoLibraryIcon,
icon: (
<DynamicMenuIcon
path={'album/recentlyPlayed'}
icon={VideoLibraryOutlinedIcon}
activeIcon={VideoLibraryIcon}
/>
),
params: 'sort=play_date&order=DESC&filter={"recently_played":true}',
},
mostPlayed: {
icon: RepeatIcon,
icon: <RepeatIcon />,
params: 'sort=play_count&order=DESC&filter={"recently_played":true}',
},
}

View File

@ -1,9 +1,7 @@
import AlbumIcon from '@material-ui/icons/Album'
import AlbumList from './AlbumList'
import AlbumShow from './AlbumShow'
export default {
list: AlbumList,
show: AlbumShow,
icon: AlbumIcon,
}

View File

@ -1,7 +1,16 @@
import MicIcon from '@material-ui/icons/Mic'
import React from 'react'
import ArtistList from './ArtistList'
import DynamicMenuIcon from '../layout/DynamicMenuIcon'
import MicNoneOutlinedIcon from '@material-ui/icons/MicNoneOutlined'
import MicIcon from '@material-ui/icons/Mic'
export default {
list: ArtistList,
icon: MicIcon,
icon: (
<DynamicMenuIcon
path={'artist'}
icon={MicNoneOutlinedIcon}
activeIcon={MicIcon}
/>
),
}

View File

@ -0,0 +1,23 @@
import PropTypes from 'prop-types'
import { useLocation } from 'react-router-dom'
import { createElement } from 'react'
const DynamicMenuIcon = ({ icon, activeIcon, path }) => {
const location = useLocation()
if (!activeIcon) {
return createElement(icon, { 'data-testid': 'icon' })
}
return location.pathname.startsWith('/' + path)
? createElement(activeIcon, { 'data-testid': 'activeIcon' })
: createElement(icon, { 'data-testid': 'icon' })
}
DynamicMenuIcon.propTypes = {
path: PropTypes.string.isRequired,
icon: PropTypes.object.isRequired,
activeIcon: PropTypes.object,
}
export default DynamicMenuIcon

View File

@ -0,0 +1,56 @@
import * as React from 'react'
import { cleanup, render } from '@testing-library/react'
import { createMemoryHistory } from 'history'
import { Router } from 'react-router-dom'
import StarIcon from '@material-ui/icons/Star'
import StarBorderIcon from '@material-ui/icons/StarBorder'
import DynamicMenuIcon from './DynamicMenuIcon'
describe('<DynamicMenuIcon />', () => {
it('renders icon if no activeIcon is specified', () => {
const history = createMemoryHistory()
const route = '/test'
history.push(route)
const { getByTestId } = render(
<Router history={history}>
<DynamicMenuIcon icon={StarIcon} path={'test'} />
</Router>
)
expect(getByTestId('icon')).not.toBeNull()
})
it('renders icon if path does not match the URL', () => {
const history = createMemoryHistory()
const route = '/path'
history.push(route)
const { getByTestId } = render(
<Router history={history}>
<DynamicMenuIcon
icon={StarIcon}
activeIcon={StarBorderIcon}
path={'otherpath'}
/>
</Router>
)
expect(getByTestId('icon')).not.toBeNull()
})
it('renders activeIcon if path matches the URL', () => {
const history = createMemoryHistory()
const route = '/path'
history.push(route)
const { getByTestId } = render(
<Router history={history}>
<DynamicMenuIcon
icon={StarIcon}
activeIcon={StarBorderIcon}
path={'path'}
/>
</Router>
)
expect(getByTestId('activeIcon')).not.toBeNull()
})
})

View File

@ -1,6 +1,6 @@
import React, { useState, createElement } from 'react'
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { useMediaQuery } from '@material-ui/core'
import { makeStyles, useMediaQuery } from '@material-ui/core'
import { useTranslate, MenuItemLink, getResources } from 'react-admin'
import { withRouter } from 'react-router-dom'
import LibraryMusicIcon from '@material-ui/icons/LibraryMusic'
@ -11,6 +11,13 @@ import inflection from 'inflection'
import albumLists from '../album/albumLists'
import { HelpDialog } from '../dialogs'
const useStyles = makeStyles((theme) => ({
active: {
color: theme.palette.text.primary,
fontWeight: 'bold',
},
}))
const translatedResourceName = (resource, translate) =>
translate(`resources.${resource.name}.name`, {
smart_count: 2,
@ -27,6 +34,7 @@ const Menu = ({ onMenuClick, dense, logout }) => {
const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const open = useSelector((state) => state.admin.ui.sidebarOpen)
const translate = useTranslate()
const classes = useStyles()
const resources = useSelector(getResources)
// TODO State is not persisted in mobile when you close the sidebar menu. Move to redux?
@ -44,10 +52,9 @@ const Menu = ({ onMenuClick, dense, logout }) => {
<MenuItemLink
key={resource.name}
to={`/${resource.name}`}
activeClassName={classes.active}
primaryText={translatedResourceName(resource, translate)}
leftIcon={
(resource.icon && createElement(resource.icon)) || <ViewListIcon />
}
leftIcon={resource.icon || <ViewListIcon />}
onClick={onMenuClick}
sidebarIsOpen={open}
dense={dense}
@ -70,8 +77,9 @@ const Menu = ({ onMenuClick, dense, logout }) => {
<MenuItemLink
key={albumListAddress}
to={albumListAddress}
activeClassName={classes.active}
primaryText={name}
leftIcon={(al.icon && createElement(al.icon)) || <ViewListIcon />}
leftIcon={al.icon || <ViewListIcon />}
onClick={onMenuClick}
sidebarIsOpen={open}
dense={dense}

View File

@ -1,13 +1,22 @@
import PlaylistIcon from '../icons/Playlist'
import React from 'react'
import QueueMusicOutlinedIcon from '@material-ui/icons/QueueMusicOutlined'
import QueueMusicIcon from '@material-ui/icons/QueueMusic'
import DynamicMenuIcon from '../layout/DynamicMenuIcon'
import PlaylistList from './PlaylistList'
import PlaylistEdit from './PlaylistEdit'
import PlaylistCreate from './PlaylistCreate'
import PlaylistShow from './PlaylistShow'
export default {
icon: PlaylistIcon,
list: PlaylistList,
create: PlaylistCreate,
edit: PlaylistEdit,
show: PlaylistShow,
icon: (
<DynamicMenuIcon
path={'playlist'}
icon={QueueMusicOutlinedIcon}
activeIcon={QueueMusicIcon}
/>
),
}

View File

@ -1,7 +1,16 @@
import MusicNoteIcon from '@material-ui/icons/MusicNote'
import React from 'react'
import SongList from './SongList'
import MusicNoteOutlinedIcon from '@material-ui/icons/MusicNoteOutlined'
import MusicNoteIcon from '@material-ui/icons/MusicNote'
import DynamicMenuIcon from '../layout/DynamicMenuIcon'
export default {
list: SongList,
icon: MusicNoteIcon,
icon: (
<DynamicMenuIcon
path={'song'}
icon={MusicNoteOutlinedIcon}
activeIcon={MusicNoteIcon}
/>
),
}