feat: InsertImageModal, img cmd in TextEditor, Make AddComment Controlled

This commit is contained in:
MTG2000
2022-04-30 11:52:49 +03:00
parent 4f7411e049
commit 618b214853
12 changed files with 180 additions and 17 deletions

View File

@@ -0,0 +1,17 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import InsertImageModal from './InsertImageModal';
import { ModalsDecorator } from 'src/utils/storybook/decorators';
export default {
title: 'Shared/TextEditor/Insert Image Modal',
component: InsertImageModal,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof InsertImageModal>;
const Template: ComponentStory<typeof InsertImageModal> = (args) => <InsertImageModal {...args} />;
export const Default = Template.bind({});

View File

@@ -0,0 +1,78 @@
import React, { FormEvent, useState } from 'react'
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { motion } from 'framer-motion'
import { IoClose } from 'react-icons/io5'
import Button from 'src/Components/Button/Button'
interface Props extends ModalCard {
onInsert: (img: { src: string, alt?: string }) => void
}
export default function InsertImageModal({ onClose, direction, onInsert, ...props }: Props) {
const [urlInput, setUrlInput] = useState("")
const [altInput, setAltInput] = useState("")
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
if (urlInput.length > 10) {
onInsert({ src: urlInput, alt: altInput })
onClose?.();
}
}
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[660px] p-24 rounded-xl relative"
>
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold'>Add Image</h2>
<form onSubmit={handleSubmit}>
<div className="grid md:grid-cols-3 gap-16 mt-32">
<div className='md:col-span-2'>
<p className="text-body5">
Image URL
</p>
<div className="input-wrapper mt-8 relative">
<input
type='text'
className="input-text"
value={urlInput}
onChange={e => setUrlInput(e.target.value)}
placeholder='https://images.com/my-image'
/>
</div>
</div>
<div>
<p className="text-body5">
Alt Text
</p>
<div className="input-wrapper mt-8 relative">
<input
type='text'
className="input-text"
value={altInput}
onChange={e => setAltInput(e.target.value)}
placeholder=''
/>
</div>
</div>
</div>
<div className="flex gap-16 justify-end mt-32">
<Button onClick={onClose}>
Cancel
</Button>
<Button type='submit' color='primary' >
Add
</Button>
</div>
</form>
</motion.div>
)
}

View File

@@ -1,10 +1,12 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { FormProvider, useForm } from 'react-hook-form';
import { WithModals } from 'src/utils/storybook/decorators';
import TextEditor from './TextEditor';
export default {
title: 'Shared/TextEditor',
decorators: [WithModals],
component: TextEditor,
} as ComponentMeta<typeof TextEditor>;

View File

