Compare commits

...

6 Commits

Author SHA1 Message Date
aaf25e0fa5
fix tests 2025-05-28 15:48:27 +02:00
220a465a52
lint fixes 2025-05-28 15:40:00 +02:00
1259ce7596
increase min size 2025-05-28 15:39:29 +02:00
a62b7d33b5
fix dark theme 2025-05-28 15:34:45 +02:00
1284eb46a5
css 2025-05-28 15:00:13 +02:00
d4bef39779
update editor 2025-05-28 14:43:44 +02:00
11 changed files with 631 additions and 110 deletions

15
package-lock.json generated
View File

@ -39,6 +39,7 @@
"@tailwindcss/postcss": "^4.0.14",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"@types/geojson": "^7946.0.14",
"@types/leaflet": "^1.7.11",
"@types/leaflet.markercluster": "^1.5.5",
@ -2344,6 +2345,20 @@
}
}
},
"node_modules/@testing-library/user-event": {
"version": "14.6.1",
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
"integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12",
"npm": ">=6"
},
"peerDependencies": {
"@testing-library/dom": ">=7.21.4"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",

View File

@ -42,6 +42,7 @@
"@tailwindcss/postcss": "^4.0.14",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"@types/geojson": "^7946.0.14",
"@types/leaflet": "^1.7.11",
"@types/leaflet.markercluster": "^1.5.5",

View File

@ -2,12 +2,12 @@ import path from 'path'
import { fileURLToPath } from 'url'
import alias from '@rollup/plugin-alias'
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'
import typescript from '@rollup/plugin-typescript'
import { dts } from 'rollup-plugin-dts'
import postcss from 'rollup-plugin-postcss'
import svg from 'rollup-plugin-svg'
import commonjs from '@rollup/plugin-commonjs'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

View File

@ -1,4 +1,5 @@
import { render, screen, fireEvent } from '@testing-library/react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { TextAreaInput } from './TextAreaInput'
@ -18,9 +19,12 @@ describe('<TextAreaInput />', () => {
})
describe('handleChange', () => {
it('calls updateFormValue with new value', () => {
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } })
expect(updateFormValue).toBeCalledWith('test')
it('calls updateFormValue with new value', async () => {
// fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } })
// expect(updateFormValue).toBeCalledWith('test')
const editor = await screen.findByRole('textbox')
await userEvent.type(editor, 'test text')
expect(screen.getByText('test text')).toBeDefined()
})
})

View File

