@haruhimemoe/ui 0.8.0 → 0.10.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/CHANGELOG.md +23 -1
- package/README.md +171 -3
- package/dist/components/basics/Prose.d.ts +9 -6
- package/dist/components/basics/Prose.js +4 -4
- package/dist/components/mdx/Callout.d.ts +24 -0
- package/dist/components/mdx/Callout.js +22 -0
- package/dist/components/mdx/CodeBlock.d.ts +26 -0
- package/dist/components/mdx/CodeBlock.js +48 -0
- package/dist/components/mdx/CodeCopyButton.d.ts +21 -0
- package/dist/components/mdx/CodeCopyButton.js +34 -0
- package/dist/components/mdx/MdxBlockquote.d.ts +23 -0
- package/dist/components/mdx/MdxBlockquote.js +17 -0
- package/dist/components/mdx/MdxHeading.d.ts +26 -0
- package/dist/components/mdx/MdxHeading.js +23 -0
- package/dist/components/mdx/MdxLink.d.ts +29 -0
- package/dist/components/mdx/MdxLink.js +26 -0
- package/dist/components/mdx/MdxPre.d.ts +25 -0
- package/dist/components/mdx/MdxPre.js +39 -0
- package/dist/components/mdx/MdxTable.d.ts +20 -0
- package/dist/components/mdx/MdxTable.js +23 -0
- package/dist/components/mdx/highlighter.d.ts +37 -0
- package/dist/components/mdx/highlighter.js +66 -0
- package/dist/components/mdx/mdxComponents.d.ts +21 -0
- package/dist/components/mdx/mdxComponents.js +22 -0
- package/dist/components/mdx/parseCodeMeta.d.ts +21 -0
- package/dist/components/mdx/parseCodeMeta.js +40 -0
- package/dist/components/mdx/textOf.d.ts +17 -0
- package/dist/components/mdx/textOf.js +28 -0
- package/dist/components/osu/PlayerCard.d.ts +59 -0
- package/dist/components/osu/PlayerCard.js +40 -0
- package/dist/components/osu/playerLinks.d.ts +47 -0
- package/dist/components/osu/playerLinks.js +65 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mdx.d.ts +17 -0
- package/dist/mdx.js +17 -0
- package/dist/remark/callouts.d.ts +20 -0
- package/dist/remark/callouts.js +58 -0
- package/dist/remark/codeMeta.d.ts +17 -0
- package/dist/remark/codeMeta.js +22 -0
- package/dist/remark/headingIds.d.ts +16 -0
- package/dist/remark/headingIds.js +28 -0
- package/dist/remark/index.d.ts +29 -0
- package/dist/remark/index.js +33 -0
- package/dist/remark/mdast.d.ts +42 -0
- package/dist/remark/mdast.js +37 -0
- package/dist/remark/slugify.d.ts +23 -0
- package/dist/remark/slugify.js +40 -0
- package/dist/shiki.d.ts +13 -0
- package/dist/shiki.js +38 -0
- package/dist/theme.css +18 -0
- package/package.json +28 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/callouts.ts
|
|
3
|
+
* @desc A remark plugin for GitHub-style callouts: a blockquote whose first paragraph starts
|
|
4
|
+
* with `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` or `[!CAUTION]` (case-insensitive)
|
|
5
|
+
* gets `data.hProperties.dataCallout` set to one of three rendered types, and the marker
|
|
6
|
+
* text is stripped from the paragraph (the paragraph itself is dropped if the marker was
|
|
7
|
+
* its only content).
|
|
8
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
9
|
+
* @created Sat Oct 3, 2026
|
|
10
|
+
* @modified Sat Oct 3, 2026
|
|
11
|
+
*/
|
|
12
|
+
import { type MdNode } from "./mdast.js";
|
|
13
|
+
/** The three callout types this plugin renders. */
|
|
14
|
+
export type CalloutType = "note" | "tip" | "warning";
|
|
15
|
+
/**
|
|
16
|
+
* @function remarkCallouts
|
|
17
|
+
* @returns {(tree: MdNode) => void} a remark transformer that types matching blockquotes and
|
|
18
|
+
* strips their marker text
|
|
19
|
+
*/
|
|
20
|
+
export declare const remarkCallouts: () => (tree: MdNode) => void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/callouts.ts
|
|
3
|
+
* @desc A remark plugin for GitHub-style callouts: a blockquote whose first paragraph starts
|
|
4
|
+
* with `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` or `[!CAUTION]` (case-insensitive)
|
|
5
|
+
* gets `data.hProperties.dataCallout` set to one of three rendered types, and the marker
|
|
6
|
+
* text is stripped from the paragraph (the paragraph itself is dropped if the marker was
|
|
7
|
+
* its only content).
|
|
8
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
9
|
+
* @created Sat Oct 3, 2026
|
|
10
|
+
* @modified Sat Oct 3, 2026
|
|
11
|
+
*/
|
|
12
|
+
import { setProperty, walk } from "./mdast.js";
|
|
13
|
+
const TYPES = {
|
|
14
|
+
note: "note",
|
|
15
|
+
tip: "tip",
|
|
16
|
+
important: "tip",
|
|
17
|
+
warning: "warning",
|
|
18
|
+
caution: "warning",
|
|
19
|
+
};
|
|
20
|
+
const MARKER = /^\[!(note|tip|important|warning|caution)\][ \t]*(?:\r?\n)?/i;
|
|
21
|
+
/**
|
|
22
|
+
* @function remarkCallouts
|
|
23
|
+
* @returns {(tree: MdNode) => void} a remark transformer that types matching blockquotes and
|
|
24
|
+
* strips their marker text
|
|
25
|
+
*/
|
|
26
|
+
export const remarkCallouts = () => (tree) => {
|
|
27
|
+
walk(tree, (node) => {
|
|
28
|
+
if (node.type !== "blockquote")
|
|
29
|
+
return;
|
|
30
|
+
const paragraph = node.children?.[0];
|
|
31
|
+
if (paragraph?.type !== "paragraph" || !paragraph.children)
|
|
32
|
+
return;
|
|
33
|
+
// A parser may split "[!NOTE]" over several text nodes: join the leading run first.
|
|
34
|
+
let end = 0;
|
|
35
|
+
while (paragraph.children[end]?.type === "text")
|
|
36
|
+
end++;
|
|
37
|
+
if (end === 0)
|
|
38
|
+
return;
|
|
39
|
+
const text = paragraph.children
|
|
40
|
+
.slice(0, end)
|
|
41
|
+
.map((child) => child.value ?? "")
|
|
42
|
+
.join("");
|
|
43
|
+
const match = MARKER.exec(text);
|
|
44
|
+
if (!match)
|
|
45
|
+
return;
|
|
46
|
+
const key = match[1].toLowerCase();
|
|
47
|
+
const calloutType = TYPES[key];
|
|
48
|
+
if (!calloutType)
|
|
49
|
+
return;
|
|
50
|
+
setProperty(node, "dataCallout", calloutType);
|
|
51
|
+
const rest = text.slice(match[0].length);
|
|
52
|
+
paragraph.children.splice(0, end, ...(rest ? [{ type: "text", value: rest }] : []));
|
|
53
|
+
if (paragraph.children[0]?.type === "break")
|
|
54
|
+
paragraph.children.shift();
|
|
55
|
+
if (paragraph.children.length === 0)
|
|
56
|
+
node.children?.shift();
|
|
57
|
+
});
|
|
58
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/codeMeta.ts
|
|
3
|
+
* @desc A remark plugin that copies a fenced code block's meta string (the text after the
|
|
4
|
+
* language on the opening fence, e.g. `title="a.ts" {2}`) onto the node as
|
|
5
|
+
* `data.hProperties.dataMeta`, so mdast-util-to-hast renders it as `data-meta` and
|
|
6
|
+
* CodeBlock can read it client-side.
|
|
7
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
8
|
+
* @created Sat Oct 3, 2026
|
|
9
|
+
* @modified Sat Oct 3, 2026
|
|
10
|
+
*/
|
|
11
|
+
import { type MdNode } from "./mdast.js";
|
|
12
|
+
/**
|
|
13
|
+
* @function remarkCodeMeta
|
|
14
|
+
* @returns {(tree: MdNode) => void} a remark transformer that sets `dataMeta` on every code node
|
|
15
|
+
* that has a non-empty `meta` string
|
|
16
|
+
*/
|
|
17
|
+
export declare const remarkCodeMeta: () => (tree: MdNode) => void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/codeMeta.ts
|
|
3
|
+
* @desc A remark plugin that copies a fenced code block's meta string (the text after the
|
|
4
|
+
* language on the opening fence, e.g. `title="a.ts" {2}`) onto the node as
|
|
5
|
+
* `data.hProperties.dataMeta`, so mdast-util-to-hast renders it as `data-meta` and
|
|
6
|
+
* CodeBlock can read it client-side.
|
|
7
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
8
|
+
* @created Sat Oct 3, 2026
|
|
9
|
+
* @modified Sat Oct 3, 2026
|
|
10
|
+
*/
|
|
11
|
+
import { setProperty, walk } from "./mdast.js";
|
|
12
|
+
/**
|
|
13
|
+
* @function remarkCodeMeta
|
|
14
|
+
* @returns {(tree: MdNode) => void} a remark transformer that sets `dataMeta` on every code node
|
|
15
|
+
* that has a non-empty `meta` string
|
|
16
|
+
*/
|
|
17
|
+
export const remarkCodeMeta = () => (tree) => {
|
|
18
|
+
walk(tree, (node) => {
|
|
19
|
+
if (node.type === "code" && node.meta)
|
|
20
|
+
setProperty(node, "dataMeta", node.meta);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/headingIds.ts
|
|
3
|
+
* @desc A remark plugin that gives every depth-2 and depth-3 heading a GitHub-style id slugged
|
|
4
|
+
* from its text, deduplicated across the document. A heading that already has an id
|
|
5
|
+
* (`data.hProperties.id`) keeps it, and a heading whose text slugs to an empty string gets
|
|
6
|
+
* no id at all.
|
|
7
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
8
|
+
* @created Sat Oct 3, 2026
|
|
9
|
+
* @modified Sat Oct 3, 2026
|
|
10
|
+
*/
|
|
11
|
+
import { type MdNode } from "./mdast.js";
|
|
12
|
+
/**
|
|
13
|
+
* @function remarkHeadingIds
|
|
14
|
+
* @returns {(tree: MdNode) => void} a remark transformer that ids depth-2/3 headings
|
|
15
|
+
*/
|
|
16
|
+
export declare const remarkHeadingIds: () => (tree: MdNode) => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/headingIds.ts
|
|
3
|
+
* @desc A remark plugin that gives every depth-2 and depth-3 heading a GitHub-style id slugged
|
|
4
|
+
* from its text, deduplicated across the document. A heading that already has an id
|
|
5
|
+
* (`data.hProperties.id`) keeps it, and a heading whose text slugs to an empty string gets
|
|
6
|
+
* no id at all.
|
|
7
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
8
|
+
* @created Sat Oct 3, 2026
|
|
9
|
+
* @modified Sat Oct 3, 2026
|
|
10
|
+
*/
|
|
11
|
+
import { setProperty, textContent, walk } from "./mdast.js";
|
|
12
|
+
import { createSlugger } from "./slugify.js";
|
|
13
|
+
/**
|
|
14
|
+
* @function remarkHeadingIds
|
|
15
|
+
* @returns {(tree: MdNode) => void} a remark transformer that ids depth-2/3 headings
|
|
16
|
+
*/
|
|
17
|
+
export const remarkHeadingIds = () => (tree) => {
|
|
18
|
+
const slug = createSlugger();
|
|
19
|
+
walk(tree, (node) => {
|
|
20
|
+
if (node.type !== "heading" || (node.depth !== 2 && node.depth !== 3))
|
|
21
|
+
return;
|
|
22
|
+
if (node.data?.hProperties?.id)
|
|
23
|
+
return;
|
|
24
|
+
const id = slug(textContent(node));
|
|
25
|
+
if (id)
|
|
26
|
+
setProperty(node, "id", id);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/index.ts
|
|
3
|
+
* @desc The public entry point for `@haruhimemoe/ui/remark`: one remark plugin for MDX and
|
|
4
|
+
* react-markdown that combines code meta, callouts and heading ids, plus the individual
|
|
5
|
+
* plugins and the slug helpers for callers who want only one piece.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Sat Oct 3, 2026
|
|
8
|
+
* @modified Sat Oct 3, 2026
|
|
9
|
+
*/
|
|
10
|
+
import type { MdNode } from "./mdast.js";
|
|
11
|
+
export type { CalloutType } from "./callouts.js";
|
|
12
|
+
export { remarkCallouts } from "./callouts.js";
|
|
13
|
+
export { remarkCodeMeta } from "./codeMeta.js";
|
|
14
|
+
export { remarkHeadingIds } from "./headingIds.js";
|
|
15
|
+
export { createSlugger, slugify } from "./slugify.js";
|
|
16
|
+
/** Which of the three plugins `remarkHaruhime` runs; each defaults to on. */
|
|
17
|
+
export type RemarkHaruhimeOptions = {
|
|
18
|
+
codeMeta?: boolean;
|
|
19
|
+
callouts?: boolean;
|
|
20
|
+
headingIds?: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @function remarkHaruhime
|
|
24
|
+
* @param options {RemarkHaruhimeOptions} per-plugin opt-outs; omit to run all three
|
|
25
|
+
* @returns {(tree: MdNode) => void} a remark transformer running code meta, callouts and heading
|
|
26
|
+
* ids over one tree. A default export, because Turbopack only takes MDX plugins by
|
|
27
|
+
* module name: `remarkPlugins: ["@haruhimemoe/ui/remark"]`.
|
|
28
|
+
*/
|
|
29
|
+
export default function remarkHaruhime(options?: RemarkHaruhimeOptions): (tree: MdNode) => void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/index.ts
|
|
3
|
+
* @desc The public entry point for `@haruhimemoe/ui/remark`: one remark plugin for MDX and
|
|
4
|
+
* react-markdown that combines code meta, callouts and heading ids, plus the individual
|
|
5
|
+
* plugins and the slug helpers for callers who want only one piece.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Sat Oct 3, 2026
|
|
8
|
+
* @modified Sat Oct 3, 2026
|
|
9
|
+
*/
|
|
10
|
+
import { remarkCallouts } from "./callouts.js";
|
|
11
|
+
import { remarkCodeMeta } from "./codeMeta.js";
|
|
12
|
+
import { remarkHeadingIds } from "./headingIds.js";
|
|
13
|
+
export { remarkCallouts } from "./callouts.js";
|
|
14
|
+
export { remarkCodeMeta } from "./codeMeta.js";
|
|
15
|
+
export { remarkHeadingIds } from "./headingIds.js";
|
|
16
|
+
export { createSlugger, slugify } from "./slugify.js";
|
|
17
|
+
/**
|
|
18
|
+
* @function remarkHaruhime
|
|
19
|
+
* @param options {RemarkHaruhimeOptions} per-plugin opt-outs; omit to run all three
|
|
20
|
+
* @returns {(tree: MdNode) => void} a remark transformer running code meta, callouts and heading
|
|
21
|
+
* ids over one tree. A default export, because Turbopack only takes MDX plugins by
|
|
22
|
+
* module name: `remarkPlugins: ["@haruhimemoe/ui/remark"]`.
|
|
23
|
+
*/
|
|
24
|
+
export default function remarkHaruhime(options = {}) {
|
|
25
|
+
return (tree) => {
|
|
26
|
+
if (options.codeMeta !== false)
|
|
27
|
+
remarkCodeMeta()(tree);
|
|
28
|
+
if (options.callouts !== false)
|
|
29
|
+
remarkCallouts()(tree);
|
|
30
|
+
if (options.headingIds !== false)
|
|
31
|
+
remarkHeadingIds()(tree);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/mdast.ts
|
|
3
|
+
* @desc The slice of an mdast node these remark plugins read and write, plus three small
|
|
4
|
+
* helpers (`walk`, `setProperty`, `textContent`) that the plugins in this directory share.
|
|
5
|
+
* No `unist-util-visit` or `@types/mdast` import: this type is the whole contract.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Sat Oct 3, 2026
|
|
8
|
+
* @modified Sat Oct 3, 2026
|
|
9
|
+
*/
|
|
10
|
+
/** The slice of an mdast node these plugins read and write. */
|
|
11
|
+
export type MdNode = {
|
|
12
|
+
type: string;
|
|
13
|
+
children?: MdNode[];
|
|
14
|
+
value?: string;
|
|
15
|
+
meta?: string | null;
|
|
16
|
+
depth?: number;
|
|
17
|
+
data?: {
|
|
18
|
+
hProperties?: Record<string, unknown>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @function walk
|
|
24
|
+
* @param node {MdNode} the node to start from
|
|
25
|
+
* @param visit {(node: MdNode) => void} called on `node`, then on every descendant, depth first
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
export declare const walk: (node: MdNode, visit: (node: MdNode) => void) => void;
|
|
29
|
+
/**
|
|
30
|
+
* @function setProperty
|
|
31
|
+
* @param node {MdNode} the node to annotate
|
|
32
|
+
* @param key {string} the hast property name (camelCase, e.g. `dataMeta`)
|
|
33
|
+
* @param value {unknown} the property's value
|
|
34
|
+
* @returns {void} sets one HTML attribute that mdast-util-to-hast copies onto the rendered element
|
|
35
|
+
*/
|
|
36
|
+
export declare const setProperty: (node: MdNode, key: string, value: unknown) => void;
|
|
37
|
+
/**
|
|
38
|
+
* @function textContent
|
|
39
|
+
* @param node {MdNode} the node to read
|
|
40
|
+
* @returns {string} the node's plain text: its value, or its children's text joined
|
|
41
|
+
*/
|
|
42
|
+
export declare const textContent: (node: MdNode) => string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/mdast.ts
|
|
3
|
+
* @desc The slice of an mdast node these remark plugins read and write, plus three small
|
|
4
|
+
* helpers (`walk`, `setProperty`, `textContent`) that the plugins in this directory share.
|
|
5
|
+
* No `unist-util-visit` or `@types/mdast` import: this type is the whole contract.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Sat Oct 3, 2026
|
|
8
|
+
* @modified Sat Oct 3, 2026
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @function walk
|
|
12
|
+
* @param node {MdNode} the node to start from
|
|
13
|
+
* @param visit {(node: MdNode) => void} called on `node`, then on every descendant, depth first
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
export const walk = (node, visit) => {
|
|
17
|
+
visit(node);
|
|
18
|
+
for (const child of node.children ?? [])
|
|
19
|
+
walk(child, visit);
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @function setProperty
|
|
23
|
+
* @param node {MdNode} the node to annotate
|
|
24
|
+
* @param key {string} the hast property name (camelCase, e.g. `dataMeta`)
|
|
25
|
+
* @param value {unknown} the property's value
|
|
26
|
+
* @returns {void} sets one HTML attribute that mdast-util-to-hast copies onto the rendered element
|
|
27
|
+
*/
|
|
28
|
+
export const setProperty = (node, key, value) => {
|
|
29
|
+
node.data ??= {};
|
|
30
|
+
node.data.hProperties = { ...node.data.hProperties, [key]: value };
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* @function textContent
|
|
34
|
+
* @param node {MdNode} the node to read
|
|
35
|
+
* @returns {string} the node's plain text: its value, or its children's text joined
|
|
36
|
+
*/
|
|
37
|
+
export const textContent = (node) => typeof node.value === "string" ? node.value : (node.children ?? []).map(textContent).join("");
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/slugify.ts
|
|
3
|
+
* @desc GitHub-style heading slugs: lowercase, drop punctuation (keeping letters, marks, digits
|
|
4
|
+
* and underscores from any script), turn each whitespace character into a hyphen, and
|
|
5
|
+
* suffix repeats `-1`, `-2` the way GitHub's own heading anchors do.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Sat Oct 3, 2026
|
|
8
|
+
* @modified Sat Oct 3, 2026
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @function slugify
|
|
12
|
+
* @param text {string} the heading text
|
|
13
|
+
* @returns {string} a lowercase, hyphenated slug (letters/marks/digits/underscores from any
|
|
14
|
+
* script survive; everything else but whitespace and existing hyphens is dropped)
|
|
15
|
+
*/
|
|
16
|
+
export declare const slugify: (text: string) => string;
|
|
17
|
+
/**
|
|
18
|
+
* @function createSlugger
|
|
19
|
+
* @returns {(text: string) => string} a function that slugs `text` and, on a repeat (the slug
|
|
20
|
+
* itself, or a literal heading that already claimed the suffixed form), appends `-1`,
|
|
21
|
+
* `-2`, … until it finds an unused candidate, like GitHub's heading anchors
|
|
22
|
+
*/
|
|
23
|
+
export declare const createSlugger: () => ((text: string) => string);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/remark/slugify.ts
|
|
3
|
+
* @desc GitHub-style heading slugs: lowercase, drop punctuation (keeping letters, marks, digits
|
|
4
|
+
* and underscores from any script), turn each whitespace character into a hyphen, and
|
|
5
|
+
* suffix repeats `-1`, `-2` the way GitHub's own heading anchors do.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Sat Oct 3, 2026
|
|
8
|
+
* @modified Sat Oct 3, 2026
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @function slugify
|
|
12
|
+
* @param text {string} the heading text
|
|
13
|
+
* @returns {string} a lowercase, hyphenated slug (letters/marks/digits/underscores from any
|
|
14
|
+
* script survive; everything else but whitespace and existing hyphens is dropped)
|
|
15
|
+
*/
|
|
16
|
+
export const slugify = (text) => text
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^\p{L}\p{M}\p{N}\p{Pc}\s-]/gu, "")
|
|
20
|
+
.replace(/\s/g, "-");
|
|
21
|
+
/**
|
|
22
|
+
* @function createSlugger
|
|
23
|
+
* @returns {(text: string) => string} a function that slugs `text` and, on a repeat (the slug
|
|
24
|
+
* itself, or a literal heading that already claimed the suffixed form), appends `-1`,
|
|
25
|
+
* `-2`, … until it finds an unused candidate, like GitHub's heading anchors
|
|
26
|
+
*/
|
|
27
|
+
export const createSlugger = () => {
|
|
28
|
+
const occurrences = new Map();
|
|
29
|
+
return (text) => {
|
|
30
|
+
const base = slugify(text);
|
|
31
|
+
let candidate = base;
|
|
32
|
+
while (occurrences.has(candidate)) {
|
|
33
|
+
const count = (occurrences.get(base) ?? 0) + 1;
|
|
34
|
+
occurrences.set(base, count);
|
|
35
|
+
candidate = `${base}-${count}`;
|
|
36
|
+
}
|
|
37
|
+
occurrences.set(candidate, 0);
|
|
38
|
+
return candidate;
|
|
39
|
+
};
|
|
40
|
+
};
|
package/dist/shiki.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/shiki.ts
|
|
3
|
+
* @desc The `@haruhimemoe/ui/shiki` subpath: registers the Shiki loader CodeBlock highlights
|
|
4
|
+
* with (a core highlighter, the CSS-variables theme, a fixed language set and the no-WASM
|
|
5
|
+
* JS regex engine). Apps that render code install `shiki` and import this once, for its
|
|
6
|
+
* side effect: `import "@haruhimemoe/ui/shiki";` (in mdx-components.tsx, say). Nothing
|
|
7
|
+
* else in the package names Shiki, so an app that skips this import builds without it
|
|
8
|
+
* and gets plain code blocks.
|
|
9
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
10
|
+
* @created Sat Oct 3, 2026
|
|
11
|
+
* @modified Sat Oct 3, 2026
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/dist/shiki.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/shiki.ts
|
|
3
|
+
* @desc The `@haruhimemoe/ui/shiki` subpath: registers the Shiki loader CodeBlock highlights
|
|
4
|
+
* with (a core highlighter, the CSS-variables theme, a fixed language set and the no-WASM
|
|
5
|
+
* JS regex engine). Apps that render code install `shiki` and import this once, for its
|
|
6
|
+
* side effect: `import "@haruhimemoe/ui/shiki";` (in mdx-components.tsx, say). Nothing
|
|
7
|
+
* else in the package names Shiki, so an app that skips this import builds without it
|
|
8
|
+
* and gets plain code blocks.
|
|
9
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
10
|
+
* @created Sat Oct 3, 2026
|
|
11
|
+
* @modified Sat Oct 3, 2026
|
|
12
|
+
*/
|
|
13
|
+
import { setHighlighterLoader, THEME } from "./components/mdx/highlighter.js";
|
|
14
|
+
/**
|
|
15
|
+
* @function loadShiki
|
|
16
|
+
* @returns {Promise<HighlighterCore>} a Shiki core highlighter with the package's theme and
|
|
17
|
+
* languages, loaded through dynamic import()
|
|
18
|
+
*/
|
|
19
|
+
const loadShiki = async () => {
|
|
20
|
+
const [{ createCssVariablesTheme, createHighlighterCore }, { createJavaScriptRegexEngine }] = await Promise.all([import("shiki/core"), import("shiki/engine/javascript")]);
|
|
21
|
+
return createHighlighterCore({
|
|
22
|
+
themes: [createCssVariablesTheme({ name: THEME, variablePrefix: "--shiki-" })],
|
|
23
|
+
langs: [
|
|
24
|
+
import("shiki/langs/typescript.mjs"),
|
|
25
|
+
import("shiki/langs/tsx.mjs"),
|
|
26
|
+
import("shiki/langs/javascript.mjs"),
|
|
27
|
+
import("shiki/langs/json.mjs"),
|
|
28
|
+
import("shiki/langs/bash.mjs"),
|
|
29
|
+
import("shiki/langs/css.mjs"),
|
|
30
|
+
import("shiki/langs/html.mjs"),
|
|
31
|
+
import("shiki/langs/markdown.mjs"),
|
|
32
|
+
import("shiki/langs/diff.mjs"),
|
|
33
|
+
import("shiki/langs/yaml.mjs"),
|
|
34
|
+
],
|
|
35
|
+
engine: createJavaScriptRegexEngine(),
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
setHighlighterLoader(loadShiki);
|
package/dist/theme.css
CHANGED
|
@@ -45,6 +45,24 @@
|
|
|
45
45
|
--font-sans: var(--font-nunito), ui-sans-serif, system-ui, sans-serif;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/* CodeBlock reads these as Shiki's CSS-variables theme. Every token keeps 4.5:1 against both b6
|
|
49
|
+
(the block's background) and b4 (the highlighted-line tint) at every integer --hue. The keyword
|
|
50
|
+
and link tokens get their own hue-derived value instead of var(--color-h1): that variable's
|
|
51
|
+
default lightness (76%) falls to 4.33:1 against b4 at hue 240. */
|
|
52
|
+
:root {
|
|
53
|
+
--shiki-foreground: var(--color-c2);
|
|
54
|
+
--shiki-background: var(--color-b6);
|
|
55
|
+
--shiki-token-keyword: hsl(var(--hue) 100% 78%);
|
|
56
|
+
--shiki-token-string: hsl(calc(var(--hue) + 150) 55% 75%);
|
|
57
|
+
--shiki-token-string-expression: hsl(calc(var(--hue) + 150) 55% 75%);
|
|
58
|
+
--shiki-token-constant: hsl(calc(var(--hue) + 40) 55% 74%);
|
|
59
|
+
--shiki-token-function: hsl(calc(var(--hue) + 200) 55% 75%);
|
|
60
|
+
--shiki-token-parameter: hsl(calc(var(--hue) + 60) 55% 75%);
|
|
61
|
+
--shiki-token-comment: var(--color-c4);
|
|
62
|
+
--shiki-token-punctuation: var(--color-c3);
|
|
63
|
+
--shiki-token-link: hsl(var(--hue) 100% 78%);
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
@layer base {
|
|
49
67
|
:focus-visible {
|
|
50
68
|
outline: 2px solid var(--color-h1);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haruhimemoe/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "React components for the haruhime.moe osu! tools on Next.js: the osu!-web-style palette as a Tailwind theme, buttons, cards, form fields and confirms, filter controls, tables, osu! beatmap display pieces, a command palette and the site header and footer.",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "React components for the haruhime.moe osu! tools on Next.js: the osu!-web-style palette as a Tailwind theme, buttons, cards, form fields and confirms, filter controls, tables, osu! beatmap display pieces and player cards, a command palette and the site header and footer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"osu",
|
|
7
7
|
"osu!",
|
|
@@ -26,6 +26,18 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"default": "./dist/index.js"
|
|
28
28
|
},
|
|
29
|
+
"./mdx": {
|
|
30
|
+
"types": "./dist/mdx.d.ts",
|
|
31
|
+
"default": "./dist/mdx.js"
|
|
32
|
+
},
|
|
33
|
+
"./remark": {
|
|
34
|
+
"types": "./dist/remark/index.d.ts",
|
|
35
|
+
"default": "./dist/remark/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./shiki": {
|
|
38
|
+
"types": "./dist/shiki.d.ts",
|
|
39
|
+
"default": "./dist/shiki.js"
|
|
40
|
+
},
|
|
29
41
|
"./theme.css": "./dist/theme.css",
|
|
30
42
|
"./package.json": "./package.json"
|
|
31
43
|
},
|
|
@@ -35,7 +47,8 @@
|
|
|
35
47
|
"CHANGELOG.md"
|
|
36
48
|
],
|
|
37
49
|
"sideEffects": [
|
|
38
|
-
"**/*.css"
|
|
50
|
+
"**/*.css",
|
|
51
|
+
"./dist/shiki.js"
|
|
39
52
|
],
|
|
40
53
|
"engines": {
|
|
41
54
|
"node": ">=22.12"
|
|
@@ -67,16 +80,26 @@
|
|
|
67
80
|
"next": ">=16.0.0 <17",
|
|
68
81
|
"react": ">=19.0.0",
|
|
69
82
|
"react-dom": ">=19.0.0",
|
|
83
|
+
"shiki": ">=4.5.0 <5",
|
|
70
84
|
"tailwindcss": ">=4.1.0 <5"
|
|
71
85
|
},
|
|
86
|
+
"peerDependenciesMeta": {
|
|
87
|
+
"shiki": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
72
91
|
"devDependencies": {
|
|
73
92
|
"@axe-core/playwright": "4.13.0",
|
|
74
93
|
"@biomejs/biome": "2.5.14",
|
|
94
|
+
"@mdx-js/loader": "3.1.1",
|
|
95
|
+
"@mdx-js/react": "3.1.1",
|
|
96
|
+
"@next/mdx": "16.3.6",
|
|
75
97
|
"@tailwindcss/postcss": "4.3.3",
|
|
76
98
|
"@testing-library/dom": "10.4.2",
|
|
77
99
|
"@testing-library/jest-dom": "7.0.1",
|
|
78
100
|
"@testing-library/react": "16.3.3",
|
|
79
101
|
"@testing-library/user-event": "14.6.7",
|
|
102
|
+
"@types/mdx": "2.0.14",
|
|
80
103
|
"@types/node": "26.6.2",
|
|
81
104
|
"@types/react": "19.3.0",
|
|
82
105
|
"@types/react-dom": "19.3.0",
|
|
@@ -88,6 +111,8 @@
|
|
|
88
111
|
"playwright": "1.63.0",
|
|
89
112
|
"react": "19.3.0",
|
|
90
113
|
"react-dom": "19.3.0",
|
|
114
|
+
"remark-gfm": "4.0.1",
|
|
115
|
+
"shiki": "4.5.0",
|
|
91
116
|
"tailwindcss": "4.3.3",
|
|
92
117
|
"typescript": "7.0.2",
|
|
93
118
|
"vitest": "5.0.1"
|