@vonaffenfels/slate-editor 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,11 @@
1
1
  import {
2
- Accordion,
3
- AccordionItem,
4
- Button,
5
- FormLabel,
6
- Note,
7
- Switch,
8
- Heading,
9
- IconButton,
10
- Icon,
11
- } from "@contentful/forma-36-react-components";
12
-
13
- import "../scss/sidebarEditor.scss";
2
+ useState, useEffect,
3
+ } from "react";
14
4
  import {SidebarEditorField} from "./SidebarEditor/SidebarEditorField";
15
5
  import {ToolMargin} from "./Tools/Margin";
16
6
 
7
+ import "../scss/sidebarEditor.scss";
8
+
17
9
  const SidebarEditor = ({
18
10
  sdk,
19
11
  storybookElement,
@@ -23,6 +15,11 @@ const SidebarEditor = ({
23
15
  onDelete,
24
16
  onMove,
25
17
  }) => {
18
+ const [versions, setVersions] = useState([]);
19
+ const [lastChangedProperty, setLastChangedProperty] = useState(null);
20
+ const [versionCount, setVersionCount] = useState(0);
21
+ const [currentVersion, setCurrentVersion] = useState(0);
22
+
26
23
  const fields = {
27
24
  fields: {},
28
25
  tables: {},
@@ -49,6 +46,18 @@ const SidebarEditor = ({
49
46
  const handleFieldValueChange = (
50
47
  fieldKey, value, mvpFieldKey, mvpIndex,
51
48
  ) => {
49
+ let targetFieldKey = mvpFieldKey ? `${fieldKey}-${mvpIndex}-${mvpFieldKey}` : fieldKey;
50
+
51
+ if (lastChangedProperty !== targetFieldKey) {
52
+ addVersion(storybookElement);
53
+
54
+ if (mvpFieldKey) {
55
+ setLastChangedProperty(`${fieldKey}-${mvpIndex}-${mvpFieldKey}`);
56
+ } else {
57
+ setLastChangedProperty(fieldKey);
58
+ }
59
+ }
60
+
52
61
  if (onChange) {
53
62
  if (mvpFieldKey !== undefined && mvpIndex !== undefined) {
54
63
  let newMVPValue = storybookElement.attributes[fieldKey].map((f, index) => {
@@ -62,110 +71,241 @@ const SidebarEditor = ({
62
71
  return f;
63
72
  });
64
73
 
65
- onChange({
74
+ let newStorybookElement = {
66
75
  ...storybookElement,
67
76
  attributes: {
68
77
  ...storybookElement.attributes,
69
78
  [fieldKey]: newMVPValue,
70
79
  },
71
- });
80
+ };
81
+
82
+ onChange(newStorybookElement);
72
83
  } else {
73
- onChange({
84
+ let newStorybookElement = {
74
85
  ...storybookElement,
75
86
  attributes: {
76
87
  ...storybookElement.attributes,
77
88
  [fieldKey]: value,
78
89
  },
79
- });
90
+ };
91
+
92
+ onChange(newStorybookElement);
93
+ }
94
+ }
95
+ };
96
+
97
+ const addVersion = (content) => {
98
+ let newVersionCount = versionCount + 1;
99
+
100
+ setVersions([...versions, {
101
+ content,
102
+ id: newVersionCount,
103
+ }]);
104
+
105
+ setCurrentVersion(newVersionCount + 1);
106
+ setVersionCount(newVersionCount);
107
+ };
108
+
109
+ const undo = () => {
110
+ let targetVersionId = currentVersion - 1;
111
+ let targetVersion = versions.find(v => v.id === targetVersionId);
112
+
113
+ if (targetVersion) {
114
+ if (currentVersion > versionCount) {
115
+ addVersion(storybookElement);
80
116
  }
117
+
118
+ onChange(targetVersion.content);
119
+ setCurrentVersion(targetVersionId);
120
+ setLastChangedProperty(null);
121
+ } else {
122
+ console.error(`Version with ID ${targetVersionId} not found`, versions);
123
+ }
124
+ };
125
+
126
+ const redo = () => {
127
+ let targetVersionId = currentVersion + 1;
128
+ let targetVersion = versions.find(v => v.id === targetVersionId);
129
+
130
+ if (targetVersion) {
131
+ onChange(targetVersion.content);
132
+ setCurrentVersion(targetVersionId);
133
+ } else {
134
+ console.error(`Version with ID ${targetVersionId} not found`, versions);
135
+ }
136
+ };
137
+
138
+ const resetVersions = () => {
139
+ setVersionCount(0);
140
+ setCurrentVersion(0);
141
+ setLastChangedProperty(null);
142
+ setVersions([]);
143
+ };
144
+
145
+ const handleBlockSelectChange = (block) => {
146
+ if (onChange) {
147
+ setLastChangedProperty(null);
148
+ addVersion(storybookElement);
149
+ onChange(block);
81
150
  }
82
151
  };
83
152
 
153
+ const handleVariantSelectChange = (variant) => {
154
+ if (onChange) {
155
+ setLastChangedProperty(null);
156
+ addVersion(storybookElement);
157
+ onChange(variant);
158
+ }
159
+ };
160
+
161
+ useEffect(() => resetVersions, [storybookElement?.editorAttributes?.ref]);
162
+
84
163
  return (
85
164
  <div id="sidebar-editor">
86
- <div className="mb-2">
87
- <div className="flex items-center">
88
- <div className="grow pr-2">
89
- <BlockSelect stories={storybookStories} active={storybookElement} onChange={onChange} className="font-bold"/>
165
+ <div>
166
+ <div className="flex items-center justify-end">
167
+ <div className="grow">
168
+ {storybookElement?.block && (
169
+ <div className="flex items-center">
170
+ <div className="icon-button-group mr-1">
171
+ <IconButton title="Rückgängig" onClick={undo} disabled={currentVersion <= 1}>↺</IconButton>
172
+ <IconButton title="Wiederherstellen" onClick={redo} disabled={currentVersion >= versionCount || versionCount === 0}>↻</IconButton>
173
+ </div>
174
+ <div className="icon-button-group mr-1">
175
+ <IconButton title="Nach oben verschieben" onClick={() => onMove && onMove(storybookElement, "up")}>
176
+
177
+ </IconButton>
178
+ <IconButton title="Nach unten verschieben" onClick={() => onMove && onMove(storybookElement, "down")}>
179
+
180
+ </IconButton>
181
+ </div>
182
+ <IconButton title="Löschen" onClick={() => onDelete && onDelete(storybookElement)}>
183
+ 🗑
184
+ </IconButton>
185
+ <div className="ml-1 flex items-center">
186
+ <ToolMargin
187
+ margin={storybookElement.attributes.margin}
188
+ onChange={value => handleFieldValueChange("margin", value)} />
189
+ </div>
190
+ </div>
191
+ )}
90
192
  </div>
91
- {!!onClose && <div className="cursor-pointer p-1" onClick={onClose} title="Schließen"><Icon icon="Close" /></div>}
193
+ {!!onClose && <IconButton onClick={onClose} title="Schließen">⨯</IconButton>}
92
194
  </div>
195
+ <hr className="mt-2" style={{borderColor: "#cfd9e0"}}/>
93
196
  </div>
94
- {storybookElement?.block && (
95
- <div>
96
- <VariantSelect story={story} onChange={onChange} className="mb-2" />
97
- <div className="flex items-center">
98
- <select
99
- value={storybookElement.attributes.blockWidth}
100
- onChange={e => handleFieldValueChange("blockWidth", e.target.value)}
101
- className="grow">
102
- <option disabled>Breite wählen</option>
103
- <option value={undefined}>Volle Breite</option>
104
- <option value={"article"}>Artikel Breite</option>
105
- <option value={"site"}>Seiten Breite</option>
106
- <option value={"small"}>Klein</option>
107
- </select>
108
- <div className="cursor-pointer p-2" title="Nach oben verschieben" onClick={() => onMove && onMove(storybookElement, "up")}>
109
- <Icon icon="ArrowUp" />
110
- </div>
111
- <div className="cursor-pointer p-2" title="Nach unten verschieben" onClick={() => onMove && onMove(storybookElement, "down")}>
112
- <Icon icon="ArrowDown" />
113
- </div>
114
- <div className="p-2 pt-3">
115
- <ToolMargin
116
- margin={storybookElement.attributes.margin}
117
- onChange={value => handleFieldValueChange("margin", value)} />
118
- </div>
119
- <div className="cursor-pointer p-2" title="Löschen" onClick={() => onDelete && onDelete(storybookElement)}>
120
- <Icon icon="Delete" />
197
+ <div className="grow overflow-y-auto pr-2 pt-2">
198
+ <div className="mb-2">
199
+ <BlockSelect stories={storybookStories} active={storybookElement} onChange={handleBlockSelectChange} />
200
+ </div>
201
+ {storybookElement?.block && (
202
+ <div>
203
+ <div className="mb-2 grid grid-flow-col grid-cols-2 gap-2">
204
+ <VariantSelect className="w-full" story={story} onChange={handleVariantSelectChange} />
205
+ <BlockWidthSelect className="w-full" value={storybookElement.attributes.blockWidth} onChange={e => handleFieldValueChange("blockWidth", e.target.value)} />
121
206
  </div>
207
+ <hr className="my-4" style={{borderColor: "#cfd9e0"}}/>
122
208
  </div>
123
- <hr className="my-4" style={{borderColor: "rgb(174, 193, 204)"}}/>
124
- </div>
125
- )}
126
- {!!story && (
127
- <>
128
- {Object.keys(fields.fields).map(key => {
129
- const field = fields.fields[key];
130
-
131
- return <SidebarEditorField
132
- sdk={sdk}
133
- value={storybookElement?.attributes?.[key]}
134
- key={key}
135
- storybookElement={storybookElement}
136
- fieldKey={key}
137
- field={field}
138
- onChange={(
139
- key, value, mvpField, mvpIndex,
140
- ) => handleFieldValueChange(
141
- key, value, mvpField, mvpIndex,
142
- )}
143
- />;
144
- })}
145
- {Object.keys(fields.tables).map(tableKey => {
146
- return (
147
- <details key={`accordion-item-${tableKey}`}>
148
- <summary>{tableKey}</summary>
149
- <div className="mt-4">
150
- {Object.keys(fields.tables[tableKey]).map(key => {
151
- const field = fields.tables[tableKey][key];
152
-
153
- return <SidebarEditorField
154
- sdk={sdk}
155
- value={storybookElement?.attributes?.[key]}
156
- key={key}
157
- fieldKey={key}
158
- story={storybookElement}
159
- field={field}
160
- onChange={value => handleFieldValueChange(key, value)}
161
- />;
162
- })}
163
- </div>
164
- </details>
165
- );
166
- })}
167
- </>
168
- )}
209
+ )}
210
+ {!!story && (
211
+ <>
212
+ {Object.keys(fields.fields).map(key => {
213
+ const field = fields.fields[key];
214
+
215
+ return <SidebarEditorField
216
+ sdk={sdk}
217
+ value={storybookElement?.attributes?.[key]}
218
+ key={key}
219
+ storybookElement={storybookElement}
220
+ fieldKey={key}
221
+ field={field}
222
+ onChange={(
223
+ key, value, mvpField, mvpIndex,
224
+ ) => handleFieldValueChange(
225
+ key, value, mvpField, mvpIndex,
226
+ )}
227
+ />;
228
+ })}
229
+ {Object.keys(fields.tables).map(tableKey => {
230
+ return (
231
+ <details key={`accordion-item-${tableKey}`}>
232
+ <summary>{tableKey}</summary>
233
+ <div className="mt-4">
234
+ {Object.keys(fields.tables[tableKey]).map(key => {
235
+ const field = fields.tables[tableKey][key];
236
+
237
+ return <SidebarEditorField
238
+ sdk={sdk}
239
+ value={storybookElement?.attributes?.[key]}
240
+ key={key}
241
+ fieldKey={key}
242
+ story={storybookElement}
243
+ field={field}
244
+ onChange={value => handleFieldValueChange(key, value)}
245
+ />;
246
+ })}
247
+ </div>
248
+ </details>
249
+ );
250
+ })}
251
+ </>
252
+ )}
253
+ </div>
254
+ </div>
255
+ );
256
+ };
257
+
258
+ export const IconButton = ({
259
+ children,
260
+ disabled,
261
+ size = "medium",
262
+ className,
263
+ ...props
264
+ }) => {
265
+ let classNames = "icon-button cursor-pointer select-none !p-1";
266
+
267
+ if (disabled) {
268
+ classNames += " disabled";
269
+ }
270
+
271
+ if (className) {
272
+ classNames += ` ${className}`;
273
+ }
274
+
275
+ switch (size) {
276
+ case "small":
277
+ classNames += " !text-xs min-w-[28px]";
278
+ break;
279
+ case "medium":
280
+ default:
281
+ classNames += " !text-sm min-w-[32px]";
282
+ }
283
+
284
+ return (
285
+ <div className={classNames} {...props}>
286
+ {children}
287
+ </div>
288
+ );
289
+ };
290
+
291
+ const BlockWidthSelect = ({
292
+ value,
293
+ onChange,
294
+ className,
295
+ }) => {
296
+ return (
297
+ <div>
298
+ <label className="block">Breite</label>
299
+ <select
300
+ value={value || ""}
301
+ onChange={onChange}
302
+ className={className}>
303
+ <option disabled>Breite wählen</option>
304
+ <option value={""}>Volle Breite</option>
305
+ <option value={"article"}>Artikel Breite</option>
306
+ <option value={"site"}>Seiten Breite</option>
307
+ <option value={"small"}>Klein</option>
308
+ </select>
169
309
  </div>
170
310
  );
171
311
  };
@@ -177,21 +317,26 @@ const VariantSelect = ({
177
317
  }) => {
178
318
  const stories = story?.stories?.filter(s => s.title !== story.title) || [];
179
319
 
180
- if (stories.length <= 1 || !onChange) {
320
+ if (!onChange) {
181
321
  return null;
182
322
  }
183
323
 
184
- return <select
185
- className={className}
186
- onChange={e => onChange && onChange({
187
- block: story.id,
188
- attributes: {...stories.find(s => s.title === e.target.value)?.args || {}},
189
- })}>
190
- <option>Preset wählen</option>
191
- {stories.map(s => (
192
- <option key={`variant-option-${s.title}`} value={s.title}>{s.title}</option>
193
- ))}
194
- </select>;
324
+ return (
325
+ <div className={className}>
326
+ <label className="block">Preset</label>
327
+ <select
328
+ disabled={stories.length <= 1}
329
+ onChange={e => onChange && onChange({
330
+ block: story.id,
331
+ attributes: {...stories.find(s => s.title === e.target.value)?.args || {}},
332
+ })}>
333
+ <option>Preset wählen</option>
334
+ {stories.map(s => (
335
+ <option key={`variant-option-${s.title}`} value={s.title}>{s.title}</option>
336
+ ))}
337
+ </select>
338
+ </div>
339
+ );
195
340
  };
196
341
 
197
342
  const BlockSelect = ({
@@ -211,27 +356,31 @@ const BlockSelect = ({
211
356
 
212
357
  // reset to the first story
213
358
  onChange({
359
+ ...active,
214
360
  block: story.id,
215
361
  attributes: {...(story?.stories?.[0]?.args || {})},
216
362
  });
217
363
  };
218
364
 
219
365
  return (
220
- <select
221
- onChange={e => onSelectChange(stories.find(s => s.id === e.target.value))}
222
- className={className}
223
- >
224
- <option>Element wählen</option>
225
- {stories.map(s => (
226
- <option
227
- key={`variant-option-${s.id}`}
228
- selected={active?.block === s.id}
229
- value={s.id}
230
- >
231
- {s.title}
232
- </option>
233
- ))}
234
- </select>
366
+ <div className={className}>
367
+ <label className="block">Element</label>
368
+ <select
369
+ onChange={e => onSelectChange(stories.find(s => s.id === e.target.value))}
370
+ className="font-bold"
371
+ >
372
+ <option>Element wählen</option>
373
+ {stories.map(s => (
374
+ <option
375
+ key={`variant-option-${s.id}`}
376
+ selected={active?.block === s.id}
377
+ value={s.id}
378
+ >
379
+ {s.title}
380
+ </option>
381
+ ))}
382
+ </select>
383
+ </div>
235
384
  );
236
385
  };
237
386
 
@@ -26,7 +26,10 @@ export const Portal = ({children}) => {
26
26
  return ReactDOM.createPortal(children, window.document.body);
27
27
  };
28
28
 
29
- export const Toolbar = ({hover}) => {
29
+ export const Toolbar = ({
30
+ hover,
31
+ onSaveClick,
32
+ }) => {
30
33
  const ref = useRef();
31
34
  const editor = useSlate();
32
35
 
@@ -90,39 +93,41 @@ export const Toolbar = ({hover}) => {
90
93
  })}
91
94
  >
92
95
  <div className="toolbar-btns">
93
- <FormatButtonBold/>
94
- <FormatButtonItalic/>
95
- <FormatButtonUnderline/>
96
- <FormatButtonStrikethrough/>
97
-
98
- <LinkButton/>
99
-
100
- <ToobarHoverExpandButton>
101
- <BlockButtonHeading level={2}/>
102
- <BlockButtonHeading level={3}/>
103
- <BlockButtonHeading level={4}/>
104
- </ToobarHoverExpandButton>
105
-
106
- <ToobarHoverExpandButton>
107
- <BlockButtonUnorderedList/>
108
- <BlockButtonOrderedList/>
109
- <BlockButtonArrowList/>
110
- </ToobarHoverExpandButton>
111
-
112
- <BlockButtonBlockquote/>
113
-
114
- <ToobarHoverExpandButton>
115
- <AlignButtonLeft/>
116
- <AlignButtonCenter/>
117
- <AlignButtonRight/>
118
- </ToobarHoverExpandButton>
119
-
120
- <ToobarHoverExpandButton>
121
- <StorybookButton/>
122
- <InsertDividerButton/>
123
- <InsertGridButton/>
124
- </ToobarHoverExpandButton>
125
-
96
+ <div className="flex grow">
97
+ <FormatButtonBold/>
98
+ <FormatButtonItalic/>
99
+ <FormatButtonUnderline/>
100
+ <FormatButtonStrikethrough/>
101
+
102
+ <LinkButton/>
103
+
104
+ <ToobarHoverExpandButton>
105
+ <BlockButtonHeading level={2}/>
106
+ <BlockButtonHeading level={3}/>
107
+ <BlockButtonHeading level={4}/>
108
+ </ToobarHoverExpandButton>
109
+
110
+ <ToobarHoverExpandButton>
111
+ <BlockButtonUnorderedList/>
112
+ <BlockButtonOrderedList/>
113
+ <BlockButtonArrowList/>
114
+ </ToobarHoverExpandButton>
115
+
116
+ <BlockButtonBlockquote/>
117
+
118
+ <ToobarHoverExpandButton>
119
+ <AlignButtonLeft/>
120
+ <AlignButtonCenter/>
121
+ <AlignButtonRight/>
122
+ </ToobarHoverExpandButton>
123
+
124
+ <ToobarHoverExpandButton>
125
+ <StorybookButton/>
126
+ <InsertDividerButton/>
127
+ <InsertGridButton/>
128
+ </ToobarHoverExpandButton>
129
+ </div>
130
+ {!!onSaveClick && <button className="!w-auto !border-0 !bg-green-600 !text-xs font-bold text-white hover:!bg-green-700" onClick={onSaveClick}>Änderungen speichern</button>}
126
131
  </div>
127
132
  </Menu>;
128
133
  }
package/src/dev/App.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import React, {useEffect} from 'react';
2
2
  import BlockEditor from "../BlockEditor";
3
+ import Renderer from "../Renderer";
3
4
  // eslint-disable-next-line import/no-unresolved
4
5
  import "scss/demo.scss";
5
6
  import "./index.css";
@@ -53,6 +54,11 @@ function App() {
53
54
  </div>
54
55
  <div className="editor-demo-output">
55
56
  <textarea value={JSON.stringify(value)}/>
57
+ <Renderer
58
+ value={value}
59
+ storybookComponentLoader={(block) => {
60
+ return componentLoader(block);
61
+ }}/>
56
62
  </div>
57
63
  </div>
58
64
  );
@@ -1,9 +1,75 @@
1
1
  export default function TestStory({
2
- title, ...props
2
+ title,
3
+ mvp,
4
+ ...props
3
5
  }) {
4
6
  return <div className="bg-green flex flex-col space-y-8 p-4 text-black">
5
7
  <h1>{title}</h1>
8
+ <div className="grid grid-cols-4">
9
+ <div className="border border-black p-4" data-teasermanager-slot="test-1">
10
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad aperiam beatae consequatur consequuntur deserunt exercitationem fugit, magni necessitatibus pariatur quae quia, quisquam saepe sunt, tenetur!
11
+ </div>
12
+ <div className="border border-black p-4" data-teasermanager-slot="test-2">
13
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestias, obcaecati, unde! In labore nesciunt optio, possimus praesentium sunt temporibus vero.
14
+ </div>
15
+ <div className="border border-black p-4" data-teasermanager-slot="test-3">
16
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi, rem, sapiente! At consequatur cum distinctio dolorem doloribus eligendi labore necessitatibus nemo officia, perspiciatis porro quisquam quos, saepe vitae voluptas. Eligendi?
17
+ </div>
18
+ <div className="border border-black p-4" data-teasermanager-slot="test-4">
19
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad alias beatae doloribus ex, impedit labore maxime perspiciatis quas. Alias aspernatur cupiditate, error impedit ipsa libero obcaecati reprehenderit sit sunt veniam.
20
+ </div>
21
+ <div className="border border-black p-4" data-teasermanager-slot="test-5">
22
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Asperiores at consequatur cupiditate dolore, dolorem, eligendi et eum explicabo harum illum inventore maxime molestias porro quod recusandae, reiciendis repellat repudiandae saepe sint tempora tempore temporibus veniam voluptates. Dignissimos explicabo iusto reprehenderit.
23
+ </div>
24
+ <div className="border border-black p-4" data-teasermanager-slot="test-6">
25
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad aperiam beatae consequatur consequuntur deserunt exercitationem fugit, magni necessitatibus pariatur quae quia, quisquam saepe sunt, tenetur!
26
+ </div>
27
+ <div className="border border-black p-4" data-teasermanager-slot="test-7">
28
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestias, obcaecati, unde! In labore nesciunt optio, possimus praesentium sunt temporibus vero.
29
+ </div>
30
+ <div className="border border-black p-4" data-teasermanager-slot="test-8">
31
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi, rem, sapiente! At consequatur cum distinctio dolorem doloribus eligendi labore necessitatibus nemo officia, perspiciatis porro quisquam quos, saepe vitae voluptas. Eligendi?
32
+ </div>
33
+ <div className="border border-black p-4" data-teasermanager-slot="test-9">
34
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad alias beatae doloribus ex, impedit labore maxime perspiciatis quas. Alias aspernatur cupiditate, error impedit ipsa libero obcaecati reprehenderit sit sunt veniam.
35
+ </div>
36
+ <div className="border border-black p-4" data-teasermanager-slot="test-10">
37
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Asperiores at consequatur cupiditate dolore, dolorem, eligendi et eum explicabo harum illum inventore maxime molestias porro quod recusandae, reiciendis repellat repudiandae saepe sint tempora tempore temporibus veniam voluptates. Dignissimos explicabo iusto reprehenderit.
38
+ </div>
39
+ <div className="border border-black p-4" data-teasermanager-slot="test-11">
40
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad aperiam beatae consequatur consequuntur deserunt exercitationem fugit, magni necessitatibus pariatur quae quia, quisquam saepe sunt, tenetur!
41
+ </div>
42
+ <div className="border border-black p-4" data-teasermanager-slot="test-12">
43
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestias, obcaecati, unde! In labore nesciunt optio, possimus praesentium sunt temporibus vero.
44
+ </div>
45
+ <div className="border border-black p-4" data-teasermanager-slot="test-13">
46
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi, rem, sapiente! At consequatur cum distinctio dolorem doloribus eligendi labore necessitatibus nemo officia, perspiciatis porro quisquam quos, saepe vitae voluptas. Eligendi?
47
+ </div>
48
+ <div className="border border-black p-4" data-teasermanager-slot="test-14">
49
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad alias beatae doloribus ex, impedit labore maxime perspiciatis quas. Alias aspernatur cupiditate, error impedit ipsa libero obcaecati reprehenderit sit sunt veniam.
50
+ </div>
51
+ <div className="border border-black p-4" data-teasermanager-slot="test-15">
52
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Asperiores at consequatur cupiditate dolore, dolorem, eligendi et eum explicabo harum illum inventore maxime molestias porro quod recusandae, reiciendis repellat repudiandae saepe sint tempora tempore temporibus veniam voluptates. Dignissimos explicabo iusto reprehenderit.
53
+ </div>
54
+ <div className="border border-black p-4" data-teasermanager-slot="test-16">
55
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad aperiam beatae consequatur consequuntur deserunt exercitationem fugit, magni necessitatibus pariatur quae quia, quisquam saepe sunt, tenetur!
56
+ </div>
57
+ </div>
6
58
  <h2>Props</h2>
7
- <div>{JSON.stringify(props)}</div>
59
+ {!!mvp && mvp.length > 0 && (
60
+ <ul>
61
+ {mvp.map(m => (
62
+ <li key={m.feld1}>
63
+ <b>{m.feld1}</b>:
64
+ <span>{m.feld2}</span>
65
+ </li>
66
+ ))}
67
+ </ul>
68
+ )}
69
+ <div>{JSON.stringify({
70
+ ...props,
71
+ mvp,
72
+ title,
73
+ })}</div>
8
74
  </div>;
9
75
  }