diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx index f33c113a..f819984b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx @@ -2,10 +2,17 @@ import { useBlockNoteEditor, useComponentsContext, useSelectedBlocks, + ComponentProps, } from '@blocknote/react'; -import { ReactNode, useMemo } from 'react'; +import { mergeRefs } from "@mantine/hooks"; -import { Box, BoxButton, Text } from '@/components'; +import { ReactNode, useMemo, forwardRef, useRef, useCallback, useState, createContext } from 'react'; + +import { + Menu as MantineMenu, +} from "@mantine/core"; + +import {Box, Text } from '@/components'; import { Doc } from '../../doc-management'; import { AIActions, useAIRewrite } from '../api/useAIRewrite'; @@ -32,116 +39,45 @@ export function AIGroupButton({ doc }: AIGroupButtonProps) { AI - - {[ - { - label: ( - <> - - text_fields - {' '} - Use as prompt - - ), - action: 'prompt', - }, - { - label: ( - <> - - refresh - {' '} - Rephrase - - ), - action: 'rephrase', - }, - { - label: ( - <> - - summarize - {' '} - Summarize - - ), - action: 'summarize', - }, - { - label: ( - <> - - check - {' '} - Correct - - ), - action: 'correct', - }, - { - label: ( - <> - - translate - {' '} - French - - ), - action: 'translate_fr', - }, - { - label: ( - <> - - translate - {' '} - English - - ), - action: 'translate_en', - }, - { - label: ( - <> - - translate - {' '} - German - - ), - action: 'translate_de', - }, - ].map(({ label, action }) => ( - - ))} + + text_fields}> + Use as prompt + + refresh}> + Rephrase + + summarize}> + Summarize + + check}> + Correct + + ); } -interface AIButtonProps { +interface AIMenuItemProps { action: AIActions; - label: ReactNode; docId: Doc['id']; + children: ReactNode; + icon: ReactNode; } -const AIButton = ({ action, label, docId }: AIButtonProps) => { +const AIMenuItem = ({ action, docId, children, icon }: AIMenuItemProps) => { const editor = useBlockNoteEditor(); + const Components = useComponentsContext()!; const { mutateAsync: requestAI, isPending } = useAIRewrite(); - const handleRephraseAI = async (action: AIActions) => { + const handleAIAction = useCallback(async () => { const textCursorPosition = editor.getSelectedText(); const newText = await requestAI({ @@ -152,27 +88,122 @@ const AIButton = ({ action, label, docId }: AIButtonProps) => { editor.insertInlineContent([ newText, - //{ type: 'text', text: 'World', styles: { bold: true } }, ]); - }; + }, [editor, requestAI, docId, action]); return ( - void handleRephraseAI(action)} - $hasTransition - $radius="6px" - $width="100%" - $justify="flex-start" - $align="center" - $gap="1rem" - $css="&:hover{background-color: #f2f8ff;}text-transform: capitalize!important;" - $direction="row" + } > - {label} - {isPending && } - + {children} + ); }; + +interface TranslateMenuProps { + docId: Doc['id']; +} + +const TranslateMenu = ({ docId }: TranslateMenuProps) => { + const Components = useComponentsContext()!; + + return ( + translate} close> + + + Translate + + + + + English + + + French + + + German + + + + ); +}; + +const SubMenuContext = createContext< + | { + onMenuMouseOver: () => void; + onMenuMouseLeave: () => void; + } + | undefined +>(undefined); + + +const SubMenu = forwardRef< + HTMLButtonElement, + ComponentProps["Generic"]["Menu"]["Root"] +>((props, ref) => { + const { + children, + onOpenChange, + position, + icon, + sub, // not used + ...rest + } = props; + + const [opened, setOpened] = useState(false); + + const itemRef = useRef(null); + + const menuCloseTimer = useRef | undefined>(); + + const mouseLeave = useCallback(() => { + if (menuCloseTimer.current) { + clearTimeout(menuCloseTimer.current); + } + menuCloseTimer.current = setTimeout(() => { + setOpened(false); + }, 250); + }, []); + + const mouseOver = useCallback(() => { + if (menuCloseTimer.current) { + clearTimeout(menuCloseTimer.current); + } + setOpened(true); + }, []); + + return ( + + + onOpenChange?.(false)} + onOpen={() => onOpenChange?.(true)} + position={position}> + {children} + + + + ); +}); \ No newline at end of file