instui-markdown 0.0.6

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.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # vite-plus-starter
2
+
3
+ A starter for creating a Vite Plus project.
4
+
5
+ ## Development
6
+
7
+ - Install dependencies:
8
+
9
+ ```bash
10
+ vp install
11
+ ```
12
+
13
+ - Run the unit tests:
14
+
15
+ ```bash
16
+ vp test
17
+ ```
18
+
19
+ - Build the library:
20
+
21
+ ```bash
22
+ vp pack
23
+ ```
package/dist/index.mjs ADDED
@@ -0,0 +1,753 @@
1
+ import { MDXProvider } from "@mdx-js/react";
2
+ import ReactMarkdown from "react-markdown";
3
+ import remarkGfm from "remark-gfm";
4
+ import rehypeRaw from "rehype-raw";
5
+ import rehypeSlug from "rehype-slug";
6
+ import rehypeAutolinkHeadings from "rehype-autolink-headings";
7
+ import { rehypeColorCodes, rehypeInstUIIconTokens, rehypeUnwrapBlockquoteParagraphs } from "rehype-instui-markdown";
8
+ import { Text } from "@instructure/ui-text/v11_7";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ import { Link } from "@instructure/ui-link/v11_7";
11
+ import * as InstUIIcons from "@instructure/ui-icons";
12
+ import { ExternalLinkInstUIIcon, LinkInstUIIcon } from "@instructure/ui-icons";
13
+ import { color2hex, colorToHex8, colorToRGB, isValid } from "@instructure/ui-color-utils";
14
+ import { InlineSVG } from "@instructure/ui-svg-images";
15
+ import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useId, useRef, useState } from "react";
16
+ import { Img } from "@instructure/ui-img";
17
+ import { View } from "@instructure/ui-view";
18
+ import { Heading } from "@instructure/ui-heading/v11_7";
19
+ import { List } from "@instructure/ui-list/v11_7";
20
+ import { Checkbox } from "@instructure/ui-checkbox/v11_7";
21
+ import { Badge } from "@instructure/ui-badge/v11_7";
22
+ import { SourceCodeEditor } from "@instructure/ui-source-code-editor/v11_7";
23
+ import { Alert } from "@instructure/ui-alerts/v11_7";
24
+ import { Table } from "@instructure/ui-table/v11_7";
25
+ //#region src/components/text-components.tsx
26
+ function createEmComponent() {
27
+ return ({ children }) => /* @__PURE__ */ jsx(Text, {
28
+ as: "span",
29
+ fontStyle: "italic",
30
+ children
31
+ });
32
+ }
33
+ function createStrongComponent() {
34
+ return ({ children }) => /* @__PURE__ */ jsx(Text, {
35
+ as: "span",
36
+ weight: "bold",
37
+ children
38
+ });
39
+ }
40
+ function createDelComponent() {
41
+ return ({ children }) => /* @__PURE__ */ jsx(Text, {
42
+ as: "del",
43
+ children
44
+ });
45
+ }
46
+ function createInsComponent() {
47
+ return ({ children }) => /* @__PURE__ */ jsx(Text, {
48
+ as: "ins",
49
+ children
50
+ });
51
+ }
52
+ function createSubComponent() {
53
+ return ({ children }) => /* @__PURE__ */ jsx(Text, {
54
+ as: "sub",
55
+ children
56
+ });
57
+ }
58
+ function createSupComponent() {
59
+ return ({ children }) => /* @__PURE__ */ jsx(Text, {
60
+ as: "sup",
61
+ children
62
+ });
63
+ }
64
+ function createPComponent() {
65
+ return ({ children }) => /* @__PURE__ */ jsx(Text, {
66
+ as: "p",
67
+ children
68
+ });
69
+ }
70
+ //#endregion
71
+ //#region src/helpers.tsx
72
+ const EDITOR_LANGUAGES = new Set([
73
+ "json",
74
+ "yaml",
75
+ "markdown",
76
+ "css",
77
+ "html",
78
+ "javascript"
79
+ ]);
80
+ /**
81
+ * Mapping between admonition markers and InstUI alert variants.
82
+ */
83
+ const ALERT_VARIANTS = {
84
+ NOTE: "info",
85
+ TIP: "success",
86
+ IMPORTANT: "warning",
87
+ WARNING: "warning",
88
+ CAUTION: "error"
89
+ };
90
+ const ALERT_PREFIX = /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/;
91
+ createContext(null);
92
+ /**
93
+ * Maps markdown fenced-code language classes to editor language values.
94
+ */
95
+ function mapCodeLanguage(className) {
96
+ const parsed = className?.match(/language-([\w-]+)/)?.[1]?.toLowerCase();
97
+ if (!parsed) return "markdown";
98
+ if (parsed === "js" || parsed === "jsx" || parsed === "ts" || parsed === "tsx") return "javascript";
99
+ if (parsed === "yml") return "yaml";
100
+ return EDITOR_LANGUAGES.has(parsed) ? parsed : "markdown";
101
+ }
102
+ /**
103
+ * Converts markdown code node children to normalized source text.
104
+ */
105
+ function normalizeCodeValue(content) {
106
+ if (typeof content === "string") return content.replace(/\n$/, "");
107
+ if (Array.isArray(content)) return content.join("").replace(/\n$/, "");
108
+ return "";
109
+ }
110
+ /**
111
+ * Determines whether a code node should be treated as inline code.
112
+ */
113
+ function isInlineCode(children, className) {
114
+ if (className?.includes("language-")) return false;
115
+ if (typeof children === "string") return !children.includes("\n");
116
+ if (Array.isArray(children)) return !children.some((child) => typeof child === "string" && child.includes("\n"));
117
+ return true;
118
+ }
119
+ /**
120
+ * Removes markdown admonition prefix text from a node tree.
121
+ */
122
+ function stripAlertPrefix(node) {
123
+ if (typeof node === "string") {
124
+ const match = node.match(ALERT_PREFIX);
125
+ if (!match) return {
126
+ found: false,
127
+ node
128
+ };
129
+ return {
130
+ found: true,
131
+ marker: match[1],
132
+ node: node.replace(ALERT_PREFIX, "")
133
+ };
134
+ }
135
+ if (Array.isArray(node)) {
136
+ const updatedChildren = [];
137
+ let marker;
138
+ let found = false;
139
+ for (const child of node) {
140
+ if (found) {
141
+ updatedChildren.push(child);
142
+ continue;
143
+ }
144
+ const result = stripAlertPrefix(child);
145
+ updatedChildren.push(result.node);
146
+ if (result.found) {
147
+ marker = result.marker;
148
+ found = true;
149
+ }
150
+ }
151
+ return found ? {
152
+ found: true,
153
+ marker,
154
+ node: /* @__PURE__ */ jsx(Fragment, { children: updatedChildren })
155
+ } : {
156
+ found: false,
157
+ node
158
+ };
159
+ }
160
+ if (!isValidElement(node)) return {
161
+ found: false,
162
+ node
163
+ };
164
+ const originalChildren = Children.toArray(node.props.children);
165
+ const updatedChildren = [];
166
+ let marker;
167
+ let found = false;
168
+ for (const child of originalChildren) {
169
+ if (found) {
170
+ updatedChildren.push(child);
171
+ continue;
172
+ }
173
+ const result = stripAlertPrefix(child);
174
+ updatedChildren.push(result.node);
175
+ if (result.found) {
176
+ marker = result.marker;
177
+ found = true;
178
+ }
179
+ }
180
+ if (!found) return {
181
+ found: false,
182
+ node
183
+ };
184
+ return {
185
+ found: true,
186
+ marker,
187
+ node: cloneElement(node, void 0, updatedChildren)
188
+ };
189
+ }
190
+ /**
191
+ * Filters markdown children so only React element nodes remain.
192
+ */
193
+ function onlyElementChildren(children) {
194
+ return Children.toArray(children).filter((child) => isValidElement(child));
195
+ }
196
+ /**
197
+ * Returns true when an href points to an external destination.
198
+ */
199
+ function isExternalHref(href) {
200
+ if (!href) return false;
201
+ return /^(https?:)?\/\//i.test(href) || /^(mailto|tel):/i.test(href);
202
+ }
203
+ /**
204
+ * Converts a valid CSS color value into normalized hex output for swatches.
205
+ */
206
+ function normalizeColorForSwatch(colorValue) {
207
+ if (!isValid(colorValue)) return;
208
+ return colorToRGB(colorValue).a < 1 ? colorToHex8(colorValue) : color2hex(colorValue);
209
+ }
210
+ /**
211
+ * Returns true when a color value is a literal color token form.
212
+ */
213
+ function isLiteralColorValue(colorValue) {
214
+ return /^(#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgba?\([^)]*\)|hsla?\([^)]*\))$/i.test(colorValue.trim());
215
+ }
216
+ /**
217
+ * Recursively normalizes raw SVG nodes into InstUI InlineSVG wrappers.
218
+ */
219
+ function normalizeSvgElements(node) {
220
+ if (!isValidElement(node)) return node;
221
+ const childProps = node.props;
222
+ const normalizedChildren = Children.map(childProps.children, (child) => normalizeSvgElements(child));
223
+ if (node.type === "svg") return /* @__PURE__ */ jsx(InlineSVG, {
224
+ inline: false,
225
+ className: typeof childProps.className === "string" ? childProps.className : void 0,
226
+ viewBox: typeof childProps.viewBox === "string" ? childProps.viewBox : void 0,
227
+ width: typeof childProps.width === "string" || typeof childProps.width === "number" ? childProps.width : void 0,
228
+ height: typeof childProps.height === "string" || typeof childProps.height === "number" ? childProps.height : void 0,
229
+ role: typeof childProps.role === "string" ? childProps.role : void 0,
230
+ children: normalizedChildren
231
+ });
232
+ return cloneElement(node, void 0, normalizedChildren);
233
+ }
234
+ /**
235
+ * Extracts plain text from arbitrary React node content.
236
+ */
237
+ function textFromNode(node) {
238
+ if (typeof node === "string" || typeof node === "number") return String(node);
239
+ if (Array.isArray(node)) return node.map((child) => textFromNode(child)).join("");
240
+ if (isValidElement(node)) return textFromNode(node.props.children);
241
+ return "";
242
+ }
243
+ /**
244
+ * Compares table cell text with numeric-aware fallback string sorting.
245
+ */
246
+ function compareTableCellValues(a, b) {
247
+ const trimmedA = a.trim();
248
+ const trimmedB = b.trim();
249
+ const numA = Number(trimmedA);
250
+ const numB = Number(trimmedB);
251
+ if (Number.isFinite(numA) && Number.isFinite(numB)) return numA - numB;
252
+ return trimmedA.localeCompare(trimmedB, void 0, {
253
+ numeric: true,
254
+ sensitivity: "base"
255
+ });
256
+ }
257
+ //#endregion
258
+ //#region src/components/link-component.tsx
259
+ function createLinkComponent(options) {
260
+ return ({ children, href, title, className, ...props }) => {
261
+ const ariaLabel = typeof props["aria-label"] === "string" ? props["aria-label"] : void 0;
262
+ if (className?.includes("mdx-heading-anchor") || ariaLabel === "Permalink") {
263
+ if (!options.showPermalinks) return null;
264
+ return /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Link, {
265
+ href,
266
+ title,
267
+ "aria-label": ariaLabel ?? title ?? "Permalink",
268
+ className: options.permalinkClassName,
269
+ children: /* @__PURE__ */ jsx(LinkInstUIIcon, { size: "x-small" })
270
+ })] });
271
+ }
272
+ if (isExternalHref(href)) return /* @__PURE__ */ jsxs(Link, {
273
+ href,
274
+ title,
275
+ target: "_blank",
276
+ rel: "noopener noreferrer",
277
+ children: [
278
+ children,
279
+ " ",
280
+ options.showExternalIcon && /* @__PURE__ */ jsx(ExternalLinkInstUIIcon, {
281
+ size: "x-small",
282
+ "aria-hidden": "true"
283
+ })
284
+ ]
285
+ });
286
+ return /* @__PURE__ */ jsx(Link, {
287
+ href,
288
+ title,
289
+ children
290
+ });
291
+ };
292
+ }
293
+ //#endregion
294
+ //#region src/components/img-component.tsx
295
+ function createImgComponent() {
296
+ return ({ src, alt, title }) => {
297
+ if (!src) return null;
298
+ return /* @__PURE__ */ jsx(View, {
299
+ as: "span",
300
+ margin: "small 0",
301
+ display: "block",
302
+ children: /* @__PURE__ */ jsx(Img, {
303
+ src,
304
+ alt: alt ?? "",
305
+ title,
306
+ display: "block",
307
+ constrain: "cover"
308
+ })
309
+ });
310
+ };
311
+ }
312
+ //#endregion
313
+ //#region src/components/heading-component.tsx
314
+ function createHeadingComponent(level) {
315
+ if (level === "h6") return ({ children, id }) => /* @__PURE__ */ jsx(Heading, {
316
+ as: "h6",
317
+ level: "h5",
318
+ margin: "small 0",
319
+ id,
320
+ children
321
+ });
322
+ return ({ children, id }) => /* @__PURE__ */ jsx(Heading, {
323
+ level,
324
+ margin: "small 0",
325
+ id,
326
+ children
327
+ });
328
+ }
329
+ //#endregion
330
+ //#region src/components/list-components.tsx
331
+ function createUlComponent() {
332
+ return ({ children, className }) => {
333
+ const isUnstyled = className?.includes("contains-task-list");
334
+ return /* @__PURE__ */ jsx(List, {
335
+ as: "ul",
336
+ isUnstyled,
337
+ margin: isUnstyled ? "small none small small" : "small 0",
338
+ children: onlyElementChildren(children)
339
+ });
340
+ };
341
+ }
342
+ function createOlComponent() {
343
+ return ({ children }) => /* @__PURE__ */ jsx(List, {
344
+ as: "ol",
345
+ margin: "small 0",
346
+ children: onlyElementChildren(children)
347
+ });
348
+ }
349
+ function createLiComponent() {
350
+ return ({ children }) => {
351
+ const isTaskItem = Children.toArray(children).some((child) => isValidElement(child) && child.props.type === "checkbox");
352
+ return /* @__PURE__ */ jsx(List.Item, {
353
+ padding: isTaskItem ? "none none x-small" : void 0,
354
+ spacing: isTaskItem ? "none" : void 0,
355
+ children
356
+ });
357
+ };
358
+ }
359
+ function createInputComponent() {
360
+ return ({ type, checked }) => {
361
+ if (type === "checkbox") return /* @__PURE__ */ jsx(Checkbox, {
362
+ size: "small",
363
+ label: "",
364
+ checked: Boolean(checked),
365
+ disabled: true,
366
+ inline: true
367
+ });
368
+ return null;
369
+ };
370
+ }
371
+ //#endregion
372
+ //#region src/components/code-components.tsx
373
+ function createCodeComponent(options) {
374
+ return ({ children, className }) => {
375
+ if (isInlineCode(children, className)) {
376
+ const inlineCodeValue = normalizeCodeValue(children).trim();
377
+ const normalizedInlineColor = options.showColorCodes ? normalizeColorForSwatch(inlineCodeValue) : void 0;
378
+ if (normalizedInlineColor) return /* @__PURE__ */ jsxs(Fragment, { children: [
379
+ /* @__PURE__ */ jsx(Text, {
380
+ as: "code",
381
+ children
382
+ }),
383
+ " ",
384
+ /* @__PURE__ */ jsx(Badge, {
385
+ standalone: true,
386
+ type: "notification",
387
+ variant: "primary",
388
+ pulse: false,
389
+ themeOverride: { colorPrimary: normalizedInlineColor }
390
+ })
391
+ ] });
392
+ return /* @__PURE__ */ jsx(Text, {
393
+ as: "code",
394
+ children
395
+ });
396
+ }
397
+ return /* @__PURE__ */ jsx(SourceCodeEditor, {
398
+ label: "Code",
399
+ language: mapCodeLanguage(className),
400
+ value: normalizeCodeValue(children),
401
+ readOnly: options.codeReadOnly,
402
+ editable: options.codeEditable,
403
+ lineNumbers: options.codeLineNumbers,
404
+ lineWrapping: options.codeLineWrapping,
405
+ spellCheck: options.codeSpellCheck,
406
+ highlightActiveLine: options.codeHighlightActiveLine,
407
+ direction: options.codeDirection
408
+ });
409
+ };
410
+ }
411
+ function createPreComponent() {
412
+ return ({ children }) => /* @__PURE__ */ jsx(View, {
413
+ as: "div",
414
+ margin: "small 0",
415
+ children
416
+ });
417
+ }
418
+ //#endregion
419
+ //#region src/components/blockquote-component.tsx
420
+ function createBlockquoteComponent(options) {
421
+ return ({ children }) => {
422
+ const result = stripAlertPrefix(children);
423
+ if (result.found && result.marker) return /* @__PURE__ */ jsx(Alert, {
424
+ variant: ALERT_VARIANTS[result.marker],
425
+ margin: "small small medium",
426
+ renderCloseButtonLabel: options.showAlertCloseButton ? "Close" : void 0,
427
+ onDismiss: options.showAlertCloseButton ? () => void 0 : void 0,
428
+ children: result.node
429
+ });
430
+ return /* @__PURE__ */ jsx(View, {
431
+ as: "div",
432
+ borderWidth: "0 0 0 large",
433
+ borderColor: "info",
434
+ padding: "xxx-small none xxx-small small",
435
+ margin: "small 0 medium small",
436
+ children: /* @__PURE__ */ jsx(Text, {
437
+ fontStyle: "italic",
438
+ children
439
+ })
440
+ });
441
+ };
442
+ }
443
+ //#endregion
444
+ //#region src/components/span-component.tsx
445
+ function createSpanComponent(options) {
446
+ return ({ children, className, ...props }) => {
447
+ if (className?.includes("icon-token") && options.showIcons) {
448
+ const iconName = props["data-icon"];
449
+ const inlineIconColor = props["data-icon-color"];
450
+ if (iconName) {
451
+ const withoutPrefix = iconName.startsWith("Icon") ? iconName.slice(4) : iconName;
452
+ const isSolid = withoutPrefix.endsWith("Solid");
453
+ const isInstUI = withoutPrefix.endsWith("InstUIIcon");
454
+ let root = withoutPrefix;
455
+ if (isSolid) root = withoutPrefix.slice(0, -5);
456
+ else if (isInstUI) root = withoutPrefix.slice(0, -10);
457
+ else if (withoutPrefix.endsWith("Line")) root = withoutPrefix.slice(0, -4);
458
+ const IconComponent = (isSolid ? [`Icon${root}Solid`] : isInstUI ? [`${root}InstUIIcon`] : [
459
+ `${root}InstUIIcon`,
460
+ `Icon${root}Line`,
461
+ `Icon${root}`
462
+ ]).map((name) => InstUIIcons[name]).find(Boolean);
463
+ if (IconComponent) {
464
+ const resolvedColor = inlineIconColor ?? options.iconColor;
465
+ const normalizedIconColor = resolvedColor && isLiteralColorValue(resolvedColor) ? normalizeColorForSwatch(resolvedColor) : void 0;
466
+ if (normalizedIconColor) return /* @__PURE__ */ jsx("span", {
467
+ style: { color: normalizedIconColor },
468
+ children: /* @__PURE__ */ jsx(IconComponent, {
469
+ title: iconName,
470
+ color: "inherit"
471
+ })
472
+ });
473
+ return /* @__PURE__ */ jsx(IconComponent, {
474
+ title: iconName,
475
+ color: (resolvedColor === "currentColor" ? "inherit" : resolvedColor) ?? "inherit"
476
+ });
477
+ }
478
+ }
479
+ }
480
+ if (className?.includes("color-code") && options.showColorCodes) {
481
+ const colorValue = props["data-color"];
482
+ if (colorValue) {
483
+ const normalizedColor = normalizeColorForSwatch(colorValue);
484
+ if (normalizedColor) return /* @__PURE__ */ jsxs(Fragment, { children: [
485
+ children,
486
+ " ",
487
+ /* @__PURE__ */ jsx(Badge, {
488
+ type: "notification",
489
+ variant: "primary",
490
+ placement: "start",
491
+ pulse: false,
492
+ themeOverride: { colorPrimary: normalizedColor },
493
+ standalone: true,
494
+ margin: "none none xxx-small"
495
+ })
496
+ ] });
497
+ }
498
+ }
499
+ return /* @__PURE__ */ jsx(Text, {
500
+ as: "span",
501
+ children
502
+ });
503
+ };
504
+ }
505
+ //#endregion
506
+ //#region src/components/table-components.tsx
507
+ const TableSortContext = createContext(null);
508
+ function createTableComponent(options) {
509
+ return ({ children }) => {
510
+ const [sortBy, setSortBy] = useState(options.tableSortable ? 0 : null);
511
+ const [sortDirection, setSortDirection] = useState("ascending");
512
+ const headerIndexByIdRef = useRef(/* @__PURE__ */ new Map());
513
+ const nextHeaderIndexRef = useRef(0);
514
+ const registerHeader = useCallback((headerId) => {
515
+ const existing = headerIndexByIdRef.current.get(headerId);
516
+ if (existing !== void 0) return existing;
517
+ const index = nextHeaderIndexRef.current;
518
+ nextHeaderIndexRef.current += 1;
519
+ headerIndexByIdRef.current.set(headerId, index);
520
+ return index;
521
+ }, []);
522
+ const requestSort = useCallback((index) => {
523
+ if (sortBy === index) {
524
+ setSortDirection(sortDirection === "ascending" ? "descending" : "ascending");
525
+ return;
526
+ }
527
+ setSortBy(index);
528
+ setSortDirection("ascending");
529
+ }, [sortBy, sortDirection]);
530
+ return /* @__PURE__ */ jsx(TableSortContext.Provider, {
531
+ value: {
532
+ registerHeader,
533
+ sortBy,
534
+ sortDirection,
535
+ requestSort
536
+ },
537
+ children: /* @__PURE__ */ jsx(View, {
538
+ as: "div",
539
+ overflowX: "auto",
540
+ children: /* @__PURE__ */ jsx(Table, {
541
+ caption: "",
542
+ margin: "small 0",
543
+ layout: options.tableLayout === "stacked" ? "auto" : options.tableLayout,
544
+ hover: options.tableHover,
545
+ children
546
+ })
547
+ })
548
+ });
549
+ };
550
+ }
551
+ function createTheadComponent() {
552
+ return ({ children }) => {
553
+ const rows = Children.toArray(children).filter((child) => isValidElement(child));
554
+ return /* @__PURE__ */ jsx(Table.Head, { children: rows });
555
+ };
556
+ }
557
+ function createTbodyComponent(options) {
558
+ return ({ children }) => {
559
+ const tableSort = useContext(TableSortContext);
560
+ const rows = Children.toArray(children).filter((child) => isValidElement(child));
561
+ if (!options.tableSortable || !tableSort || tableSort.sortBy === null) return /* @__PURE__ */ jsx(Table.Body, { children: rows });
562
+ const sortIndex = tableSort.sortBy;
563
+ const sortedRows = [...rows].toSorted((leftRow, rightRow) => {
564
+ const leftCells = Children.toArray(leftRow.props.children);
565
+ const rightCells = Children.toArray(rightRow.props.children);
566
+ const result = compareTableCellValues(textFromNode(leftCells[sortIndex] ?? ""), textFromNode(rightCells[sortIndex] ?? ""));
567
+ return tableSort.sortDirection === "ascending" ? result : -result;
568
+ });
569
+ return /* @__PURE__ */ jsx(Table.Body, { children: sortedRows });
570
+ };
571
+ }
572
+ function createTrComponent() {
573
+ return ({ children }) => {
574
+ const cells = Children.toArray(children).filter((child) => isValidElement(child));
575
+ return /* @__PURE__ */ jsx(Table.Row, { children: cells });
576
+ };
577
+ }
578
+ function createThComponent(options) {
579
+ return ({ children }) => {
580
+ const tableSort = useContext(TableSortContext);
581
+ const headerId = useId();
582
+ const sortIndex = tableSort?.registerHeader(headerId) ?? -1;
583
+ const sortDirection = tableSort?.sortBy === sortIndex ? tableSort.sortDirection : "none";
584
+ return /* @__PURE__ */ jsx(Table.ColHeader, {
585
+ id: headerId,
586
+ sortDirection: options.tableSortable ? sortDirection : void 0,
587
+ onRequestSort: options.tableSortable && tableSort ? () => {
588
+ tableSort.requestSort(sortIndex);
589
+ } : void 0,
590
+ children
591
+ });
592
+ };
593
+ }
594
+ function createTdComponent() {
595
+ return ({ children }) => /* @__PURE__ */ jsx(Table.Cell, { children });
596
+ }
597
+ //#endregion
598
+ //#region src/components/other-components.tsx
599
+ function createHrComponent() {
600
+ return () => /* @__PURE__ */ jsx(View, {
601
+ as: "hr",
602
+ margin: "1rem 0",
603
+ borderWidth: "small 0 0",
604
+ borderColor: "primary"
605
+ });
606
+ }
607
+ function createSectionComponent() {
608
+ return ({ children, className }) => /* @__PURE__ */ jsx(View, {
609
+ as: "section",
610
+ margin: className?.includes("footnotes") ? "medium 0 0" : "0",
611
+ borderWidth: className?.includes("footnotes") ? "small 0 0" : "none",
612
+ borderColor: "primary",
613
+ padding: className?.includes("footnotes") ? "small 0 0" : "0",
614
+ children
615
+ });
616
+ }
617
+ function createSvgComponent() {
618
+ return ({ children, className, viewBox, width, height, role, ..._props } = {}) => /* @__PURE__ */ jsx(View, {
619
+ as: "figure",
620
+ children: /* @__PURE__ */ jsx(InlineSVG, {
621
+ inline: false,
622
+ className,
623
+ viewBox,
624
+ width,
625
+ height,
626
+ role,
627
+ children
628
+ })
629
+ });
630
+ }
631
+ //#endregion
632
+ //#region src/create-components.tsx
633
+ /**
634
+ * Creates a React Markdown component map backed by InstUI components.
635
+ */
636
+ function createInstuiMarkdownComponents(renderOptions = {}) {
637
+ const showAlertCloseButton = Boolean(renderOptions.alert?.closeButton);
638
+ const tableLayout = renderOptions.table?.display ?? "auto";
639
+ const tableHover = Boolean(renderOptions.table?.hover);
640
+ const tableSortable = Boolean(renderOptions.table?.sortable);
641
+ const showExternalIcon = Boolean(renderOptions.link?.externalIcon);
642
+ const showPermalinks = Boolean(renderOptions.link?.permalinks);
643
+ const permalinkClassName = renderOptions.link?.permalinkClassName ?? "mdx-heading-anchor";
644
+ const codeEditable = renderOptions.code?.editable ?? false;
645
+ const codeReadOnly = renderOptions.code?.readOnly ?? true;
646
+ const codeLineNumbers = renderOptions.code?.lineNumbers;
647
+ const codeLineWrapping = renderOptions.code?.lineWrapping;
648
+ const codeSpellCheck = renderOptions.code?.spellCheck;
649
+ const codeHighlightActiveLine = renderOptions.code?.highlightActiveLine;
650
+ const codeDirection = renderOptions.code?.direction;
651
+ const showColorCodes = Boolean(renderOptions.color?.enabled);
652
+ const showIcons = Boolean(renderOptions.icons?.enabled);
653
+ const iconColor = renderOptions.icons?.color;
654
+ return {
655
+ span: createSpanComponent({
656
+ showColorCodes,
657
+ showIcons,
658
+ iconColor
659
+ }),
660
+ a: createLinkComponent({
661
+ showExternalIcon,
662
+ showPermalinks,
663
+ permalinkClassName
664
+ }),
665
+ img: createImgComponent(),
666
+ p: createPComponent(),
667
+ em: createEmComponent(),
668
+ strong: createStrongComponent(),
669
+ del: createDelComponent(),
670
+ code: createCodeComponent({
671
+ showColorCodes,
672
+ codeEditable,
673
+ codeReadOnly,
674
+ codeLineNumbers,
675
+ codeLineWrapping,
676
+ codeSpellCheck,
677
+ codeHighlightActiveLine,
678
+ codeDirection
679
+ }),
680
+ pre: createPreComponent(),
681
+ blockquote: createBlockquoteComponent({ showAlertCloseButton }),
682
+ hr: createHrComponent(),
683
+ h1: createHeadingComponent("h1"),
684
+ h2: createHeadingComponent("h2"),
685
+ h3: createHeadingComponent("h3"),
686
+ h4: createHeadingComponent("h4"),
687
+ h5: createHeadingComponent("h5"),
688
+ h6: createHeadingComponent("h6"),
689
+ ins: createInsComponent(),
690
+ ul: createUlComponent(),
691
+ ol: createOlComponent(),
692
+ li: createLiComponent(),
693
+ input: createInputComponent(),
694
+ section: createSectionComponent(),
695
+ svg: createSvgComponent(),
696
+ sub: createSubComponent(),
697
+ sup: createSupComponent(),
698
+ table: createTableComponent({
699
+ tableLayout,
700
+ tableHover,
701
+ tableSortable
702
+ }),
703
+ thead: createTheadComponent(),
704
+ tbody: createTbodyComponent({ tableSortable }),
705
+ tr: createTrComponent(),
706
+ th: createThComponent({ tableSortable }),
707
+ td: createTdComponent()
708
+ };
709
+ }
710
+ /**
711
+ * Default markdown component map using package defaults.
712
+ */
713
+ const instuiMarkdownComponents = createInstuiMarkdownComponents();
714
+ //#endregion
715
+ //#region src/index.tsx
716
+ /**
717
+ * Renders a markdown string with InstUI-backed element mappings.
718
+ */
719
+ function InstuiMarkdown({ children, renderOptions }) {
720
+ const components = createInstuiMarkdownComponents(renderOptions);
721
+ const showPermalinks = Boolean(renderOptions?.link?.permalinks);
722
+ const permalinkClassName = renderOptions?.link?.permalinkClassName ?? "mdx-heading-anchor";
723
+ return /* @__PURE__ */ jsx(ReactMarkdown, {
724
+ remarkPlugins: [remarkGfm],
725
+ rehypePlugins: [
726
+ rehypeRaw,
727
+ rehypeSlug,
728
+ ...showPermalinks ? [[rehypeAutolinkHeadings, {
729
+ behavior: "append",
730
+ properties: {
731
+ className: [permalinkClassName],
732
+ "aria-label": "Permalink"
733
+ }
734
+ }]] : [],
735
+ rehypeUnwrapBlockquoteParagraphs,
736
+ rehypeInstUIIconTokens,
737
+ rehypeColorCodes
738
+ ],
739
+ components,
740
+ children
741
+ });
742
+ }
743
+ /**
744
+ * Provides InstUI markdown components to MDX content.
745
+ */
746
+ function InstuiMdxProvider({ children, renderOptions }) {
747
+ return /* @__PURE__ */ jsx(MDXProvider, {
748
+ components: createInstuiMarkdownComponents(renderOptions),
749
+ children: normalizeSvgElements(children)
750
+ });
751
+ }
752
+ //#endregion
753
+ export { InstuiMarkdown, InstuiMdxProvider, createInstuiMarkdownComponents, instuiMarkdownComponents };
package/index.d.mts ADDED
@@ -0,0 +1,54 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Components } from "react-markdown";
3
+
4
+ export interface InstuiMarkdownRenderOptions {
5
+ alert?: {
6
+ closeButton?: boolean;
7
+ };
8
+ table?: {
9
+ display?: "auto" | "stacked" | "fixed";
10
+ hover?: boolean;
11
+ sortable?: boolean;
12
+ };
13
+ link?: {
14
+ externalIcon?: boolean;
15
+ permalinks?: boolean;
16
+ permalinkClassName?: string;
17
+ };
18
+ code?: {
19
+ editable?: boolean;
20
+ readOnly?: boolean;
21
+ lineNumbers?: boolean;
22
+ lineWrapping?: boolean;
23
+ spellCheck?: boolean;
24
+ highlightActiveLine?: boolean;
25
+ direction?: "ltr" | "rtl";
26
+ };
27
+ color?: {
28
+ enabled?: boolean;
29
+ };
30
+ icons?: {
31
+ enabled?: boolean;
32
+ color?: string;
33
+ };
34
+ }
35
+
36
+ export interface InstuiMarkdownProps {
37
+ children: string;
38
+ renderOptions?: InstuiMarkdownRenderOptions;
39
+ }
40
+
41
+ export interface InstuiMdxProviderProps {
42
+ children: ReactNode;
43
+ renderOptions?: InstuiMarkdownRenderOptions;
44
+ }
45
+
46
+ export declare const instuiMarkdownComponents: Components;
47
+
48
+ export declare function createInstuiMarkdownComponents(
49
+ renderOptions?: InstuiMarkdownRenderOptions,
50
+ ): Components;
51
+
52
+ export declare function InstuiMarkdown(props: InstuiMarkdownProps): ReactNode;
53
+
54
+ export declare function InstuiMdxProvider(props: InstuiMdxProviderProps): ReactNode;
package/index.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Components } from "react-markdown";
3
+
4
+ export interface InstuiMarkdownRenderOptions {
5
+ alert?: {
6
+ closeButton?: boolean;
7
+ };
8
+ table?: {
9
+ display?: "auto" | "stacked" | "fixed";
10
+ hover?: boolean;
11
+ sortable?: boolean;
12
+ };
13
+ link?: {
14
+ externalIcon?: boolean;
15
+ permalinks?: boolean;
16
+ permalinkClassName?: string;
17
+ };
18
+ code?: {
19
+ editable?: boolean;
20
+ readOnly?: boolean;
21
+ lineNumbers?: boolean;
22
+ lineWrapping?: boolean;
23
+ spellCheck?: boolean;
24
+ highlightActiveLine?: boolean;
25
+ direction?: "ltr" | "rtl";
26
+ };
27
+ color?: {
28
+ enabled?: boolean;
29
+ };
30
+ icons?: {
31
+ enabled?: boolean;
32
+ color?: string;
33
+ };
34
+ }
35
+
36
+ export interface InstuiMarkdownProps {
37
+ children: string;
38
+ renderOptions?: InstuiMarkdownRenderOptions;
39
+ }
40
+
41
+ export interface InstuiMdxProviderProps {
42
+ children: ReactNode;
43
+ renderOptions?: InstuiMarkdownRenderOptions;
44
+ }
45
+
46
+ export declare const instuiMarkdownComponents: Components;
47
+
48
+ export declare function createInstuiMarkdownComponents(
49
+ renderOptions?: InstuiMarkdownRenderOptions,
50
+ ): Components;
51
+
52
+ export declare function InstuiMarkdown(props: InstuiMarkdownProps): ReactNode;
53
+
54
+ export declare function InstuiMdxProvider(props: InstuiMdxProviderProps): ReactNode;
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "instui-markdown",
3
+ "version": "0.0.6",
4
+ "description": "InstUI markdown renderer components",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist",
8
+ "index.d.mts",
9
+ "index.d.ts"
10
+ ],
11
+ "type": "module",
12
+ "types": "./index.d.mts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./index.d.mts",
16
+ "default": "./dist/index.mjs"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "vp pack",
25
+ "dev": "vp pack --watch",
26
+ "test": "vp test",
27
+ "check": "vp check",
28
+ "prepublishOnly": "vp run build"
29
+ },
30
+ "dependencies": {
31
+ "rehype-instui-markdown": "workspace:*"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "catalog:",
35
+ "@types/react": "*",
36
+ "@types/react-dom": "*",
37
+ "typescript": "catalog:",
38
+ "vite-plus": "catalog:"
39
+ },
40
+ "peerDependencies": {
41
+ "@instructure/ui-alerts": "*",
42
+ "@instructure/ui-badge": "*",
43
+ "@instructure/ui-checkbox": "*",
44
+ "@instructure/ui-color-utils": "*",
45
+ "@instructure/ui-heading": "*",
46
+ "@instructure/ui-icons": "*",
47
+ "@instructure/ui-img": "*",
48
+ "@instructure/ui-link": "*",
49
+ "@instructure/ui-list": "*",
50
+ "@instructure/ui-source-code-editor": "*",
51
+ "@instructure/ui-svg-images": "*",
52
+ "@instructure/ui-table": "*",
53
+ "@instructure/ui-text": "*",
54
+ "@instructure/ui-view": "*",
55
+ "@mdx-js/react": "*",
56
+ "react": "*",
57
+ "react-markdown": "*",
58
+ "rehype-autolink-headings": "*",
59
+ "rehype-raw": "*",
60
+ "rehype-slug": "*",
61
+ "remark-gfm": "*"
62
+ }
63
+ }