New sidemenu for notes editor (#6527)

@Bonapara @lucasbordeau

This PR addresses issue #6489:

- Created an entire sidemenu for the block editor.

## Review Request

Please review the implementation of the custom sidemenu. 

## Outstanding Issues

1. Sidemenu Positioning:
   - The current placement is determined by the BlockNote package.
   - I need assistance in positioning it according to the Figma designs.
- Attempted adding margin to the sidemenu, but this solution doesn't
scale well across different screen sizes.

2. Props Spreading in `CustomSidemenu.tsx`:
- Unable to avoid props spreading due to the third-party BlockNote
components.
   - Added eslint-disable comments as a temporary solution.

Your insights on these challenges would be greatly appreciated,
especially regarding the sidemenu positioning and any potential
alternatives to props spreading.




https://github.com/user-attachments/assets/4914a037-a115-4189-88bc-a41d121d309d

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
nitin 2024-08-07 11:53:05 +05:30 committed by GitHub
parent 1ed31d9d68
commit 0c99bfbc31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 200 additions and 2 deletions

View File

@ -7,6 +7,7 @@ import { ClipboardEvent } from 'react';
import { blockSchema } from '@/activities/blocks/schema';
import { getSlashMenu } from '@/activities/blocks/slashMenu';
import { CustomSideMenu } from '@/ui/input/editor/components/CustomSideMenu';
import {
CustomSlashMenu,
SuggestionItem,
@ -35,7 +36,12 @@ const StyledEditor = styled.div`
font-style: normal;
}
& .mantine-ActionIcon-icon {
height: 20px;
width: 20px;
background: transparent;
}
& .bn-editor {
padding-inline: 36px;
}
& .bn-container .bn-drag-handle {
width: 20px;
@ -45,6 +51,48 @@ const StyledEditor = styled.div`
display: flex;
align-items: center;
}
& .bn-drag-handle-menu {
background: ${({ theme }) => theme.background.transparent.secondary};
backdrop-filter: blur(12px) saturate(200%) contrast(50%) brightness(130%);
box-shadow:
0px 2px 4px rgba(0, 0, 0, 0.04),
2px 4px 16px rgba(0, 0, 0, 0.12);
min-width: 160px;
min-height: 96px;
padding: 4px;
border-radius: 8px;
border: 1px solid ${({ theme }) => theme.border.color.medium};
left: 26px;
}
& .mantine-Menu-item {
background-color: transparent;
min-width: 152px;
min-height: 32px;
font-style: normal;
font-family: ${({ theme }) => theme.font.family};
font-weight: ${({ theme }) => theme.font.weight.regular};
color: ${({ theme }) => theme.font.color.secondary};
}
& .mantine-ActionIcon-root:hover {
box-shadow:
0px 0px 4px rgba(0, 0, 0, 0.08),
0px 2px 4px rgba(0, 0, 0, 0.04);
background: ${({ theme }) => theme.background.transparent.primary};
backdrop-filter: blur(20px);
border: 1px solid ${({ theme }) => theme.border.color.light};
}
& .bn-side-menu .mantine-UnstyledButton-root:not(.mantine-Menu-item) svg {
height: 20px;
width: 20px;
}
& .bn-mantine .bn-side-menu > [draggable='true'] {
margin-bottom: 5px;
}
& .bn-color-picker-dropdown {
margin-left: 8px;
}
`;
export const BlockEditor = ({
@ -83,7 +131,9 @@ export const BlockEditor = ({
editor={editor}
theme={blockNoteTheme}
slashMenu={false}
sideMenu={false}
>
<CustomSideMenu editor={editor} />
<SuggestionMenuController
triggerCharacter={'/'}
getItems={async (query) =>

View File

@ -0,0 +1,42 @@
import { blockSchema } from '@/activities/blocks/schema';
import { useComponentsContext } from '@blocknote/react';
type CustomAddBlockItemProps = {
editor: typeof blockSchema.BlockNoteEditor;
children: React.ReactNode; // Adding the children prop
};
type ContentItem = {
type: string;
text: string;
styles: any;
};
export const CustomAddBlockItem = ({
editor,
children,
}: CustomAddBlockItemProps) => {
const Components = useComponentsContext();
const handleClick = () => {
const blockIdentifier = editor.getTextCursorPosition().block;
const currentBlockContent = blockIdentifier?.content as
| Array<ContentItem>
| undefined;
const [firstElement] = currentBlockContent || [];
if (firstElement === undefined) {
editor.openSelectionMenu('/');
} else {
editor.sideMenu.addBlock();
editor.openSelectionMenu('/');
editor.sideMenu.unfreezeMenu();
}
};
return (
<Components.Generic.Menu.Item onClick={handleClick}>
{children}
</Components.Generic.Menu.Item>
);
};

View File

@ -0,0 +1,67 @@
import { blockSchema } from '@/activities/blocks/schema';
import { CustomAddBlockItem } from '@/ui/input/editor/components/CustomAddBlockItem';
import { CustomSideMenuOptions } from '@/ui/input/editor/components/CustomSideMenuOptions';
import {
BlockColorsItem,
DragHandleButton,
DragHandleMenu,
RemoveBlockItem,
SideMenu,
SideMenuController,
} from '@blocknote/react';
import styled from '@emotion/styled';
import { IconColorSwatch, IconPlus, IconTrash } from 'twenty-ui';
type CustomSideMenuProps = {
editor: typeof blockSchema.BlockNoteEditor;
};
const StyledDivToCreateGap = styled.div`
width: ${({ theme }) => theme.spacing(2)};
`;
export const CustomSideMenu = ({ editor }: CustomSideMenuProps) => {
return (
<SideMenuController
sideMenu={(props) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<SideMenu {...props}>
<DragHandleButton
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
dragHandleMenu={(props) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<DragHandleMenu {...props}>
<CustomAddBlockItem editor={editor}>
<CustomSideMenuOptions
LeftIcon={IconPlus}
text={'Add Block'}
Variant="normal"
/>
</CustomAddBlockItem>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<BlockColorsItem {...props}>
<CustomSideMenuOptions
LeftIcon={IconColorSwatch}
text={'Change Color'}
Variant="normal"
/>
</BlockColorsItem>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<RemoveBlockItem {...props}>
{' '}
<CustomSideMenuOptions
LeftIcon={IconTrash}
text={'Delete'}
Variant="danger"
/>
</RemoveBlockItem>
</DragHandleMenu>
)}
/>
<StyledDivToCreateGap />
</SideMenu>
)}
/>
);
};

View File

@ -0,0 +1,39 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComponent } from 'twenty-ui';
const StyledContainer = styled.div<{ Variant: Variants }>`
color: ${({ theme, Variant }) =>
Variant === 'danger' ? theme.color.red : 'inherit'};
align-items: center;
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledTextContainer = styled.div``;
type CustomSideMenuOptionsProps = {
LeftIcon: IconComponent; // Any valid React node (e.g., a component)
Variant: Variants;
text: string;
};
type Variants = 'normal' | 'danger';
export const CustomSideMenuOptions = ({
LeftIcon,
Variant,
text,
}: CustomSideMenuOptionsProps) => {
const theme = useTheme();
return (
<StyledContainer Variant={Variant}>
<LeftIcon
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
></LeftIcon>
<StyledTextContainer>{text}</StyledTextContainer>
</StyledContainer>
);
};

View File

@ -4,7 +4,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import styled from '@emotion/styled';
const StyledShowPageActivityContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(2)};
margin-top: ${({ theme }) => theme.spacing(6)};
width: 100%;
`;
export const ShowPageActivityContainer = ({

View File

@ -1,5 +1,5 @@
import { MouseEvent } from 'react';
import styled from '@emotion/styled';
import { MouseEvent } from 'react';
import { HOVER_BACKGROUND, IconComponent } from 'twenty-ui';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';