chore: Refactors towards shared menu component (#4445)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
</>
|
||||
)}
|
||||
{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;
|
||||
@@ -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>
|
||||
|
||||
{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>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user