Refactor context menu components to use hooks

This commit is contained in:
Jordan Eldredge 2019-12-16 09:19:19 -08:00
parent e4713beb00
commit e8c225e8f5
7 changed files with 57 additions and 42 deletions

View file

@ -1,19 +1,11 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import ContextMenu from "./ContextMenu";
type DivProps = React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
>;
interface Props extends React.HTMLAttributes<HTMLDivElement> {
handle: React.ReactNode;
renderMenu: () => React.ReactNode;
top?: boolean;
bottom?: boolean;
}
interface State {
selected: boolean;
}
function getNodeOffset(node: HTMLDivElement | null) {
if (node == null) {
@ -26,10 +18,18 @@ function getNodeOffset(node: HTMLDivElement | null) {
return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
}
// Trigger a context menu relative to the child element when the user
// left-clicks on the child.
//
// For a component that triggers relative to the user's cursor on right-click
// see `<ContextMenuWrapper />`.
function ContextMenuTarget(props: Props) {
const handleNode = useRef<HTMLDivElement>(null);
const [selected, setSelected] = useState(false);
useEffect(() => {
if (!selected) {
return;
}
function handleGlobalClick(e: MouseEvent) {
if (
selected &&
@ -58,7 +58,7 @@ function ContextMenuTarget(props: Props) {
{ top: 0, left: 0 };
}, [selected]);
const { handle, children, top, bottom, ...passThroughProps } = props;
const { renderMenu, children, top, bottom, ...passThroughProps } = props;
return (
<div {...passThroughProps}>
<div
@ -67,7 +67,7 @@ function ContextMenuTarget(props: Props) {
ref={handleNode}
onClick={() => setSelected(!selected)}
>
{handle}
{children}
</div>
<ContextMenu
selected={selected}
@ -76,7 +76,7 @@ function ContextMenuTarget(props: Props) {
top={top}
bottom={bottom}
>
{children}
{renderMenu()}
</ContextMenu>
</div>
);

View file

@ -6,11 +6,10 @@ interface Props {
children: ReactNode;
}
interface State {
selected: boolean;
offsetTop: number | null;
offsetLeft: number | null;
}
// Trigger a context menu at the user's cursor position when the user right
// clicks within this component.
// For a component that triggers relative to a given component when the user
// left-clicks see `<ContextMenuTarget />`.
// TODO: Consider using nested contexts to ensure we don't ever have multiple
// non-nested context menus open at a time.

View file

@ -17,19 +17,27 @@ interface DispatchProps {
}
const PresetsContextMenu = (props: DispatchProps) => (
<ContextMenuTarget top id="presets-context" handle={<div id="presets" />}>
<Parent label="Load">
{builtin.presets.map(preset => (
<Node
key={preset.name}
onClick={() => props.setEqFromObject(preset)}
label={preset.name}
/>
))}
<Hr />
<Node onClick={props.openEqfFileDialog} label="From Eqf..." />
</Parent>
<Node onClick={props.downloadPreset} label="Save" />
<ContextMenuTarget
top
id="presets-context"
renderMenu={() => (
<>
<Parent label="Load">
{builtin.presets.map(preset => (
<Node
key={preset.name}
onClick={() => props.setEqFromObject(preset)}
label={preset.name}
/>
))}
<Hr />
<Node onClick={props.openEqfFileDialog} label="From Eqf..." />
</Parent>
<Node onClick={props.downloadPreset} label="Save" />
</>
)}
>
<div id="presets" />
</ContextMenuTarget>
);

View file

@ -25,8 +25,8 @@ const ClutterBar = React.memo(() => {
const doubled = useTypedSelector(Selectors.getDoubled);
return (
<div id="clutter-bar">
<ContextMenuTarget bottom handle={<div id="button-o" />}>
<OptionsContextMenu />
<ContextMenuTarget bottom renderMenu={() => <OptionsContextMenu />}>
<div id="button-o" />
</ContextMenuTarget>
<div id="button-a" />
<div id="button-i" />

View file

@ -88,9 +88,9 @@ const MainWindow = React.memo(({ analyser, filePickers }: Props) => {
<ContextMenuTarget
id="option-context"
bottom
handle={<ClickedDiv id="option" title="Winamp Menu" />}
renderMenu={() => <MainContextMenu filePickers={filePickers} />}
>
<MainContextMenu filePickers={filePickers} />
<ClickedDiv id="option" title="Winamp Menu" />
</ContextMenuTarget>
{mainShade && <MiniTime />}
<Minimize />

View file

@ -11,11 +11,15 @@ interface DispatchProps {
const MiscOptionsContextMenu = (props: DispatchProps) => (
<ContextMenuTarget
style={{ width: "100%", height: "100%" }}
top
handle={<div />}
renderMenu={() => (
<Node
onClick={props.downloadHtmlPlaylist}
label="Generate HTML playlist"
/>
)}
>
<Node onClick={props.downloadHtmlPlaylist} label="Generate HTML playlist" />
<div />
</ContextMenuTarget>
);

View file

@ -22,12 +22,16 @@ const SortContextMenu = (props: DispatchProps) => (
<ContextMenuTarget
style={{ width: "100%", height: "100%" }}
top
handle={<div />}
renderMenu={() => (
<>
<Node label="Sort list by title" onClick={props.sortListByTitle} />
<Hr />
<Node label="Reverse list" onClick={props.reverseList} />
<Node label="Randomize list" onClick={props.randomizeList} />
</>
)}
>
<Node label="Sort list by title" onClick={props.sortListByTitle} />
<Hr />
<Node label="Reverse list" onClick={props.reverseList} />
<Node label="Randomize list" onClick={props.randomizeList} />
<div />
</ContextMenuTarget>
);