@dbcdk/react-components 0.0.209 → 0.0.211

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.
Files changed (58) hide show
  1. package/dist/client.cjs +10 -0
  2. package/dist/client.d.ts +4 -0
  3. package/dist/client.js +2 -0
  4. package/dist/components/forms/markdown-editor/MarkdownEditor.cjs +475 -0
  5. package/dist/components/forms/markdown-editor/MarkdownEditor.d.ts +68 -0
  6. package/dist/components/forms/markdown-editor/MarkdownEditor.js +468 -0
  7. package/dist/components/forms/markdown-editor/MarkdownEditor.module.css +202 -0
  8. package/dist/components/forms/markdown-editor/MarkdownViewer.cjs +29 -0
  9. package/dist/components/forms/markdown-editor/MarkdownViewer.d.ts +27 -0
  10. package/dist/components/forms/markdown-editor/MarkdownViewer.js +23 -0
  11. package/dist/components/forms/markdown-editor/MarkdownViewer.module.css +97 -0
  12. package/dist/components/forms/markdown-editor/markdown.utils.cjs +325 -0
  13. package/dist/components/forms/markdown-editor/markdown.utils.d.ts +66 -0
  14. package/dist/components/forms/markdown-editor/markdown.utils.js +310 -0
  15. package/dist/components/listbox/Listbox.cjs +2 -2
  16. package/dist/components/listbox/Listbox.d.ts +2 -2
  17. package/dist/components/listbox/Listbox.js +2 -2
  18. package/dist/components/meta-bar/MetaBar.cjs +2 -2
  19. package/dist/components/meta-bar/MetaBar.d.ts +2 -1
  20. package/dist/components/meta-bar/MetaBar.js +2 -2
  21. package/dist/components/meta-bar/MetaBar.module.css +7 -0
  22. package/dist/components/nav-bar/NavBar.d.ts +2 -1
  23. package/dist/components/overlay/tooltip/TooltipProvider.cjs +10 -1
  24. package/dist/components/overlay/tooltip/TooltipProvider.js +11 -2
  25. package/dist/components/overlay/tooltip/useTooltipTrigger.cjs +9 -2
  26. package/dist/components/overlay/tooltip/useTooltipTrigger.js +9 -2
  27. package/dist/components/sidebar/Sidebar.cjs +2 -1
  28. package/dist/components/sidebar/Sidebar.js +2 -1
  29. package/dist/components/sidebar/components/SidebarItem.cjs +34 -13
  30. package/dist/components/sidebar/components/SidebarItem.d.ts +4 -2
  31. package/dist/components/sidebar/components/SidebarItem.js +30 -13
  32. package/dist/components/sidebar/components/SidebarItem.module.css +16 -0
  33. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.cjs +41 -116
  34. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +4 -5
  35. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +42 -117
  36. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +17 -25
  37. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.cjs +1 -1
  38. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +1 -1
  39. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +0 -1
  40. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.cjs +1 -2
  41. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.d.ts +1 -2
  42. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.js +1 -2
  43. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +4 -10
  44. package/dist/components/sidebar/components/sidebar-items/SidebarItems.cjs +11 -19
  45. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +11 -19
  46. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +0 -1
  47. package/dist/components/sidebar/providers/SidebarProvider.cjs +9 -7
  48. package/dist/components/sidebar/providers/SidebarProvider.d.ts +2 -1
  49. package/dist/components/sidebar/providers/SidebarProvider.js +9 -7
  50. package/dist/components/tabs/Tabs.cjs +83 -69
  51. package/dist/components/tabs/Tabs.d.ts +5 -1
  52. package/dist/components/tabs/Tabs.js +83 -69
  53. package/dist/components/tabs/Tabs.module.css +39 -0
  54. package/dist/index.cjs +213 -208
  55. package/dist/index.css +2 -0
  56. package/dist/index.d.ts +2 -0
  57. package/dist/index.js +1 -0
  58. package/package.json +4 -1
