feat: text editor foundations, toolbar list, toolbar btns

This commit is contained in:
MTG2000
2022-04-26 18:25:14 +03:00
parent 563650538c
commit 2c615bcf9f
8 changed files with 5648 additions and 96 deletions

5435
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,9 @@
"@react-hookz/web": "^13.2.1",
"@react-spring/web": "^9.4.4",
"@reduxjs/toolkit": "^1.8.1",
"@remirror/pm": "^1.0.16",
"@remirror/react": "^1.0.34",
"@szhsin/react-menu": "^3.0.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
"@testing-library/user-event": "^13.5.0",
@@ -50,6 +53,7 @@
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-topbar-progress-indicator": "^4.1.1",
"remirror": "^1.0.77",
"typescript": "^4.6.3",
"web-vitals": "^2.1.4",
"webln": "^0.3.0",

View File

@@ -10,7 +10,8 @@ export default {
const Template: ComponentStory<typeof CopyToClipboard> = (args) => <div className="flex h-[400px] justify-center items-center"><div className="input-wrapper mt-32 max-w-[320px] mx-auto">
<input
className="input-field overflow-ellipsis"
type='text'
className="input-text overflow-ellipsis"
value={'Some Text To Copy'}
/>
<CopyToClipboard {...args} text="Some Text To Copy" />

View File

@@ -0,0 +1,20 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import TextEditor from './TextEditor';
export default {
title: 'Shared/TextEditor',
component: TextEditor,
} as ComponentMeta<typeof TextEditor>;
const Template: ComponentStory<typeof TextEditor> = (args) => <TextEditor {...args as any} />
export const Default = Template.bind({});
Default.args = {
}

View File

@@ -0,0 +1,80 @@
import 'remirror/styles/all.css';
import {
BlockquoteExtension,
BoldExtension,
BulletListExtension,
CodeBlockExtension,
CodeExtension,
HardBreakExtension,
HeadingExtension,
ItalicExtension,
LinkExtension,
ListItemExtension,
MarkdownExtension,
NodeFormattingExtension,
OrderedListExtension,
PlaceholderExtension,
StrikeExtension,
TableExtension,
TrailingNodeExtension,
UnderlineExtension,
} from 'remirror/extensions';
import { ExtensionPriority } from 'remirror';
import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
import { useCallback } from 'react';
import Toolbar from './Toolbar/Toolbar';
interface Props {
placeholder?: string;
initialContent?: string;
}
export default function TextEditor({ placeholder, initialContent = 'Hello everyone\n How are you doing today ??' }: Props) {
const extensions = useCallback(
() => [
new PlaceholderExtension({ placeholder }),
new LinkExtension({ autoLink: true }),
new BoldExtension(),
// new StrikeExtension(),
new UnderlineExtension(),
new ItalicExtension(),
new HeadingExtension(),
new LinkExtension(),
new BlockquoteExtension(),
new BulletListExtension(),
new OrderedListExtension(),
new ListItemExtension({ priority: ExtensionPriority.High, enableCollapsible: true }),
// new TaskListExtension(),
// new CodeExtension(),
// new CodeBlockExtension(),
// new TrailingNodeExtension(),
// new TableExtension(),
new MarkdownExtension({ copyAsMarkdown: false }),
new NodeFormattingExtension(),
/**
* `HardBreakExtension` allows us to create a newline inside paragraphs.
* e.g. in a list item
*/
new HardBreakExtension(),
],
[placeholder],
);
const { manager, } = useRemirror({
extensions,
stringHandler: 'markdown',
});
return (
<div className='remirror-theme'>
<Remirror manager={manager} initialContent={initialContent}>
<Toolbar />
<EditorComponent />
</Remirror>
</div>
);
};

View File

@@ -0,0 +1,154 @@
import { useActive, useChainedCommands, useCommands } from '@remirror/react';
import { FiBold, FiItalic, FiType, FiUnderline, FiAlignCenter, FiAlignLeft, FiAlignRight } from 'react-icons/fi'
import { FaListOl, FaListUl, FaUndo, FaRedo } from 'react-icons/fa'
import {
Menu,
MenuItem,
MenuButton
} from '@szhsin/react-menu';
import '@szhsin/react-menu/dist/index.css';
import '@szhsin/react-menu/dist/transitions/slide.css';
interface Props {
cmd: Command
}
export default function ToolButton({ cmd: _cmd }: Props) {
const commands = useCommands();
const active = useActive();
const chain = useChainedCommands();
// commands.undo
// active.list
if (_cmd === 'heading') {
return <Menu menuButton={
<MenuButton>
<button
className={`
w-36 h-36 flex justify-center items-center
${active.heading({}) ?
'font-bold bg-gray-200 text-black'
:
'hover:bg-gray-100'
}
${!commands.toggleHeading.enabled() && 'opacity-40 text-gray-600 pointer-events-none'}
`}
>
<FiType />
</button>
</MenuButton>
} transition>
{Array(6).fill(0).map((_, idx) => <MenuItem
className={`
py-8 px-16 hover:bg-gray-100
${active.heading({ level: idx + 1 }) && 'font-bold bg-gray-200'}
`}
onClick={() => chain.toggleHeading({ level: idx + 1 }).focus().run()}
>
Heading{idx + 1}
</MenuItem>)}
</Menu>
}
if (isCommand(_cmd)) {
const { activeCmd, cmd, Icon } = cmdToBtn[_cmd]
return (
<button
className={`
w-36 h-36 flex justify-center items-center
${(activeCmd && active[activeCmd]()) ?
'font-bold bg-gray-200 text-black'
:
'hover:bg-gray-100'
}
${!commands[cmd].enabled() && 'opacity-40 text-gray-600 pointer-events-none'}
`}
onClick={() => chain[cmd]().focus().run()}
>
<Icon />
</button>
)
}
return <></>
}
const cmdToBtn = {
'bold': {
cmd: 'toggleBold',
activeCmd: 'bold',
Icon: FiBold
},
'italic': {
cmd: 'toggleItalic',
activeCmd: 'italic',
Icon: FiItalic
},
underline: {
cmd: 'toggleUnderline',
activeCmd: 'underline',
Icon: FiUnderline
},
heading: {
cmd: 'toggleHeading',
activeCmd: 'heading',
Icon: FiType,
},
leftAlign: {
cmd: 'leftAlign',
activeCmd: null,
Icon: FiAlignLeft,
},
centerAlign: {
cmd: 'centerAlign',
activeCmd: null,
Icon: FiAlignCenter,
},
rightAlign: {
cmd: 'rightAlign',
activeCmd: null,
Icon: FiAlignRight,
},
bulletList: {
cmd: 'toggleBulletList',
activeCmd: 'bulletList',
Icon: FaListUl,
},
orderedList: {
cmd: 'toggleOrderedList',
activeCmd: 'orderedList',
Icon: FaListOl,
},
undo: {
cmd: 'undo',
activeCmd: null,
Icon: FaUndo,
},
redo: {
cmd: 'redo',
activeCmd: null,
Icon: FaRedo,
},
} as const
type Command = keyof typeof cmdToBtn
function isCommand(cmd: string): cmd is Command {
return cmd in cmdToBtn;
}

View File

@@ -0,0 +1,36 @@
import React from 'react'
import { ToolbarItemUnion, } from '@remirror/react';
import ToolButton from './ToolButton';
interface Props {
items: ToolbarItemUnion
}
export default function Toolbar() {
return (
<div className='flex gap-24'>
<div className="flex">
<ToolButton cmd='bold' />
<ToolButton cmd='italic' />
<ToolButton cmd='underline' />
<ToolButton cmd='heading' />
</div>
<div className="flex">
<ToolButton cmd='leftAlign' />
<ToolButton cmd='centerAlign' />
<ToolButton cmd='rightAlign' />
<ToolButton cmd='bulletList' />
<ToolButton cmd='orderedList' />
</div>
<div className="flex ml-auto">
<ToolButton cmd='undo' />
<ToolButton cmd='redo' />
</div>
</div>
)
}

View File

@@ -115,3 +115,15 @@ svg {
display: none;
}
}
.remirror-editor-wrapper ul {
list-style: disc !important;
padding: revert;
margin: revert;
}
.remirror-editor-wrapper ol {
list-style: decimal !important;
padding: revert;
margin: revert;
}