degmods.com/src/components/Inputs.tsx

280 lines
7.0 KiB
TypeScript
Raw Normal View History

2024-08-27 10:53:34 +00:00
import Link from '@tiptap/extension-link'
import { Editor, EditorContent, useEditor } from '@tiptap/react'
2024-08-27 10:53:34 +00:00
import StarterKit from '@tiptap/starter-kit'
import React, { useEffect } from 'react'
import '../styles/styles.css'
2024-08-27 10:53:34 +00:00
import '../styles/tiptap.scss'
2024-07-21 16:30:03 +00:00
2024-07-12 09:22:31 +00:00
interface InputFieldProps {
label: string
description?: string
2024-07-21 16:30:03 +00:00
type?: 'text' | 'textarea' | 'richtext'
2024-07-12 09:22:31 +00:00
placeholder: string
name: string
inputMode?: 'url'
2024-07-21 16:30:03 +00:00
value: string
error?: string
2024-07-21 16:30:03 +00:00
onChange: (name: string, value: string) => void
2024-07-12 09:22:31 +00:00
}
2024-07-21 16:30:03 +00:00
export const InputField = React.memo(
({
label,
description,
type = 'text',
placeholder,
name,
inputMode,
value,
error,
2024-07-21 16:30:03 +00:00
onChange
}: InputFieldProps) => {
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
onChange(name, e.target.value)
}
return (
<div className='inputLabelWrapperMain'>
<label className='form-label labelMain'>{label}</label>
{description && <p className='labelDescriptionMain'>{description}</p>}
{type === 'textarea' ? (
<textarea
className='inputMain'
placeholder={placeholder}
name={name}
value={value}
onChange={handleChange}
></textarea>
) : type === 'richtext' ? (
2024-08-27 10:53:34 +00:00
<RichTextEditor
content={value}
updateContent={(content) => onChange(name, content)}
2024-07-21 16:30:03 +00:00
/>
) : (
<input
type={type}
className='inputMain'
placeholder={placeholder}
name={name}
inputMode={inputMode}
value={value}
onChange={handleChange}
/>
)}
{error && <InputError message={error} />}
2024-07-21 16:30:03 +00:00
</div>
)
}
2024-07-12 09:22:31 +00:00
)
type InputErrorProps = {
message: string
}
export const InputError = ({ message }: InputErrorProps) => {
if (!message) return null
return (
<div className='errorMain'>
<div className='errorMainColor'></div>
<p className='errorMainText'>{message}</p>
</div>
)
}
2024-07-12 09:22:31 +00:00
interface CheckboxFieldProps {
label: string
name: string
2024-07-21 16:30:03 +00:00
isChecked: boolean
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
2024-07-12 09:22:31 +00:00
}
2024-07-21 16:30:03 +00:00
export const CheckboxField = React.memo(
({ label, name, isChecked, handleChange }: CheckboxFieldProps) => (
<div className='inputLabelWrapperMain inputLabelWrapperMainAlt inputLabelWrapperMainAltStylized'>
<label className='form-label labelMain'>{label}</label>
2024-07-12 09:22:31 +00:00
<input
2024-07-21 16:30:03 +00:00
type='checkbox'
className='CheckboxMain'
name={name}
checked={isChecked}
onChange={handleChange}
2024-07-12 09:22:31 +00:00
/>
</div>
2024-07-21 16:30:03 +00:00
)
2024-07-12 09:22:31 +00:00
)
2024-08-27 10:53:34 +00:00
type RichTextEditorProps = {
content: string
updateContent: (updatedContent: string) => void
}
const RichTextEditor = ({ content, updateContent }: RichTextEditorProps) => {
const editor = useEditor({
extensions: [StarterKit, Link],
onUpdate: ({ editor }) => {
// Update the state when the editor content changes
updateContent(editor.getHTML())
},
content
})
// Update editor content when the `content` prop changes
useEffect(() => {
if (editor && editor.getHTML() !== content) {
editor.commands.setContent(content, false)
}
}, [content, editor])
2024-08-27 10:53:34 +00:00
return (
<div className='inputMain'>
{editor && (
<>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
</>
)}
2024-08-27 10:53:34 +00:00
</div>
)
}
type MenuBarProps = {
editor: Editor
}
2024-08-27 10:53:34 +00:00
const MenuBar = ({ editor }: MenuBarProps) => {
2024-08-27 10:53:34 +00:00
const setLink = () => {
const url = prompt('URL')
if (url) {
return editor.chain().focus().setLink({ href: url }).run()
}
return false
}
const unsetLink = () => editor.chain().focus().unsetLink().run()
const buttons: MenuBarButtonProps[] = [
{
label: 'Bold',
disabled: !editor.can().chain().focus().toggleBold().run(),
isActive: editor.isActive('bold'),
onClick: () => editor.chain().focus().toggleBold().run()
},
{
label: 'Italic',
disabled: !editor.can().chain().focus().toggleItalic().run(),
isActive: editor.isActive('italic'),
onClick: () => editor.chain().focus().toggleItalic().run()
},
{
label: 'Strike',
disabled: !editor.can().chain().focus().toggleStrike().run(),
isActive: editor.isActive('strike'),
onClick: () => editor.chain().focus().toggleStrike().run()
},
{
label: 'Clear marks',
onClick: () => editor.chain().focus().unsetAllMarks().run()
},
{
label: 'Clear nodes',
onClick: () => editor.chain().focus().clearNodes().run()
},
{
label: 'Paragraph',
isActive: editor.isActive('paragraph'),
onClick: () => editor.chain().focus().toggleStrike().run()
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...[1, 2, 3, 4, 5, 6].map((level: any) => ({
label: `H${level}`,
isActive: editor.isActive('heading', { level }),
onClick: () => editor.chain().focus().toggleHeading({ level }).run()
})),
{
label: 'Bullet list',
isActive: editor.isActive('bulletList'),
onClick: () => editor.chain().focus().toggleBulletList().run()
},
{
label: 'Ordered list',
isActive: editor.isActive('orderedList'),
onClick: () => editor.chain().focus().toggleOrderedList().run()
},
{
label: 'Code block',
isActive: editor.isActive('codeBlock'),
onClick: () => editor.chain().focus().toggleCodeBlock().run()
},
{
label: 'Blockquote',
isActive: editor.isActive('blockquote'),
onClick: () => editor.chain().focus().toggleBlockquote().run()
},
{
label: 'Link',
isActive: editor.isActive('link'),
onClick: editor.isActive('link') ? unsetLink : setLink
},
{
label: 'Horizontal rule',
onClick: () => editor.chain().focus().setHorizontalRule().run()
},
{
label: 'Hard break',
onClick: () => editor.chain().focus().setHardBreak().run()
},
{
label: 'Undo',
disabled: !editor.can().chain().focus().undo().run(),
onClick: () => editor.chain().focus().undo().run()
},
{
label: 'Redo',
disabled: !editor.can().chain().focus().redo().run(),
onClick: () => editor.chain().focus().redo().run()
}
]
return (
<div className='control-group'>
<div className='button-group'>
{buttons.map(({ label, disabled, isActive, onClick }) => (
<MenuBarButton
key={label}
label={label}
disabled={disabled}
isActive={isActive}
onClick={onClick}
/>
))}
</div>
</div>
)
}
interface MenuBarButtonProps {
label: string
isActive?: boolean
disabled?: boolean
onClick: () => boolean
}
const MenuBarButton = ({
label,
isActive = false,
disabled = false,
onClick
}: MenuBarButtonProps) => (
<button
onClick={onClick}
disabled={disabled}
2024-08-27 11:39:04 +00:00
className={`btn btnMain btnMainTipTap ${isActive ? 'is-active' : ''}`}
2024-08-27 10:53:34 +00:00
>
{label}
</button>
)