Skip to content

Commit

Permalink
Merge pull request #401 from qoretechnologies/feature/rich-editor-sty…
Browse files Browse the repository at this point in the history
…ling

Added simple styling to rich text editor.
  • Loading branch information
Foxhoundn authored Jul 24, 2024
2 parents 0d7f35e + 1948cd8 commit 18db02a
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
20 changes: 19 additions & 1 deletion src/components/Effect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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`
Expand Down
105 changes: 85 additions & 20 deletions src/components/RichTextEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ export interface IReqoreRichTextEditorProps
panelProps?: IReqorePanelProps;

actions?: {
bold?: boolean;
italic?: boolean;
underline?: boolean;
code?: boolean;
styling?: boolean;
undo?: boolean;
redo?: boolean;
};
Expand Down Expand Up @@ -128,7 +125,15 @@ const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => {
}

return (
<ReqoreSpan inline {...attributes}>
<ReqoreSpan
inline
{...attributes}
effect={{
weight: leaf.bold ? 'bold' : 'normal',
italic: leaf.italic,
underline: leaf.underline,
}}
>
{children}
</ReqoreSpan>
);
Expand Down Expand Up @@ -228,35 +233,95 @@ 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<IReqorePanelAction[]>(() => {
const _actions: IReqorePanelAction[] = [...(panelProps?.actions || [])];

if (!actions) {
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 (
<ReqorePanel
Expand Down
43 changes: 36 additions & 7 deletions src/stories/RichTextEditor/RichTextEditor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ const meta = {
render: (args) => {
const [value, setValue] = useState(args.value);

return <ReqoreRichTextEditor {...args} value={value} onChange={(val) => setValue(val)} />;
return (
<ReqoreRichTextEditor
{...args}
value={value}
onChange={(val) => {
setValue(val);
args.onChange?.(val);
}}
/>
);
},
argTypes: {
...MinimalArg,
Expand Down Expand Up @@ -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]'));
},
};

0 comments on commit 18db02a

Please sign in to comment.