mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-28 23:03:41 +03:00
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:
parent
1ed31d9d68
commit
0c99bfbc31
@ -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) =>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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 = ({
|
||||
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user