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

@@ -70,7 +70,7 @@ export function actionToKBar(
return [];
}
const resolvedIcon = resolve<React.ReactElement<any>>(action.icon, context);
const resolvedIcon = resolve<React.ReactElement>(action.icon, context);
const resolvedChildren = resolve<Action[]>(action.children, context);
const resolvedSection = resolve<string>(action.section, context);
const resolvedName = resolve<string>(action.name, context);

View File

@@ -1,6 +1,7 @@
import { LocationDescriptor } from "history";
import { CheckmarkIcon } from "outline-icons";
import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import { MenuItem as BaseMenuItem } from "reakit/Menu";
import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
@@ -8,6 +9,7 @@ import MenuIconWrapper from "../MenuIconWrapper";
type Props = {
onClick?: (event: React.SyntheticEvent) => void | Promise<void>;
active?: boolean;
selected?: boolean;
disabled?: boolean;
dangerous?: boolean;
@@ -18,18 +20,23 @@ type Props = {
hide?: () => void;
level?: number;
icon?: React.ReactElement;
children?: React.ReactNode;
};
const MenuItem: React.FC<Props> = ({
onClick,
children,
selected,
disabled,
as,
hide,
icon,
...rest
}) => {
const MenuItem = (
{
onClick,
children,
active,
selected,
disabled,
as,
hide,
icon,
...rest
}: Props,
ref: React.Ref<HTMLAnchorElement>
) => {
const handleClick = React.useCallback(
(ev) => {
if (onClick) {
@@ -63,10 +70,14 @@ const MenuItem: React.FC<Props> = ({
{(props) => (
<MenuAnchor
{...props}
$toggleable={selected !== undefined}
$active={active}
as={onClick ? "button" : as}
onClick={handleClick}
onMouseDown={handleMouseDown}
ref={mergeRefs([
ref,
props.ref as React.RefObject<HTMLAnchorElement>,
])}
>
{selected !== undefined && (
<>
@@ -97,6 +108,7 @@ type MenuAnchorProps = {
disabled?: boolean;
dangerous?: boolean;
disclosure?: boolean;
$active?: boolean;
};
export const MenuAnchorCSS = css<MenuAnchorProps>`
@@ -104,6 +116,7 @@ export const MenuAnchorCSS = css<MenuAnchorProps>`
margin: 0;
border: 0;
padding: 12px;
border-radius: 4px;
padding-left: ${(props) => 12 + (props.level || 0) * 10}px;
width: 100%;
min-height: 32px;
@@ -146,6 +159,20 @@ export const MenuAnchorCSS = css<MenuAnchorProps>`
}
}
}
${
props.$active &&
`
color: ${props.theme.white};
background: ${props.dangerous ? props.theme.danger : props.theme.primary};
box-shadow: none;
cursor: var(--pointer);
svg {
fill: ${props.theme.white};
}
`
}
`};
${breakpoint("tablet")`
@@ -160,4 +187,4 @@ export const MenuAnchor = styled.a`
${MenuAnchorCSS}
`;
export default MenuItem;
export default React.forwardRef<HTMLAnchorElement, Props>(MenuItem);

View File

@@ -11,5 +11,5 @@ export default function Separator(rest: React.HTMLAttributes<HTMLHRElement>) {
}
const HorizontalRule = styled.hr`
margin: 0.5em 12px;
margin: 6px 0;
`;

View File

@@ -205,7 +205,7 @@ export const Background = styled(Scrollable)<BackgroundProps>`
max-width: 100%;
background: ${(props) => props.theme.menuBackground};
border-radius: 6px;
padding: 6px 0;
padding: 6px;
min-width: 180px;
min-height: 44px;
max-height: 75vh;

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>
);

View File

@@ -41,7 +41,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
name: "heading",
title: dictionary.h1,
keywords: "h1 heading1 title",
icon: Heading1Icon,
icon: <Heading1Icon />,
shortcut: "^ ⇧ 1",
attrs: { level: 1 },
},
@@ -49,7 +49,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
name: "heading",
title: dictionary.h2,
keywords: "h2 heading2",
icon: Heading2Icon,
icon: <Heading2Icon />,
shortcut: "^ ⇧ 2",
attrs: { level: 2 },
},
@@ -57,7 +57,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
name: "heading",
title: dictionary.h3,
keywords: "h3 heading3",
icon: Heading3Icon,
icon: <Heading3Icon />,
shortcut: "^ ⇧ 3",
attrs: { level: 3 },
},
@@ -67,20 +67,20 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
{
name: "checkbox_list",
title: dictionary.checkboxList,
icon: TodoListIcon,
icon: <TodoListIcon />,
keywords: "checklist checkbox task",
shortcut: "^ ⇧ 7",
},
{
name: "bullet_list",
title: dictionary.bulletList,
icon: BulletedListIcon,
icon: <BulletedListIcon />,
shortcut: "^ ⇧ 8",
},
{
name: "ordered_list",
title: dictionary.orderedList,
icon: OrderedListIcon,
icon: <OrderedListIcon />,
shortcut: "^ ⇧ 9",
},
{
@@ -89,52 +89,52 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
{
name: "image",
title: dictionary.image,
icon: ImageIcon,
icon: <ImageIcon />,
keywords: "picture photo",
},
{
name: "link",
title: dictionary.link,
icon: LinkIcon,
icon: <LinkIcon />,
shortcut: `${metaDisplay} k`,
keywords: "link url uri href",
},
{
name: "attachment",
title: dictionary.file,
icon: AttachmentIcon,
icon: <AttachmentIcon />,
keywords: "file upload attach",
},
{
name: "table",
title: dictionary.table,
icon: TableIcon,
icon: <TableIcon />,
attrs: { rowsCount: 3, colsCount: 3 },
},
{
name: "blockquote",
title: dictionary.quote,
icon: BlockQuoteIcon,
icon: <BlockQuoteIcon />,
shortcut: `${metaDisplay} ]`,
},
{
name: "code_block",
title: dictionary.codeBlock,
icon: CodeIcon,
icon: <CodeIcon />,
shortcut: "^ ⇧ \\",
keywords: "script",
},
{
name: "hr",
title: dictionary.hr,
icon: HorizontalRuleIcon,
icon: <HorizontalRuleIcon />,
shortcut: `${metaDisplay} _`,
keywords: "horizontal rule break line",
},
{
name: "hr",
title: dictionary.pageBreak,
icon: PageBreakIcon,
icon: <PageBreakIcon />,
keywords: "page print break line",
attrs: { markup: "***" },
},
@@ -142,19 +142,19 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
name: "date",
title: dictionary.insertDate,
keywords: "clock",
icon: CalendarIcon,
icon: <CalendarIcon />,
},
{
name: "time",
title: dictionary.insertTime,
keywords: "clock",
icon: ClockIcon,
icon: <ClockIcon />,
},
{
name: "datetime",
title: dictionary.insertDateTime,
keywords: "clock",
icon: CalendarIcon,
icon: <CalendarIcon />,
},
{
name: "separator",
@@ -162,21 +162,21 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
{
name: "container_notice",
title: dictionary.infoNotice,
icon: InfoIcon,
icon: <InfoIcon />,
keywords: "notice card information",
attrs: { style: "info" },
},
{
name: "container_notice",
title: dictionary.warningNotice,
icon: WarningIcon,
icon: <WarningIcon />,
keywords: "notice card error",
attrs: { style: "warning" },
},
{
name: "container_notice",
title: dictionary.tipNotice,
icon: StarredIcon,
icon: <StarredIcon />,
keywords: "notice card suggestion",
attrs: { style: "tip" },
},
@@ -186,7 +186,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
{
name: "code_block",
title: "Mermaid Diagram",
icon: () => <Img src="/images/mermaidjs.png" alt="Mermaid Diagram" />,
icon: <Img src="/images/mermaidjs.png" alt="Mermaid Diagram" />,
keywords: "diagram flowchart",
attrs: { language: "mermaidjs" },
},

View File

@@ -1,5 +1,6 @@
import { PageBreakIcon, HorizontalRuleIcon } from "outline-icons";
import { EditorState } from "prosemirror-state";
import * as React from "react";
import isNodeActive from "@shared/editor/queries/isNodeActive";
import { MenuItem } from "@shared/editor/types";
import { Dictionary } from "~/hooks/useDictionary";
@@ -16,14 +17,14 @@ export default function dividerMenuItems(
tooltip: dictionary.pageBreak,
attrs: { markup: "***" },
active: isNodeActive(schema.nodes.hr, { markup: "***" }),
icon: PageBreakIcon,
icon: <PageBreakIcon />,
},
{
name: "hr",
tooltip: dictionary.hr,
attrs: { markup: "---" },
active: isNodeActive(schema.nodes.hr, { markup: "---" }),
icon: HorizontalRuleIcon,
icon: <HorizontalRuleIcon />,
},
];
}

View File

@@ -11,9 +11,11 @@ import {
TodoListIcon,
InputIcon,
HighlightIcon,
ItalicIcon,
} from "outline-icons";
import { EditorState } from "prosemirror-state";
import { isInTable } from "prosemirror-tables";
import * as React from "react";
import isInCode from "@shared/editor/queries/isInCode";
import isInList from "@shared/editor/queries/isInList";
import isMarkActive from "@shared/editor/queries/isMarkActive";
@@ -36,7 +38,7 @@ export default function formattingMenuItems(
{
name: "placeholder",
tooltip: dictionary.placeholder,
icon: InputIcon,
icon: <InputIcon />,
active: isMarkActive(schema.marks.placeholder),
visible: isTemplate,
},
@@ -47,28 +49,35 @@ export default function formattingMenuItems(
{
name: "strong",
tooltip: dictionary.strong,
icon: BoldIcon,
icon: <BoldIcon />,
active: isMarkActive(schema.marks.strong),
visible: !isCode,
},
{
name: "em",
tooltip: dictionary.em,
icon: <ItalicIcon />,
active: isMarkActive(schema.marks.em),
visible: !isCode,
},
{
name: "strikethrough",
tooltip: dictionary.strikethrough,
icon: StrikethroughIcon,
icon: <StrikethroughIcon />,
active: isMarkActive(schema.marks.strikethrough),
visible: !isCode,
},
{
name: "highlight",
tooltip: dictionary.mark,
icon: HighlightIcon,
icon: <HighlightIcon />,
active: isMarkActive(schema.marks.highlight),
visible: !isTemplate && !isCode,
},
{
name: "code_inline",
tooltip: dictionary.codeInline,
icon: CodeIcon,
icon: <CodeIcon />,
active: isMarkActive(schema.marks.code_inline),
},
{
@@ -78,7 +87,7 @@ export default function formattingMenuItems(
{
name: "heading",
tooltip: dictionary.heading,
icon: Heading1Icon,
icon: <Heading1Icon />,
active: isNodeActive(schema.nodes.heading, { level: 1 }),
attrs: { level: 1 },
visible: allowBlocks && !isCode,
@@ -86,7 +95,7 @@ export default function formattingMenuItems(
{
name: "heading",
tooltip: dictionary.subheading,
icon: Heading2Icon,
icon: <Heading2Icon />,
active: isNodeActive(schema.nodes.heading, { level: 2 }),
attrs: { level: 2 },
visible: allowBlocks && !isCode,
@@ -94,7 +103,7 @@ export default function formattingMenuItems(
{
name: "blockquote",
tooltip: dictionary.quote,
icon: BlockQuoteIcon,
icon: <BlockQuoteIcon />,
active: isNodeActive(schema.nodes.blockquote),
attrs: { level: 2 },
visible: allowBlocks && !isCode,
@@ -106,7 +115,7 @@ export default function formattingMenuItems(
{
name: "checkbox_list",
tooltip: dictionary.checkboxList,
icon: TodoListIcon,
icon: <TodoListIcon />,
keywords: "checklist checkbox task",
active: isNodeActive(schema.nodes.checkbox_list),
visible: (allowBlocks || isList) && !isCode,
@@ -114,14 +123,14 @@ export default function formattingMenuItems(
{
name: "bullet_list",
tooltip: dictionary.bulletList,
icon: BulletedListIcon,
icon: <BulletedListIcon />,
active: isNodeActive(schema.nodes.bullet_list),
visible: (allowBlocks || isList) && !isCode,
},
{
name: "ordered_list",
tooltip: dictionary.orderedList,
icon: OrderedListIcon,
icon: <OrderedListIcon />,
active: isNodeActive(schema.nodes.ordered_list),
visible: (allowBlocks || isList) && !isCode,
},
@@ -132,7 +141,7 @@ export default function formattingMenuItems(
{
name: "link",
tooltip: dictionary.createLink,
icon: LinkIcon,
icon: <LinkIcon />,
active: isMarkActive(schema.marks.link),
attrs: { href: "" },
visible: !isCode,

View File

@@ -7,6 +7,7 @@ import {
AlignImageCenterIcon,
} from "outline-icons";
import { EditorState } from "prosemirror-state";
import * as React from "react";
import isNodeActive from "@shared/editor/queries/isNodeActive";
import { MenuItem } from "@shared/editor/types";
import { Dictionary } from "~/hooks/useDictionary";
@@ -27,14 +28,14 @@ export default function imageMenuItems(
{
name: "alignLeft",
tooltip: dictionary.alignLeft,
icon: AlignImageLeftIcon,
icon: <AlignImageLeftIcon />,
visible: true,
active: isLeftAligned,
},
{
name: "alignCenter",
tooltip: dictionary.alignCenter,
icon: AlignImageCenterIcon,
icon: <AlignImageCenterIcon />,
visible: true,
active: (state) =>
isNodeActive(schema.nodes.image)(state) &&
@@ -44,7 +45,7 @@ export default function imageMenuItems(
{
name: "alignRight",
tooltip: dictionary.alignRight,
icon: AlignImageRightIcon,
icon: <AlignImageRightIcon />,
visible: true,
active: isRightAligned,
},
@@ -55,21 +56,21 @@ export default function imageMenuItems(
{
name: "downloadImage",
tooltip: dictionary.downloadImage,
icon: DownloadIcon,
icon: <DownloadIcon />,
visible: !!fetch,
active: () => false,
},
{
name: "replaceImage",
tooltip: dictionary.replaceImage,
icon: ReplaceIcon,
icon: <ReplaceIcon />,
visible: true,
active: () => false,
},
{
name: "deleteImage",
tooltip: dictionary.deleteImage,
icon: TrashIcon,
icon: <TrashIcon />,
visible: true,
active: () => false,
},

View File

@@ -1,4 +1,5 @@
import { TrashIcon } from "outline-icons";
import * as React from "react";
import { MenuItem } from "@shared/editor/types";
import { Dictionary } from "~/hooks/useDictionary";
@@ -7,7 +8,7 @@ export default function tableMenuItems(dictionary: Dictionary): MenuItem[] {
{
name: "deleteTable",
tooltip: dictionary.deleteTable,
icon: TrashIcon,
icon: <TrashIcon />,
active: () => false,
},
];

View File

@@ -7,6 +7,7 @@ import {
InsertRightIcon,
} from "outline-icons";
import { EditorState } from "prosemirror-state";
import * as React from "react";
import isNodeActive from "@shared/editor/queries/isNodeActive";
import { MenuItem } from "@shared/editor/types";
import { Dictionary } from "~/hooks/useDictionary";
@@ -23,7 +24,7 @@ export default function tableColMenuItems(
{
name: "setColumnAttr",
tooltip: dictionary.alignLeft,
icon: AlignLeftIcon,
icon: <AlignLeftIcon />,
attrs: { index, alignment: "left" },
active: isNodeActive(schema.nodes.th, {
colspan: 1,
@@ -34,7 +35,7 @@ export default function tableColMenuItems(
{
name: "setColumnAttr",
tooltip: dictionary.alignCenter,
icon: AlignCenterIcon,
icon: <AlignCenterIcon />,
attrs: { index, alignment: "center" },
active: isNodeActive(schema.nodes.th, {
colspan: 1,
@@ -45,7 +46,7 @@ export default function tableColMenuItems(
{
name: "setColumnAttr",
tooltip: dictionary.alignRight,
icon: AlignRightIcon,
icon: <AlignRightIcon />,
attrs: { index, alignment: "right" },
active: isNodeActive(schema.nodes.th, {
colspan: 1,
@@ -59,13 +60,13 @@ export default function tableColMenuItems(
{
name: rtl ? "addColumnAfter" : "addColumnBefore",
tooltip: rtl ? dictionary.addColumnAfter : dictionary.addColumnBefore,
icon: InsertLeftIcon,
icon: <InsertLeftIcon />,
active: () => false,
},
{
name: rtl ? "addColumnBefore" : "addColumnAfter",
tooltip: rtl ? dictionary.addColumnBefore : dictionary.addColumnAfter,
icon: InsertRightIcon,
icon: <InsertRightIcon />,
active: () => false,
},
{
@@ -74,7 +75,7 @@ export default function tableColMenuItems(
{
name: "deleteColumn",
tooltip: dictionary.deleteColumn,
icon: TrashIcon,
icon: <TrashIcon />,
active: () => false,
},
];

View File

@@ -1,5 +1,6 @@
import { TrashIcon, InsertAboveIcon, InsertBelowIcon } from "outline-icons";
import { EditorState } from "prosemirror-state";
import * as React from "react";
import { MenuItem } from "@shared/editor/types";
import { Dictionary } from "~/hooks/useDictionary";
@@ -12,7 +13,7 @@ export default function tableRowMenuItems(
{
name: "addRowAfter",
tooltip: dictionary.addRowBefore,
icon: InsertAboveIcon,
icon: <InsertAboveIcon />,
attrs: { index: index - 1 },
active: () => false,
visible: index !== 0,
@@ -20,7 +21,7 @@ export default function tableRowMenuItems(
{
name: "addRowAfter",
tooltip: dictionary.addRowAfter,
icon: InsertBelowIcon,
icon: <InsertBelowIcon />,
attrs: { index },
active: () => false,
},
@@ -30,7 +31,7 @@ export default function tableRowMenuItems(
{
name: "deleteRow",
tooltip: dictionary.deleteRow,
icon: TrashIcon,
icon: <TrashIcon />,
active: () => false,
},
];

View File

@@ -677,7 +677,6 @@ const MaxWidth = styled(Flex)<MaxWidthProps>`
padding-bottom: 16px;
${breakpoint("tablet")`
padding: 0 44px;
margin: 4px auto 12px;
max-width: ${(props: MaxWidthProps) =>
props.isFullWidth

View File

@@ -9,7 +9,7 @@ export default function DisabledEmbed(props: Props & ThemeProps<DefaultTheme>) {
<Widget
title={props.embed.title}
href={props.attrs.href}
icon={props.embed.icon(undefined)}
icon={props.embed.icon}
context={props.attrs.href.replace(/^https?:\/\//, "")}
isSelected={props.isSelected}
theme={props.theme}

View File

@@ -68,7 +68,7 @@ const Img = styled(Image)`
`;
export class EmbedDescriptor {
icon: React.FC<any>;
icon?: React.ReactNode;
name?: string;
title?: string;
shortcut?: string;
@@ -127,264 +127,262 @@ const embeds: EmbedDescriptor[] = [
title: "Abstract",
keywords: "design",
defaultHidden: true,
icon: () => <Img src="/images/abstract.png" alt="Abstract" />,
icon: <Img src="/images/abstract.png" alt="Abstract" />,
component: Abstract,
}),
new EmbedDescriptor({
title: "Airtable",
keywords: "spreadsheet",
icon: () => <Img src="/images/airtable.png" alt="Airtable" />,
icon: <Img src="/images/airtable.png" alt="Airtable" />,
component: Airtable,
}),
new EmbedDescriptor({
title: "Berrycast",
keywords: "video",
defaultHidden: true,
icon: () => <Img src="/images/berrycast.png" alt="Berrycast" />,
icon: <Img src="/images/berrycast.png" alt="Berrycast" />,
component: Berrycast,
}),
new EmbedDescriptor({
title: "Bilibili",
keywords: "video",
defaultHidden: true,
icon: () => <Img src="/images/bilibili.png" alt="Bilibili" />,
icon: <Img src="/images/bilibili.png" alt="Bilibili" />,
component: Bilibili,
}),
new EmbedDescriptor({
title: "Cawemo",
keywords: "bpmn process",
defaultHidden: true,
icon: () => <Img src="/images/cawemo.png" alt="Cawemo" />,
icon: <Img src="/images/cawemo.png" alt="Cawemo" />,
component: Cawemo,
}),
new EmbedDescriptor({
title: "ClickUp",
keywords: "project",
icon: () => <Img src="/images/clickup.png" alt="ClickUp" />,
icon: <Img src="/images/clickup.png" alt="ClickUp" />,
component: ClickUp,
}),
new EmbedDescriptor({
title: "Codepen",
keywords: "code editor",
icon: () => <Img src="/images/codepen.png" alt="Codepen" />,
icon: <Img src="/images/codepen.png" alt="Codepen" />,
component: Codepen,
}),
new EmbedDescriptor({
title: "DBDiagram",
keywords: "diagrams database",
icon: () => <Img src="/images/dbdiagram.png" alt="DBDiagram" />,
icon: <Img src="/images/dbdiagram.png" alt="DBDiagram" />,
component: DBDiagram,
}),
new EmbedDescriptor({
title: "Descript",
keywords: "audio",
icon: () => <Img src="/images/descript.png" alt="Descript" />,
icon: <Img src="/images/descript.png" alt="Descript" />,
component: Descript,
}),
new EmbedDescriptor({
title: "Figma",
keywords: "design svg vector",
icon: () => <Img src="/images/figma.png" alt="Figma" />,
icon: <Img src="/images/figma.png" alt="Figma" />,
component: Figma,
}),
new EmbedDescriptor({
title: "Framer",
keywords: "design prototyping",
icon: () => <Img src="/images/framer.png" alt="Framer" />,
icon: <Img src="/images/framer.png" alt="Framer" />,
component: Framer,
}),
new EmbedDescriptor({
title: "GitHub Gist",
keywords: "code",
icon: () => <Img src="/images/github-gist.png" alt="GitHub" />,
icon: <Img src="/images/github-gist.png" alt="GitHub" />,
component: Gist,
}),
new EmbedDescriptor({
title: "Gliffy",
keywords: "diagram",
icon: () => <Img src="/images/gliffy.png" alt="Gliffy" />,
icon: <Img src="/images/gliffy.png" alt="Gliffy" />,
component: Gliffy,
}),
new EmbedDescriptor({
title: "Diagrams.net",
keywords: "diagrams drawio",
icon: () => <Img src="/images/diagrams.png" alt="Diagrams.net" />,
icon: <Img src="/images/diagrams.png" alt="Diagrams.net" />,
component: Diagrams,
}),
new EmbedDescriptor({
title: "Google Drawings",
keywords: "drawings",
icon: () => <Img src="/images/google-drawings.png" alt="Google Drawings" />,
icon: <Img src="/images/google-drawings.png" alt="Google Drawings" />,
component: GoogleDrawings,
}),
new EmbedDescriptor({
title: "Google Drive",
keywords: "drive",
icon: () => <Img src="/images/google-drive.png" alt="Google Drive" />,
icon: <Img src="/images/google-drive.png" alt="Google Drive" />,
component: GoogleDrive,
}),
new EmbedDescriptor({
title: "Google Docs",
keywords: "documents word",
icon: () => <Img src="/images/google-docs.png" alt="Google Docs" />,
icon: <Img src="/images/google-docs.png" alt="Google Docs" />,
component: GoogleDocs,
}),
new EmbedDescriptor({
title: "Google Sheets",
keywords: "excel spreadsheet",
icon: () => <Img src="/images/google-sheets.png" alt="Google Sheets" />,
icon: <Img src="/images/google-sheets.png" alt="Google Sheets" />,
component: GoogleSheets,
}),
new EmbedDescriptor({
title: "Google Slides",
keywords: "presentation slideshow",
icon: () => <Img src="/images/google-slides.png" alt="Google Slides" />,
icon: <Img src="/images/google-slides.png" alt="Google Slides" />,
component: GoogleSlides,
}),
new EmbedDescriptor({
title: "Google Calendar",
keywords: "calendar",
icon: () => <Img src="/images/google-calendar.png" alt="Google Calendar" />,
icon: <Img src="/images/google-calendar.png" alt="Google Calendar" />,
component: GoogleCalendar,
}),
new EmbedDescriptor({
title: "Google Data Studio",
keywords: "bi business intelligence",
icon: () => (
<Img src="/images/google-datastudio.png" alt="Google Data Studio" />
),
icon: <Img src="/images/google-datastudio.png" alt="Google Data Studio" />,
component: GoogleDataStudio,
}),
new EmbedDescriptor({
title: "Google Forms",
keywords: "form survey",
icon: () => <Img src="/images/google-forms.png" alt="Google Forms" />,
icon: <Img src="/images/google-forms.png" alt="Google Forms" />,
component: GoogleForms,
}),
new EmbedDescriptor({
title: "Grist",
keywords: "spreadsheet",
icon: () => <Img src="/images/grist.png" alt="Grist" />,
icon: <Img src="/images/grist.png" alt="Grist" />,
component: Grist,
}),
new EmbedDescriptor({
title: "InVision",
keywords: "design prototype",
defaultHidden: true,
icon: () => <Img src="/images/invision.png" alt="InVision" />,
icon: <Img src="/images/invision.png" alt="InVision" />,
component: InVision,
}),
new EmbedDescriptor({
title: "JSFiddle",
keywords: "code",
defaultHidden: true,
icon: () => <Img src="/images/jsfiddle.png" alt="JSFiddle" />,
icon: <Img src="/images/jsfiddle.png" alt="JSFiddle" />,
component: JSFiddle,
}),
new EmbedDescriptor({
title: "Loom",
keywords: "video screencast",
icon: () => <Img src="/images/loom.png" alt="Loom" />,
icon: <Img src="/images/loom.png" alt="Loom" />,
component: Loom,
}),
new EmbedDescriptor({
title: "Lucidchart",
keywords: "chart",
icon: () => <Img src="/images/lucidchart.png" alt="Lucidchart" />,
icon: <Img src="/images/lucidchart.png" alt="Lucidchart" />,
component: Lucidchart,
}),
new EmbedDescriptor({
title: "Marvel",
keywords: "design prototype",
icon: () => <Img src="/images/marvel.png" alt="Marvel" />,
icon: <Img src="/images/marvel.png" alt="Marvel" />,
component: Marvel,
}),
new EmbedDescriptor({
title: "Mindmeister",
keywords: "mindmap",
icon: () => <Img src="/images/mindmeister.png" alt="Mindmeister" />,
icon: <Img src="/images/mindmeister.png" alt="Mindmeister" />,
component: Mindmeister,
}),
new EmbedDescriptor({
title: "Miro",
keywords: "whiteboard",
icon: () => <Img src="/images/miro.png" alt="Miro" />,
icon: <Img src="/images/miro.png" alt="Miro" />,
component: Miro,
}),
new EmbedDescriptor({
title: "Mode",
keywords: "analytics",
defaultHidden: true,
icon: () => <Img src="/images/mode-analytics.png" alt="Mode" />,
icon: <Img src="/images/mode-analytics.png" alt="Mode" />,
component: ModeAnalytics,
}),
new EmbedDescriptor({
title: "Otter.ai",
keywords: "audio transcription meeting notes",
defaultHidden: true,
icon: () => <Img src="/images/otter.png" alt="Otter.ai" />,
icon: <Img src="/images/otter.png" alt="Otter.ai" />,
component: Otter,
}),
new EmbedDescriptor({
title: "Pitch",
keywords: "presentation",
defaultHidden: true,
icon: () => <Img src="/images/pitch.png" alt="Pitch" />,
icon: <Img src="/images/pitch.png" alt="Pitch" />,
component: Pitch,
}),
new EmbedDescriptor({
title: "Prezi",
keywords: "presentation",
icon: () => <Img src="/images/prezi.png" alt="Prezi" />,
icon: <Img src="/images/prezi.png" alt="Prezi" />,
component: Prezi,
}),
new EmbedDescriptor({
title: "Scribe",
keywords: "screencast",
icon: () => <Img src="/images/scribe.png" alt="Scribe" />,
icon: <Img src="/images/scribe.png" alt="Scribe" />,
component: Scribe,
}),
new EmbedDescriptor({
title: "Spotify",
keywords: "music",
icon: () => <Img src="/images/spotify.png" alt="Spotify" />,
icon: <Img src="/images/spotify.png" alt="Spotify" />,
component: Spotify,
}),
new EmbedDescriptor({
title: "Tldraw",
keywords: "draw schematics diagrams",
visible: false,
icon: () => <Img src="/images/tldraw.png" alt="Tldraw" />,
icon: <Img src="/images/tldraw.png" alt="Tldraw" />,
component: Tldraw,
}),
new EmbedDescriptor({
title: "Trello",
keywords: "kanban",
icon: () => <Img src="/images/trello.png" alt="Trello" />,
icon: <Img src="/images/trello.png" alt="Trello" />,
component: Trello,
}),
new EmbedDescriptor({
title: "Typeform",
keywords: "form survey",
icon: () => <Img src="/images/typeform.png" alt="Typeform" />,
icon: <Img src="/images/typeform.png" alt="Typeform" />,
component: Typeform,
}),
new EmbedDescriptor({
title: "Vimeo",
keywords: "video",
icon: () => <Img src="/images/vimeo.png" alt="Vimeo" />,
icon: <Img src="/images/vimeo.png" alt="Vimeo" />,
component: Vimeo,
}),
new EmbedDescriptor({
title: "Whimsical",
keywords: "whiteboard",
icon: () => <Img src="/images/whimsical.png" alt="Whimsical" />,
icon: <Img src="/images/whimsical.png" alt="Whimsical" />,
component: Whimsical,
}),
new EmbedDescriptor({
title: "YouTube",
keywords: "google video",
icon: () => <Img src="/images/youtube.png" alt="YouTube" />,
icon: <Img src="/images/youtube.png" alt="YouTube" />,
component: YouTube,
}),
];

View File

@@ -3,7 +3,9 @@ import { MenuItem } from "../types";
type Item = MenuItem | EmbedDescriptor;
export default function filterExcessSeparators(items: Item[]): Item[] {
export default function filterExcessSeparators<T extends Item>(
items: T[]
): T[] {
return items
.reduce((acc, item) => {
// trim separator if the previous item was a separator
@@ -14,7 +16,7 @@ export default function filterExcessSeparators(items: Item[]): Item[] {
return acc;
}
return [...acc, item];
}, [] as Item[])
}, [] as T[])
.filter((item, index, arr) => {
if (
item.name === "separator" &&

View File

@@ -15,7 +15,7 @@ export enum EventType {
}
export type MenuItem = {
icon?: typeof React.Component | React.FC<any>;
icon?: React.ReactElement;
name?: string;
title?: string;
shortcut?: string;