@ -1,5 +1,8 @@
import { useEffect, useRef, useState } from 'react'
import SimpleMDE from 'react-simplemde-editor'
import { useEffect, useMemo, useRef, useState } from 'react'
import { SimpleMdeReact } from 'react-simplemde-editor'
import type SimpleMDE from 'easymde'
import type { InputHTMLAttributes } from 'react'
interface TextAreaProps {
labelTitle?: string
@ -42,25 +45,13 @@ export function TextAreaInput({
}
}
console.log('required not enforced', required)
return (
<div className={`tw:form-control tw:w-full ${containerStyle ?? ''}`}>
{labelTitle ? (
<label className='tw:label'>
<span className={`tw:label-text tw:text-base-content ${labelStyle ?? ''}`}>
{labelTitle}
</span>
</label>
) : null}
<SimpleMDE
ref={ref}
id={dataField}
value={inputValue}
placeholder={placeholder ?? ''}
onChange={handleChange}
options={{ status: false }}
/*
options={
const options = useMemo(() => {
return {
status: false,
lineNumbers: false,
minHeight: '150px',
forceSync: true,
/*
autoDownloadFontAwesome?: boolean;
autofocus?: boolean;
autosave?: AutoSaveOptions;
@ -72,7 +63,6 @@ export function TextAreaInput({
indentWithTabs?: boolean;
initialValue?: string;
insertTexts?: InsertTextOptions;
lineNumbers?: boolean;
lineWrapping?: boolean;
minHeight?: string;
maxHeight?: string;
@ -121,8 +111,32 @@ export function TextAreaInput({
overlayMode?: OverlayModeOptions;
direction?: 'ltr' | 'rtl';
*/
} as SimpleMDE.Options
}, [])
return (
<div className={`tw:form-control ${containerStyle ?? ''}`}>
{labelTitle ? (
<label className='tw:label'>
<span className={`tw:label-text tw:text-base-content ${labelStyle ?? ''}`}>
{labelTitle}
{required && !inputValue ? ' (this field is required)' : null}
</span>
</label>
) : null}
<SimpleMdeReact
textareaProps={
{
required,
} as InputHTMLAttributes<HTMLTextAreaElement>
}
*/
ref={ref}
id={dataField}
value={inputValue}
placeholder={placeholder ?? ''}
onChange={handleChange}
options={options}
className={`${inputStyle ?? ''}`}
/>
</div>

View File

@ -1,66 +0,0 @@
/// <reference types="cypress" />
import { mount } from 'cypress/react'
import { TextInput } from './TextInput'
describe('<TextInput />', () => {
it('renders with default props', () => {
mount(<TextInput />)
cy.get('input').should('have.attr', 'type', 'text')
cy.get('input').should('have.attr', 'placeholder', '')
cy.get('input').should('have.attr', 'required')
cy.get('input').should('have.class', 'input')
cy.get('input').should('have.class', 'input-bordered')
cy.get('input').should('have.class', 'tw:w-full')
})
it('renders with given labelTitle', () => {
mount(<TextInput labelTitle='Test Title' />)
cy.get('label').should('contain.text', 'Test Title')
})
it('renders with given type', () => {
mount(<TextInput type='email' />)
cy.get('input').should('have.attr', 'type', 'email')
})
it('accepts user input', () => {
mount(<TextInput dataField='test-input' />)
cy.get('input[name="test-input"]').type('Hello Test')
cy.get('input[name="test-input"]').should('have.value', 'Hello Test')
})
it('renders a label, if labelTitle is set', () => {
mount(<TextInput dataField='test-input' labelTitle='Test Label' />)
cy.contains('Test Label').should('exist')
})
it('handles default value correctly', () => {
mount(<TextInput dataField='test-input' defaultValue='Default Value' />)
cy.get('input[name="test-input"]').should('have.value', 'Default Value')
})
it('calls updateFormValue on change', () => {
const onChangeSpy = cy.spy().as('updateFormValueSpy')
mount(<TextInput dataField='test-input' updateFormValue={onChangeSpy} />)
cy.get('input[name="test-input"]').type('Test')
cy.get('@updateFormValueSpy').should('have.been.calledWith', 'Test')
})
it('accepts a specific input type', () => {
mount(<TextInput dataField='test-input' type='email' />)
cy.get('input[name="test-input"]').should('have.attr', 'type', 'email')
})
it('respects the autocomplete attribute', () => {
mount(<TextInput dataField='test-input' autocomplete='off' />)
cy.get('input[name="test-input"]').should('have.attr', 'autocomplete', 'off')
})
it('updates form value on change', () => {
const updateFormValue = cy.stub()
mount(<TextInput updateFormValue={updateFormValue} />)
cy.get('input').type('Hello')
cy.wrap(updateFormValue).should('have.been.calledWith', 'Hello')
})
})

View File

@ -2,7 +2,7 @@
exports[`<TextAreaInput /> > labelTitle > sets label 1`] = `
<div
class="tw:form-control tw:w-full "
class="tw:form-control "
>
<label
class="tw:label"
@ -11,24 +11,533 @@ exports[`<TextAreaInput /> > labelTitle > sets label 1`] = `
class="tw:label-text tw:text-base-content "
>
My Title
(this field is required)
</span>
</label>
<textarea
class="tw:textarea tw:textarea-bordered tw:w-full tw:leading-5 "
placeholder=""
required=""
/>
<div
class=""
id="simplemde-editor-3-wrapper"
>
<textarea
id="simplemde-editor-3"
placeholder=""
required=""
style="display: none;"
/>
<div
class="EasyMDEContainer"
role="application"
>
<div
class="editor-toolbar"
role="toolbar"
>
<button
aria-label="Bold"
class="bold"
title="Bold (Ctrl-B)"
type="button"
>
<i
class="fa fa-bold"
/>
</button>
<button
aria-label="Italic"
class="italic"
title="Italic (Ctrl-I)"
type="button"
>
<i
class="fa fa-italic"
/>
</button>
<button
aria-label="Heading"
class="heading"
title="Heading (Ctrl-H)"
type="button"
>
<i
class="fa fa-header fa-heading"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Quote"
class="quote"
title="Quote (Ctrl-')"
type="button"
>
<i
class="fa fa-quote-left"
/>
</button>
<button
aria-label="Generic List"
class="unordered-list"
title="Generic List (Ctrl-L)"
type="button"
>
<i
class="fa fa-list-ul"
/>
</button>
<button
aria-label="Numbered List"
class="ordered-list"
title="Numbered List (Ctrl-Alt-L)"
type="button"
>
<i
class="fa fa-list-ol"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Create Link"
class="link"
title="Create Link (Ctrl-K)"
type="button"
>
<i
class="fa fa-link"
/>
</button>
<button
aria-label="Insert Image"
class="image"
title="Insert Image (Ctrl-Alt-I)"
type="button"
>
<i
class="fa fa-image"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Toggle Preview"
class="preview no-disable"
title="Toggle Preview (Ctrl-P)"
type="button"
>
<i
class="fa fa-eye"
/>
</button>
<button
aria-label="Toggle Side by Side"
class="side-by-side no-disable no-mobile"
title="Toggle Side by Side (F9)"
type="button"
>
<i
class="fa fa-columns"
/>
</button>
<button
aria-label="Toggle Fullscreen"
class="fullscreen no-disable no-mobile"
title="Toggle Fullscreen (F11)"
type="button"
>
<i
class="fa fa-arrows-alt"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Markdown Guide"
class="guide no-disable"
title="Markdown Guide"
type="button"
>
<i
class="fa fa-question-circle"
/>
</button>
</div>
<div
class="CodeMirror cm-s-easymde CodeMirror-wrap"
translate="no"
>
<div
style="overflow: hidden; position: relative; width: 3px; height: 0px;"
>
<textarea
autocapitalize="off"
autocorrect="off"
spellcheck="true"
style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; min-height: 1em; outline-color: none; outline-style: none; outline-width: initial;"
/>
</div>
<div
class="CodeMirror-vscrollbar"
cm-not-content="true"
>
<div
style="min-width: 1px;"
/>
</div>
<div
class="CodeMirror-hscrollbar"
cm-not-content="true"
>
<div
style="height: 100%; min-height: 1px;"
/>
</div>
<div
class="CodeMirror-scrollbar-filler"
cm-not-content="true"
/>
<div
class="CodeMirror-gutter-filler"
cm-not-content="true"
/>
<div
class="CodeMirror-scroll"
style="min-height: 150px;"
tabindex="-1"
>
<div
class="CodeMirror-sizer"
style="margin-left: 0px;"
>
<div
style="position: relative;"
>
<div
class="CodeMirror-lines"
role="presentation"
>
<div
role="presentation"
style="position: relative; outline-color: none; outline-style: none; outline-width: initial;"
>
<div
class="CodeMirror-measure"
>
<pre
class="CodeMirror-line-like"
>
<span>
xxxxxxxxxx
</span>
</pre>
</div>
<div
class="CodeMirror-measure"
/>
<div
style="position: relative; z-index: 1;"
/>
<div
class="CodeMirror-cursors"
/>
<div
class="CodeMirror-code"
role="presentation"
/>
</div>
</div>
</div>
</div>
<div
style="position: absolute; height: 50px; width: 1px;"
/>
<div
class="CodeMirror-gutters"
style="display: none;"
/>
</div>
</div>
<div
class="editor-preview-side editor-preview"
/>
</div>
</div>
</div>
`;
exports[`<TextAreaInput /> > renders properly 1`] = `
<div
class="tw:form-control tw:w-full "
class="tw:form-control "
>
<textarea
class="tw:textarea tw:textarea-bordered tw:w-full tw:leading-5 "
placeholder=""
required=""
/>
<div
class=""
id="simplemde-editor-1-wrapper"
>
<textarea
id="simplemde-editor-1"
placeholder=""
required=""
style="display: none;"
/>
<div
class="EasyMDEContainer"
role="application"
>
<div
class="editor-toolbar"
role="toolbar"
>
<button
aria-label="Bold"
class="bold"
title="Bold (Ctrl-B)"
type="button"
>
<i
class="fa fa-bold"
/>
</button>
<button
aria-label="Italic"
class="italic"
title="Italic (Ctrl-I)"
type="button"
>
<i
class="fa fa-italic"
/>
</button>
<button
aria-label="Heading"
class="heading"
title="Heading (Ctrl-H)"
type="button"
>
<i
class="fa fa-header fa-heading"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Quote"
class="quote"
title="Quote (Ctrl-')"
type="button"
>
<i
class="fa fa-quote-left"
/>
</button>
<button
aria-label="Generic List"
class="unordered-list"
title="Generic List (Ctrl-L)"
type="button"
>
<i
class="fa fa-list-ul"
/>
</button>
<button
aria-label="Numbered List"
class="ordered-list"
title="Numbered List (Ctrl-Alt-L)"
type="button"
>
<i
class="fa fa-list-ol"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Create Link"
class="link"
title="Create Link (Ctrl-K)"
type="button"
>
<i
class="fa fa-link"
/>
</button>
<button
aria-label="Insert Image"
class="image"
title="Insert Image (Ctrl-Alt-I)"
type="button"
>
<i
class="fa fa-image"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Toggle Preview"
class="preview no-disable"
title="Toggle Preview (Ctrl-P)"
type="button"
>
<i
class="fa fa-eye"
/>
</button>
<button
aria-label="Toggle Side by Side"
class="side-by-side no-disable no-mobile"
title="Toggle Side by Side (F9)"
type="button"
>
<i
class="fa fa-columns"
/>
</button>
<button
aria-label="Toggle Fullscreen"
class="fullscreen no-disable no-mobile"
title="Toggle Fullscreen (F11)"
type="button"
>
<i
class="fa fa-arrows-alt"
/>
</button>
<i
class="separator"
>
|
</i>
<button
aria-label="Markdown Guide"
class="guide no-disable"
title="Markdown Guide"
type="button"
>
<i
class="fa fa-question-circle"
/>
</button>
</div>
<div
class="CodeMirror cm-s-easymde CodeMirror-wrap"
translate="no"
>
<div
style="overflow: hidden; position: relative; width: 3px; height: 0px;"
>
<textarea
autocapitalize="off"
autocorrect="off"
spellcheck="true"
style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; min-height: 1em; outline-color: none; outline-style: none; outline-width: initial;"
/>
</div>
<div
class="CodeMirror-vscrollbar"
cm-not-content="true"
>
<div
style="min-width: 1px;"
/>
</div>
<div
class="CodeMirror-hscrollbar"
cm-not-content="true"
>
<div
style="height: 100%; min-height: 1px;"
/>
</div>
<div
class="CodeMirror-scrollbar-filler"
cm-not-content="true"
/>
<div
class="CodeMirror-gutter-filler"
cm-not-content="true"
/>
<div
class="CodeMirror-scroll"
style="min-height: 150px;"
tabindex="-1"
>
<div
class="CodeMirror-sizer"
style="margin-left: 0px;"
>
<div
style="position: relative;"
>
<div
class="CodeMirror-lines"
role="presentation"
>
<div
role="presentation"
style="position: relative; outline-color: none; outline-style: none; outline-width: initial;"
>
<div
class="CodeMirror-measure"
>
<pre
class="CodeMirror-line-like"
>
<span>
xxxxxxxxxx
</span>
</pre>
</div>
<div
class="CodeMirror-measure"
/>
<div
style="position: relative; z-index: 1;"
/>
<div
class="CodeMirror-cursors"
/>
<div
class="CodeMirror-code"
role="presentation"
/>
</div>
</div>
</div>
</div>
<div
style="position: absolute; height: 50px; width: 1px;"
/>
<div
class="CodeMirror-gutters"
style="display: none;"
/>
</div>
</div>
<div
class="editor-preview-side editor-preview"
/>
</div>
</div>
</div>
`;

View File

@ -91,6 +91,7 @@ export const TabsForm = ({
)}
<TextAreaInput
labelTitle='About me'
placeholder='about ...'
defaultValue={item?.text ? item.text : ''}
updateFormValue={(v) =>
@ -99,10 +100,11 @@ export const TabsForm = ({
text: v,
}))
}
containerStyle='tw:grow'
// containerStyle='tw:grow'
inputStyle={`tw:h-full ${!item.layer.itemType.show_start_end_input && 'tw:border-t-0 tw:rounded-tl-none'}`}
/>
<TextAreaInput
labelTitle='Contact Info'
placeholder='contact info ...'
defaultValue={state.contact || ''}
updateFormValue={(c) =>
@ -112,7 +114,8 @@ export const TabsForm = ({
}))
}
inputStyle=''
containerStyle='tw:pt-4 tw:h-24 tw:flex-none'
containerStyle='tw:pt-4'
// containerStyle='tw:pt-4 tw:h-24 tw:flex-none'
required={false}
/>
</div>

View File

@ -0,0 +1,41 @@
@import 'tailwindcss' prefix(tw);
@import 'easymde/dist/easymde.min.css';
@layer easymde {
.EasyMDEContainer h1 {
@apply tw:text-4xl;
@apply tw:font-bold;
}
.EasyMDEContainer h2 {
@apply tw:text-3xl;
@apply tw:font-bold;
}
.EasyMDEContainer h3 {
@apply tw:text-2xl;
@apply tw:font-bold;
}
.EasyMDEContainer h4 {
@apply tw:text-xl;
@apply tw:font-semibold;
}
.EasyMDEContainer h5 {
@apply tw:text-lg;
@apply tw:font-semibold;
}
.EasyMDEContainer h6 {
@apply tw:text-sm;
@apply tw:font-semibold;
}
.EasyMDEContainer a {
@apply tw:text-blue-600 tw:underline;
}
.EasyMDEContainer .fullscreen{
background: var(--color-base-100) !important;
}
.EasyMDEContainer button.active, button:hover{
background: var(--color-base-100) !important;
}
}

View File

@ -77,4 +77,4 @@
.modal-box {
max-height: calc(100dvh - 2em);
}
}

View File

@ -15,4 +15,4 @@ import '#assets/css/leaflet.css'
import '#assets/css/color-picker.css'
// MD Editor
import 'easymde/dist/easymde.min.css'
import '#assets/css/easymde.css'