Skip to content

Commit

Permalink
chore(home): enhance nested menus UX
Browse files Browse the repository at this point in the history
  • Loading branch information
AXeL-dev committed Sep 20, 2022
1 parent 950e327 commit 39f663b
Show file tree
Hide file tree
Showing 17 changed files with 568 additions and 514 deletions.
9 changes: 9 additions & 0 deletions src/helpers/react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

export const childrenWithProps = (children: React.ReactNode, props: object) =>
React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, props);
}
return child;
});
6 changes: 6 additions & 0 deletions src/store/selectors/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export const selectViewFilters = (view: HomeView) =>
}
});

export const selectActiveViewFiltersCount = (view: HomeView) =>
createSelector(
selectViewFilters(view),
(filters) => Object.values(filters).filter(Boolean).length,
);

export const selectViewSorting = (view: HomeView) =>
createSelector(selectSettings, (settings) => {
switch (view) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import React, { useRef, useState } from 'react';
import {
IconButton,
MenuItem,
ListItemIcon,
ListItemText,
} from '@mui/material';
import React, { useState } from 'react';
import { IconButton } from '@mui/material';
import { StyledMenu } from 'ui/components/shared';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import SortIcon from '@mui/icons-material/Sort';
import { HomeView, Nullable } from 'types';
import { NestedMenuRef } from 'ui/components/shared/StyledMenu/NestedMenu';
import ViewSorting from '../common/ViewSorting';
import ViewSorting from '../CommonMenus/ViewSorting';

interface AllViewOptionsProps {}

function AllViewOptions(props: AllViewOptionsProps) {
const [anchorEl, setAnchorEl] = useState<Nullable<HTMLElement>>(null);
const open = Boolean(anchorEl);
const menusRef = useRef<{ [key: string]: NestedMenuRef | null }>({
sorting: null,
});

const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
Expand Down Expand Up @@ -50,17 +40,8 @@ function AllViewOptions(props: AllViewOptionsProps) {
open={open}
onClose={handleClose}
>
<MenuItem onClick={menusRef.current['sorting']?.open}>
<ListItemIcon>
<SortIcon />
</ListItemIcon>
<ListItemText>Sort by</ListItemText>
</MenuItem>
<ViewSorting view={HomeView.All} parentMenuOpen={open} />
</StyledMenu>
<ViewSorting
ref={(ref) => (menusRef.current['sorting'] = ref)}
view={HomeView.All}
/>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useMemo } from 'react';
import { ListItemIcon, ListItemText } from '@mui/material';
import {
CheckableMenuItem,
NestedMenuItem,
NestedMenuItemProps,
} from 'ui/components/shared';
import { useAppDispatch, useAppSelector } from 'store';
import { selectViewFilters } from 'store/selectors/settings';
import { setViewFilters } from 'store/reducers/settings';
import { HomeView, ViewFilters as Filters } from 'types';
import FilterAltIcon from '@mui/icons-material/FilterAlt';

export interface ViewFilterOption {
label: string;
value: keyof Filters;
}

interface ViewFiltersProps extends Omit<NestedMenuItemProps, 'label'> {
view: HomeView;
options: ViewFilterOption[];
}

function ViewFilters(props: ViewFiltersProps) {
const { view, options, ...rest } = props;
const filters = useAppSelector(selectViewFilters(view));
const dispatch = useAppDispatch();
const activeFiltersCount = useMemo(
() => Object.values(filters).filter(Boolean).length,
[filters],
);

const handleFilterToggle = (key: keyof Filters) => {
dispatch(
setViewFilters({
view,
filters: {
[key]: !filters[key],
},
}),
);
};

return (
<NestedMenuItem
label={
<>
<ListItemIcon>
<FilterAltIcon />
</ListItemIcon>
<ListItemText>Filters ({activeFiltersCount})</ListItemText>
</>
}
{...rest}
>
{options.map(({ label, value }, index) => (
<CheckableMenuItem
key={index}
checked={!!filters[value]}
onClick={() => handleFilterToggle(value)}
>
{label}
</CheckableMenuItem>
))}
</NestedMenuItem>
);
}

export default ViewFilters;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { ListItemIcon, ListItemText } from '@mui/material';
import {
CheckableMenuItem,
NestedMenuItem,
NestedMenuItemProps,
} from 'ui/components/shared';
import { useAppDispatch, useAppSelector } from 'store';
import { selectViewSorting } from 'store/selectors/settings';
import { setViewSorting } from 'store/reducers/settings';
import { HomeView, ViewSorting as Sorting } from 'types';
import SortIcon from '@mui/icons-material/Sort';

interface ViewSortingOption {
label: string;
value: keyof Sorting;
}

const options: ViewSortingOption[] = [
{
label: 'Publish date',
value: 'publishDate',
},
];

interface ViewSortingProps extends Omit<NestedMenuItemProps, 'label'> {
view: HomeView;
}

function ViewSorting(props: ViewSortingProps) {
const { view, ...rest } = props;
const sorting = useAppSelector(selectViewSorting(view));
const dispatch = useAppDispatch();

const handleSortToggle = (key: keyof Sorting) => {
dispatch(
setViewSorting({
view,
sorting: {
[key]: !sorting[key],
},
}),
);
};

return (
<NestedMenuItem
label={
<>
<ListItemIcon>
<SortIcon />
</ListItemIcon>
<ListItemText>Sort by</ListItemText>
</>
}
isFirstItem
{...rest}
>
{options.map(({ label, value }, index) => (
<CheckableMenuItem
key={index}
checked={!!sorting[value]}
onClick={() => handleSortToggle(value)}
>
{label}
</CheckableMenuItem>
))}
</NestedMenuItem>
);
}

export default ViewSorting;
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from 'react';
import { CheckableMenuItem } from 'ui/components/shared';
import { ListItemIcon, ListItemText } from '@mui/material';
import {
CheckableMenuItem,
NestedMenuItem,
NestedMenuItemProps,
} from 'ui/components/shared';
import { useAppDispatch, useAppSelector } from 'store';
import { selectRecentVideosSeniority } from 'store/selectors/settings';
import { setSettings } from 'store/reducers/settings';
import { VideosSeniority } from 'types';
import NestedMenu, {
NestedMenuRef,
} from 'ui/components/shared/StyledMenu/NestedMenu';
import HistoryIcon from '@mui/icons-material/History';

const options = [
{
Expand All @@ -31,12 +34,10 @@ const options = [
},
];

interface RecentVideosSeniorityProps {}
interface RecentVideosSeniorityProps
extends Omit<NestedMenuItemProps, 'label'> {}

const RecentVideosSeniority = React.forwardRef<
NestedMenuRef,
RecentVideosSeniorityProps
>((props, ref) => {
function RecentVideosSeniority(props: RecentVideosSeniorityProps) {
const seniority = useAppSelector(selectRecentVideosSeniority);
const dispatch = useAppDispatch();

Expand All @@ -45,12 +46,21 @@ const RecentVideosSeniority = React.forwardRef<
};

return (
<NestedMenu
id="seniority-menu"
ref={ref}
style={{
minWidth: 160,
<NestedMenuItem
label={
<>
<ListItemIcon>
<HistoryIcon />
</ListItemIcon>
<ListItemText>Videos seniority</ListItemText>
</>
}
MenuProps={{
style: {
minWidth: 160,
},
}}
{...props}
>
{options.map(({ label, value }, index) => (
<CheckableMenuItem
Expand All @@ -61,8 +71,8 @@ const RecentVideosSeniority = React.forwardRef<
{label}
</CheckableMenuItem>
))}
</NestedMenu>
</NestedMenuItem>
);
});
}

export default RecentVideosSeniority;
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import React, { useState, useRef } from 'react';
import { ListItemIcon, ListItemText, MenuItem } from '@mui/material';
import { useForwardedRef } from 'hooks';
import React, { useState } from 'react';
import { useAppDispatch, useAppSelector } from 'store';
import { setVideosFlag } from 'store/reducers/videos';
import { selectRecentOnlyVideos } from 'store/selectors/videos';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import DoDisturbOnIcon from '@mui/icons-material/DoDisturbOn';
import VisibilityIcon from '@mui/icons-material/Visibility';
import {
ConfirmationDialog,
ConfirmationDialogProps,
NestedMenuItem,
NestedMenuItemProps,
NestedMenuItemRef,
} from 'ui/components/shared';
import NestedMenu, {
NestedMenuRef,
} from 'ui/components/shared/StyledMenu/NestedMenu';

interface RecentViewMoreActionsProps {}
interface RecentViewMoreActionsProps
extends Omit<NestedMenuItemProps, 'label'> {}

const RecentViewMoreActions = React.forwardRef<
NestedMenuRef,
RecentViewMoreActionsProps
>((props, ref) => {
const menuRef = useForwardedRef<NestedMenuRef>(ref);
function RecentViewMoreActions(props: RecentViewMoreActionsProps) {
const ref = useRef<NestedMenuItemRef>(null);
const videos = useAppSelector(selectRecentOnlyVideos());
const dispatch = useAppDispatch();
const [confirmationDialogProps, setConfirmationDialogProps] =
Expand All @@ -31,8 +29,8 @@ const RecentViewMoreActions = React.forwardRef<
onClose: () => {},
});

const closeMenu = () => {
menuRef.current?.close();
const handleMenuClose = () => {
ref.current?.closeMenu();
};

const handleMarkVideosAsSeen = () => {
Expand All @@ -55,7 +53,7 @@ const RecentViewMoreActions = React.forwardRef<
}));
},
});
closeMenu();
handleMenuClose();
};

const handleMarkVideosAsIgnored = () => {
Expand All @@ -78,17 +76,22 @@ const RecentViewMoreActions = React.forwardRef<
}));
},
});
closeMenu();
handleMenuClose();
};

return (
<>
<NestedMenu
id="more-actions-menu"
ref={menuRef}
style={{
minWidth: 160,
}}
<NestedMenuItem
ref={ref}
label={
<>
<ListItemIcon>
<MoreHorizIcon />
</ListItemIcon>
<ListItemText>More</ListItemText>
</>
}
{...props}
>
<MenuItem
onClick={handleMarkVideosAsSeen}
Expand All @@ -108,10 +111,10 @@ const RecentViewMoreActions = React.forwardRef<
</ListItemIcon>
<ListItemText>Mark unflagged videos as ignored</ListItemText>
</MenuItem>
</NestedMenu>
</NestedMenuItem>
<ConfirmationDialog {...confirmationDialogProps} />
</>
);
});
}

export default RecentViewMoreActions;
Loading

0 comments on commit 39f663b

Please sign in to comment.