From 2e0455ed24db6eeefeee1d215af5b53f0a315567 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Fri, 16 Feb 2024 04:26:37 +0100 Subject: [PATCH 01/46] Cleaned up UI components and examples --- .../custom-ui/.bnexample.json | 3 +- examples/02-ui-components/custom-ui/App.tsx | 9 +- .../02-ui-components/custom-ui/ColorMenu.tsx | 9 +- .../custom-ui/CustomFormattingToolbar.tsx | 9 +- .../custom-ui/CustomSideMenu.tsx | 4 +- .../custom-ui/CustomSlashMenu.tsx | 143 ++++++++---------- .../02-ui-components/custom-ui/LinkMenu.tsx | 4 +- .../02-ui-components/custom-ui/package.json | 3 +- .../formatting-toolbar-buttons/App.tsx | 8 +- .../CustomFormattingToolbar.tsx | 36 ++--- .../side-menu-buttons/App.tsx | 18 ++- .../side-menu-buttons/CustomSideMenu.tsx | 5 +- .../side-menu-drag-handle-items/App.tsx | 27 ++-- .../CustomDragHandleMenu.tsx | 16 +- .../02-ui-components/slash-menu-items/App.tsx | 24 ++- .../slash-menu-items/CustomSlashMenuItems.tsx | 27 +--- .../ui-elements-remove/App.tsx | 15 +- .../DefaultButtons/BasicTextStyleButton.tsx | 123 +++++++++++++++ .../DefaultButtons/ColorStyleButton.tsx | 126 +++++++++++---- .../DefaultButtons/CreateLinkButton.tsx | 40 +++-- .../DefaultButtons/ImageCaptionButton.tsx | 129 +++++++++++----- .../DefaultButtons/NestBlockButtons.tsx | 51 ++++--- .../DefaultButtons/ReplaceImageButton.tsx | 20 ++- .../DefaultButtons/TextAlignButton.tsx | 104 +++++++++++-- .../DefaultButtons/ToggledStyleButton.tsx | 83 ---------- .../DefaultDropdowns/BlockTypeDropdown.tsx | 45 +++--- .../DefaultFormattingToolbar.tsx | 48 +++--- .../DefaultPositionedFormattingToolbar.tsx | 33 ++-- .../DefaultHyperlinkToolbar.tsx | 9 +- .../DefaultPositionedHyperlinkToolbar.tsx | 34 ++--- .../ImageToolbar/DefaultImageToolbar.tsx | 94 ++++++++---- .../DefaultPositionedImageToolbar.tsx | 31 ++-- .../SideMenu/DefaultButtons/DragHandle.tsx | 23 ++- .../SideMenu/DefaultPositionedSideMenu.tsx | 19 +-- .../components/SideMenu/DefaultSideMenu.tsx | 1 - .../DefaultButtons/BlockColorsButton.tsx | 119 +++++++++++++-- .../DefaultButtons/RemoveBlockButton.tsx | 25 ++- .../DragHandleMenu/DefaultDragHandleMenu.tsx | 17 ++- .../DragHandleMenu/DragHandleMenu.tsx | 2 - .../DefaultPositionedSuggestionMenu.tsx | 24 ++- .../DefaultSuggestionMenu.test.tsx | 3 - .../SuggestionMenu/DefaultSuggestionMenu.tsx | 23 ++- packages/react/src/editor/BlockNoteContext.ts | 16 ++ .../react/src/editor/BlockNoteDefaultUI.tsx | 13 +- packages/react/src/editor/BlockNoteView.tsx | 6 +- packages/react/src/index.ts | 2 +- playground/src/examples.gen.tsx | 3 +- 47 files changed, 1040 insertions(+), 586 deletions(-) create mode 100644 packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx delete mode 100644 packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx diff --git a/examples/02-ui-components/custom-ui/.bnexample.json b/examples/02-ui-components/custom-ui/.bnexample.json index eac608c3b1..fe8dafc8db 100644 --- a/examples/02-ui-components/custom-ui/.bnexample.json +++ b/examples/02-ui-components/custom-ui/.bnexample.json @@ -4,6 +4,7 @@ "group": "Advanced Examples", "order": 1, "dependencies": { - "@mantine/core": "^7.4.2" + "@mantine/core": "^7.4.2", + "react-icons": "^4.3.1" } } \ No newline at end of file diff --git a/examples/02-ui-components/custom-ui/App.tsx b/examples/02-ui-components/custom-ui/App.tsx index 3ed32703d9..775a4b0ca7 100644 --- a/examples/02-ui-components/custom-ui/App.tsx +++ b/examples/02-ui-components/custom-ui/App.tsx @@ -10,11 +10,14 @@ export default function App() { const editor = useBlockNote(); return ( - - {/*Adding custom UI elements*/} + - + ); } diff --git a/examples/02-ui-components/custom-ui/ColorMenu.tsx b/examples/02-ui-components/custom-ui/ColorMenu.tsx index 9b4c5ab3c0..3d17deabc4 100644 --- a/examples/02-ui-components/custom-ui/ColorMenu.tsx +++ b/examples/02-ui-components/custom-ui/ColorMenu.tsx @@ -1,8 +1,5 @@ -import { - FormattingToolbarProps, - useEditorChange, - useEditorSelectionChange, -} from "@blocknote/react"; +import { BlockNoteEditor } from "@blocknote/core"; +import { useEditorChange, useEditorSelectionChange } from "@blocknote/react"; import { HTMLAttributes, useState } from "react"; import { MdFormatColorText } from "react-icons/md"; @@ -18,7 +15,7 @@ export const colors = [ // Formatting Toolbar sub menu for changing text and background color export const ColorMenu = ( - props: FormattingToolbarProps & HTMLAttributes + props: { editor: BlockNoteEditor } & HTMLAttributes ) => { const { editor, className, ...rest } = props; diff --git a/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx b/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx index 3d1abcad65..ae46435f46 100644 --- a/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx +++ b/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx @@ -1,8 +1,5 @@ -import { - FormattingToolbarProps, - useEditorChange, - useEditorSelectionChange, -} from "@blocknote/react"; +import { BlockNoteEditor } from "@blocknote/core"; +import { useEditorChange, useEditorSelectionChange } from "@blocknote/react"; import { useState } from "react"; import { MdAddLink, @@ -30,7 +27,7 @@ type CustomFormattingToolbarState = { backgroundColor: string; }; -export const CustomFormattingToolbar = (props: FormattingToolbarProps) => { +export const CustomFormattingToolbar = (props: { editor: BlockNoteEditor }) => { // Function to get the state of toolbar buttons (active/inactive) const getState = (): CustomFormattingToolbarState => { const block = props.editor.getTextCursorPosition().block; diff --git a/examples/02-ui-components/custom-ui/CustomSideMenu.tsx b/examples/02-ui-components/custom-ui/CustomSideMenu.tsx index 54eb96d5b4..aced346fb0 100644 --- a/examples/02-ui-components/custom-ui/CustomSideMenu.tsx +++ b/examples/02-ui-components/custom-ui/CustomSideMenu.tsx @@ -1,10 +1,8 @@ -import { BlockNoteEditor } from "@blocknote/core"; import { DefaultPositionedSideMenu } from "@blocknote/react"; import { RxDragHandleHorizontal } from "react-icons/rx"; -export const CustomSideMenu = (props: { editor: BlockNoteEditor }) => ( +export const CustomSideMenu = () => ( ( // Side menu consists of only a drag handle
{ const editor = props.editor; - return
TODO
; - // TODO - // { - // // Sorts items by group - // const groups: Record = {}; - // for (const item of props.filteredItems) { - // if (!groups[item.group]) { - // groups[item.group] = []; - // } + return ( + + filterSuggestionItems(getDefaultReactSlashMenuItems(), query) + } + suggestionMenuComponent={(props) => { + const groups: Record< + string, + ReturnType + > = {}; + for (const item of props.items) { + if (!groups[item.group]) { + groups[item.group] = []; + } - // groups[item.group].push(item); - // } + groups[item.group].push(item); + } - // // If query matches no items, show "No matches" message - // if (props.filteredItems.length === 0) { - // return
No matches
; - // } + // If query matches no items, show "No matches" message + if (props.items.length === 0) { + return
No matches
; + } - // return ( - //
- // {Object.entries(groups).map(([group, items]) => ( - // // Component for each group - //
- // {/*Group label*/} - //
{group}
- // {/*Group items*/} - //
- // {items.map((item) => { - // const Icon = - // item.name in icons - // ? icons[item.name as keyof typeof icons] - // : "div"; - // return ( - // - // ); - // })} - //
- //
- // ))} - //
- // ); - // }} - // /> - // ); + return ( +
+ {Object.entries(groups).map(([group, items]) => ( + // Component for each group +
+ {/*Group label*/} +
{group}
+ {/*Group items*/} +
+ {items.map( + ( + item: ReturnType< + typeof getDefaultReactSlashMenuItems + >[number] + ) => { + const Icon = item.icon; + return ( + + ); + } + )} +
+
+ ))} +
+ ); + }} + /> + ); }; diff --git a/examples/02-ui-components/custom-ui/LinkMenu.tsx b/examples/02-ui-components/custom-ui/LinkMenu.tsx index e951e0b93f..7d66c1ab97 100644 --- a/examples/02-ui-components/custom-ui/LinkMenu.tsx +++ b/examples/02-ui-components/custom-ui/LinkMenu.tsx @@ -1,9 +1,9 @@ -import { FormattingToolbarProps } from "@blocknote/react"; import { HTMLAttributes, useState } from "react"; +import { BlockNoteEditor } from "@blocknote/core"; // Formatting Toolbar sub menu for creating links export const LinkMenu = ( - props: FormattingToolbarProps & HTMLAttributes + props: { editor: BlockNoteEditor } & HTMLAttributes ) => { const { editor, className, ...rest } = props; diff --git a/examples/02-ui-components/custom-ui/package.json b/examples/02-ui-components/custom-ui/package.json index 0d1f7f0fdc..0404b63cc3 100644 --- a/examples/02-ui-components/custom-ui/package.json +++ b/examples/02-ui-components/custom-ui/package.json @@ -15,7 +15,8 @@ "@blocknote/react": "^0.11.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "@mantine/core": "^7.4.2" + "@mantine/core": "^7.4.2", + "react-icons": "^4.3.1" }, "devDependencies": { "@types/react": "^18.0.25", diff --git a/examples/02-ui-components/formatting-toolbar-buttons/App.tsx b/examples/02-ui-components/formatting-toolbar-buttons/App.tsx index 41ed8abda3..7e2779281f 100644 --- a/examples/02-ui-components/formatting-toolbar-buttons/App.tsx +++ b/examples/02-ui-components/formatting-toolbar-buttons/App.tsx @@ -1,10 +1,16 @@ import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; +import { CustomFormattingToolbar } from "./CustomFormattingToolbar"; + export default function App() { // Creates a new editor instance. const editor = useBlockNote(); // Renders the editor instance. - return {/* TODO */}; + return ( + + + + ); } diff --git a/examples/02-ui-components/formatting-toolbar-buttons/CustomFormattingToolbar.tsx b/examples/02-ui-components/formatting-toolbar-buttons/CustomFormattingToolbar.tsx index fc93b1d001..41e2a3d905 100644 --- a/examples/02-ui-components/formatting-toolbar-buttons/CustomFormattingToolbar.tsx +++ b/examples/02-ui-components/formatting-toolbar-buttons/CustomFormattingToolbar.tsx @@ -1,46 +1,46 @@ import { + BasicTextStyleButton, BlockTypeDropdown, ColorStyleButton, CreateLinkButton, - FormattingToolbarProps, ImageCaptionButton, NestBlockButton, ReplaceImageButton, TextAlignButton, - ToggledStyleButton, Toolbar, UnnestBlockButton, } from "@blocknote/react"; import { CustomButton } from "./CustomButton"; +import { BlockNoteEditor } from "@blocknote/core"; -export function CustomFormattingToolbar(props: FormattingToolbarProps) { +export function CustomFormattingToolbar(props: { editor: BlockNoteEditor }) { return ( - + {/*Custom button to toggle blue text & background color.*/} - - + + - - - - + + + + {/* Added code toggle button */} - + - - - + + + - + - - + + - + ); } diff --git a/examples/02-ui-components/side-menu-buttons/App.tsx b/examples/02-ui-components/side-menu-buttons/App.tsx index ceec9e3d59..bdee48dac0 100644 --- a/examples/02-ui-components/side-menu-buttons/App.tsx +++ b/examples/02-ui-components/side-menu-buttons/App.tsx @@ -1,18 +1,22 @@ -import { BlockNoteView, useBlockNote } from "@blocknote/react"; +import { + BlockNoteView, + DefaultPositionedSideMenu, + useBlockNote, +} from "@blocknote/react"; import "@blocknote/react/style.css"; +import { CustomSideMenu } from "./CustomSideMenu"; + export default function App() { // Creates a new editor instance. const editor = useBlockNote(); // Renders the editor instance. return ( - - {/* TODO - - - - */} + + } + /> ); } diff --git a/examples/02-ui-components/side-menu-buttons/CustomSideMenu.tsx b/examples/02-ui-components/side-menu-buttons/CustomSideMenu.tsx index ef9e43afac..d3e5f2487c 100644 --- a/examples/02-ui-components/side-menu-buttons/CustomSideMenu.tsx +++ b/examples/02-ui-components/side-menu-buttons/CustomSideMenu.tsx @@ -6,8 +6,11 @@ import { } from "@blocknote/react"; import "@blocknote/react/style.css"; import { MdDelete } from "react-icons/md"; +import { BlockNoteEditor } from "@blocknote/core"; -export const CustomSideMenu = (props: SideMenuProps) => ( +export const CustomSideMenu = ( + props: { editor: BlockNoteEditor } & SideMenuProps +) => ( - {/* TODO */} - {/* - - - + ( - - )} */} - {/* /> */} + } + /> + )} + /> ); } diff --git a/examples/02-ui-components/side-menu-drag-handle-items/CustomDragHandleMenu.tsx b/examples/02-ui-components/side-menu-drag-handle-items/CustomDragHandleMenu.tsx index 264e90addc..6d65c7eb3d 100644 --- a/examples/02-ui-components/side-menu-drag-handle-items/CustomDragHandleMenu.tsx +++ b/examples/02-ui-components/side-menu-drag-handle-items/CustomDragHandleMenu.tsx @@ -6,9 +6,21 @@ import { RemoveBlockButton, } from "@blocknote/react"; import "@blocknote/react/style.css"; +import { + BlockSchema, + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; -export const CustomDragHandleMenu = ( - props: DragHandleMenuProps +export const CustomDragHandleMenu = < + BSchema extends BlockSchema = DefaultBlockSchema, + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +>( + props: DragHandleMenuProps ) => { return ( diff --git a/examples/02-ui-components/slash-menu-items/App.tsx b/examples/02-ui-components/slash-menu-items/App.tsx index bbcdd03620..af1b16923e 100644 --- a/examples/02-ui-components/slash-menu-items/App.tsx +++ b/examples/02-ui-components/slash-menu-items/App.tsx @@ -1,14 +1,28 @@ -import { BlockNoteView, useBlockNote } from "@blocknote/react"; +import { + BlockNoteView, + DefaultPositionedSuggestionMenu, + getDefaultReactSlashMenuItems, + useBlockNote, +} from "@blocknote/react"; import "@blocknote/react/style.css"; +import { filterSuggestionItems } from "@blocknote/core"; +import { customSlashMenuItems } from "./CustomSlashMenuItems"; // import { customSlashMenuItems } from "./CustomSlashMenuItems"; export default function App() { // Creates a new editor instance. - const editor = useBlockNote({ - // slashMenuItems: customSlashMenuItems, TODO - }); + const editor = useBlockNote(); // Renders the editor instance. - return ; + return ( + + + filterSuggestionItems(customSlashMenuItems, query) + } + /> + + ); } diff --git a/examples/02-ui-components/slash-menu-items/CustomSlashMenuItems.tsx b/examples/02-ui-components/slash-menu-items/CustomSlashMenuItems.tsx index 7aeb9d4e3a..d17445005c 100644 --- a/examples/02-ui-components/slash-menu-items/CustomSlashMenuItems.tsx +++ b/examples/02-ui-components/slash-menu-items/CustomSlashMenuItems.tsx @@ -1,14 +1,5 @@ -import { - BlockNoteEditor, - DefaultBlockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema, - PartialBlock, -} from "@blocknote/core"; -import { - getDefaultReactSlashMenuItems, - ReactSlashMenuItem, -} from "@blocknote/react"; +import { BlockNoteEditor, PartialBlock } from "@blocknote/core"; +import { getDefaultReactSlashMenuItems } from "@blocknote/react"; import { HiOutlineGlobeAlt } from "react-icons/hi"; // Command to insert "Hello World" in bold in a new block below. @@ -17,11 +8,7 @@ const insertHelloWorld = (editor: BlockNoteEditor) => { const currentBlock = editor.getTextCursorPosition().block; // New block we want to insert. - const helloWorldBlock: PartialBlock< - DefaultBlockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema - > = { + const helloWorldBlock: PartialBlock = { type: "paragraph", content: [{ type: "text", text: "Hello World", styles: { bold: true } }], }; @@ -31,13 +18,13 @@ const insertHelloWorld = (editor: BlockNoteEditor) => { }; // Custom Slash Menu item which executes the above function. -const insertHelloWorldItem: ReactSlashMenuItem = { - name: "Insert Hello World", - execute: insertHelloWorld, +const insertHelloWorldItem = { + title: "Insert Hello World", + onItemClick: insertHelloWorld, aliases: ["helloworld", "hw"], group: "Other", icon: , - hint: "Used to insert a block with 'Hello World' below.", + subtext: "Used to insert a block with 'Hello World' below.", }; // List containing all default Slash Menu Items, as well as our custom one. diff --git a/examples/02-ui-components/ui-elements-remove/App.tsx b/examples/02-ui-components/ui-elements-remove/App.tsx index 29da124b9b..b2d789dfea 100644 --- a/examples/02-ui-components/ui-elements-remove/App.tsx +++ b/examples/02-ui-components/ui-elements-remove/App.tsx @@ -14,12 +14,13 @@ export default function App() { // Renders the editor instance. return ( - - {/* TODO - - - - */} - + ); } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx new file mode 100644 index 0000000000..802d028432 --- /dev/null +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx @@ -0,0 +1,123 @@ +import { + BlockNoteEditor, + BlockSchema, + formatKeyboardShortcut, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { useMemo, useState } from "react"; +import { + RiBold, + RiCodeFill, + RiItalic, + RiStrikethrough, + RiUnderline, +} from "react-icons/ri"; + +import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton"; +import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange"; +import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; +import { useBlockNoteEditor } from "../../../editor/BlockNoteContext"; +import { IconType } from "react-icons"; + +type BasicTextStyle = "bold" | "italic" | "underline" | "strike" | "code"; + +const icons = { + bold: RiBold, + italic: RiItalic, + underline: RiUnderline, + strike: RiStrikethrough, + code: RiCodeFill, +} satisfies Record; + +const shortcuts = { + bold: "Mod+B", + italic: "Mod+I", + underline: "Mod+U", + strike: "Mod+Shift+X", + code: "", +} satisfies Record; + +function checkBasicTextStyleInSchema