@@ -67,7 +67,7 @@ export default function TextEditor({ placeholder, initialContent }: Props) {
new CodeBlockExtension({
supportedLanguages: [javascript, typescript]
}),
new ImageExtension({ enableResizing: true }),
new ImageExtension(),
// new TrailingNodeExtension(),
// new TableExtension(),
new MarkdownExtension({ copyAsMarkdown: false }),

View File

@@ -1,6 +1,6 @@
import { useActive, useChainedCommands, useCommands } from '@remirror/react';
import { FiBold, FiItalic, FiType, FiUnderline, FiAlignCenter, FiAlignLeft, FiAlignRight, FiCode } from 'react-icons/fi'
import { FaListOl, FaListUl, FaUndo, FaRedo } from 'react-icons/fa'
import { FaListOl, FaListUl, FaUndo, FaRedo, FaImage } from 'react-icons/fa'
import {
Menu,
@@ -10,6 +10,8 @@ import {
import '@szhsin/react-menu/dist/index.css';
import '@szhsin/react-menu/dist/transitions/slide.css';
import { BiCodeCurly } from 'react-icons/bi';
import { useAppDispatch } from 'src/utils/hooks';
import { openModal } from 'src/redux/features/modals.slice';
interface Props {
cmd: Command
@@ -32,6 +34,7 @@ export default function ToolButton({ cmd: _cmd, classes }: Props) {
const commands = useCommands();
const active = useActive();
const dispatch = useAppDispatch()
// const chain = useChainedCommands();
// commands.toggleCo
@@ -71,6 +74,38 @@ export default function ToolButton({ cmd: _cmd, classes }: Props) {
}
if (_cmd === 'img') {
const { activeCmd, cmd, tip, Icon } = cmdToBtn[_cmd];
const onClick = () => {
dispatch(openModal({
Modal: "InsertImageModal",
props: {
onInsert: ({ src, alt }) => {
commands.insertImage({
src,
alt,
})
}
}
}))
}
return (
<button
type='button'
data-tip={tip}
className={`
${buttonClasses}
${(activeCmd && active[activeCmd]()) && activeClasses}
${commands[cmd].enabled({ src: "" }) ? enabledClasses : disabledClasses}
`}
onClick={onClick}
>
<Icon className={iconClasses} />
</button>
)
}
if (isCommand(_cmd)) {
const { activeCmd, cmd, tip, Icon } = cmdToBtn[_cmd]
@@ -176,6 +211,12 @@ const cmdToBtn = {
tip: "Code Block",
Icon: BiCodeCurly,
},
img: {
cmd: 'insertImage',
activeCmd: 'image',
tip: "Insert Image",
Icon: FaImage,
},
} as const

View File

@@ -11,7 +11,7 @@ export default function Toolbar() {
return (
<div className='flex gap-24 bg-gray-100'>
<div className='flex flex-wrap gap-24 bg-gray-100'>
<div className="flex">
<ToolButton cmd='heading' />
<ToolButton cmd='bold' />
@@ -26,6 +26,7 @@ export default function Toolbar() {
<ToolButton cmd='rightAlign' />
<ToolButton cmd='bulletList' />
<ToolButton cmd='orderedList' />
<ToolButton cmd='img' />
</div>

View File

@@ -14,11 +14,12 @@ import {
PlaceholderExtension,
} from 'remirror/extensions';
import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import TextEditorComponents from 'src/Components/Inputs/TextEditor';
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar';
import Toolbar from './Toolbar';
import Button from 'src/Components/Button/Button';
import { debounce } from 'remirror';
interface Props {
@@ -38,6 +39,8 @@ export default function AddComment({ initialContent, name }: Props) {
return extension;
}, []);
const valueRef = useRef<string>("");
const extensions = useCallback(
() => [
@@ -60,33 +63,40 @@ export default function AddComment({ initialContent, name }: Props) {
);
const { manager } = useRemirror({
const { manager, state, onChange, } = useRemirror({
extensions,
stringHandler: 'markdown',
content: initialContent ?? ''
});
const submitComment = () => {
console.log(valueRef.current);
manager.view.updateState(manager.createState({ content: manager.createEmptyDoc() }))
}
return (
<div className={`remirror-theme ${styles.wrapper} p-24 border rounded-12`}>
<Remirror
manager={manager}
initialContent={initialContent}
state={state}
onChange={e => {
const markdown = e.helpers.getMarkdown(e.state)
valueRef.current = markdown;
onChange(e);
}}
>
<div className="flex gap-16 items-start pb-24 border-b border-gray-200 focus-within:border-primary-500">
<div className="mt-16 shrink-0"><Avatar width={48} src='https://i.pravatar.cc/150?img=1' /></div>
{/* <textarea
rows={2}
className="w-full border-0 text-gray-500 font-medium focus:!ring-0 resize-none"
placeholder='Leave a comment...'
ref={textAreaRef}
/> */}
<div className="flex-grow">
<EditorComponent />
<EditorComponent
/>
</div>
</div>
<div className="flex gap-16 mt-16">
<Toolbar />
<Button color='primary' className='ml-auto'>Submit</Button>
<Button onClick={submitComment} color='primary' className='ml-auto'>Submit</Button>
</div>
{/* <TextEditorComponents.SaveModule name={name} /> */}
</Remirror>

View File

@@ -67,7 +67,7 @@ export default function ContentEditor({ placeholder, initialContent, name }: Pro
new CodeBlockExtension({
supportedLanguages: [javascript, typescript]
}),
new ImageExtension({ enableResizing: true }),
new ImageExtension(),
// new TrailingNodeExtension(),
// new TableExtension(),
new MarkdownExtension({ copyAsMarkdown: false }),

View File

@@ -7,7 +7,7 @@ interface Props {
export default function Toolbar() {
return (
<div className='flex gap-36 bg-gray-100'>
<div className='flex flex-wrap gap-36 bg-gray-100'>
<div className="flex">
<TextEditorComponents.ToolButton cmd='heading' />
<TextEditorComponents.ToolButton cmd='bold' />
@@ -22,6 +22,7 @@ export default function Toolbar() {
<TextEditorComponents.ToolButton cmd='rightAlign' />
<TextEditorComponents.ToolButton cmd='bulletList' />
<TextEditorComponents.ToolButton cmd='orderedList' />
<TextEditorComponents.ToolButton cmd='img' />
</div>

View File

@@ -1,4 +1,5 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { WithModals } from 'src/utils/storybook/decorators';
import CreatePostPage from './CreatePostPage';
@@ -8,6 +9,9 @@ export default {
argTypes: {
backgroundColor: { control: 'color' },
},
decorators: [
WithModals
]
} as ComponentMeta<typeof CreatePostPage>;

View File

@@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Login_ScanningWalletCard, Login_ExternalWalletCard, Login_NativeWalletCard, Login_SuccessCard } from "src/Components/Modals/Login";
import { ProjectDetailsCard } from "src/features/Projects/pages/ProjectPage/ProjectDetailsCard";
import VoteCard from "src/features/Projects/pages/ProjectPage/VoteCard/VoteCard";
import InsertImageModal from 'src/Components/Inputs/TextEditor/InsertImageModal/InsertImageModal'
import { Claim_FundWithdrawCard, Claim_CopySignatureCard, Claim_GenerateSignatureCard, Claim_SubmittedCard } from "src/features/Projects/pages/ProjectPage/ClaimProject";
import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer";
import { ComponentProps } from "react";
@@ -26,6 +27,7 @@ export const ALL_MODALS = {
Claim_CopySignatureCard,
Claim_SubmittedCard,
Claim_FundWithdrawCard,
InsertImageModal
}
type ExcludeBaseModalProps<U> = Omit<U, keyof ModalCard>

View File

@@ -18,6 +18,7 @@ import 'react-loading-skeleton/dist/skeleton.css'
import { ApolloProvider } from '@apollo/client';
import { apolloClient } from '../apollo';
import { FormProvider, useForm, UseFormProps } from 'react-hook-form';
import ModalsContainer from 'src/Components/Modals/ModalsContainer/ModalsContainer';
// Enable the Mocks Service Worker
@@ -25,7 +26,7 @@ import { FormProvider, useForm, UseFormProps } from 'react-hook-form';
if (process.env.STORYBOOK_ENABLE_MOCKS) {
worker.start({
onUnhandledRequest: 'bypass'
onUnhandledRequest: 'bypass',
})
}
@@ -106,6 +107,7 @@ export const centerDecorator: DecoratorFn = (Story) => {
</div>
}
export const WrapForm: (options?: Partial<UseFormProps>) => DecoratorFn = options => {
const Func: DecoratorFn = (Story) => {
const methods = useForm(options);
@@ -116,3 +118,8 @@ export const WrapForm: (options?: Partial<UseFormProps>) => DecoratorFn = option
return Func
}
export const WithModals: DecoratorFn = (Component) => <>
<Component />
<ModalsContainer />
</>