From 1948cd8d474d7e2ab5b317eee579108d450cfa8f Mon Sep 17 00:00:00 2001 From: Foxhoundn Date: Wed, 24 Jul 2024 11:19:12 +0200 Subject: [PATCH] Added simple styling to rich text editor. --- package.json | 2 +- src/components/Effect/index.tsx | 20 +++- src/components/RichTextEditor/index.tsx | 105 ++++++++++++++---- .../RichTextEditor/RichTextEditor.stories.tsx | 43 +++++-- 4 files changed, 141 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 2cddab7d..15f8edd3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@qoretechnologies/reqore", - "version": "0.48.1", + "version": "0.48.2", "description": "ReQore is a highly theme-able and modular UI library for React", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/components/Effect/index.tsx b/src/components/Effect/index.tsx index 985c4b44..db43b077 100644 --- a/src/components/Effect/index.tsx +++ b/src/components/Effect/index.tsx @@ -106,9 +106,13 @@ export interface IReqoreEffect extends IReqoreEffectFilters { animationSpeed?: 1 | 2 | 3 | 4 | 5; }; noWrap?: boolean; - color?: TReqoreEffectColor; spaced?: number; + weight?: number | 'thin' | 'light' | 'normal' | 'bold' | 'thick'; + italic?: boolean; + underline?: boolean; + color?: TReqoreEffectColor; + uppercase?: boolean; textSize?: TSizes | string; textAlign?: 'left' | 'center' | 'right'; @@ -352,6 +356,20 @@ export const StyledEffect = styled.span` `; }} +${({ effect }: IReqoreTextEffectProps) => + effect && effect.italic + ? css` + font-style: italic; + ` + : undefined} + +${({ effect }: IReqoreTextEffectProps) => + effect && effect.underline + ? css` + text-decoration: underline; + ` + : undefined} + ${({ effect }: IReqoreTextEffectProps) => effect && effect.noWrap ? css` diff --git a/src/components/RichTextEditor/index.tsx b/src/components/RichTextEditor/index.tsx index 91f9ee56..af84ad0b 100644 --- a/src/components/RichTextEditor/index.tsx +++ b/src/components/RichTextEditor/index.tsx @@ -57,10 +57,7 @@ export interface IReqoreRichTextEditorProps panelProps?: IReqorePanelProps; actions?: { - bold?: boolean; - italic?: boolean; - underline?: boolean; - code?: boolean; + styling?: boolean; undo?: boolean; redo?: boolean; }; @@ -128,7 +125,15 @@ const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => { } return ( - + {children} ); @@ -228,6 +233,24 @@ export const ReqoreRichTextEditor = ({ return size(value) === 1 && size(value[0].children) === 1 && value[0].children[0].text === ''; }, [value]); + const isMarkActive = useCallback( + (editor: Editor, format: string) => { + const marks = Editor.marks(editor); + return marks ? marks[format] === true : false; + }, + [editor, Editor, target] + ); + + const toggleMark = useCallback((editor: Editor, format: string) => { + const isActive = isMarkActive(editor, format); + + if (isActive) { + Editor.removeMark(editor, format); + } else { + Editor.addMark(editor, format, true); + } + }, []); + const panelActions = useMemo(() => { const _actions: IReqorePanelAction[] = [...(panelProps?.actions || [])]; @@ -235,28 +258,70 @@ export const ReqoreRichTextEditor = ({ return _actions; } - if (actions.undo) { + if (actions.styling) { _actions.push({ - disabled: editor.history.undos.length === 0, - icon: 'ArrowGoBackLine', - onClick: () => { - editor.undo(); - }, + group: [ + { + icon: 'Bold', + compact: true, + intent: isMarkActive(editor, 'bold') ? 'info' : undefined, + onMouseDown: () => { + toggleMark(editor, 'bold'); + }, + }, + { + icon: 'Italic', + compact: true, + intent: isMarkActive(editor, 'italic') ? 'info' : undefined, + onMouseDown: () => { + toggleMark(editor, 'italic'); + }, + }, + { + icon: 'Underline', + compact: true, + intent: isMarkActive(editor, 'underline') ? 'info' : undefined, + onMouseDown: () => { + toggleMark(editor, 'underline'); + }, + }, + ], }); } - if (actions.redo) { - _actions.push({ - disabled: editor.history.redos.length === 0, - icon: 'ArrowGoForwardLine', - onClick: () => { - editor.redo(); - }, - }); + if (actions.undo || actions.redo) { + const undoRedoActions: IReqorePanelAction = { + fixed: true, + group: [], + }; + + if (actions.undo) { + undoRedoActions.group.push({ + disabled: editor.history.undos.length === 0, + compact: true, + icon: 'ArrowGoBackLine', + onClick: () => { + editor.undo(); + }, + }); + } + + if (actions.redo) { + undoRedoActions.group.push({ + disabled: editor.history.redos.length === 0, + compact: true, + icon: 'ArrowGoForwardLine', + onClick: () => { + editor.redo(); + }, + }); + } + + _actions.push(undoRedoActions); } return _actions; - }, [actions, value]); + }, [actions, value, target, Editor.marks(editor), editor]); return ( { const [value, setValue] = useState(args.value); - return setValue(val)} />; + return ( + { + setValue(val); + args.onChange?.(val); + }} + /> + ); }, argTypes: { ...MinimalArg, @@ -184,22 +193,42 @@ export const WithCustomTags: Story = { export const WithActions: Story = { args: { actions: { + styling: true, undo: true, redo: true, }, + onChange: (val) => console.log(val), }, play: async () => { await userEvent.click(document.querySelector('div[contenteditable]')); await userEvent.keyboard('Hello'); - await expect(document.querySelector('.reqore-button')).toBeEnabled(); - await expect(document.querySelectorAll('.reqore-button')[1]).toBeDisabled(); + await expect(document.querySelectorAll('.reqore-button')[3]).toBeEnabled(); + await expect(document.querySelectorAll('.reqore-button')[4]).toBeDisabled(); - await userEvent.click(document.querySelector('.reqore-button')); - await expect(document.querySelector('.reqore-button')).toBeDisabled(); - await expect(document.querySelectorAll('.reqore-button')[1]).toBeEnabled(); + await userEvent.click(document.querySelectorAll('.reqore-button')[3]); + await expect(document.querySelectorAll('.reqore-button')[3]).toBeDisabled(); + await expect(document.querySelectorAll('.reqore-button')[4]).toBeEnabled(); - await userEvent.click(document.querySelectorAll('.reqore-button')[1]); + await userEvent.click(document.querySelectorAll('.reqore-button')[4]); await expect(document.querySelector('.reqore-textarea')).toHaveTextContent('Hello'); }, }; + +export const WithStyling: Story = { + args: { + actions: { + styling: true, + }, + value: [ + { + type: 'paragraph', + children: [{ text: 'This is a styled text', bold: true, italic: true, underline: true }], + }, + ], + onChange: (val) => console.log(val), + }, + play: async () => { + await userEvent.click(document.querySelector('div[contenteditable]')); + }, +};