diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index 28dc88295..5ef956bde 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -39,6 +39,7 @@ "@dnd-kit/modifiers": "9.0.0", "@emoji-mart/data": "1.2.1", "@emoji-mart/react": "1.1.1", + "@floating-ui/react": "^0.27.19", "@fontsource-variable/inter": "5.3.0", "@fontsource-variable/material-symbols-outlined": "5.3.3", "@fontsource/material-icons": "5.3.0", diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsDragHandleMenu.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsDragHandleMenu.tsx new file mode 100644 index 000000000..198f246f0 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsDragHandleMenu.tsx @@ -0,0 +1,66 @@ +/** + * Overrides the default DragHandleMenu with a custom implementation for the Docs editor. + * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx + */ +import { + BlockColorsItem, + DragHandleMenu, + RemoveBlockItem, + TableColumnHeaderItem, + TableRowHeaderItem, + useComponentsContext, + useDictionary, +} from '@blocknote/react'; + +import { Box } from '@/components'; +import { PresentBlockItem } from '@/docs/doc-presenter/components/PresentBlockItem'; +import ColorIcon from '@/icons/color.svg'; +import TableHeaderColumnIcon from '@/icons/table-header-column.svg'; +import TableHeaderRowIcon from '@/icons/table-header-row.svg'; +import TrashIcon from '@/icons/trash.svg'; + +import { LinkToBlockItem } from './LinkToBlockItem'; +import { SideMenuSeparator } from './SideMenuSeparator'; + +export const DocsDragHandleMenu = () => { + const Components = useComponentsContext(); + const dict = useDictionary(); + + if (Components === undefined) { + return null; + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx index 529a6ede2..fcacad77b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx @@ -1,74 +1,105 @@ /** - * Overrides the default DragHandleMenu with a custom implementation for the Docs editor. - * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx + * Overrides the default SideMenu with a custom implementation for the Docs editor. + * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/SideMenu.tsx */ +import type { Block } from '@blocknote/core'; +import { SideMenuExtension } from '@blocknote/core/extensions'; import { - BlockColorsItem, - DragHandleMenu, - RemoveBlockItem, SideMenu, SideMenuController, - TableColumnHeaderItem, - TableRowHeaderItem, - useComponentsContext, - useDictionary, + useBlockNoteEditor, + useExtensionState, } from '@blocknote/react'; +import { offset } from '@floating-ui/react'; +import { useMemo } from 'react'; -import { Box, HorizontalSeparator } from '@/components'; -import { PresentBlockItem } from '@/docs/doc-presenter/components/PresentBlockItem'; -import ColorIcon from '@/icons/color.svg'; -import TableHeaderColumnIcon from '@/icons/table-header-column.svg'; -import TableHeaderRowIcon from '@/icons/table-header-row.svg'; -import TrashIcon from '@/icons/trash.svg'; +import type { + DocsBlockNoteEditor, + DocsBlockSchema, + DocsInlineContentSchema, + DocsStyleSchema, +} from '../../types'; -import { LinkToBlockItem } from './LinkToBlockItem'; -import { TableHeaderSeparator } from './TableHeaderSeparator'; +import { DocsDragHandleMenu } from './DocsDragHandleMenu'; -const DocsDragHandleMenu = () => { - const Components = useComponentsContext(); - const dict = useDictionary(); - - if (Components === undefined) { - return null; +/** + * Blocknote v0.54.0 + * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/SideMenuController.tsx#L21 + * + * Blocknote changed the way the side menu is positioned. + * Because we are modifying the height of the heading blocks we need to adjust the offset of the side menu to match the + * new height of the heading blocks. + * + * @todo Try to see with Blocknote if we can have a better way to handle this, maybe a prop to set the offset of the side menu. + */ +function getBlockOffset( + editor: DocsBlockNoteEditor, + block: Block, +): number { + if (block.type === 'heading') { + switch (block.props.level) { + case 1: + return 13; + case 2: + return 7; + case 3: + return 4; + default: + return 0; + } } + // File blocks without a URL all render the same "Add file" button, + // regardless of their type. + if ( + editor.schema.blockSpecs[block.type]?.implementation.meta + ?.fileBlockAccept && + (!('url' in block.props) || !block.props.url) + ) { + return 12; + } + + if (block.type === 'file') { + return 4; + } + + if (block.type === 'audio' || block.type === 'table') { + return 15; + } + + return 0; +} + +export const DocsSideMenu = () => { + const editor = useBlockNoteEditor< + DocsBlockSchema, + DocsInlineContentSchema, + DocsStyleSchema + >(); + const block = useExtensionState(SideMenuExtension, { + selector: (state) => + state?.block as Block< + DocsBlockSchema, + DocsInlineContentSchema, + DocsStyleSchema + > | null, + }); + + const floatingUIOptions = useMemo( + () => ({ + useFloatingOptions: { + middleware: [ + offset({ crossAxis: block ? getBlockOffset(editor, block) : 0 }), + ], + }, + }), + [editor, block], + ); + return ( - - - - - - - - - - - - - - - - - - - - - - - + } + /> ); }; - -export const DocsSideMenu = () => ( - } - /> -); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/SideMenuSeparator.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/SideMenuSeparator.tsx new file mode 100644 index 000000000..8909f6922 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/SideMenuSeparator.tsx @@ -0,0 +1,38 @@ +import { SideMenuExtension } from '@blocknote/core/extensions'; +import { useBlockNoteEditor, useExtensionState } from '@blocknote/react'; + +import { type BoxType, HorizontalSeparator } from '@/components'; + +import type { DocsBlockNoteEditor } from '../../types'; + +type SideMenuSeparatorProps = { + typeDisplay?: 'table'; + typeHide?: 'codeBlock'; +} & BoxType; + +export const SideMenuSeparator = ({ + typeDisplay, + typeHide, + ...props +}: SideMenuSeparatorProps) => { + const editor: DocsBlockNoteEditor = useBlockNoteEditor(); + const block = useExtensionState(SideMenuExtension, { + editor, + selector: (state) => state?.block, + }); + + if ( + typeDisplay && + (block === undefined || + block.type !== typeDisplay || + !editor.settings.tables.headers) + ) { + return null; + } + + if (typeHide && (block === undefined || block.type === typeHide)) { + return null; + } + + return ; +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/TableHeaderSeparator.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/TableHeaderSeparator.tsx deleted file mode 100644 index bae9030f2..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/TableHeaderSeparator.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { SideMenuExtension } from '@blocknote/core/extensions'; -import { useBlockNoteEditor, useExtensionState } from '@blocknote/react'; - -import { HorizontalSeparator } from '@/components'; - -import type { DocsBlockNoteEditor } from '../../types'; - -export const TableHeaderSeparator = () => { - const editor: DocsBlockNoteEditor = useBlockNoteEditor(); - const block = useExtensionState(SideMenuExtension, { - editor, - selector: (state) => state?.block, - }); - - if ( - block === undefined || - block.type !== 'table' || - !editor.settings.tables.headers - ) { - return null; - } - - return ; -}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx index d5bc96493..f43d5f014 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx @@ -192,9 +192,6 @@ export const DocsEditorStyle = createGlobalStyle` var(--c--contextuals--border--semantic--brand--tertiary); background: var(--c--contextuals--border--semantic--brand--secondary); } - .bn-side-menu[data-block-type='divider'] { - height: 28px; - } /** * Checklist items @@ -251,15 +248,6 @@ export const DocsEditorStyle = createGlobalStyle` --level: 0.875rem; } } - .bn-side-menu[data-block-type='heading'][data-level='1'] { - height: 54px; - } - .bn-side-menu[data-block-type='heading'][data-level='2'] { - height: 43px; - } - .bn-side-menu[data-block-type='heading'][data-level='3'] { - height: 35px; - } & .bn-default-styles h1 { font-size: 1.875rem; } @@ -309,15 +297,6 @@ export const DocsEditorStyle = createGlobalStyle` & .bn-editor { padding-right: 10px; } - .bn-side-menu[data-block-type='heading'][data-level='1'] { - height: 46px; - } - .bn-side-menu[data-block-type='heading'][data-level='2'] { - height: 40px; - } - .bn-side-menu[data-block-type='heading'][data-level='3'] { - height: 40px; - } [data-content-type='heading'] { --level: 1.6rem; &[data-level='2'] {