mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-18 14:54:23 +01:00
feat: text editor foundations, toolbar list, toolbar btns
This commit is contained in:
5435
package-lock.json
generated
5435
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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" />
|
||||
|
||||
20
src/Components/Inputs/TextEditor/TextEditor.stories.tsx
Normal file
20
src/Components/Inputs/TextEditor/TextEditor.stories.tsx
Normal 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 = {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
80
src/Components/Inputs/TextEditor/TextEditor.tsx
Normal file
80
src/Components/Inputs/TextEditor/TextEditor.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
154
src/Components/Inputs/TextEditor/Toolbar/ToolButton.tsx
Normal file
154
src/Components/Inputs/TextEditor/Toolbar/ToolButton.tsx
Normal 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;
|
||||
}
|
||||
36
src/Components/Inputs/TextEditor/Toolbar/Toolbar.tsx
Normal file
36
src/Components/Inputs/TextEditor/Toolbar/Toolbar.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user