@vonaffenfels/slate-editor 1.0.2 → 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 "./SidebarEditor.css";
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,109 +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);
80
93
  }
81
94
  }
82
95
  };
83
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);
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);
150
+ }
151
+ };
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
-
110
- </div>
111
- <div className="cursor-pointer p-2" title="Nach unten verschieben" onClick={() => onMove && onMove(storybookElement, "down")}>
112
-
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
- 🗑
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
- <Accordion>
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
+ })}
146
229
  {Object.keys(fields.tables).map(tableKey => {
147
230
  return (
148
- <AccordionItem key={`accordion-item-${tableKey}`} title={tableKey}>
149
- {Object.keys(fields.tables[tableKey]).map(key => {
150
- const field = fields.tables[tableKey][key];
151
-
152
- return <SidebarEditorField
153
- sdk={sdk}
154
- value={storybookElement?.attributes?.[key]}
155
- key={key}
156
- fieldKey={key}
157
- story={storybookElement}
158
- field={field}
159
- onChange={value => handleFieldValueChange(key, value)}
160
- />;
161
- })}
162
- </AccordionItem>
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>
163
249
  );
164
250
  })}
165
- </Accordion>
166
- </>
167
- )}
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>
168
309
  </div>
169
310
  );
170
311
  };
@@ -176,21 +317,26 @@ const VariantSelect = ({
176
317
  }) => {
177
318
  const stories = story?.stories?.filter(s => s.title !== story.title) || [];
178
319
 
179
- if (stories.length <= 1 || !onChange) {
320
+ if (!onChange) {
180
321
  return null;
181
322
  }
182
323
 
183
- return <select
184
- className={className}
185
- onChange={e => onChange && onChange({
186
- block: story.id,
187
- attributes: {...stories.find(s => s.title === e.target.value)?.args || {}},
188
- })}>
189
- <option>Preset wählen</option>
190
- {stories.map(s => (
191
- <option key={`variant-option-${s.title}`} value={s.title}>{s.title}</option>
192
- ))}
193
- </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
+ );
194
340
  };
195
341
 
196
342
  const BlockSelect = ({
@@ -210,27 +356,31 @@ const BlockSelect = ({
210
356
 
211
357
  // reset to the first story
212
358
  onChange({
359
+ ...active,
213
360
  block: story.id,
214
361
  attributes: {...(story?.stories?.[0]?.args || {})},
215
362
  });
216
363
  };
217
364
 
218
365
  return (
219
- <select
220
- onChange={e => onSelectChange(stories.find(s => s.id === e.target.value))}
221
- className={className}
222
- >
223
- <option>Element wählen</option>
224
- {stories.map(s => (
225
- <option
226
- key={`variant-option-${s.id}`}
227
- selected={active?.block === s.id}
228
- value={s.id}
229
- >
230
- {s.title}
231
- </option>
232
- ))}
233
- </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>
234
384
  );
235
385
  };
236
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
  }