@reapi/docs-ui 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.
@@ -0,0 +1,83 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { Root } from 'mdast';
4
+
5
+ type DocsComponent = (props: any) => ReactNode;
6
+ declare const defaultComponents: Record<string, DocsComponent>;
7
+ declare function MdxContent({ ast, components, }: {
8
+ ast: Root;
9
+ components?: Record<string, DocsComponent>;
10
+ }): react.JSX.Element;
11
+
12
+ type AnyRecord = Record<string, unknown>;
13
+ declare function DocsEmbedProvider({ specs, children, }: {
14
+ specs: Record<string, AnyRecord>;
15
+ children?: ReactNode;
16
+ }): react.JSX.Element;
17
+ /** `<ApiOperation api="petstore" operation="GET /pets" />` */
18
+ declare function ApiOperation({ api, operation }: {
19
+ api?: string;
20
+ operation?: string;
21
+ }): react.JSX.Element;
22
+ /** `<ApiSchema api="petstore" name="Pet" />` */
23
+ declare function ApiSchema({ api, name }: {
24
+ api?: string;
25
+ name?: string;
26
+ }): react.JSX.Element;
27
+
28
+ declare const Note: (p: {
29
+ title?: string;
30
+ children?: ReactNode;
31
+ }) => react.JSX.Element;
32
+ declare const Warning: (p: {
33
+ title?: string;
34
+ children?: ReactNode;
35
+ }) => react.JSX.Element;
36
+ declare const Tip: (p: {
37
+ title?: string;
38
+ children?: ReactNode;
39
+ }) => react.JSX.Element;
40
+
41
+ declare function Tab({ children }: {
42
+ title?: string;
43
+ children?: ReactNode;
44
+ }): react.JSX.Element;
45
+ /** `<Tabs><Tab title="npm">…</Tab><Tab title="pnpm">…</Tab></Tabs>` */
46
+ declare function Tabs({ children }: {
47
+ children?: ReactNode;
48
+ }): react.JSX.Element | null;
49
+
50
+ /**
51
+ * `<CodeGroup>` with fenced code blocks as children — each block's
52
+ * ```lang title meta becomes the tab label (falling back to the language).
53
+ */
54
+ declare function CodeGroup({ children }: {
55
+ children?: ReactNode;
56
+ }): react.JSX.Element | null;
57
+
58
+ declare function Card({ title, href, children, }: {
59
+ title?: string;
60
+ href?: string;
61
+ children?: ReactNode;
62
+ }): react.JSX.Element;
63
+ declare function CardGroup({ cols, children }: {
64
+ cols?: string;
65
+ children?: ReactNode;
66
+ }): react.JSX.Element;
67
+
68
+ /** Native <details> — interactive without any client JS (RSC-friendly). */
69
+ declare function Accordion({ title, children }: {
70
+ title?: string;
71
+ children?: ReactNode;
72
+ }): react.JSX.Element;
73
+ declare function AccordionGroup({ children }: {
74
+ children?: ReactNode;
75
+ }): react.JSX.Element;
76
+
77
+ /** A captioned figure wrapper (images, screenshots). */
78
+ declare function Frame({ caption, children }: {
79
+ caption?: string;
80
+ children?: ReactNode;
81
+ }): react.JSX.Element;
82
+
83
+ export { Accordion, AccordionGroup, ApiOperation, ApiSchema, Card, CardGroup, CodeGroup, type DocsComponent, DocsEmbedProvider, Frame, MdxContent, Note, Tab, Tabs, Tip, Warning, defaultComponents };
package/dist/index.js ADDED
@@ -0,0 +1,350 @@
1
+ // src/renderer.tsx
2
+ import { Fragment as Fragment2, createElement } from "react";
3
+
4
+ // src/components/callout.tsx
5
+ import { Info, AlertTriangle, Lightbulb } from "lucide-react";
6
+ import { cn } from "@reapi/spec-ui";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var VARIANTS = {
9
+ note: {
10
+ icon: Info,
11
+ classes: "border-sky-200 bg-sky-50 text-sky-900 dark:border-sky-900 dark:bg-sky-950 dark:text-sky-200"
12
+ },
13
+ warning: {
14
+ icon: AlertTriangle,
15
+ classes: "border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-200"
16
+ },
17
+ tip: {
18
+ icon: Lightbulb,
19
+ classes: "border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950 dark:text-emerald-200"
20
+ }
21
+ };
22
+ function Callout({
23
+ variant,
24
+ title,
25
+ children
26
+ }) {
27
+ const { icon: Icon, classes } = VARIANTS[variant];
28
+ return /* @__PURE__ */ jsxs("div", { className: cn("not-prose my-4 flex gap-2.5 rounded-lg border p-3 text-sm", classes), children: [
29
+ /* @__PURE__ */ jsx(Icon, { className: "mt-0.5 size-4 shrink-0" }),
30
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 [&>p]:my-0 [&>p+p]:mt-2", children: [
31
+ title && /* @__PURE__ */ jsx("p", { className: "mb-1 font-semibold", children: title }),
32
+ children
33
+ ] })
34
+ ] });
35
+ }
36
+ var Note = (p) => /* @__PURE__ */ jsx(Callout, { variant: "note", ...p });
37
+ var Warning = (p) => /* @__PURE__ */ jsx(Callout, { variant: "warning", ...p });
38
+ var Tip = (p) => /* @__PURE__ */ jsx(Callout, { variant: "tip", ...p });
39
+
40
+ // src/components/tabs.tsx
41
+ import { Children, isValidElement, useState } from "react";
42
+ import { cn as cn2 } from "@reapi/spec-ui";
43
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
44
+ function Tab({ children }) {
45
+ return /* @__PURE__ */ jsx2("div", { children });
46
+ }
47
+ function Tabs({ children }) {
48
+ const items = Children.toArray(children).filter(
49
+ (c) => isValidElement(c) && c.type === Tab
50
+ );
51
+ const [active, setActive] = useState(0);
52
+ if (!items.length) return null;
53
+ return /* @__PURE__ */ jsxs2("div", { className: "not-prose my-4 rounded-lg border", children: [
54
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-1 border-b bg-muted/40 px-2 pt-1.5", children: items.map((item, i) => /* @__PURE__ */ jsx2(
55
+ "button",
56
+ {
57
+ type: "button",
58
+ onClick: () => setActive(i),
59
+ className: cn2(
60
+ "-mb-px rounded-t-md border-x border-t px-3 py-1.5 text-xs font-medium",
61
+ i === active ? "border-border bg-background" : "border-transparent text-muted-foreground hover:text-foreground"
62
+ ),
63
+ children: item.props.title ?? `Tab ${i + 1}`
64
+ },
65
+ i
66
+ )) }),
67
+ /* @__PURE__ */ jsx2("div", { className: "p-3 text-sm [&>p]:my-0 [&>p+p]:mt-2", children: items[active] })
68
+ ] });
69
+ }
70
+
71
+ // src/components/code-group.tsx
72
+ import { Children as Children2, isValidElement as isValidElement2, useState as useState2 } from "react";
73
+ import { cn as cn3 } from "@reapi/spec-ui";
74
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
75
+ function CodeGroup({ children }) {
76
+ const blocks = Children2.toArray(children).filter(isValidElement2);
77
+ const [active, setActive] = useState2(0);
78
+ if (!blocks.length) return null;
79
+ const label = (el, i) => {
80
+ const props = el.props;
81
+ return props["data-title"] ?? props["data-language"] ?? `Code ${i + 1}`;
82
+ };
83
+ return /* @__PURE__ */ jsxs3("div", { className: "not-prose my-4 overflow-hidden rounded-lg border bg-muted/30", children: [
84
+ /* @__PURE__ */ jsx3("div", { className: "flex gap-1 border-b bg-muted/60 px-2 pt-1.5", children: blocks.map((b, i) => /* @__PURE__ */ jsx3(
85
+ "button",
86
+ {
87
+ type: "button",
88
+ onClick: () => setActive(i),
89
+ className: cn3(
90
+ "-mb-px rounded-t-md px-3 py-1.5 font-mono text-[11px] font-medium",
91
+ i === active ? "bg-background" : "text-muted-foreground hover:text-foreground"
92
+ ),
93
+ children: label(b, i)
94
+ },
95
+ i
96
+ )) }),
97
+ /* @__PURE__ */ jsx3("div", { className: "[&_pre]:my-0 [&_pre]:rounded-none [&_pre]:border-0", children: blocks[active] })
98
+ ] });
99
+ }
100
+
101
+ // src/components/card.tsx
102
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
103
+ function Card({
104
+ title,
105
+ href,
106
+ children
107
+ }) {
108
+ const body = /* @__PURE__ */ jsxs4(Fragment, { children: [
109
+ title && /* @__PURE__ */ jsx4("p", { className: "mb-1 text-sm font-semibold", children: title }),
110
+ /* @__PURE__ */ jsx4("div", { className: "text-sm text-muted-foreground [&>p]:my-0 [&>p+p]:mt-2", children })
111
+ ] });
112
+ const classes = "not-prose block rounded-lg border p-4";
113
+ return href ? /* @__PURE__ */ jsx4("a", { href, className: `${classes} transition-colors hover:bg-accent`, children: body }) : /* @__PURE__ */ jsx4("div", { className: classes, children: body });
114
+ }
115
+ function CardGroup({ cols, children }) {
116
+ const n = Math.min(Math.max(Number(cols) || 2, 1), 4);
117
+ return /* @__PURE__ */ jsx4(
118
+ "div",
119
+ {
120
+ className: "not-prose my-4 grid gap-3",
121
+ style: { gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` },
122
+ children
123
+ }
124
+ );
125
+ }
126
+
127
+ // src/components/accordion.tsx
128
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
129
+ function Accordion({ title, children }) {
130
+ return /* @__PURE__ */ jsxs5("details", { className: "not-prose group my-2 rounded-lg border", children: [
131
+ /* @__PURE__ */ jsx5("summary", { className: "cursor-pointer select-none px-4 py-2.5 text-sm font-medium marker:text-muted-foreground", children: title ?? "Details" }),
132
+ /* @__PURE__ */ jsx5("div", { className: "border-t px-4 py-3 text-sm [&>p]:my-0 [&>p+p]:mt-2", children })
133
+ ] });
134
+ }
135
+ function AccordionGroup({ children }) {
136
+ return /* @__PURE__ */ jsx5("div", { className: "not-prose my-4 [&>details]:my-0 [&>details+details]:-mt-px [&>details]:rounded-none [&>details:first-child]:rounded-t-lg [&>details:last-child]:rounded-b-lg", children });
137
+ }
138
+
139
+ // src/components/frame.tsx
140
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
141
+ function Frame({ caption, children }) {
142
+ return /* @__PURE__ */ jsxs6("figure", { className: "not-prose my-4 overflow-hidden rounded-lg border", children: [
143
+ /* @__PURE__ */ jsx6("div", { className: "flex justify-center bg-muted/30 p-3 [&_img]:rounded-md", children }),
144
+ caption && /* @__PURE__ */ jsx6("figcaption", { className: "border-t px-3 py-1.5 text-center text-xs text-muted-foreground", children: caption })
145
+ ] });
146
+ }
147
+
148
+ // src/components/api-embeds.tsx
149
+ import { createContext, useContext } from "react";
150
+ import {
151
+ ConstraintPills,
152
+ Markdown,
153
+ OperationSection,
154
+ SchemaFields,
155
+ createRefResolver,
156
+ isRef
157
+ } from "@reapi/spec-ui";
158
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
159
+ var EmbedSpecsContext = createContext({});
160
+ function DocsEmbedProvider({
161
+ specs,
162
+ children
163
+ }) {
164
+ return /* @__PURE__ */ jsx7(EmbedSpecsContext.Provider, { value: specs, children });
165
+ }
166
+ function MissingEmbed({ children }) {
167
+ return /* @__PURE__ */ jsx7("div", { className: "not-prose my-4 rounded-lg border border-dashed px-4 py-3 text-sm text-muted-foreground", children });
168
+ }
169
+ function ApiOperation({ api, operation }) {
170
+ const specs = useContext(EmbedSpecsContext);
171
+ const spec = api ? specs[api] : void 0;
172
+ const parts = (operation ?? "").trim().split(/\s+/);
173
+ if (!api || parts.length !== 2) {
174
+ return /* @__PURE__ */ jsx7(MissingEmbed, { children: '<ApiOperation> needs api="\u2026" and operation="GET /path"' });
175
+ }
176
+ if (!spec) {
177
+ return /* @__PURE__ */ jsx7(MissingEmbed, { children: `API "${api}" is not published \u2014 publish it to render this embed.` });
178
+ }
179
+ const [method, path] = parts;
180
+ const op = spec.paths?.[path]?.[method.toLowerCase()];
181
+ if (!op) {
182
+ return /* @__PURE__ */ jsx7(MissingEmbed, { children: `Operation ${method.toUpperCase()} ${path} not found in "${api}".` });
183
+ }
184
+ return /* @__PURE__ */ jsx7("div", { className: "not-prose my-4 rounded-lg border px-5 [&>section]:border-t-0 [&>section]:py-5", children: /* @__PURE__ */ jsx7(
185
+ OperationSection,
186
+ {
187
+ spec,
188
+ id: `embed-${method}-${path}`.replace(/[^a-zA-Z0-9-]+/g, "-"),
189
+ method: method.toUpperCase(),
190
+ path,
191
+ resolve: createRefResolver(spec, null)
192
+ }
193
+ ) });
194
+ }
195
+ function ApiSchema({ api, name }) {
196
+ const specs = useContext(EmbedSpecsContext);
197
+ const spec = api ? specs[api] : void 0;
198
+ if (!api || !name) {
199
+ return /* @__PURE__ */ jsx7(MissingEmbed, { children: '<ApiSchema> needs api="\u2026" and name="\u2026"' });
200
+ }
201
+ if (!spec) {
202
+ return /* @__PURE__ */ jsx7(MissingEmbed, { children: `API "${api}" is not published \u2014 publish it to render this embed.` });
203
+ }
204
+ const schema = (spec.components?.schemas ?? {})[name];
205
+ if (!schema) {
206
+ return /* @__PURE__ */ jsx7(MissingEmbed, { children: `Schema "${name}" not found in "${api}".` });
207
+ }
208
+ return /* @__PURE__ */ jsxs7("div", { className: "not-prose my-4 rounded-lg border px-4 py-3", children: [
209
+ /* @__PURE__ */ jsx7("p", { className: "font-mono text-sm font-semibold", children: name }),
210
+ !isRef(schema) && typeof schema.description === "string" && schema.description && /* @__PURE__ */ jsx7(Markdown, { compact: true, children: schema.description }),
211
+ /* @__PURE__ */ jsx7(ConstraintPills, { schema }),
212
+ /* @__PURE__ */ jsx7("div", { className: "mt-1", children: /* @__PURE__ */ jsx7(SchemaFields, { schema, resolve: createRefResolver(spec, null) }) })
213
+ ] });
214
+ }
215
+
216
+ // src/renderer.tsx
217
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
218
+ var defaultComponents = {
219
+ Note,
220
+ Warning,
221
+ Tip,
222
+ Tabs,
223
+ Tab,
224
+ CodeGroup,
225
+ Card,
226
+ CardGroup,
227
+ Accordion,
228
+ AccordionGroup,
229
+ Frame,
230
+ ApiOperation,
231
+ ApiSchema
232
+ };
233
+ function jsxProps(node) {
234
+ const out = {};
235
+ for (const attr of node.attributes ?? []) {
236
+ if (attr.type !== "mdxJsxAttribute" || typeof attr.name !== "string") continue;
237
+ out[attr.name] = attr.value === null || attr.value === void 0 ? true : attr.value;
238
+ }
239
+ return out;
240
+ }
241
+ function render(node, key, components) {
242
+ const kids = (extra) => (extra ?? node.children ?? []).map((c, i) => render(c, i, components));
243
+ switch (node.type) {
244
+ case "root":
245
+ return /* @__PURE__ */ jsx8(Fragment2, { children: kids() }, key);
246
+ case "text":
247
+ return node.value ?? "";
248
+ case "paragraph":
249
+ return /* @__PURE__ */ jsx8("p", { children: kids() }, key);
250
+ case "heading": {
251
+ const depth = Math.min(Math.max(Number(node.depth) || 2, 1), 6);
252
+ const id = node.data?.id;
253
+ return createElement(`h${depth}`, { key, id, className: "scroll-mt-20" }, kids());
254
+ }
255
+ case "emphasis":
256
+ return /* @__PURE__ */ jsx8("em", { children: kids() }, key);
257
+ case "strong":
258
+ return /* @__PURE__ */ jsx8("strong", { children: kids() }, key);
259
+ case "delete":
260
+ return /* @__PURE__ */ jsx8("del", { children: kids() }, key);
261
+ case "inlineCode":
262
+ return /* @__PURE__ */ jsx8("code", { children: node.value ?? "" }, key);
263
+ case "code": {
264
+ const lang = typeof node.lang === "string" ? node.lang : void 0;
265
+ const meta = typeof node.meta === "string" ? node.meta.trim() : void 0;
266
+ return /* @__PURE__ */ jsx8("pre", { "data-language": lang, "data-title": meta || void 0, children: /* @__PURE__ */ jsx8("code", { className: lang ? `language-${lang}` : void 0, children: node.value ?? "" }) }, key);
267
+ }
268
+ case "blockquote":
269
+ return /* @__PURE__ */ jsx8("blockquote", { children: kids() }, key);
270
+ case "list":
271
+ return node.ordered ? /* @__PURE__ */ jsx8("ol", { start: typeof node.start === "number" ? node.start : void 0, children: kids() }, key) : /* @__PURE__ */ jsx8("ul", { children: kids() }, key);
272
+ case "listItem": {
273
+ const checked = node.checked;
274
+ const children = node.spread === true ? node.children ?? [] : (node.children ?? []).flatMap((c) => c.type === "paragraph" ? c.children ?? [] : [c]);
275
+ return /* @__PURE__ */ jsxs8("li", { className: checked != null ? "list-none" : void 0, children: [
276
+ checked != null && /* @__PURE__ */ jsx8("input", { type: "checkbox", checked: checked === true, readOnly: true, className: "mr-1.5 align-middle" }),
277
+ kids(children)
278
+ ] }, key);
279
+ }
280
+ case "thematicBreak":
281
+ return /* @__PURE__ */ jsx8("hr", {}, key);
282
+ case "break":
283
+ return /* @__PURE__ */ jsx8("br", {}, key);
284
+ case "link": {
285
+ const href = typeof node.url === "string" ? node.url : "#";
286
+ const external = /^https?:\/\//.test(href);
287
+ return /* @__PURE__ */ jsx8("a", { href, target: external ? "_blank" : void 0, rel: external ? "noreferrer" : void 0, children: kids() }, key);
288
+ }
289
+ case "image":
290
+ return /* @__PURE__ */ jsx8(
291
+ "img",
292
+ {
293
+ src: typeof node.url === "string" ? node.url : void 0,
294
+ alt: typeof node.alt === "string" ? node.alt : "",
295
+ title: typeof node.title === "string" ? node.title : void 0
296
+ },
297
+ key
298
+ );
299
+ case "table": {
300
+ const rows = node.children ?? [];
301
+ const align = node.align ?? [];
302
+ const [head, ...body] = rows;
303
+ const renderRow = (row, ri, header) => /* @__PURE__ */ jsx8("tr", { children: (row.children ?? []).map(
304
+ (cell, ci) => createElement(
305
+ header ? "th" : "td",
306
+ { key: ci, style: align[ci] ? { textAlign: align[ci] } : void 0 },
307
+ cell.children?.map((c, i) => render(c, i, components))
308
+ )
309
+ ) }, ri);
310
+ return /* @__PURE__ */ jsxs8("table", { children: [
311
+ head && /* @__PURE__ */ jsx8("thead", { children: renderRow(head, 0, true) }),
312
+ /* @__PURE__ */ jsx8("tbody", { children: body.map((r, i) => renderRow(r, i, false)) })
313
+ ] }, key);
314
+ }
315
+ case "mdxJsxFlowElement":
316
+ case "mdxJsxTextElement": {
317
+ const name = typeof node.name === "string" ? node.name : "";
318
+ const Component = components[name];
319
+ if (!Component) return /* @__PURE__ */ jsx8(Fragment2, { children: kids() }, key);
320
+ return /* @__PURE__ */ jsx8(Component, { ...jsxProps(node), children: node.children?.length ? kids() : void 0 }, key);
321
+ }
322
+ default:
323
+ return node.children ? /* @__PURE__ */ jsx8(Fragment2, { children: kids() }, key) : null;
324
+ }
325
+ }
326
+ function MdxContent({
327
+ ast,
328
+ components
329
+ }) {
330
+ const registry = components ? { ...defaultComponents, ...components } : defaultComponents;
331
+ return /* @__PURE__ */ jsx8(Fragment3, { children: render(ast, 0, registry) });
332
+ }
333
+ export {
334
+ Accordion,
335
+ AccordionGroup,
336
+ ApiOperation,
337
+ ApiSchema,
338
+ Card,
339
+ CardGroup,
340
+ CodeGroup,
341
+ DocsEmbedProvider,
342
+ Frame,
343
+ MdxContent,
344
+ Note,
345
+ Tab,
346
+ Tabs,
347
+ Tip,
348
+ Warning,
349
+ defaultComponents
350
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@reapi/docs-ui",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "AST→React renderer + preset components for the ReAPI docs dialect",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "lucide-react": "^0.469.0",
17
+ "@reapi/spec-ui": "0.1.0"
18
+ },
19
+ "peerDependencies": {
20
+ "react": "^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/mdast": "^4.0.4",
24
+ "@types/react": "^19.0.0",
25
+ "@types/react-dom": "^19.0.0",
26
+ "react": "^19.1.0",
27
+ "react-dom": "^19.1.0",
28
+ "tsup": "^8.4.0",
29
+ "typescript": "^5.7.3",
30
+ "vitest": "^4.1.8",
31
+ "@reapi/docs-compile": "0.1.0"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "vitest --run",
42
+ "build": "tsup"
43
+ }
44
+ }