instui-markdown 0.0.9 → 0.1.0

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/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ import rehypeRaw from "rehype-raw";
5
5
  import rehypeSlug from "rehype-slug";
6
6
  import rehypeAutolinkHeadings from "rehype-autolink-headings";
7
7
  import { rehypeColorCodes, rehypeInstUIIconTokens, rehypeUnwrapBlockquoteParagraphs } from "rehype-instui-markdown";
8
+ import * as SimpleIcons from "simple-icons";
8
9
  import { Text } from "@instructure/ui-text/v11_7";
9
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
11
  import { Link } from "@instructure/ui-link/v11_7";
@@ -22,6 +23,50 @@ import { Badge } from "@instructure/ui-badge/v11_7";
22
23
  import { SourceCodeEditor } from "@instructure/ui-source-code-editor/v11_7";
23
24
  import { Alert } from "@instructure/ui-alerts/v11_7";
24
25
  import { Table } from "@instructure/ui-table/v11_7";
26
+ //#region src/simple-icons-resolver.ts
27
+ function isSimpleIconLike(value) {
28
+ return typeof value === "object" && value !== null && "path" in value && "title" in value && typeof value.path === "string";
29
+ }
30
+ function normalizeSimpleIconCode(code) {
31
+ return code.toLowerCase().replace(/[^a-z0-9]+/g, "");
32
+ }
33
+ function toSimpleIconExportName(code) {
34
+ return `si${code.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((word) => word.toLowerCase()).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("")}`;
35
+ }
36
+ function buildSimpleIconLookup(iconsRegistry) {
37
+ const lookup = /* @__PURE__ */ new Map();
38
+ for (const [exportName, iconCandidate] of Object.entries(iconsRegistry)) {
39
+ if (!exportName.startsWith("si") || !isSimpleIconLike(iconCandidate)) continue;
40
+ const slug = iconCandidate.slug ?? exportName.slice(2);
41
+ lookup.set(normalizeSimpleIconCode(slug), {
42
+ title: iconCandidate.title,
43
+ path: iconCandidate.path,
44
+ viewBox: iconCandidate.viewBox ?? "0 0 24 24"
45
+ });
46
+ }
47
+ return lookup;
48
+ }
49
+ function createSimpleIconsResolver(iconsRegistry) {
50
+ const lookup = buildSimpleIconLookup(iconsRegistry);
51
+ return (code) => {
52
+ const normalizedCode = normalizeSimpleIconCode(code);
53
+ const normalizedWithoutPrefix = normalizedCode.startsWith("si") ? normalizedCode.slice(2) : normalizedCode;
54
+ const bySlug = lookup.get(normalizedCode) ?? (normalizedCode.startsWith("si") ? lookup.get(normalizedWithoutPrefix) : void 0);
55
+ if (bySlug) return bySlug;
56
+ const exportNameCandidates = [toSimpleIconExportName(code)];
57
+ if (normalizedCode.startsWith("si")) exportNameCandidates.push(toSimpleIconExportName(normalizedWithoutPrefix));
58
+ for (const exportName of exportNameCandidates) {
59
+ const iconCandidate = iconsRegistry[exportName];
60
+ if (isSimpleIconLike(iconCandidate)) return {
61
+ title: iconCandidate.title,
62
+ path: iconCandidate.path,
63
+ viewBox: iconCandidate.viewBox ?? "0 0 24 24"
64
+ };
65
+ }
66
+ };
67
+ }
68
+ const resolveSimpleIconToken = createSimpleIconsResolver(SimpleIcons);
69
+ //#endregion
25
70
  //#region src/components/text-components.tsx
