instui-markdown 0.0.7 → 0.0.9
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 +181 -11
- package/dist/index.mjs +86 -26
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,23 +1,193 @@
|
|
|
1
|
-
#
|
|
1
|
+
# instui-markdown
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React components for rendering markdown with [Instructure UI](https://instructure.design) components.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
- Install dependencies:
|
|
5
|
+
## Installation
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
|
|
8
|
+
npm install instui-markdown rehype-instui-markdown
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
You also need the InstUI peer dependencies your app will use. At minimum:
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
|
-
|
|
14
|
+
npm install react react-markdown remark-gfm rehype-raw rehype-slug \
|
|
15
|
+
@instructure/ui-text @instructure/ui-heading @instructure/ui-link \
|
|
16
|
+
@instructure/ui-list @instructure/ui-view
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## Usage
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
### `InstuiMarkdown`
|
|
22
|
+
|
|
23
|
+
Renders a markdown string using InstUI components.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { InstuiMarkdown } from "instui-markdown";
|
|
27
|
+
|
|
28
|
+
<InstuiMarkdown>{markdownString}</InstuiMarkdown>;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Pass `renderOptions` to control rendering behavior:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<InstuiMarkdown
|
|
35
|
+
renderOptions={{
|
|
36
|
+
link: { permalinks: true, externalIcon: true },
|
|
37
|
+
table: { sortable: true, hover: true },
|
|
38
|
+
code: { lineNumbers: true },
|
|
39
|
+
color: { enabled: true },
|
|
40
|
+
icons: { enabled: true },
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
{markdownString}
|
|
44
|
+
</InstuiMarkdown>
|
|
23
45
|
```
|
|
46
|
+
|
|
47
|
+
### `InstuiMdxProvider`
|
|
48
|
+
|
|
49
|
+
Wraps MDX content and maps MDX elements to InstUI components.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { InstuiMdxProvider } from "instui-markdown";
|
|
53
|
+
import Content from "./content.mdx";
|
|
54
|
+
|
|
55
|
+
<InstuiMdxProvider renderOptions={renderOptions}>
|
|
56
|
+
<Content />
|
|
57
|
+
</InstuiMdxProvider>;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `createInstuiMarkdownComponents`
|
|
61
|
+
|
|
62
|
+
Returns the component map directly if you need to pass it to `react-markdown` or an MDX provider yourself.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import ReactMarkdown from "react-markdown";
|
|
66
|
+
import { createInstuiMarkdownComponents } from "instui-markdown";
|
|
67
|
+
|
|
68
|
+
const components = createInstuiMarkdownComponents(renderOptions);
|
|
69
|
+
|
|
70
|
+
<ReactMarkdown components={components}>{markdown}</ReactMarkdown>;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Render options
|
|
74
|
+
|
|
75
|
+
### `alert`
|
|
76
|
+
|
|
77
|
+
Controls alert-styled blockquotes. Uses [GFM alert syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts): `> [!NOTE]`, `> [!TIP]`, `> [!IMPORTANT]`, `> [!WARNING]`, `> [!CAUTION]`.
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
| ------------- | --------- | ------- | ------------------------------------ |
|
|
81
|
+
| `closeButton` | `boolean` | `false` | Shows a dismiss button on each alert |
|
|
82
|
+
|
|
83
|
+
### `table`
|
|
84
|
+
|
|
85
|
+
| Option | Type | Default | Description |
|
|
86
|
+
| ---------- | -------------------------------- | -------- | ------------------------------- |
|
|
87
|
+
| `display` | `"auto" \| "stacked" \| "fixed"` | `"auto"` | InstUI table layout mode |
|
|
88
|
+
| `hover` | `boolean` | `false` | Highlights rows on hover |
|
|
89
|
+
| `sortable` | `boolean` | `false` | Enables sortable column headers |
|
|
90
|
+
|
|
91
|
+
### `link`
|
|
92
|
+
|
|
93
|
+
| Option | Type | Default | Description |
|
|
94
|
+
| -------------------- | --------- | ---------------------- | ------------------------------------------ |
|
|
95
|
+
| `externalIcon` | `boolean` | `false` | Adds an icon to external links |
|
|
96
|
+
| `permalinks` | `boolean` | `false` | Appends a permalink anchor to each heading |
|
|
97
|
+
| `permalinkClassName` | `string` | `"mdx-heading-anchor"` | CSS class on permalink anchor elements |
|
|
98
|
+
|
|
99
|
+
### `code`
|
|
100
|
+
|
|
101
|
+
Fenced code blocks render using the InstUI `SourceCodeEditor`. Supported languages: `json`, `yaml`, `markdown`, `css`, `html`, `javascript` (and aliases `js`, `jsx`, `ts`, `tsx`, `yml`).
|
|
102
|
+
|
|
103
|
+
| Option | Type | Default | Description |
|
|
104
|
+
| --------------------- | ---------------- | ------- | -------------------------------------- |
|
|
105
|
+
| `editable` | `boolean` | `false` | Allows editing code blocks |
|
|
106
|
+
| `readOnly` | `boolean` | `true` | Marks code blocks as read-only |
|
|
107
|
+
| `lineNumbers` | `boolean` | `false` | Shows line numbers |
|
|
108
|
+
| `lineWrapping` | `boolean` | `false` | Wraps long lines |
|
|
109
|
+
| `spellCheck` | `boolean` | `false` | Enables spell check in editable blocks |
|
|
110
|
+
| `highlightActiveLine` | `boolean` | `false` | Highlights the cursor's line |
|
|
111
|
+
| `direction` | `"ltr" \| "rtl"` | `"ltr"` | Text direction |
|
|
112
|
+
|
|
113
|
+
### `color`
|
|
114
|
+
|
|
115
|
+
Wraps color literals in a visual swatch. Supports `#hex`, `rgb()`, `rgba()`, `hsl()`, and `hsla()` formats. Skips color values inside code fences.
|
|
116
|
+
|
|
117
|
+
| Option | Type | Default | Description |
|
|
118
|
+
| --------- | --------- | ------- | ----------------------------- |
|
|
119
|
+
| `enabled` | `boolean` | `false` | Renders color swatches inline |
|
|
120
|
+
|
|
121
|
+
### `icons`
|
|
122
|
+
|
|
123
|
+
Renders icon tokens from `:iconName:` syntax. InstUI lookup runs first, then optional SimpleIcons lookup can run as a fallback through an app-provided resolver. You can omit the `Icon` prefix and the `Line`/`Solid` suffix for InstUI names, so `:Heart:`, `:HeartLine:`, and `:IconHeartLine:` all resolve to the same InstUI icon.
|
|
124
|
+
|
|
125
|
+
Add an optional hex color with a pipe: `:Heart|#F00:`.
|
|
126
|
+
|
|
127
|
+
Skips icon tokens inside code fences.
|
|
128
|
+
|
|
129
|
+
| Option | Type | Default | Description |
|
|
130
|
+
| ----------------------- | --------------------------------------- | ----------- | ------------------------------------------------------------------ |
|
|
131
|
+
| `enabled` | `boolean` | `false` | Enables token-based icon rendering |
|
|
132
|
+
| `color` | `string` | `undefined` | Default color for all icon providers |
|
|
133
|
+
| `providers.instui` | `boolean` | `true` | Enables InstUI lookup (`@instructure/ui-icons`) |
|
|
134
|
+
| `providers.simpleIcons` | `boolean` | `true` | Enables SimpleIcons fallback lookup |
|
|
135
|
+
| `simpleIcons.color` | `string` | `undefined` | Default color for SimpleIcons output (falls back to `icons.color`) |
|
|
136
|
+
| `simpleIcons.resolve` | `(code: string) => SimpleIconTokenData` | `undefined` | App-provided resolver for SimpleIcons SVG path data |
|
|
137
|
+
|
|
138
|
+
Example with SimpleIcons fallback:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<InstuiMarkdown
|
|
142
|
+
renderOptions={{
|
|
143
|
+
icons: {
|
|
144
|
+
enabled: true,
|
|
145
|
+
providers: { instui: true, simpleIcons: true },
|
|
146
|
+
simpleIcons: {
|
|
147
|
+
resolve: (code) => {
|
|
148
|
+
if (code.toLowerCase() === "github") {
|
|
149
|
+
return {
|
|
150
|
+
title: "GitHub",
|
|
151
|
+
path: "M12 0C5.37 0 0 5.37 0 12...",
|
|
152
|
+
viewBox: "0 0 24 24",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
{":GitHub: and :HeartLine:"}
|
|
162
|
+
</InstuiMarkdown>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
If no enabled provider can resolve a token, the original token text is left in place.
|
|
166
|
+
|
|
167
|
+
## Peer dependencies
|
|
168
|
+
|
|
169
|
+
`instui-markdown` requires the following peer dependencies. Install only what your content uses — the components map each markdown element to the matching InstUI package.
|
|
170
|
+
|
|
171
|
+
| Package | Used for |
|
|
172
|
+
| ------------------------------------ | ------------------------------------- |
|
|
173
|
+
| `react` | All components |
|
|
174
|
+
| `react-markdown` | Markdown parsing |
|
|
175
|
+
| `remark-gfm` | GFM tables, task lists, strikethrough |
|
|
176
|
+
| `rehype-raw` | Inline HTML in markdown |
|
|
177
|
+
| `rehype-slug` | Heading IDs |
|
|
178
|
+
| `rehype-autolink-headings` | Heading permalink anchors |
|
|
179
|
+
| `@mdx-js/react` | `InstuiMdxProvider` |
|
|
180
|
+
| `@instructure/ui-alerts` | `> [!NOTE]` and other alerts |
|
|
181
|
+
| `@instructure/ui-badge` | Color swatch badges |
|
|
182
|
+
| `@instructure/ui-checkbox` | Task list items |
|
|
183
|
+
| `@instructure/ui-color-utils` | Color contrast for swatches |
|
|
184
|
+
| `@instructure/ui-heading` | `h1`–`h6` |
|
|
185
|
+
| `@instructure/ui-icons` | Icon tokens and permalink icons |
|
|
186
|
+
| `@instructure/ui-img` | Images |
|
|
187
|
+
| `@instructure/ui-link` | Links |
|
|
188
|
+
| `@instructure/ui-list` | Ordered and unordered lists |
|
|
189
|
+
| `@instructure/ui-source-code-editor` | Fenced code blocks |
|
|
190
|
+
| `@instructure/ui-svg-images` | Inline SVG elements |
|
|
191
|
+
| `@instructure/ui-table` | Tables |
|
|
192
|
+
| `@instructure/ui-text` | Paragraphs and inline text |
|
|
193
|
+
| `@instructure/ui-view` | Block wrappers and horizontal rules |
|
package/dist/index.mjs
CHANGED
|
@@ -442,38 +442,90 @@ function createBlockquoteComponent(options) {
|
|
|
442
442
|
}
|
|
443
443
|
//#endregion
|
|
444
444
|
//#region src/components/span-component.tsx
|
|
445
|
+
function toPascalCase(str) {
|
|
446
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
447
|
+
}
|
|
448
|
+
function parseIconTokenName(iconName) {
|
|
449
|
+
const withoutPrefix = iconName.startsWith("Icon") ? iconName.slice(4) : iconName;
|
|
450
|
+
const isSolid = withoutPrefix.endsWith("Solid");
|
|
451
|
+
const isInstUI = withoutPrefix.endsWith("InstUIIcon");
|
|
452
|
+
let root = withoutPrefix;
|
|
453
|
+
if (isSolid) root = withoutPrefix.slice(0, -5);
|
|
454
|
+
else if (isInstUI) root = withoutPrefix.slice(0, -10);
|
|
455
|
+
else if (withoutPrefix.endsWith("Line")) root = withoutPrefix.slice(0, -4);
|
|
456
|
+
return {
|
|
457
|
+
withoutPrefix,
|
|
458
|
+
isSolid,
|
|
459
|
+
isInstUI,
|
|
460
|
+
root
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function getSimpleIconCodes(iconName, root, withoutPrefix) {
|
|
464
|
+
const camel = root.charAt(0).toLowerCase() + root.slice(1);
|
|
465
|
+
const lower = root.toLowerCase();
|
|
466
|
+
return Array.from(new Set([
|
|
467
|
+
iconName,
|
|
468
|
+
withoutPrefix,
|
|
469
|
+
root,
|
|
470
|
+
camel,
|
|
471
|
+
lower
|
|
472
|
+
]));
|
|
473
|
+
}
|
|
445
474
|
function createSpanComponent(options) {
|
|
446
475
|
return ({ children, className, ...props }) => {
|
|
447
476
|
if (className?.includes("icon-token") && options.showIcons) {
|
|
448
477
|
const iconName = props["data-icon"];
|
|
449
478
|
const inlineIconColor = props["data-icon-color"];
|
|
450
479
|
if (iconName) {
|
|
451
|
-
const withoutPrefix
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
480
|
+
const { withoutPrefix, isSolid, isInstUI, root } = parseIconTokenName(iconName);
|
|
481
|
+
if (options.enableInstuiIcons) {
|
|
482
|
+
const normalizedRoot = toPascalCase(root);
|
|
483
|
+
const IconComponent = (isSolid ? [`Icon${normalizedRoot}Solid`] : isInstUI ? [`${normalizedRoot}InstUIIcon`] : [
|
|
484
|
+
`${normalizedRoot}InstUIIcon`,
|
|
485
|
+
`Icon${normalizedRoot}Line`,
|
|
486
|
+
`Icon${normalizedRoot}`
|
|
487
|
+
]).map((name) => InstUIIcons[name]).find(Boolean);
|
|
488
|
+
if (IconComponent) {
|
|
489
|
+
const resolvedColor = inlineIconColor ?? options.iconColor;
|
|
490
|
+
const normalizedIconColor = resolvedColor && isLiteralColorValue(resolvedColor) ? normalizeColorForSwatch(resolvedColor) : void 0;
|
|
491
|
+
const sizeProps = IconComponent.displayName?.startsWith("InstUIIcon_") ? { size: "x-small" } : {};
|
|
492
|
+
if (normalizedIconColor) return /* @__PURE__ */ jsx("span", {
|
|
493
|
+
style: { color: normalizedIconColor },
|
|
494
|
+
children: /* @__PURE__ */ jsx(IconComponent, {
|
|
495
|
+
title: iconName,
|
|
496
|
+
color: "inherit",
|
|
497
|
+
...sizeProps
|
|
498
|
+
})
|
|
499
|
+
});
|
|
500
|
+
return /* @__PURE__ */ jsx(IconComponent, {
|
|
469
501
|
title: iconName,
|
|
470
|
-
color: "inherit"
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
502
|
+
color: (resolvedColor === "currentColor" ? "inherit" : resolvedColor) ?? "inherit",
|
|
503
|
+
...sizeProps
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (options.enableSimpleIcons && options.resolveSimpleIcon) {
|
|
508
|
+
const simpleIcon = getSimpleIconCodes(iconName, root, withoutPrefix).map((code) => options.resolveSimpleIcon?.(code)).find(Boolean);
|
|
509
|
+
if (simpleIcon?.path) {
|
|
510
|
+
const resolvedColor = inlineIconColor ?? options.simpleIconColor ?? options.iconColor;
|
|
511
|
+
const normalizedIconColor = resolvedColor && isLiteralColorValue(resolvedColor) ? normalizeColorForSwatch(resolvedColor) : void 0;
|
|
512
|
+
const label = simpleIcon.title ?? root;
|
|
513
|
+
return /* @__PURE__ */ jsx("span", {
|
|
514
|
+
style: normalizedIconColor ? { color: normalizedIconColor } : void 0,
|
|
515
|
+
children: /* @__PURE__ */ jsxs(InlineSVG, {
|
|
516
|
+
inline: false,
|
|
517
|
+
viewBox: simpleIcon.viewBox ?? "0 0 24 24",
|
|
518
|
+
width: simpleIcon.width ?? "1em",
|
|
519
|
+
height: simpleIcon.height ?? "1em",
|
|
520
|
+
role: "img",
|
|
521
|
+
"aria-label": label,
|
|
522
|
+
children: [/* @__PURE__ */ jsx("title", { children: label }), /* @__PURE__ */ jsx("path", {
|
|
523
|
+
d: simpleIcon.path,
|
|
524
|
+
fill: "currentColor"
|
|
525
|
+
})]
|
|
526
|
+
})
|
|
527
|
+
});
|
|
528
|
+
}
|
|
477
529
|
}
|
|
478
530
|
}
|
|
479
531
|
}
|
|
@@ -651,11 +703,19 @@ function createInstuiMarkdownComponents(renderOptions = {}) {
|
|
|
651
703
|
const showColorCodes = Boolean(renderOptions.color?.enabled);
|
|
652
704
|
const showIcons = Boolean(renderOptions.icons?.enabled);
|
|
653
705
|
const iconColor = renderOptions.icons?.color;
|
|
706
|
+
const enableInstuiIcons = renderOptions.icons?.providers?.instui ?? true;
|
|
707
|
+
const enableSimpleIcons = renderOptions.icons?.providers?.simpleIcons ?? true;
|
|
708
|
+
const simpleIconColor = renderOptions.icons?.simpleIcons?.color;
|
|
709
|
+
const resolveSimpleIcon = renderOptions.icons?.simpleIcons?.resolve;
|
|
654
710
|
return {
|
|
655
711
|
span: createSpanComponent({
|
|
656
712
|
showColorCodes,
|
|
657
713
|
showIcons,
|
|
658
|
-
iconColor
|
|
714
|
+
iconColor,
|
|
715
|
+
enableInstuiIcons,
|
|
716
|
+
enableSimpleIcons,
|
|
717
|
+
simpleIconColor,
|
|
718
|
+
resolveSimpleIcon
|
|
659
719
|
}),
|
|
660
720
|
a: createLinkComponent({
|
|
661
721
|
showExternalIcon,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instui-markdown",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "InstUI markdown renderer components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"rehype-instui-markdown": "0.0.
|
|
24
|
+
"rehype-instui-markdown": "0.0.9"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^24",
|