fixed flex layout

This commit is contained in:
Anton Tranelis 2025-06-06 16:28:27 +02:00
parent 047c640a4e
commit 467fa6262e
10 changed files with 129 additions and 121 deletions

15
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@tanstack/react-query": "^5.17.8", "@tanstack/react-query": "^5.17.8",
"@tiptap/extension-color": "^2.12.0", "@tiptap/extension-color": "^2.12.0",
"@tiptap/extension-image": "^2.12.0", "@tiptap/extension-image": "^2.12.0",
"@tiptap/extension-placeholder": "^2.14.0",
"@tiptap/extension-youtube": "^2.12.0", "@tiptap/extension-youtube": "^2.12.0",
"@tiptap/pm": "^2.12.0", "@tiptap/pm": "^2.12.0",
"@tiptap/react": "^2.12.0", "@tiptap/react": "^2.12.0",
@ -2656,6 +2657,20 @@
"@tiptap/core": "^2.7.0" "@tiptap/core": "^2.7.0"
} }
}, },
"node_modules/@tiptap/extension-placeholder": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.14.0.tgz",
"integrity": "sha512-xzfjHvuukbch4i5O/5uyS2K2QgNEaMKi6e6GExTTgVwnFjKfJmgTqee33tt5JCqSItBvtSZlU3SX/vpiaIof+w==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-strike": { "node_modules/@tiptap/extension-strike": {
"version": "2.12.0", "version": "2.12.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.12.0.tgz", "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.12.0.tgz",

View File

@ -101,6 +101,7 @@
"@tanstack/react-query": "^5.17.8", "@tanstack/react-query": "^5.17.8",
"@tiptap/extension-color": "^2.12.0", "@tiptap/extension-color": "^2.12.0",
"@tiptap/extension-image": "^2.12.0", "@tiptap/extension-image": "^2.12.0",
"@tiptap/extension-placeholder": "^2.14.0",
"@tiptap/extension-youtube": "^2.12.0", "@tiptap/extension-youtube": "^2.12.0",
"@tiptap/pm": "^2.12.0", "@tiptap/pm": "^2.12.0",
"@tiptap/react": "^2.12.0", "@tiptap/react": "^2.12.0",

View File

@ -1,44 +1,18 @@
import Color from '@tiptap/extension-color' import { Color } from '@tiptap/extension-color'
import Image from '@tiptap/extension-image' import { Image } from '@tiptap/extension-image'
import ListItem from '@tiptap/extension-list-item' import { Placeholder } from '@tiptap/extension-placeholder'
import TextStyle from '@tiptap/extension-text-style' import { Youtube } from '@tiptap/extension-youtube'
import Youtube from '@tiptap/extension-youtube' import { EditorContent, useEditor } from '@tiptap/react'
import { EditorProvider } from '@tiptap/react' import { StarterKit } from '@tiptap/starter-kit'
import StarterKit from '@tiptap/starter-kit' import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { Markdown } from 'tiptap-markdown' import { Markdown } from 'tiptap-markdown'
import { TextEditorMenu } from './TextEditorMenu' import { TextEditorMenu } from './TextEditorMenu'
import type { EditorEvents } from '@tiptap/react' interface RichTextEditorProps {
const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
// TextStyle.configure({ types: [ListItem.name] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
},
orderedList: {
keepMarks: true,
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
},
}),
Markdown,
Image,
Youtube.configure({
controls: false,
nocookie: true,
width: 100,
}),
]
interface TextAreaProps {
labelTitle?: string labelTitle?: string
labelStyle?: string labelStyle?: string
containerStyle?: string containerStyle?: string
dataField?: string
inputStyle?: string inputStyle?: string
defaultValue: string defaultValue: string
placeholder?: string placeholder?: string
@ -52,48 +26,67 @@ interface TextAreaProps {
*/ */
export function RichTextEditor({ export function RichTextEditor({
labelTitle, labelTitle,
dataField,
labelStyle, labelStyle,
containerStyle, containerStyle,
inputStyle,
defaultValue, defaultValue,
placeholder, placeholder,
required = true, required = true,
updateFormValue, updateFormValue,
}: TextAreaProps) { }: RichTextEditorProps) {
// const ref = useRef<HTMLTextAreaElement>(null) console.log(placeholder, required)
const [inputValue, setInputValue] = useState<string>(defaultValue)
useEffect(() => { const handleChange = () => {
setInputValue(defaultValue) const newValue: string | undefined = editor?.storage.markdown.getMarkdown()
}, [defaultValue]) if (updateFormValue && newValue) {
console.log(
labelTitle,
dataField,
labelStyle,
containerStyle,
inputStyle,
defaultValue,
placeholder,
required,
updateFormValue,
)
const handleChange = (props: EditorEvents['update']) => {
const newValue = props.editor.storage.markdown.getMarkdown()
setInputValue(newValue)
if (updateFormValue) {
updateFormValue(newValue) updateFormValue(newValue)
} }
} }
const editor = useEditor({
extensions: [
Color.configure({ types: ['textStyle', 'listItem'] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false,
},
orderedList: {
keepMarks: true,
keepAttributes: false,
},
}),
Markdown,
Image,
Youtube.configure({
controls: false,
nocookie: true,
}),
Placeholder.configure({
placeholder,
emptyEditorClass: 'is-editor-empty',
}),
],
content: defaultValue,
onUpdate: handleChange,
editorProps: {
attributes: {
class: `tw:h-full tw:max-h-full tw:p-2 tw:overflow-y-auto`,
},
},
})
useEffect(() => {
if (editor?.storage.markdown.getMarkdown() === '' || !editor?.storage.markdown.getMarkdown()) {
editor?.commands.setContent(defaultValue)
}
}, [defaultValue, editor])
return ( return (
<div <div
className={`tw:form-control tw:w-full tw:flex tw:flex-col tw:min-h-0 ${containerStyle ?? ''}`} className={`tw:form-control tw:w-full tw:flex tw:flex-col tw:min-h-0 ${containerStyle ?? ''}`}
> >
{labelTitle ? ( {labelTitle ? (
<label className='tw:label'> <label className='tw:label tw:pb-1'>
<span className={`tw:label-text tw:text-base-content ${labelStyle ?? ''}`}> <span className={`tw:label-text tw:text-base-content ${labelStyle ?? ''}`}>
{labelTitle} {labelTitle}
</span> </span>
@ -102,17 +95,12 @@ export function RichTextEditor({
<div <div
className={`editor-wrapper tw:border-base-content/20 tw:rounded-box tw:border tw:flex tw:flex-col tw:flex-1 tw:min-h-0`} className={`editor-wrapper tw:border-base-content/20 tw:rounded-box tw:border tw:flex tw:flex-col tw:flex-1 tw:min-h-0`}
> >
<EditorProvider {editor ? (
slotBefore={<TextEditorMenu />} <>
extensions={extensions} <TextEditorMenu editor={editor} />
content={inputValue} <EditorContent editor={editor} />
onUpdate={handleChange} </>
editorProps={{ ) : null}
attributes: {
class: `tw:h-full tw:max-h-full tw:p-2 tw:overflow-y-auto`,
},
}}
></EditorProvider>
</div> </div>
</div> </div>
) )

View File

@ -6,16 +6,12 @@ import H3Icon from '@heroicons/react/24/solid/H3Icon'
import ItalicIcon from '@heroicons/react/24/solid/ItalicIcon' import ItalicIcon from '@heroicons/react/24/solid/ItalicIcon'
import ListBulletIcon from '@heroicons/react/24/solid/ListBulletIcon' import ListBulletIcon from '@heroicons/react/24/solid/ListBulletIcon'
import NumberedListIcon from '@heroicons/react/24/solid/NumberedListIcon' import NumberedListIcon from '@heroicons/react/24/solid/NumberedListIcon'
import { useCurrentEditor, useEditorState } from '@tiptap/react' import { Editor, useEditorState } from '@tiptap/react'
import { FaQuoteLeft } from 'react-icons/fa6' import { FaQuoteLeft } from 'react-icons/fa6'
import { MdUndo, MdRedo, MdHorizontalRule } from 'react-icons/md' import { MdUndo, MdRedo, MdHorizontalRule } from 'react-icons/md'
export const TextEditorMenu = () => { export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
const { editor } = useCurrentEditor()
if (!editor) {
return null
}
const editorState = useEditorState({ const editorState = useEditorState({
editor, editor,
@ -63,112 +59,112 @@ export const TextEditorMenu = () => {
<> <>
<ul className='tw:menu tw:menu-horizontal tw:flex-none tw:bg-base-200 tw:rounded-box tw:w-full tw:rounded-b-none'> <ul className='tw:menu tw:menu-horizontal tw:flex-none tw:bg-base-200 tw:rounded-box tw:w-full tw:rounded-b-none'>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.canUndo ? '' : 'tw:opacity-50'}`} className={`tw:tooltip ${editorState.canUndo ? '' : 'tw:opacity-50'}`}
data-tip='Undo' data-tip='Undo'
onClick={() => editor.chain().focus().undo().run()} onClick={() => editor.chain().focus().undo().run()}
> >
<MdUndo className='tw:w-5 tw:h-5' /> <MdUndo className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.canRedo ? '' : 'tw:opacity-50'}`} className={`tw:tooltip ${editorState.canRedo ? '' : 'tw:opacity-50'}`}
data-tip='Redo' data-tip='Redo'
onClick={() => editor.chain().focus().redo().run()} onClick={() => editor.chain().focus().redo().run()}
> >
<MdRedo className='tw:w-5 tw:h-5' /> <MdRedo className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isBold ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isBold ? 'tw:bg-base-content/10' : ''}`}
data-tip='Bold' data-tip='Bold'
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
> >
<BoldIcon className='tw:w-5 tw:h-5' /> <BoldIcon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isItalic ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isItalic ? 'tw:bg-base-content/10' : ''}`}
data-tip='Italic' data-tip='Italic'
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
> >
<ItalicIcon className='tw:w-5 tw:h-5' /> <ItalicIcon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isHeading1 ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isHeading1 ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 1' data-tip='Heading 1'
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
> >
<H1Icon className='tw:w-5 tw:h-5' /> <H1Icon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isHeading2 ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isHeading2 ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 2' data-tip='Heading 2'
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
> >
<H2Icon className='tw:w-5 tw:h-5' /> <H2Icon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isHeading3 ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isHeading3 ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 3' data-tip='Heading 3'
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
> >
<H3Icon className='tw:w-5 tw:h-5' /> <H3Icon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isBulletList ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isBulletList ? 'tw:bg-base-content/10' : ''}`}
data-tip='List' data-tip='List'
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
> >
<ListBulletIcon className='tw:w-5 tw:h-5' /> <ListBulletIcon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isOrderedList ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isOrderedList ? 'tw:bg-base-content/10' : ''}`}
data-tip='List' data-tip='List'
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
> >
<NumberedListIcon className='tw:w-5 tw:h-5' /> <NumberedListIcon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isCodeBlock ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isCodeBlock ? 'tw:bg-base-content/10' : ''}`}
data-tip='Code' data-tip='Code'
onClick={() => editor.chain().focus().toggleCodeBlock().run()} onClick={() => editor.chain().focus().toggleCodeBlock().run()}
> >
<CodeBracketIcon className='tw:w-5 tw:h-5' /> <CodeBracketIcon className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className={`tw:tooltip ${editorState.isBlockquote ? 'tw:bg-base-100' : ''}`} className={`tw:tooltip ${editorState.isBlockquote ? 'tw:bg-base-content/10' : ''}`}
data-tip='Quote' data-tip='Quote'
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
> >
<FaQuoteLeft className='tw:w-5 tw:h-5' /> <FaQuoteLeft className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
<li> <li>
<a <div
className='tw:tooltip' className='tw:tooltip'
data-tip='Horizontal Line' data-tip='Horizontal Line'
onClick={() => editor.chain().focus().setHorizontalRule().run()} onClick={() => editor.chain().focus().setHorizontalRule().run()}
> >
<MdHorizontalRule className='tw:w-5 tw:h-5' /> <MdHorizontalRule className='tw:w-5 tw:h-5' />
</a> </div>
</li> </li>
</ul> </ul>
{/** <div className='control-group tiptap-toolbar'> {/** <div className='control-group tiptap-toolbar'>

View File

@ -12,7 +12,7 @@ export const ContactInfoForm = ({
setState: React.Dispatch<React.SetStateAction<any>> setState: React.Dispatch<React.SetStateAction<any>>
}) => { }) => {
return ( return (
<div className='tw:mt-4 tw:space-y-4'> <div className='tw:mt-2 tw:space-y-2'>
<div> <div>
<label <label
htmlFor='email' htmlFor='email'

View File

@ -51,7 +51,7 @@ export const GroupSubheaderForm = ({
}, [state.group_type, groupTypes]) }, [state.group_type, groupTypes])
return ( return (
<div className='tw:grid tw:grid-cols-1 tw:md:grid-cols-2 tw:gap-6'> <div className='tw:grid tw:grid-cols-1 tw:md:grid-cols-2 tw:gap-2'>
<div> <div>
<label <label
htmlFor='status' htmlFor='status'

View File

@ -37,7 +37,9 @@ export const ProfileTextForm = ({
}, [dataField]) }, [dataField])
return ( return (
<div className='tw:h-full tw:flex tw:flex-col tw:mt-4'> <div
className={`tw:min-h-0 tw:flex tw:flex-col tw:mt-2 ${size === 'full' ? 'tw:flex-1' : 'tw:h-32 tw:flex-none'}`}
>
<div className='tw:flex tw:justify-between tw:items-center'> <div className='tw:flex tw:justify-between tw:items-center'>
<label <label
htmlFor='nextAppointment' htmlFor='nextAppointment'
@ -58,8 +60,7 @@ export const ProfileTextForm = ({
})) }))
} }
labelStyle={hideInputLabel ? 'tw:hidden' : ''} labelStyle={hideInputLabel ? 'tw:hidden' : ''}
containerStyle={size === 'full' ? 'tw:grow' : 'tw:h-28 tw:flex-none'} containerStyle={size === 'full' ? 'tw:flex-1' : 'tw:h-28 tw:flex-none'}
inputStyle={size === 'full' ? 'tw:grow' : 'tw:h-28 tw:max-h-28 tw:flex-none'}
required={required} required={required}
/> />
</div> </div>

View File

@ -41,7 +41,7 @@ export const FlexForm = ({
{...templateItem.item} {...templateItem.item}
/> />
) : ( ) : (
<div className='tw:mt-2' key={templateItem.id}> <div className='tw:mt-2 tw:flex-none' key={templateItem.id}>
{templateItem.collection} form not found {templateItem.collection} form not found
</div> </div>
) )

View File

@ -100,7 +100,7 @@ export const TabsForm = ({
text: v, text: v,
})) }))
} }
containerStyle='tw:grow' containerStyle='tw:pt-2 tw:grow'
inputStyle={`tw:h-full ${!item.layer.itemType.show_start_end_input && 'tw:border-t-0 tw:rounded-tl-none'}`} inputStyle={`tw:h-full ${!item.layer.itemType.show_start_end_input && 'tw:border-t-0 tw:rounded-tl-none'}`}
/> />
<RichTextEditor <RichTextEditor
@ -114,7 +114,7 @@ export const TabsForm = ({
})) }))
} }
inputStyle='' inputStyle=''
containerStyle='tw:pt-4 tw:h-32' containerStyle='tw:pt-2 tw:h-36 tw:flex-none'
required={false} required={false}
/> />
</div> </div>

View File

@ -16,6 +16,13 @@
.editor-wrapper div { .editor-wrapper div {
min-height: 0; min-height: 0;
flex: 1; flex: 1;
display: flex; outline: none;
flex-direction: column; }
}
.tiptap p.is-editor-empty:first-child::before {
color: #adb5bd;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}