26
71
  function createEmComponent() {
27
72
  return ({ children }) => /* @__PURE__ */ jsx(Text, {
@@ -442,8 +487,12 @@ function createBlockquoteComponent(options) {
442
487
  }
443
488
  //#endregion
444
489
  //#region src/components/span-component.tsx
445
- function toPascalCase(str) {
446
- return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
490
+ const INSTUI_ICON_LOOKUP = new Map(Object.entries(InstUIIcons).map(([name, value]) => [name.toLowerCase(), value]));
491
+ function findInstUIIcon(candidates) {
492
+ for (const candidate of candidates) {
493
+ const IconComponent = INSTUI_ICON_LOOKUP.get(candidate.toLowerCase());
494
+ if (IconComponent) return IconComponent;
495
+ }
447
496
  }
448
497
  function parseIconTokenName(iconName) {
449
498
  const withoutPrefix = iconName.startsWith("Icon") ? iconName.slice(4) : iconName;
@@ -461,6 +510,9 @@ function parseIconTokenName(iconName) {
461
510
  };
462
511
  }
463
512
  function getSimpleIconCodes(iconName, root, withoutPrefix) {
513
+ const lowerWords = root.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((word) => word.toLowerCase());
514
+ const compact = lowerWords.join("");
515
+ const kebab = lowerWords.join("-");
464
516
  const camel = root.charAt(0).toLowerCase() + root.slice(1);
465
517
  const lower = root.toLowerCase();
466
518
  return Array.from(new Set([
@@ -468,7 +520,9 @@ function getSimpleIconCodes(iconName, root, withoutPrefix) {
468
520
  withoutPrefix,
469
521
  root,
470
522
  camel,
471
- lower
523
+ lower,
524
+ compact,
525
+ kebab
472
526
  ]));
473
527
  }
474
528
  function createSpanComponent(options) {
@@ -479,12 +533,11 @@ function createSpanComponent(options) {
479
533
  if (iconName) {
480
534
  const { withoutPrefix, isSolid, isInstUI, root } = parseIconTokenName(iconName);
481
535
  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);
536
+ const IconComponent = findInstUIIcon(isSolid ? [`Icon${root}Solid`] : isInstUI ? [`${root}InstUIIcon`] : [
537
+ `${root}InstUIIcon`,
538
+ `Icon${root}Line`,
539
+ `Icon${root}`
540
+ ]);
488
541
  if (IconComponent) {
489
542
  const resolvedColor = inlineIconColor ?? options.iconColor;
490
543
  const normalizedIconColor = resolvedColor && isLiteralColorValue(resolvedColor) ? normalizeColorForSwatch(resolvedColor) : void 0;
@@ -511,12 +564,18 @@ function createSpanComponent(options) {
511
564
  const normalizedIconColor = resolvedColor && isLiteralColorValue(resolvedColor) ? normalizeColorForSwatch(resolvedColor) : void 0;
512
565
  const label = simpleIcon.title ?? root;
513
566
  return /* @__PURE__ */ jsx("span", {
514
- style: normalizedIconColor ? { color: normalizedIconColor } : void 0,
567
+ style: {
568
+ display: "inline-flex",
569
+ alignItems: "center",
570
+ verticalAlign: "middle",
571
+ lineHeight: 1,
572
+ ...normalizedIconColor ? { color: normalizedIconColor } : {}
573
+ },
515
574
  children: /* @__PURE__ */ jsxs(InlineSVG, {
516
- inline: false,
575
+ inline: true,
517
576
  viewBox: simpleIcon.viewBox ?? "0 0 24 24",
518
- width: simpleIcon.width ?? "1em",
519
- height: simpleIcon.height ?? "1em",
577
+ width: simpleIcon.width ?? "1.125em",
578
+ height: simpleIcon.height ?? "1.125em",
520
579
  role: "img",
521
580
  "aria-label": label,
522
581
  children: [/* @__PURE__ */ jsx("title", { children: label }), /* @__PURE__ */ jsx("path", {
@@ -706,7 +765,6 @@ function createInstuiMarkdownComponents(renderOptions = {}) {
706
765
  const enableInstuiIcons = renderOptions.icons?.providers?.instui ?? true;
707
766
  const enableSimpleIcons = renderOptions.icons?.providers?.simpleIcons ?? true;
708
767
  const simpleIconColor = renderOptions.icons?.simpleIcons?.color;
709
- const resolveSimpleIcon = renderOptions.icons?.simpleIcons?.resolve;
710
768
  return {
711
769
  span: createSpanComponent({
712
770
  showColorCodes,
@@ -715,7 +773,7 @@ function createInstuiMarkdownComponents(renderOptions = {}) {
715
773
  enableInstuiIcons,
716
774
  enableSimpleIcons,
717
775
  simpleIconColor,
718
- resolveSimpleIcon
776
+ resolveSimpleIcon: renderOptions.icons?.simpleIcons?.resolve ?? resolveSimpleIconToken
719
777
  }),
720
778
  a: createLinkComponent({
721
779
  showExternalIcon,
@@ -810,4 +868,4 @@ function InstuiMdxProvider({ children, renderOptions }) {
810
868
  });
811
869
  }
812
870
  //#endregion
813
- export { InstuiMarkdown, InstuiMdxProvider, createInstuiMarkdownComponents, instuiMarkdownComponents };
871
+ export { InstuiMarkdown, InstuiMdxProvider, createInstuiMarkdownComponents, createSimpleIconsResolver, instuiMarkdownComponents, resolveSimpleIconToken };
package/index.d.mts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { Components } from "react-markdown";
3
3
 
4
+ export type SimpleIconResolver = (code: string) => SimpleIconTokenData | undefined;
5
+ export type SimpleIconsRegistry = Record<string, unknown>;
6
+
4
7
  export interface InstuiMarkdownRenderOptions {
5
8
  alert?: {
6
9
  closeButton?: boolean;
@@ -30,9 +33,25 @@ export interface InstuiMarkdownRenderOptions {
30
33
  icons?: {
31
34
  enabled?: boolean;
32
35
  color?: string;
36
+ providers?: {
37
+ instui?: boolean;
38
+ simpleIcons?: boolean;
39
+ };
40
+ simpleIcons?: {
41
+ color?: string;
42
+ resolve?: (code: string) => SimpleIconTokenData | undefined;
43
+ };
33
44
  };
34
45
  }
35
46
 
47
+ export interface SimpleIconTokenData {
48
+ path: string;
49
+ title?: string;
50
+ viewBox?: string;
51
+ width?: string | number;
52
+ height?: string | number;
53
+ }
54
+
36
55
  export interface InstuiMarkdownProps {
37
56
  children: string;
38
57
  renderOptions?: InstuiMarkdownRenderOptions;
@@ -52,3 +71,9 @@ export declare function createInstuiMarkdownComponents(
52
71
  export declare function InstuiMarkdown(props: InstuiMarkdownProps): ReactNode;
53
72
 
54
73
  export declare function InstuiMdxProvider(props: InstuiMdxProviderProps): ReactNode;
74
+
75
+ export declare function createSimpleIconsResolver(
76
+ iconsRegistry: SimpleIconsRegistry,
77
+ ): SimpleIconResolver;
78
+
79
+ export declare const resolveSimpleIconToken: SimpleIconResolver;
package/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { Components } from "react-markdown";
3
3
 
4
+ export type SimpleIconResolver = (code: string) => SimpleIconTokenData | undefined;
5
+ export type SimpleIconsRegistry = Record<string, unknown>;
6
+
4
7
  export interface InstuiMarkdownRenderOptions {
5
8
  alert?: {
6
9
  closeButton?: boolean;
@@ -30,9 +33,25 @@ export interface InstuiMarkdownRenderOptions {
30
33
  icons?: {
31
34
  enabled?: boolean;
32
35
  color?: string;
36
+ providers?: {
37
+ instui?: boolean;
38
+ simpleIcons?: boolean;
39
+ };
40
+ simpleIcons?: {
41
+ color?: string;
42
+ resolve?: (code: string) => SimpleIconTokenData | undefined;
43
+ };
33
44
  };
34
45
  }
35
46
 
47
+ export interface SimpleIconTokenData {
48
+ path: string;
49
+ title?: string;
50
+ viewBox?: string;
51
+ width?: string | number;
52
+ height?: string | number;
53
+ }
54
+
36
55
  export interface InstuiMarkdownProps {
37
56
  children: string;
38
57
  renderOptions?: InstuiMarkdownRenderOptions;
@@ -52,3 +71,9 @@ export declare function createInstuiMarkdownComponents(
52
71
  export declare function InstuiMarkdown(props: InstuiMarkdownProps): ReactNode;
53
72
 
54
73
  export declare function InstuiMdxProvider(props: InstuiMdxProviderProps): ReactNode;
74
+
75
+ export declare function createSimpleIconsResolver(
76
+ iconsRegistry: SimpleIconsRegistry,
77
+ ): SimpleIconResolver;
78
+
79
+ export declare const resolveSimpleIconToken: SimpleIconResolver;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instui-markdown",
3
- "version": "0.0.9",
3
+ "version": "0.1.0",
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.9"
24
+ "rehype-instui-markdown": "0.1.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^24",
@@ -51,7 +51,8 @@
51
51
  "rehype-autolink-headings": "*",
52
52
  "rehype-raw": "*",
53
53
  "rehype-slug": "*",
54
- "remark-gfm": "*"
54
+ "remark-gfm": "*",
55
+ "simple-icons": "*"
55
56
  },
56
57
  "scripts": {
57
58
  "build": "vp pack",