@@ -0,0 +1,468 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { Bold, Italic, Link, Heading, List, RemoveFormatting } from 'lucide-react';
4
+ import React, { useId, useRef, useMemo, useState, useSyncExternalStore, useEffect, useLayoutEffect } from 'react';
5
+ import { flushSync } from 'react-dom';
6
+ import { Button } from '../../../components/button/Button';
7
+ import { InputContainer } from '../../../components/forms/input-container/InputContainer';
8
+ import { getDescribedBy } from '../../../components/forms/input-container/messageIds';
9
+ import { Switch } from '../../../components/forms/switch/Switch';
10
+ import { Modal } from '../../../components/overlay/modal/Modal';
11
+ import { mergeRefs } from '../../../utils/refs.utils';
12
+ import { isPlainMarkdown, isMarkdownFormatActive, limitPlainText, markdownToPlainText, plainTextToMarkdown, formatMarkdownSelection } from './markdown.utils';
13
+ import styles from './MarkdownEditor.module.css';
14
+ import { MarkdownViewer } from './MarkdownViewer';
15
+
16
+ const DEFAULT_LABELS = {
17
+ editor: "Markdown-editor",
18
+ editTab: "Rediger",
19
+ previewTab: "Forh\xE5ndsvisning",
20
+ toolbar: "Markdown-formatering",
21
+ bold: "Fed",
22
+ italic: "Kursiv",
23
+ link: "Inds\xE6t link",
24
+ linkTextPlaceholder: "linktekst",
25
+ linkUrlPlaceholder: "inds\xE6t url",
26
+ heading: "Overskrift",
27
+ bulletList: "Punktopstilling",
28
+ literal: "Bogstavelig tekst",
29
+ plainText: "Ren tekst",
30
+ emptyPreview: "Intet at forh\xE5ndsvise endnu.",
31
+ newTab: "(\xE5bner i en ny fane)",
32
+ plainTextConfirmTitle: "Skift til ren tekst?",
33
+ plainTextConfirmBody: "Teksten indeholder formatering. I ren tekst vises formateringstegnene som almindelig tekst, og formateringen g\xE5r tabt.",
34
+ plainTextTooLongBody: "I ren tekst gemmes specialtegn med ekstra tegn, s\xE5 teksten bliver l\xE6ngere end tilladt og skal forkortes.",
35
+ plainTextConfirmAction: "Skift til ren tekst",
36
+ cancel: "Annuller"
37
+ };
38
+ const TOOLBAR_GROUPS = [
39
+ [
40
+ {
41
+ label: "bold",
42
+ icon: /* @__PURE__ */ jsx(Bold, { "aria-hidden": "true" }),
43
+ format: { type: "wrap", marker: "**" },
44
+ shortcut: "b"
45
+ },
46
+ {
47
+ label: "italic",
48
+ icon: /* @__PURE__ */ jsx(Italic, { "aria-hidden": "true" }),
49
+ format: { type: "wrap", marker: "*" },
50
+ shortcut: "i"
51
+ },
52
+ { label: "link", icon: /* @__PURE__ */ jsx(Link, { "aria-hidden": "true" }), format: { type: "link" }, shortcut: "k" }
53
+ ],
54
+ [
55
+ {
56
+ label: "heading",
57
+ icon: /* @__PURE__ */ jsx(Heading, { "aria-hidden": "true" }),
58
+ format: { type: "line", prefix: "# ", toggle: true }
59
+ }
60
+ ],
61
+ [
62
+ {
63
+ label: "bulletList",
64
+ icon: /* @__PURE__ */ jsx(List, { "aria-hidden": "true" }),
65
+ format: { type: "line", prefix: "- ", toggle: true }
66
+ }
67
+ ],
68
+ [{ label: "literal", icon: /* @__PURE__ */ jsx(RemoveFormatting, { "aria-hidden": "true" }), format: { type: "escape" } }]
69
+ ];
70
+ const TOOLBAR_ITEMS = TOOLBAR_GROUPS.flat();
71
+ const subscribeToNothing = () => () => {
72
+ };
73
+ const isApplePlatform = () => /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);
74
+ function replaceWithUndo(textarea, next) {
75
+ const current = textarea.value;
76
+ let start = 0;
77
+ while (start < current.length && start < next.length && current[start] === next[start]) start++;
78
+ let end = 0;
79
+ while (end < current.length - start && end < next.length - start && current[current.length - 1 - end] === next[next.length - 1 - end]) {
80
+ end++;
81
+ }
82
+ const inserted = next.slice(start, next.length - end);
83
+ if (typeof document.execCommand !== "function") return false;
84
+ textarea.focus();
85
+ textarea.setSelectionRange(start, current.length - end);
86
+ const applied = inserted ? document.execCommand("insertText", false, inserted) : document.execCommand("delete", false);
87
+ return applied && textarea.value === next;
88
+ }
89
+ function MarkdownEditor({
90
+ value,
91
+ onChange,
92
+ onBlur,
93
+ onFocus,
94
+ ref,
95
+ name,
96
+ placeholder,
97
+ rows = 8,
98
+ maxLength,
99
+ disabled = false,
100
+ readOnly = false,
101
+ id,
102
+ className,
103
+ defaultPlainText = false,
104
+ onPlainTextChange,
105
+ headingOffset,
106
+ labels: labelOverrides,
107
+ label,
108
+ error,
109
+ helpText,
110
+ helpTextAddition,
111
+ fullWidth,
112
+ required,
113
+ orientation = "vertical",
114
+ modified,
115
+ modifiedLabel,
116
+ labelAction,
117
+ size,
118
+ labelWidth,
119
+ labelAlignment
120
+ }) {
121
+ const labels = { ...DEFAULT_LABELS, ...labelOverrides };
122
+ const generatedId = useId();
123
+ const editorId = id != null ? id : `markdown-editor-${generatedId}`;
124
+ const panelId = `${editorId}-panel`;
125
+ const tabRefs = useRef({
126
+ edit: null,
127
+ preview: null
128
+ });
129
+ const toolbarRefs = useRef([]);
130
+ const textareaRef = useRef(null);
131
+ const mergedTextareaRef = useMemo(() => mergeRefs(textareaRef, ref), [ref]);
132
+ const pendingSelection = useRef(null);
133
+ const [activeTab, setActiveTab] = useState("edit");
134
+ const [selection, setSelection] = useState([0, 0]);
135
+ const [plainText, setPlainText] = useState(() => defaultPlainText && isPlainMarkdown(value));
136
+ const [checkedValue, setCheckedValue] = useState(value);
137
+ if (plainText && value !== checkedValue) {
138
+ setCheckedValue(value);
139
+ if (!isPlainMarkdown(value)) setPlainText(false);
140
+ }
141
+ const [confirmPlainText, setConfirmPlainText] = useState(null);
142
+ const [toolbarFocusIndex, setToolbarFocusIndex] = useState(0);
143
+ const shortcutModifier = useSyncExternalStore(
144
+ subscribeToNothing,
145
+ () => isApplePlatform() ? "\u2318" : "Ctrl+",
146
+ () => "Ctrl+"
147
+ );
148
+ const hideToolbar = activeTab === "preview";
149
+ const formattingDisabled = disabled || readOnly;
150
+ const enterPlainText = () => {
151
+ setConfirmPlainText(null);
152
+ setPlainText(true);
153
+ setSelection([0, 0]);
154
+ onPlainTextChange == null ? void 0 : onPlainTextChange(true);
155
+ const converted = plainTextToMarkdown(markdownToPlainText(value));
156
+ if (converted !== value) onChange(converted);
157
+ };
158
+ const handlePlainTextChange = (nextPlainText) => {
159
+ if (!nextPlainText) {
160
+ setPlainText(false);
161
+ setSelection([0, 0]);
162
+ onPlainTextChange == null ? void 0 : onPlainTextChange(false);
163
+ return;
164
+ }
165
+ const formatting = !isPlainMarkdown(value);
166
+ const tooLong = maxLength !== void 0 && plainTextToMarkdown(markdownToPlainText(value)).length > maxLength;
167
+ if (formatting || tooLong) setConfirmPlainText({ formatting, tooLong });
168
+ else enterPlainText();
169
+ };
170
+ useEffect(() => {
171
+ const textarea = textareaRef.current;
172
+ if (activeTab !== "preview" || !(textarea == null ? void 0 : textarea.labels)) return;
173
+ const labelElements = [...textarea.labels];
174
+ const reveal = () => {
175
+ var _a;
176
+ flushSync(() => setActiveTab("edit"));
177
+ (_a = textareaRef.current) == null ? void 0 : _a.focus();
178
+ };
179
+ labelElements.forEach((element) => element.addEventListener("click", reveal));
180
+ return () => labelElements.forEach((element) => element.removeEventListener("click", reveal));
181
+ }, [activeTab]);
182
+ useEffect(() => {
183
+ const textarea = textareaRef.current;
184
+ if (!textarea) return;
185
+ const sync = () => setSelection(
186
+ (current) => current[0] === textarea.selectionStart && current[1] === textarea.selectionEnd ? current : [textarea.selectionStart, textarea.selectionEnd]
187
+ );
188
+ textarea.addEventListener("selectionchange", sync);
189
+ document.addEventListener("selectionchange", sync);
190
+ return () => {
191
+ textarea.removeEventListener("selectionchange", sync);
192
+ document.removeEventListener("selectionchange", sync);
193
+ };
194
+ }, []);
195
+ const handleTabKeyDown = (event) => {
196
+ var _a;
197
+ const keys = {
198
+ ArrowRight: activeTab === "edit" ? "preview" : "edit",
199
+ ArrowDown: activeTab === "edit" ? "preview" : "edit",
200
+ ArrowLeft: activeTab === "edit" ? "preview" : "edit",
201
+ ArrowUp: activeTab === "edit" ? "preview" : "edit",
202
+ Home: "edit",
203
+ End: "preview"
204
+ };
205
+ const nextTab = keys[event.key];
206
+ if (!nextTab) return;
207
+ event.preventDefault();
208
+ setActiveTab(nextTab);
209
+ (_a = tabRefs.current[nextTab]) == null ? void 0 : _a.focus();
210
+ };
211
+ useLayoutEffect(() => {
212
+ const selection2 = pendingSelection.current;
213
+ const textarea = textareaRef.current;
214
+ if (!selection2 || !textarea) return;
215
+ textarea.focus();
216
+ textarea.setSelectionRange(selection2[0], selection2[1]);
217
+ setSelection(selection2);
218
+ pendingSelection.current = null;
219
+ }, [value]);
220
+ const withPlaceholders = (format) => format.type === "link" ? {
221
+ ...format,
222
+ textPlaceholder: labels.linkTextPlaceholder,
223
+ urlPlaceholder: labels.linkUrlPlaceholder
224
+ } : format;
225
+ const applyFormat = (itemFormat) => {
226
+ const format = withPlaceholders(itemFormat);
227
+ const textarea = textareaRef.current;
228
+ if (!textarea || formattingDisabled) return;
229
+ const formatted = formatMarkdownSelection(
230
+ value,
231
+ textarea.selectionStart,
232
+ textarea.selectionEnd,
233
+ format
234
+ );
235
+ if (formatted.value === value) {
236
+ textarea.focus();
237
+ textarea.setSelectionRange(formatted.selectionStart, formatted.selectionEnd);
238
+ return;
239
+ }
240
+ pendingSelection.current = [formatted.selectionStart, formatted.selectionEnd];
241
+ if (!replaceWithUndo(textarea, formatted.value)) onChange(formatted.value);
242
+ };
243
+ const isItemDisabled = (item) => formattingDisabled || item.format.type === "escape" && selection[0] === selection[1];
244
+ const enabledIndexes = TOOLBAR_ITEMS.flatMap(
245
+ (item, index) => isItemDisabled(item) ? [] : [index]
246
+ );
247
+ const tabStopIndex = enabledIndexes.includes(toolbarFocusIndex) ? toolbarFocusIndex : enabledIndexes[0];
248
+ const handleToolbarKeyDown = (event) => {
249
+ var _a;
250
+ const position = enabledIndexes.indexOf(tabStopIndex);
251
+ const targets = {
252
+ ArrowRight: enabledIndexes[(position + 1) % enabledIndexes.length],
253
+ ArrowLeft: enabledIndexes[(position - 1 + enabledIndexes.length) % enabledIndexes.length],
254
+ Home: enabledIndexes[0],
255
+ End: enabledIndexes[enabledIndexes.length - 1]
256
+ };
257
+ const target = targets[event.key];
258
+ if (target === void 0) return;
259
+ event.preventDefault();
260
+ setToolbarFocusIndex(target);
261
+ (_a = toolbarRefs.current[target]) == null ? void 0 : _a.focus();
262
+ };
263
+ const handleTextareaKeyDown = (event) => {
264
+ if (plainText || !(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return;
265
+ const item = TOOLBAR_ITEMS.find((item2) => item2.shortcut === event.key.toLowerCase());
266
+ if (!item || isItemDisabled(item)) return;
267
+ event.preventDefault();
268
+ applyFormat(item.format);
269
+ };
270
+ let itemIndex = 0;
271
+ const toolbar = /* @__PURE__ */ jsx(
272
+ "div",
273
+ {
274
+ className: [styles.toolbar, hideToolbar ? styles.toolbarHidden : ""].filter(Boolean).join(" "),
275
+ role: "toolbar",
276
+ "aria-label": labels.toolbar,
277
+ "aria-hidden": hideToolbar || void 0,
278
+ inert: hideToolbar,
279
+ onKeyDown: handleToolbarKeyDown,
280
+ children: TOOLBAR_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
281
+ groupIndex > 0 && /* @__PURE__ */ jsx("span", { className: styles.separator, "aria-hidden": "true" }),
282
+ group.map((item) => {
283
+ var _a;
284
+ const index = itemIndex++;
285
+ const shortcut = (_a = item.shortcut) == null ? void 0 : _a.toUpperCase();
286
+ return /* @__PURE__ */ jsx(
287
+ Button,
288
+ {
289
+ ref: (element) => {
290
+ toolbarRefs.current[index] = element;
291
+ },
292
+ "aria-label": labels[item.label],
293
+ title: shortcut ? `${labels[item.label]} (${shortcutModifier}${shortcut})` : labels[item.label],
294
+ "aria-keyshortcuts": shortcut ? `Control+${shortcut} Meta+${shortcut}` : void 0,
295
+ "aria-pressed": isMarkdownFormatActive(
296
+ value,
297
+ selection[0],
298
+ selection[1],
299
+ item.format
300
+ ),
301
+ className: styles.formatButton,
302
+ variant: "inline",
303
+ size: "sm",
304
+ icon: item.icon,
305
+ disabled: isItemDisabled(item),
306
+ tabIndex: index === tabStopIndex ? 0 : -1,
307
+ onFocus: () => setToolbarFocusIndex(index),
308
+ onMouseDown: (event) => event.preventDefault(),
309
+ onClick: () => applyFormat(item.format)
310
+ },
311
+ item.label
312
+ );
313
+ })
314
+ ] }, groupIndex))
315
+ }
316
+ );
317
+ return /* @__PURE__ */ jsxs(
318
+ InputContainer,
319
+ {
320
+ label,
321
+ htmlFor: editorId,
322
+ error,
323
+ helpText,
324
+ helpTextAddition,
325
+ fullWidth,
326
+ required,
327
+ orientation,
328
+ modified,
329
+ modifiedLabel,
330
+ labelAction,
331
+ size,
332
+ labelWidth,
333
+ labelAlignment,
334
+ children: [
335
+ /* @__PURE__ */ jsx("div", { className: [styles.field, className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsxs(
336
+ "div",
337
+ {
338
+ className: styles.frame,
339
+ "data-invalid": error ? "true" : void 0,
340
+ style: { "--markdown-editor-rows": rows },
341
+ children: [
342
+ /* @__PURE__ */ jsxs("div", { className: styles.header, children: [
343
+ /* @__PURE__ */ jsx("div", { className: styles.tabList, role: "tablist", "aria-label": label != null ? label : labels.editor, children: ["edit", "preview"].map((tab) => /* @__PURE__ */ jsx(
344
+ "button",
345
+ {
346
+ ref: (element) => {
347
+ tabRefs.current[tab] = element;
348
+ },
349
+ id: `${editorId}-tab-${tab}`,
350
+ className: styles.tabButton,
351
+ type: "button",
352
+ role: "tab",
353
+ "aria-selected": activeTab === tab,
354
+ "aria-controls": panelId,
355
+ tabIndex: activeTab === tab ? 0 : -1,
356
+ onClick: () => setActiveTab(tab),
357
+ onKeyDown: handleTabKeyDown,
358
+ children: tab === "edit" ? labels.editTab : labels.previewTab
359
+ },
360
+ tab
361
+ )) }),
362
+ !plainText && toolbar,
363
+ /* @__PURE__ */ jsx("div", { className: styles.modeSwitch, children: /* @__PURE__ */ jsx(
364
+ Switch,
365
+ {
366
+ noContainer: true,
367
+ size: "sm",
368
+ label: labels.plainText,
369
+ checked: plainText,
370
+ disabled: formattingDisabled,
371
+ onChange: handlePlainTextChange
372
+ }
373
+ ) })
374
+ ] }),
375
+ /* @__PURE__ */ jsxs(
376
+ "div",
377
+ {
378
+ id: panelId,
379
+ className: styles.panel,
380
+ role: "tabpanel",
381
+ "aria-labelledby": `${editorId}-tab-${activeTab}`,
382
+ children: [
383
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value, disabled }),
384
+ /* @__PURE__ */ jsx(
385
+ "textarea",
386
+ {
387
+ ref: mergedTextareaRef,
388
+ id: editorId,
389
+ className: styles.textarea,
390
+ value: plainText ? markdownToPlainText(value) : value,
391
+ onChange: (event) => {
392
+ const textarea = event.currentTarget;
393
+ if (!plainText) {
394
+ onChange(textarea.value);
395
+ return;
396
+ }
397
+ const limited = limitPlainText(
398
+ markdownToPlainText(value),
399
+ textarea.value,
400
+ maxLength
401
+ );
402
+ if (!limited) {
403
+ const caret = textarea.selectionStart - (textarea.value.length - markdownToPlainText(value).length);
404
+ queueMicrotask(() => textarea.setSelectionRange(caret, caret));
405
+ return;
406
+ }
407
+ if (limited.caret >= 0) pendingSelection.current = [limited.caret, limited.caret];
408
+ onChange(plainTextToMarkdown(limited.text));
409
+ },
410
+ onSelect: (event) => setSelection([event.currentTarget.selectionStart, event.currentTarget.selectionEnd]),
411
+ onKeyDown: handleTextareaKeyDown,
412
+ onBlur,
413
+ onFocus,
414
+ placeholder,
415
+ rows,
416
+ maxLength: plainText ? void 0 : maxLength,
417
+ disabled,
418
+ readOnly,
419
+ required,
420
+ hidden: activeTab !== "edit",
421
+ onInvalid: () => {
422
+ if (activeTab === "preview") {
423
+ flushSync(() => setActiveTab("edit"));
424
+ }
425
+ },
426
+ "aria-invalid": Boolean(error) || void 0,
427
+ "aria-describedby": getDescribedBy(editorId, { error, helpText })
428
+ }
429
+ ),
430
+ activeTab === "preview" && /* @__PURE__ */ jsx(
431
+ MarkdownViewer,
432
+ {
433
+ className: styles.preview,
434
+ markdown: value,
435
+ headingOffset,
436
+ openLinksInNewTab: true,
437
+ newTabLabel: labels.newTab,
438
+ emptyMessage: labels.emptyPreview
439
+ }
440
+ )
441
+ ]
442
+ }
443
+ )
444
+ ]
445
+ }
446
+ ) }),
447
+ /* @__PURE__ */ jsx(
448
+ Modal,
449
+ {
450
+ isOpen: confirmPlainText !== null,
451
+ onRequestClose: () => setConfirmPlainText(null),
452
+ header: labels.plainTextConfirmTitle,
453
+ severity: "warning",
454
+ primaryAction: { label: labels.plainTextConfirmAction, onClick: enterPlainText },
455
+ secondaryAction: { label: labels.cancel, onClick: () => setConfirmPlainText(null) },
456
+ children: [
457
+ (confirmPlainText == null ? void 0 : confirmPlainText.formatting) && labels.plainTextConfirmBody,
458
+ (confirmPlainText == null ? void 0 : confirmPlainText.tooLong) && labels.plainTextTooLongBody
459
+ ].filter(Boolean).join(" ")
460
+ }
461
+ )
462
+ ]
463
+ }
464
+ );
465
+ }
466
+ MarkdownEditor.displayName = "MarkdownEditor";
467
+
468
+ export { MarkdownEditor };
@@ -0,0 +1,202 @@
1
+ .field {
2
+ min-width: 0;
3
+ flex: 1;
4
+ }
5
+
6
+ .frame {
7
+ min-width: 0;
8
+ overflow: hidden;
9
+ border: var(--border-width-thin) solid var(--color-border-default);
10
+ border-radius: var(--border-radius-lg);
11
+ background: var(--color-bg-surface);
12
+ }
13
+
14
+ .header {
15
+ position: relative;
16
+ display: flex;
17
+ align-items: stretch;
18
+ justify-content: flex-start;
19
+ gap: 0;
20
+ min-width: 0;
21
+ padding-inline-start: 0;
22
+ padding-inline-end: var(--spacing-xs);
23
+ border-block-end: var(--border-width-thin) solid var(--color-border-default);
24
+ background: var(--color-bg-surface);
25
+ }
26
+
27
+ .tabList {
28
+ display: flex;
29
+ flex: 0 0 auto;
30
+ align-items: stretch;
31
+ min-width: 0;
32
+ }
33
+
34
+ .tabButton {
35
+ position: relative;
36
+ display: inline-flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ min-height: var(--component-size-md);
40
+ padding: var(--spacing-xs) var(--spacing-md);
41
+ border: 0;
42
+ border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
43
+ color: var(--color-fg-muted);
44
+ background: transparent;
45
+ font: inherit;
46
+ font-size: var(--font-size-sm);
47
+ white-space: nowrap;
48
+ cursor: pointer;
49
+ }
50
+
51
+ .tabButton:first-child {
52
+ border-start-start-radius: 0;
53
+ }
54
+
55
+ .tabButton:hover {
56
+ color: var(--color-fg-default);
57
+ background: var(--opac-bg-light);
58
+ }
59
+
60
+ .tabButton[aria-selected='true'] {
61
+ color: var(--color-brand);
62
+ font-weight: var(--font-weight-semibold);
63
+ background: transparent;
64
+ }
65
+
66
+ .tabButton:focus-visible {
67
+ z-index: 1;
68
+ outline: 2px solid var(--color-field-focus);
69
+ outline-offset: -2px;
70
+ }
71
+
72
+ .panel {
73
+ background: var(--color-bg-surface);
74
+ }
75
+
76
+ /* At least as tall as the textarea, so switching tabs doesn't move the page. */
77
+ .preview {
78
+ box-sizing: border-box;
79
+ min-height: calc(
80
+ var(--markdown-editor-rows) * var(--line-height-normal) * 1em + 2 * var(--spacing-sm)
81
+ );
82
+ padding: var(--spacing-sm);
83
+ line-height: var(--line-height-normal);
84
+ }
85
+
86
+ .toolbar {
87
+ display: flex;
88
+ flex-wrap: wrap;
89
+ align-items: center;
90
+ justify-content: flex-start;
91
+ margin-inline-start: var(--spacing-lg);
92
+ min-width: 0;
93
+ gap: var(--spacing-xxs);
94
+ }
95
+
96
+ .toolbarHidden {
97
+ visibility: hidden;
98
+ }
99
+
100
+ .toolbar .formatButton.formatButton {
101
+ box-sizing: border-box;
102
+ width: var(--component-size-md);
103
+ min-width: var(--component-size-md);
104
+ height: var(--component-size-md);
105
+ min-height: var(--component-size-md);
106
+ padding: 0;
107
+ border: 0;
108
+ border-radius: var(--border-radius-sm);
109
+ color: var(--color-fg-muted);
110
+ background: transparent;
111
+ }
112
+
113
+ .toolbar .formatButton.formatButton:hover {
114
+ color: var(--color-fg-default);
115
+ background: var(--color-bg-hover-subtle);
116
+ }
117
+
118
+ .toolbar .formatButton.formatButton[aria-pressed='true'] {
119
+ color: var(--color-brand);
120
+ background: var(--opac-bg-brand);
121
+ }
122
+
123
+ .toolbar .formatButton.formatButton:focus-visible {
124
+ outline: 2px solid var(--color-field-focus);
125
+ outline-offset: -2px;
126
+ box-shadow: none;
127
+ }
128
+
129
+ .modeSwitch {
130
+ display: flex;
131
+ flex: 0 0 auto;
132
+ align-items: center;
133
+ margin-inline-start: auto;
134
+ padding-inline-start: var(--spacing-md);
135
+ }
136
+
137
+ .separator {
138
+ width: var(--border-width-thin);
139
+ height: var(--spacing-md);
140
+ margin-inline: var(--spacing-xxs);
141
+ background: var(--color-border-default);
142
+ }
143
+
144
+ .textarea {
145
+ box-sizing: border-box;
146
+ display: block;
147
+ width: 100%;
148
+ padding: var(--spacing-sm);
149
+ resize: vertical;
150
+ border: 0;
151
+ border-radius: inherit;
152
+ font: inherit;
153
+ line-height: var(--line-height-normal);
154
+ color: var(--color-fg-default);
155
+ background: var(--color-bg-surface);
156
+ }
157
+
158
+ .textarea:focus-visible {
159
+ outline: 2px solid var(--color-field-focus);
160
+ outline-offset: -2px;
161
+ }
162
+
163
+ .textarea[hidden] {
164
+ display: none;
165
+ }
166
+
167
+ .textarea:disabled {
168
+ color: var(--color-disabled-fg);
169
+ background: var(--color-disabled-bg);
170
+ cursor: not-allowed;
171
+ }
172
+
173
+ .frame[data-invalid='true'] {
174
+ border-color: var(--color-status-error);
175
+ }
176
+
177
+ @media (max-width: 767px) {
178
+ .header {
179
+ flex-wrap: wrap;
180
+ gap: 0;
181
+ padding-inline: 0;
182
+ }
183
+
184
+ .tabList {
185
+ width: 100%;
186
+ }
187
+
188
+ .modeSwitch {
189
+ position: absolute;
190
+ inset-block-start: 0;
191
+ inset-inline-end: var(--spacing-sm);
192
+ min-height: var(--component-size-md);
193
+ }
194
+
195
+ .toolbar {
196
+ width: 100%;
197
+ justify-content: flex-start;
198
+ margin-inline-start: 0;
199
+ padding: var(--spacing-xs);
200
+ border-block-start: var(--border-width-thin) solid var(--color-border-default);
201
+ }
202
+ }
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var markdown_utils = require('./markdown.utils');
5
+ var styles = require('./MarkdownViewer.module.css');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
10
+
11
+ function MarkdownViewer({
12
+ markdown,
13
+ emptyMessage,
14
+ headingOffset,
15
+ openLinksInNewTab = true,
16
+ newTabLabel = "(\xE5bner i en ny fane)",
17
+ className
18
+ }) {
19
+ const isEmpty = markdown.trim().length === 0;
20
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [styles__default.default.viewer, className].filter(Boolean).join(" "), children: isEmpty ? emptyMessage !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: styles__default.default.emptyMessage, children: emptyMessage }) : null : markdown_utils.renderMarkdown(markdown, {
21
+ headingOffset,
22
+ openLinksInNewTab,
23
+ // The space is hidden too, so links don't end in a visible underlined gap.
24
+ newTabHint: /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.visuallyHidden, children: ` ${newTabLabel}` })
25
+ }) });
26
+ }
27
+ MarkdownViewer.displayName = "MarkdownViewer";
28
+
29
+ exports.MarkdownViewer = MarkdownViewer;