chore: Refactors towards shared menu component (#4445)

This commit is contained in:
Tom Moor
2022-11-19 13:15:38 -08:00
committed by GitHub
parent 924b554281
commit ae6855f3df
21 changed files with 186 additions and 194 deletions

View File

@@ -1,8 +1,8 @@
import { findParentNode } from "prosemirror-utils";
import React from "react";
import getMenuItems from "../menus/block";
import BlockMenuItem from "./BlockMenuItem";
import CommandMenu, { Props } from "./CommandMenu";
import CommandMenuItem from "./CommandMenuItem";
type BlockMenuProps = Omit<
Props,
@@ -26,7 +26,7 @@ function BlockMenu(props: BlockMenuProps) {
filterable={true}
onClearSearch={clearSearch}
renderMenuItem={(item, _index, options) => (
<BlockMenuItem
<CommandMenuItem
onClick={options.onClick}
selected={options.selected}
icon={item.icon}

View File

@@ -62,7 +62,7 @@ type State = {
selectedIndex: number;
};
class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
class CommandMenu<T extends MenuItem> extends React.Component<Props<T>, State> {
menuRef = React.createRef<HTMLDivElement>();
inputRef = React.createRef<HTMLInputElement>();
@@ -79,7 +79,7 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
window.addEventListener("keydown", this.handleKeyDown);
}
shouldComponentUpdate(nextProps: Props, nextState: State) {
shouldComponentUpdate(nextProps: Props<T>, nextState: State) {
return (
nextProps.search !== this.props.search ||
nextProps.isActive !== this.props.isActive ||
@@ -87,7 +87,7 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
);
}
componentDidUpdate(prevProps: Props) {
componentDidUpdate(prevProps: Props<T>) {
if (!prevProps.isActive && this.props.isActive) {
// reset scroll position to top when opening menu as the contents are
// hidden, not unrendered
@@ -575,7 +575,7 @@ const LinkInputWrapper = styled.div`
`;
const LinkInput = styled(Input)`
height: 36px;
height: 32px;
width: 100%;
color: ${(props) => props.theme.textSecondary};
`;
@@ -584,7 +584,7 @@ const List = styled.ol`
list-style: none;
text-align: left;
height: 100%;
padding: 8px 0;
padding: 6px;
margin: 0;
`;
@@ -599,7 +599,7 @@ const Empty = styled.div`
color: ${(props) => props.theme.textSecondary};
font-weight: 500;
font-size: 14px;
height: 36px;
height: 32px;
padding: 0 16px;
`;
@@ -630,7 +630,7 @@ export const Wrapper = styled(Scrollable)<{
box-sizing: border-box;
pointer-events: none;
white-space: nowrap;
width: 300px;
width: 280px;
height: auto;
max-height: 324px;

View File

@@ -1,18 +1,19 @@
import * as React from "react";
import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled from "styled-components";
import MenuItem from "~/components/ContextMenu/MenuItem";
export type Props = {
selected: boolean;
disabled?: boolean;
onClick: () => void;
icon?: typeof React.Component | React.FC<any>;
icon?: React.ReactElement;
title: React.ReactNode;
shortcut?: string;
containerId?: string;
};
function BlockMenuItem({
function CommandMenuItem({
selected,
disabled,
onClick,
@@ -21,8 +22,6 @@ function BlockMenuItem({
icon,
containerId = "block-menu-container",
}: Props) {
const Icon = icon;
const ref = React.useCallback(
(node) => {
if (selected && node) {
@@ -43,56 +42,22 @@ function BlockMenuItem({
return (
<MenuItem
selected={selected}
onClick={disabled ? undefined : onClick}
ref={ref}
active={selected}
onClick={disabled ? undefined : onClick}
icon={icon}
>
{Icon && (
<>
<Icon color="currentColor" />
&nbsp;&nbsp;
</>
)}
{title}
{shortcut && <Shortcut>{shortcut}</Shortcut>}
{shortcut && <Shortcut $active={selected}>{shortcut}</Shortcut>}
</MenuItem>
);
}
const Shortcut = styled.span`
color: ${(props) => props.theme.textTertiary};
const Shortcut = styled.span<{ $active?: boolean }>`
color: ${(props) =>
props.$active ? props.theme.white50 : props.theme.textTertiary};
flex-grow: 1;
text-align: right;
`;
const MenuItem = styled.button<{
selected: boolean;
}>`
display: flex;
align-items: center;
justify-content: flex-start;
font-weight: 500;
font-size: 14px;
line-height: 1;
width: 100%;
height: 36px;
cursor: var(--pointer);
border: none;
opacity: ${(props) => (props.disabled ? ".5" : "1")};
color: ${(props) =>
props.selected ? props.theme.white : props.theme.textSecondary};
background: ${(props) => (props.selected ? props.theme.primary : "none")};
padding: 0 16px;
outline: none;
&:active {
color: ${(props) => props.theme.white};
background: ${(props) => (props.selected ? props.theme.primary : "none")};
${Shortcut} {
color: ${(props) => props.theme.textSecondary};
}
}
`;
export default BlockMenuItem;
export default CommandMenuItem;

View File

@@ -1,35 +1,23 @@
import * as React from "react";
import styled from "styled-components";
import BlockMenuItem, { Props as BlockMenuItemProps } from "./BlockMenuItem";
import CommandMenuItem, {
Props as CommandMenuItemProps,
} from "./CommandMenuItem";
const Emoji = styled.span`
font-size: 16px;
line-height: 1.6em;
`;
type Props = {
emoji: React.ReactNode;
title: React.ReactNode;
};
const EmojiTitle = ({ emoji, title }: Props) => {
return (
<p>
<Emoji className="emoji">{emoji}</Emoji>
&nbsp;&nbsp;
{title}
</p>
);
};
type EmojiMenuItemProps = Omit<BlockMenuItemProps, "shortcut" | "theme"> & {
type EmojiMenuItemProps = Omit<CommandMenuItemProps, "shortcut" | "theme"> & {
emoji: string;
};
export default function EmojiMenuItem(props: EmojiMenuItemProps) {
export default function EmojiMenuItem({ emoji, ...rest }: EmojiMenuItemProps) {
return (
<BlockMenuItem
{...props}
title={<EmojiTitle emoji={props.emoji} title={props.title} />}
<CommandMenuItem
{...rest}
icon={<Emoji className="emoji">{emoji}</Emoji>}
/>
);
}

View File

@@ -30,7 +30,6 @@ function ToolbarMenu(props: Props) {
if (item.visible === false || !item.icon) {
return null;
}
const Icon = item.icon;
const isActive = item.active ? item.active(state) : false;
return (
@@ -39,7 +38,7 @@ function ToolbarMenu(props: Props) {
onClick={() => item.name && commands[item.name](item.attrs)}
active={isActive}
>
<Icon color="currentColor" />
{React.cloneElement(item.icon, { color: "currentColor" })}
</ToolbarButton>
</Tooltip>
);