@miethe/ui 0.3.0 → 0.6.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 +51 -0
- package/README.md +776 -9
- package/dist/components/content-viewer/ArticleViewer.d.ts +42 -0
- package/dist/components/content-viewer/ArticleViewer.d.ts.map +1 -0
- package/dist/components/content-viewer/ArticleViewer.js +321 -0
- package/dist/components/content-viewer/ArticleViewer.js.map +1 -0
- package/dist/components/content-viewer/FrontmatterHeader.d.ts +32 -0
- package/dist/components/content-viewer/FrontmatterHeader.d.ts.map +1 -0
- package/dist/components/content-viewer/FrontmatterHeader.js +95 -0
- package/dist/components/content-viewer/FrontmatterHeader.js.map +1 -0
- package/dist/components/content-viewer/callouts/Callout.d.ts +43 -0
- package/dist/components/content-viewer/callouts/Callout.d.ts.map +1 -0
- package/dist/components/content-viewer/callouts/Callout.js +86 -0
- package/dist/components/content-viewer/callouts/Callout.js.map +1 -0
- package/dist/components/content-viewer/callouts/index.d.ts +2 -0
- package/dist/components/content-viewer/callouts/index.d.ts.map +1 -0
- package/dist/components/content-viewer/callouts/index.js +2 -0
- package/dist/components/content-viewer/callouts/index.js.map +1 -0
- package/dist/components/content-viewer/index.d.ts +21 -0
- package/dist/components/content-viewer/index.d.ts.map +1 -0
- package/dist/components/content-viewer/index.js +29 -0
- package/dist/components/content-viewer/index.js.map +1 -0
- package/dist/components/content-viewer/plugins/index.d.ts +5 -0
- package/dist/components/content-viewer/plugins/index.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/index.js +5 -0
- package/dist/components/content-viewer/plugins/index.js.map +1 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.d.ts +63 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.js +120 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.d.ts +44 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.js +122 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.d.ts +59 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.js +79 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.d.ts +37 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.js +82 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.js.map +1 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.d.ts +39 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.js +77 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.js.map +1 -0
- package/dist/components/content-viewer/plugins/slugify.d.ts +24 -0
- package/dist/components/content-viewer/plugins/slugify.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/slugify.js +31 -0
- package/dist/components/content-viewer/plugins/slugify.js.map +1 -0
- package/dist/components/content-viewer/sanitize.d.ts +75 -0
- package/dist/components/content-viewer/sanitize.d.ts.map +1 -0
- package/dist/components/content-viewer/sanitize.js +252 -0
- package/dist/components/content-viewer/sanitize.js.map +1 -0
- package/dist/components/content-viewer/types.d.ts +315 -0
- package/dist/components/content-viewer/types.d.ts.map +1 -0
- package/dist/components/content-viewer/types.js +8 -0
- package/dist/components/content-viewer/types.js.map +1 -0
- package/dist/components/content-viewer/variants.d.ts +71 -0
- package/dist/components/content-viewer/variants.d.ts.map +1 -0
- package/dist/components/content-viewer/variants.js +105 -0
- package/dist/components/content-viewer/variants.js.map +1 -0
- package/dist/content-viewer/ContentPane.d.ts +44 -1
- package/dist/content-viewer/ContentPane.d.ts.map +1 -1
- package/dist/content-viewer/ContentPane.js +139 -5
- package/dist/content-viewer/ContentPane.js.map +1 -1
- package/dist/content-viewer/FileTree.d.ts +23 -1
- package/dist/content-viewer/FileTree.d.ts.map +1 -1
- package/dist/content-viewer/FileTree.js +20 -5
- package/dist/content-viewer/FileTree.js.map +1 -1
- package/dist/content-viewer/index.d.ts +2 -0
- package/dist/content-viewer/index.d.ts.map +1 -1
- package/dist/content-viewer/index.js +2 -0
- package/dist/content-viewer/index.js.map +1 -1
- package/dist/diff/DiffViewer.js +3 -3
- package/dist/diff/DiffViewer.js.map +1 -1
- package/dist/discovery/discovery-card.d.ts +25 -0
- package/dist/discovery/discovery-card.d.ts.map +1 -0
- package/dist/discovery/discovery-card.js +265 -0
- package/dist/discovery/discovery-card.js.map +1 -0
- package/dist/discovery/index.d.ts +3 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +3 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/display/ContextInfoCard.d.ts +61 -0
- package/dist/display/ContextInfoCard.d.ts.map +1 -0
- package/dist/display/ContextInfoCard.js +45 -0
- package/dist/display/ContextInfoCard.js.map +1 -0
- package/dist/display/index.d.ts +2 -0
- package/dist/display/index.d.ts.map +1 -1
- package/dist/display/index.js +1 -0
- package/dist/display/index.js.map +1 -1
- package/dist/editor/CodeEditor.d.ts +39 -0
- package/dist/editor/CodeEditor.d.ts.map +1 -0
- package/dist/editor/CodeEditor.js +114 -0
- package/dist/editor/CodeEditor.js.map +1 -0
- package/dist/editor/MarkdownEditor.d.ts +3 -2
- package/dist/editor/MarkdownEditor.d.ts.map +1 -1
- package/dist/editor/MarkdownEditor.js +32 -80
- package/dist/editor/MarkdownEditor.js.map +1 -1
- package/dist/editor/SplitPreview.d.ts +10 -1
- package/dist/editor/SplitPreview.d.ts.map +1 -1
- package/dist/editor/SplitPreview.js +4 -2
- package/dist/editor/SplitPreview.js.map +1 -1
- package/dist/editor/codeLanguages.d.ts +28 -0
- package/dist/editor/codeLanguages.d.ts.map +1 -0
- package/dist/editor/codeLanguages.js +54 -0
- package/dist/editor/codeLanguages.js.map +1 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +6 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/theme.d.ts +16 -0
- package/dist/editor/theme.d.ts.map +1 -0
- package/dist/editor/theme.js +82 -0
- package/dist/editor/theme.js.map +1 -0
- package/dist/filters/filter-bar.d.ts +14 -0
- package/dist/filters/filter-bar.d.ts.map +1 -0
- package/dist/filters/filter-bar.js +47 -0
- package/dist/filters/filter-bar.js.map +1 -0
- package/dist/filters/filter-slot-config.d.ts +239 -0
- package/dist/filters/filter-slot-config.d.ts.map +1 -0
- package/dist/filters/filter-slot-config.js +24 -0
- package/dist/filters/filter-slot-config.js.map +1 -0
- package/dist/filters/index.d.ts +2 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/filters/index.js +1 -0
- package/dist/filters/index.js.map +1 -1
- package/dist/primitives/Card.d.ts +28 -0
- package/dist/primitives/Card.d.ts.map +1 -0
- package/dist/primitives/Card.js +30 -0
- package/dist/primitives/Card.js.map +1 -0
- package/dist/primitives/CollectionPicker.d.ts +47 -0
- package/dist/primitives/CollectionPicker.d.ts.map +1 -0
- package/dist/primitives/CollectionPicker.js +105 -0
- package/dist/primitives/CollectionPicker.js.map +1 -0
- package/dist/primitives/CreateEntityDialog.d.ts +144 -0
- package/dist/primitives/CreateEntityDialog.d.ts.map +1 -0
- package/dist/primitives/CreateEntityDialog.js +379 -0
- package/dist/primitives/CreateEntityDialog.js.map +1 -0
- package/dist/primitives/FormField.d.ts +29 -0
- package/dist/primitives/FormField.d.ts.map +1 -0
- package/dist/primitives/FormField.js +27 -0
- package/dist/primitives/FormField.js.map +1 -0
- package/dist/primitives/Label.d.ts +20 -0
- package/dist/primitives/Label.d.ts.map +1 -0
- package/dist/primitives/Label.js +21 -0
- package/dist/primitives/Label.js.map +1 -0
- package/dist/primitives/SecretField.d.ts +28 -0
- package/dist/primitives/SecretField.d.ts.map +1 -0
- package/dist/primitives/SecretField.js +65 -0
- package/dist/primitives/SecretField.js.map +1 -0
- package/dist/primitives/Spinner.d.ts +16 -0
- package/dist/primitives/Spinner.d.ts.map +1 -0
- package/dist/primitives/Spinner.js +34 -0
- package/dist/primitives/Spinner.js.map +1 -0
- package/dist/primitives/Switch.d.ts +32 -0
- package/dist/primitives/Switch.d.ts.map +1 -0
- package/dist/primitives/Switch.js +43 -0
- package/dist/primitives/Switch.js.map +1 -0
- package/dist/primitives/index.d.ts +16 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +9 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/utils/type-colors.d.ts.map +1 -1
- package/dist/utils/type-colors.js +4 -0
- package/dist/utils/type-colors.js.map +1 -1
- package/package.json +40 -6
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ArticleViewerProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Detect whether content is markdown or HTML.
|
|
4
|
+
*
|
|
5
|
+
* Heuristic: content is treated as HTML if, after stripping optional YAML
|
|
6
|
+
* frontmatter (lines between `---` delimiters at the very start), the
|
|
7
|
+
* remaining text begins with an HTML opening tag.
|
|
8
|
+
*
|
|
9
|
+
* Examples that resolve to "html":
|
|
10
|
+
* `<p>Hello</p>`
|
|
11
|
+
* ` <div class="callout">...</div>`
|
|
12
|
+
* `---\ntitle: doc\n---\n<article>...</article>`
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectFormat(content: string): 'markdown' | 'html';
|
|
15
|
+
/**
|
|
16
|
+
* ArticleViewer renders a markdown or pre-compiled HTML string with full GFM
|
|
17
|
+
* support, callout directive handling, typography variants, and frontmatter
|
|
18
|
+
* display.
|
|
19
|
+
*
|
|
20
|
+
* **Markdown rendering pipeline**:
|
|
21
|
+
* 1. `gray-matter` — extracts YAML frontmatter; strips from body
|
|
22
|
+
* 2. `remark-gfm` — tables, task lists, strikethrough, autolinks
|
|
23
|
+
* 3. `remark-directive` — parses `:::type` container directives
|
|
24
|
+
* 4. `remarkCallouts` — converts container directives to `<callout-type>` elements
|
|
25
|
+
* 5. `ReactMarkdown` + `components` map — maps custom elements to React components
|
|
26
|
+
* (also applies external link hardening via the `a` component override)
|
|
27
|
+
*
|
|
28
|
+
* **HTML rendering pipeline** (`format="html"` or auto-detected):
|
|
29
|
+
* 1. When `sanitize={true}` (default): `sanitizeHtml()` runs the content through
|
|
30
|
+
* `rehypeParse → rehypeExternalLinks → rehypeSanitize → rehypeStringify`
|
|
31
|
+
* 2. When `sanitize={false}`: content is rendered as-is via `dangerouslySetInnerHTML`
|
|
32
|
+
* (only safe for fully trusted, engine-compiled HTML)
|
|
33
|
+
*
|
|
34
|
+
* **Variant**: a CSS class is applied based on `variant` prop; consumer defines
|
|
35
|
+
* `--cv-{variant}-*` variables at document root for theming.
|
|
36
|
+
*
|
|
37
|
+
* **Accessibility**: external links get `rel="noopener noreferrer"` on both paths.
|
|
38
|
+
* Error boundary catches pipeline failures and calls `onError`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ArticleViewer({ content, format, variant, frontmatter: frontmatterMode, components: componentOverrides, calloutComponent, sanitize: sanitizeProp, useDOMPurify, codeHighlight, generateHeadingIds, isLoading, error, onError, className, }: ArticleViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
export default ArticleViewer;
|
|
42
|
+
//# sourceMappingURL=ArticleViewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArticleViewer.d.ts","sourceRoot":"","sources":["../../../src/components/content-viewer/ArticleViewer.tsx"],"names":[],"mappings":"AA6EA,OAAO,KAAK,EACV,kBAAkB,EAGnB,MAAM,SAAS,CAAC;AAqIjB;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAejE;AA+GD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,MAAe,EACf,OAAO,EACP,WAAW,EAAE,eAAwB,EACrC,UAAU,EAAE,kBAAkB,EAC9B,gBAAgB,EAChB,QAAQ,EAAE,YAAY,EACtB,YAAoB,EACpB,aAAqB,EACrB,kBAAyB,EACzB,SAAiB,EACjB,KAAK,EACL,OAAO,EACP,SAAS,GACV,EAAE,kBAAkB,2CAkHpB;AAED,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* ArticleViewer — read-only markdown/HTML rendering component (PU1–PU4)
|
|
5
|
+
*
|
|
6
|
+
* Renders a markdown string through the full remark pipeline:
|
|
7
|
+
* - CommonMark (base markdown)
|
|
8
|
+
* - GitHub Flavored Markdown via remark-gfm (tables, task lists, strikethrough)
|
|
9
|
+
* - Callout directives via remark-directive + custom remarkCallouts plugin
|
|
10
|
+
*
|
|
11
|
+
* Also accepts pre-compiled HTML strings (`format="html"`) for Portal artifacts.
|
|
12
|
+
* HTML input is sanitized by default via `rehype-sanitize@6` (PU3-02).
|
|
13
|
+
*
|
|
14
|
+
* YAML frontmatter is extracted via `gray-matter` (PU2-01). It is stripped
|
|
15
|
+
* from the body before rendering and optionally displayed as a collapsible
|
|
16
|
+
* header via the `frontmatter` prop (PU2-03).
|
|
17
|
+
*
|
|
18
|
+
* Typography variants are applied as CSS classes (`cv-variant-{name}`) that
|
|
19
|
+
* read CSS custom properties from document root (PU2-05). See the
|
|
20
|
+
* CSS-Variable Contract section in the content-viewer README.
|
|
21
|
+
*
|
|
22
|
+
* External links are hardened with `target="_blank" rel="noopener noreferrer"`
|
|
23
|
+
* on both markdown and HTML paths (PU3-04).
|
|
24
|
+
*
|
|
25
|
+
* Callout components and FrontmatterHeader can be overridden per-type via the
|
|
26
|
+
* `components` prop, allowing Portal and other consumers to inject their own
|
|
27
|
+
* renderers without forking the component.
|
|
28
|
+
*
|
|
29
|
+
* Phase 4 additions:
|
|
30
|
+
* - `codeHighlight` (default false) — opt-in syntax highlighting via lowlight
|
|
31
|
+
* - `generateHeadingIds` (default true) — auto-generates anchor IDs on headings
|
|
32
|
+
* - `isLoading` — shows accessible skeleton placeholder; suppresses content render
|
|
33
|
+
* - `error` — shows accessible error message; takes priority over normal render
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // Basic usage
|
|
38
|
+
* <ArticleViewer content={markdownString} />
|
|
39
|
+
*
|
|
40
|
+
* // Loading skeleton
|
|
41
|
+
* <ArticleViewer content="" isLoading />
|
|
42
|
+
*
|
|
43
|
+
* // Error state
|
|
44
|
+
* <ArticleViewer content="" error="Failed to load document" />
|
|
45
|
+
*
|
|
46
|
+
* // With code highlighting + variant + frontmatter display
|
|
47
|
+
* <ArticleViewer
|
|
48
|
+
* content={markdownWithFrontmatter}
|
|
49
|
+
* variant="editorial"
|
|
50
|
+
* frontmatter="show"
|
|
51
|
+
* codeHighlight
|
|
52
|
+
* />
|
|
53
|
+
*
|
|
54
|
+
* // Pre-compiled HTML (sanitized by default)
|
|
55
|
+
* <ArticleViewer content={htmlString} format="html" />
|
|
56
|
+
*
|
|
57
|
+
* // Trusted engine output — skip sanitization (Portal only)
|
|
58
|
+
* <ArticleViewer content={trustedHtml} format="html" sanitize={false} />
|
|
59
|
+
*
|
|
60
|
+
* // With callout + header overrides
|
|
61
|
+
* <ArticleViewer
|
|
62
|
+
* content={markdownString}
|
|
63
|
+
* components={{
|
|
64
|
+
* warning: MyWarningBanner,
|
|
65
|
+
* FrontmatterHeader: MyFrontmatterHeader,
|
|
66
|
+
* }}
|
|
67
|
+
* />
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
import { Component, useMemo } from 'react';
|
|
71
|
+
import ReactMarkdown from 'react-markdown';
|
|
72
|
+
import remarkGfm from 'remark-gfm';
|
|
73
|
+
import remarkDirective from 'remark-directive';
|
|
74
|
+
import matter from 'gray-matter';
|
|
75
|
+
import { cn } from '../../primitives/utils';
|
|
76
|
+
import { remarkCallouts as _remarkCallouts, createHighlightPlugin, createHeadingIdsPlugin, } from './plugins';
|
|
77
|
+
import { variantClass } from './variants';
|
|
78
|
+
import { FrontmatterHeader as DefaultFrontmatterHeader } from './FrontmatterHeader';
|
|
79
|
+
import { sanitizeHtml } from './sanitize';
|
|
80
|
+
// Cast avoids unified vfile version mismatch between remark-directive@2 and react-markdown@9
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
const remarkCallouts = _remarkCallouts;
|
|
83
|
+
import { NoteCallout, ReferenceCallout, WarningCallout, InfoCallout, } from './callouts';
|
|
84
|
+
/**
|
|
85
|
+
* Error boundary that catches rendering errors from the remark pipeline
|
|
86
|
+
* and reports them via the `onError` callback.
|
|
87
|
+
*/
|
|
88
|
+
class ArticleViewerErrorBoundary extends Component {
|
|
89
|
+
constructor(props) {
|
|
90
|
+
super(props);
|
|
91
|
+
this.state = { hasError: false, error: null };
|
|
92
|
+
}
|
|
93
|
+
static getDerivedStateFromError(error) {
|
|
94
|
+
return { hasError: true, error };
|
|
95
|
+
}
|
|
96
|
+
componentDidCatch(error, _errorInfo) {
|
|
97
|
+
this.props.onError?.(error);
|
|
98
|
+
}
|
|
99
|
+
render() {
|
|
100
|
+
if (this.state.hasError) {
|
|
101
|
+
return (_jsxs("div", { role: "alert", className: "rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive", children: [_jsx("p", { className: "font-semibold", children: "Failed to render content" }), this.state.error && (_jsx("p", { className: "mt-1 font-mono text-xs opacity-75", children: this.state.error.message }))] }));
|
|
102
|
+
}
|
|
103
|
+
return this.props.children;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Loading skeleton (PU4-02)
|
|
108
|
+
// ============================================================================
|
|
109
|
+
/**
|
|
110
|
+
* Accessible skeleton placeholder rendered when `isLoading=true`.
|
|
111
|
+
* Uses Tailwind `animate-pulse` for the shimmer effect.
|
|
112
|
+
* Suppresses content rendering while active.
|
|
113
|
+
*/
|
|
114
|
+
function ArticleViewerSkeleton() {
|
|
115
|
+
return (_jsxs("div", { role: "status", "aria-busy": "true", "aria-label": "Loading content", className: "article-viewer-skeleton space-y-3", children: [_jsx("div", { className: "h-6 w-2/3 rounded bg-muted animate-pulse" }), _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "h-4 w-full rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-4 w-full rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-4 w-5/6 rounded bg-muted animate-pulse" })] }), _jsxs("div", { className: "space-y-2 pt-2", children: [_jsx("div", { className: "h-4 w-full rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-4 w-4/5 rounded bg-muted animate-pulse" })] }), _jsx("span", { className: "sr-only", children: "Loading article content, please wait." })] }));
|
|
116
|
+
}
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Error display (PU4-03)
|
|
119
|
+
// ============================================================================
|
|
120
|
+
/**
|
|
121
|
+
* Accessible error message rendered when the `error` prop is set.
|
|
122
|
+
* Takes priority over normal render and boundary-caught errors.
|
|
123
|
+
*/
|
|
124
|
+
function ArticleViewerError({ error }) {
|
|
125
|
+
const message = error instanceof Error ? error.message : error;
|
|
126
|
+
return (_jsxs("div", { role: "alert", "aria-live": "assertive", className: "article-viewer-error rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive", children: [_jsx("p", { className: "font-semibold", children: "Failed to load content" }), message && (_jsx("p", { className: "mt-1 font-mono text-xs opacity-75", children: message }))] }));
|
|
127
|
+
}
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Format detection
|
|
130
|
+
// ============================================================================
|
|
131
|
+
/**
|
|
132
|
+
* Detect whether content is markdown or HTML.
|
|
133
|
+
*
|
|
134
|
+
* Heuristic: content is treated as HTML if, after stripping optional YAML
|
|
135
|
+
* frontmatter (lines between `---` delimiters at the very start), the
|
|
136
|
+
* remaining text begins with an HTML opening tag.
|
|
137
|
+
*
|
|
138
|
+
* Examples that resolve to "html":
|
|
139
|
+
* `<p>Hello</p>`
|
|
140
|
+
* ` <div class="callout">...</div>`
|
|
141
|
+
* `---\ntitle: doc\n---\n<article>...</article>`
|
|
142
|
+
*/
|
|
143
|
+
export function detectFormat(content) {
|
|
144
|
+
let trimmed = content.trimStart();
|
|
145
|
+
// Strip YAML frontmatter before detection so `---\ntitle: x\n---\n<p>` → html
|
|
146
|
+
if (trimmed.startsWith('---')) {
|
|
147
|
+
const endFm = trimmed.indexOf('\n---', 3);
|
|
148
|
+
if (endFm !== -1) {
|
|
149
|
+
trimmed = trimmed.slice(endFm + 4).trimStart();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (trimmed.startsWith('<') && /^<[a-zA-Z]/.test(trimmed)) {
|
|
153
|
+
return 'html';
|
|
154
|
+
}
|
|
155
|
+
return 'markdown';
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extract YAML frontmatter from a markdown string using `gray-matter`.
|
|
159
|
+
*
|
|
160
|
+
* - If no frontmatter is present, returns the original string as body.
|
|
161
|
+
* - If frontmatter is malformed, logs a warning and returns the original string.
|
|
162
|
+
* - Never throws; calling code always receives a valid `ParsedContent`.
|
|
163
|
+
*/
|
|
164
|
+
function parseMarkdownFrontmatter(content) {
|
|
165
|
+
try {
|
|
166
|
+
const result = matter(content);
|
|
167
|
+
return {
|
|
168
|
+
body: result.content,
|
|
169
|
+
frontmatterData: result.data,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
// Malformed YAML — warn and fall through to render body as-is
|
|
174
|
+
console.warn('[ArticleViewer] Failed to parse frontmatter:', err);
|
|
175
|
+
return { body: content, frontmatterData: {} };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// Callout component builder
|
|
180
|
+
// ============================================================================
|
|
181
|
+
/** The 4 callout types we handle */
|
|
182
|
+
const CALLOUT_TYPES = ['note', 'reference', 'warning', 'info'];
|
|
183
|
+
/** Default callout component map */
|
|
184
|
+
const DEFAULT_CALLOUT_COMPONENTS = {
|
|
185
|
+
note: NoteCallout,
|
|
186
|
+
reference: ReferenceCallout,
|
|
187
|
+
warning: WarningCallout,
|
|
188
|
+
info: InfoCallout,
|
|
189
|
+
};
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// ReactMarkdown component map
|
|
192
|
+
// ============================================================================
|
|
193
|
+
/**
|
|
194
|
+
* Build the `components` map for ReactMarkdown.
|
|
195
|
+
* Maps custom element names (e.g. `callout-note`) to the appropriate callout component.
|
|
196
|
+
* Also wires up link hardening for external URLs.
|
|
197
|
+
*/
|
|
198
|
+
function buildComponentMap(overrides, legacyFallback) {
|
|
199
|
+
const map = {};
|
|
200
|
+
// Wire callout elements: `callout-note`, `callout-reference`, etc.
|
|
201
|
+
for (const calloutType of CALLOUT_TYPES) {
|
|
202
|
+
const elementName = `callout-${calloutType}`;
|
|
203
|
+
// Priority: `components[type]` > `calloutComponent` (legacy) > default
|
|
204
|
+
const CalloutComponent = overrides?.[calloutType] ??
|
|
205
|
+
legacyFallback ??
|
|
206
|
+
DEFAULT_CALLOUT_COMPONENTS[calloutType];
|
|
207
|
+
// ReactMarkdown passes all hast element props; we forward children + className
|
|
208
|
+
const Wrapper = ({ children, className }) => (_jsx(CalloutComponent, { type: calloutType, className: className ?? undefined, children: children }));
|
|
209
|
+
Wrapper.displayName = `CalloutWrapper(${calloutType})`;
|
|
210
|
+
map[elementName] = Wrapper;
|
|
211
|
+
}
|
|
212
|
+
// External link hardening (A-UCV-06)
|
|
213
|
+
const LinkComponent = ({ href, children, ...rest }) => {
|
|
214
|
+
const isExternal = typeof href === 'string' && (href.startsWith('http://') || href.startsWith('https://'));
|
|
215
|
+
return (_jsx("a", { href: href, ...(isExternal
|
|
216
|
+
? { target: '_blank', rel: 'noopener noreferrer' }
|
|
217
|
+
: {}), ...rest, children: children }));
|
|
218
|
+
};
|
|
219
|
+
LinkComponent.displayName = 'ArticleViewerLink';
|
|
220
|
+
map['a'] = LinkComponent;
|
|
221
|
+
return map;
|
|
222
|
+
}
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// ArticleViewer
|
|
225
|
+
// ============================================================================
|
|
226
|
+
/**
|
|
227
|
+
* ArticleViewer renders a markdown or pre-compiled HTML string with full GFM
|
|
228
|
+
* support, callout directive handling, typography variants, and frontmatter
|
|
229
|
+
* display.
|
|
230
|
+
*
|
|
231
|
+
* **Markdown rendering pipeline**:
|
|
232
|
+
* 1. `gray-matter` — extracts YAML frontmatter; strips from body
|
|
233
|
+
* 2. `remark-gfm` — tables, task lists, strikethrough, autolinks
|
|
234
|
+
* 3. `remark-directive` — parses `:::type` container directives
|
|
235
|
+
* 4. `remarkCallouts` — converts container directives to `<callout-type>` elements
|
|
236
|
+
* 5. `ReactMarkdown` + `components` map — maps custom elements to React components
|
|
237
|
+
* (also applies external link hardening via the `a` component override)
|
|
238
|
+
*
|
|
239
|
+
* **HTML rendering pipeline** (`format="html"` or auto-detected):
|
|
240
|
+
* 1. When `sanitize={true}` (default): `sanitizeHtml()` runs the content through
|
|
241
|
+
* `rehypeParse → rehypeExternalLinks → rehypeSanitize → rehypeStringify`
|
|
242
|
+
* 2. When `sanitize={false}`: content is rendered as-is via `dangerouslySetInnerHTML`
|
|
243
|
+
* (only safe for fully trusted, engine-compiled HTML)
|
|
244
|
+
*
|
|
245
|
+
* **Variant**: a CSS class is applied based on `variant` prop; consumer defines
|
|
246
|
+
* `--cv-{variant}-*` variables at document root for theming.
|
|
247
|
+
*
|
|
248
|
+
* **Accessibility**: external links get `rel="noopener noreferrer"` on both paths.
|
|
249
|
+
* Error boundary catches pipeline failures and calls `onError`.
|
|
250
|
+
*/
|
|
251
|
+
export function ArticleViewer({ content, format = 'auto', variant, frontmatter: frontmatterMode = 'hide', components: componentOverrides, calloutComponent, sanitize: sanitizeProp, useDOMPurify = false, codeHighlight = false, generateHeadingIds = true, isLoading = false, error, onError, className, }) {
|
|
252
|
+
// All hooks must be called unconditionally (Rules of Hooks).
|
|
253
|
+
// Loading/error early returns happen AFTER all hooks.
|
|
254
|
+
const resolvedFormat = useMemo(() => {
|
|
255
|
+
if (format === 'auto')
|
|
256
|
+
return detectFormat(content);
|
|
257
|
+
return format;
|
|
258
|
+
}, [content, format]);
|
|
259
|
+
// PU2-01: Parse frontmatter out of markdown content
|
|
260
|
+
const { body, frontmatterData } = useMemo(() => {
|
|
261
|
+
// Only parse frontmatter from markdown (HTML passthrough has no frontmatter)
|
|
262
|
+
if (resolvedFormat === 'html') {
|
|
263
|
+
return { body: content, frontmatterData: {} };
|
|
264
|
+
}
|
|
265
|
+
return parseMarkdownFrontmatter(content);
|
|
266
|
+
}, [content, resolvedFormat]);
|
|
267
|
+
// PU3-02: Resolve sanitize flag.
|
|
268
|
+
// Default: ON for HTML input, OFF for markdown (remark never emits raw HTML).
|
|
269
|
+
const shouldSanitize = sanitizeProp !== undefined
|
|
270
|
+
? sanitizeProp
|
|
271
|
+
: resolvedFormat === 'html';
|
|
272
|
+
// PU3-02 / PU3-03: Sanitize HTML when required (synchronous via rehype or warm DOMPurify cache)
|
|
273
|
+
const htmlContent = useMemo(() => {
|
|
274
|
+
if (resolvedFormat !== 'html')
|
|
275
|
+
return '';
|
|
276
|
+
if (!shouldSanitize)
|
|
277
|
+
return content;
|
|
278
|
+
return sanitizeHtml(content, { useDOMPurify });
|
|
279
|
+
}, [content, resolvedFormat, shouldSanitize, useDOMPurify]);
|
|
280
|
+
const componentMap = useMemo(() => buildComponentMap(componentOverrides, calloutComponent), [componentOverrides, calloutComponent]);
|
|
281
|
+
// PU4-01: Build rehype plugin list (opt-in code highlighting + heading IDs)
|
|
282
|
+
// createHighlightPlugin() and createHeadingIdsPlugin() both create fresh instances
|
|
283
|
+
// per render to prevent cross-render state contamination.
|
|
284
|
+
const rehypePlugins = useMemo(() => {
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
286
|
+
const plugins = [];
|
|
287
|
+
if (generateHeadingIds) {
|
|
288
|
+
plugins.push(createHeadingIdsPlugin());
|
|
289
|
+
}
|
|
290
|
+
if (codeHighlight) {
|
|
291
|
+
plugins.push(createHighlightPlugin());
|
|
292
|
+
}
|
|
293
|
+
return plugins;
|
|
294
|
+
}, [codeHighlight, generateHeadingIds]);
|
|
295
|
+
// PU2-05: Resolve variant class
|
|
296
|
+
const variantClassName = variantClass(variant);
|
|
297
|
+
// PU2-03: Resolve frontmatter header visibility
|
|
298
|
+
const hasFrontmatter = Object.keys(frontmatterData).length > 0;
|
|
299
|
+
const showHeader = hasFrontmatter && frontmatterMode !== 'hide';
|
|
300
|
+
const headerCollapsed = frontmatterMode === 'collapse';
|
|
301
|
+
// PU2-06: Resolve FrontmatterHeader component (override or default)
|
|
302
|
+
const FrontmatterHeaderComponent = componentOverrides?.FrontmatterHeader ?? DefaultFrontmatterHeader;
|
|
303
|
+
// PU4-02: Loading state — suppress all content, show skeleton
|
|
304
|
+
if (isLoading) {
|
|
305
|
+
return _jsx(ArticleViewerSkeleton, {});
|
|
306
|
+
}
|
|
307
|
+
// PU4-03: Error prop — show accessible error message
|
|
308
|
+
if (error != null) {
|
|
309
|
+
return _jsx(ArticleViewerError, { error: error });
|
|
310
|
+
}
|
|
311
|
+
return (_jsx(ArticleViewerErrorBoundary, { onError: onError, children: _jsxs("div", { className: cn('article-viewer prose prose-sm max-w-none', 'dark:prose-invert', variantClassName, className), "data-format": resolvedFormat, "data-variant": variant, "data-sanitized": resolvedFormat === 'html' ? String(shouldSanitize) : undefined, "data-code-highlight": codeHighlight ? 'true' : undefined, children: [showHeader && (_jsx(FrontmatterHeaderComponent, { frontmatter: frontmatterData, isCollapsed: headerCollapsed })), resolvedFormat === 'html' ? (
|
|
312
|
+
// HTML path (PU3-01): render sanitized (or trusted) HTML string
|
|
313
|
+
// dangerouslySetInnerHTML is acceptable here because:
|
|
314
|
+
// - When sanitize=true: content has been cleaned by rehype-sanitize / DOMPurify
|
|
315
|
+
// - When sanitize=false: caller explicitly asserts content is trusted
|
|
316
|
+
_jsx("div", { dangerouslySetInnerHTML: { __html: htmlContent } })) : (
|
|
317
|
+
// Markdown pipeline — use body with frontmatter stripped
|
|
318
|
+
_jsx(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkDirective, remarkCallouts], rehypePlugins: rehypePlugins.length > 0 ? rehypePlugins : undefined, components: componentMap, children: body }))] }) }));
|
|
319
|
+
}
|
|
320
|
+
export default ArticleViewer;
|
|
321
|
+
//# sourceMappingURL=ArticleViewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArticleViewer.js","sourceRoot":"","sources":["../../../src/components/content-viewer/ArticleViewer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FrontmatterHeader — collapsible YAML frontmatter display (PU2-02)
|
|
3
|
+
*
|
|
4
|
+
* Renders parsed YAML frontmatter as a compact key-value table above
|
|
5
|
+
* article content. Supports 1-level object nesting; deeper values are
|
|
6
|
+
* stringified as JSON. Arrays are rendered as comma-separated values.
|
|
7
|
+
*
|
|
8
|
+
* The collapsed state can be controlled externally via `isCollapsed` /
|
|
9
|
+
* `onToggleCollapse`, or managed internally when those props are omitted.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <FrontmatterHeader
|
|
14
|
+
* frontmatter={{ title: 'My Doc', tags: ['react', 'ui'] }}
|
|
15
|
+
* isCollapsed={false}
|
|
16
|
+
* onToggleCollapse={(c) => setCollapsed(c)}
|
|
17
|
+
* />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import React from 'react';
|
|
21
|
+
import type { FrontmatterHeaderProps } from './types';
|
|
22
|
+
/**
|
|
23
|
+
* Collapsible header that displays YAML frontmatter above article content.
|
|
24
|
+
*
|
|
25
|
+
* When neither `isCollapsed` nor `onToggleCollapse` are provided, the
|
|
26
|
+
* component manages its own internal open/closed state.
|
|
27
|
+
*
|
|
28
|
+
* If `frontmatter` is empty (`{}`), the component renders nothing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function FrontmatterHeader({ frontmatter, isCollapsed, onToggleCollapse, className, }: FrontmatterHeaderProps): React.ReactElement | null;
|
|
31
|
+
export default FrontmatterHeader;
|
|
32
|
+
//# sourceMappingURL=FrontmatterHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FrontmatterHeader.d.ts","sourceRoot":"","sources":["../../../src/components/content-viewer/FrontmatterHeader.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAkEtD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,SAAS,GACV,EAAE,sBAAsB,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAmFpD;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* FrontmatterHeader — collapsible YAML frontmatter display (PU2-02)
|
|
5
|
+
*
|
|
6
|
+
* Renders parsed YAML frontmatter as a compact key-value table above
|
|
7
|
+
* article content. Supports 1-level object nesting; deeper values are
|
|
8
|
+
* stringified as JSON. Arrays are rendered as comma-separated values.
|
|
9
|
+
*
|
|
10
|
+
* The collapsed state can be controlled externally via `isCollapsed` /
|
|
11
|
+
* `onToggleCollapse`, or managed internally when those props are omitted.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <FrontmatterHeader
|
|
16
|
+
* frontmatter={{ title: 'My Doc', tags: ['react', 'ui'] }}
|
|
17
|
+
* isCollapsed={false}
|
|
18
|
+
* onToggleCollapse={(c) => setCollapsed(c)}
|
|
19
|
+
* />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { useState } from 'react';
|
|
23
|
+
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
24
|
+
import { cn } from '../../primitives/utils';
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Value renderer (1-level nesting)
|
|
27
|
+
// ============================================================================
|
|
28
|
+
/**
|
|
29
|
+
* Render a frontmatter value as a React node.
|
|
30
|
+
* - Primitives: rendered inline
|
|
31
|
+
* - Arrays: comma-separated list
|
|
32
|
+
* - Objects (1-level): indented key-value rows
|
|
33
|
+
* - Deeper nesting: JSON stringified
|
|
34
|
+
*/
|
|
35
|
+
function renderValue(value) {
|
|
36
|
+
if (value === null || value === undefined) {
|
|
37
|
+
return (_jsx("span", { className: "italic text-muted-foreground", "aria-label": "null value", children: "null" }));
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === 'boolean') {
|
|
40
|
+
return _jsx("span", { className: "text-muted-foreground", children: value ? 'true' : 'false' });
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'number') {
|
|
43
|
+
return _jsx("span", { children: value });
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'string') {
|
|
46
|
+
return _jsx("span", { children: value });
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
const stringValues = value.map((item) => {
|
|
50
|
+
if (typeof item === 'object' && item !== null) {
|
|
51
|
+
return JSON.stringify(item);
|
|
52
|
+
}
|
|
53
|
+
return String(item);
|
|
54
|
+
});
|
|
55
|
+
return _jsx("span", { children: stringValues.join(', ') });
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === 'object') {
|
|
58
|
+
// 1-level nesting: render as indented rows
|
|
59
|
+
return (_jsx("div", { className: "ml-4 mt-1 space-y-0.5", children: Object.entries(value).map(([k, v]) => (_jsxs("div", { className: "text-xs", children: [_jsx("span", { className: "font-medium text-muted-foreground", children: k }), ': ', typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v ?? '')] }, k))) }));
|
|
60
|
+
}
|
|
61
|
+
return _jsx("span", { children: String(value) });
|
|
62
|
+
}
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// FrontmatterHeader component
|
|
65
|
+
// ============================================================================
|
|
66
|
+
/**
|
|
67
|
+
* Collapsible header that displays YAML frontmatter above article content.
|
|
68
|
+
*
|
|
69
|
+
* When neither `isCollapsed` nor `onToggleCollapse` are provided, the
|
|
70
|
+
* component manages its own internal open/closed state.
|
|
71
|
+
*
|
|
72
|
+
* If `frontmatter` is empty (`{}`), the component renders nothing.
|
|
73
|
+
*/
|
|
74
|
+
export function FrontmatterHeader({ frontmatter, isCollapsed, onToggleCollapse, className, }) {
|
|
75
|
+
const entries = Object.entries(frontmatter);
|
|
76
|
+
// Internal state — used only when the caller does not control collapsed state
|
|
77
|
+
const [internalCollapsed, setInternalCollapsed] = useState(isCollapsed ?? false);
|
|
78
|
+
// Derive the effective collapsed state
|
|
79
|
+
const isControlled = isCollapsed !== undefined;
|
|
80
|
+
const effectiveCollapsed = isControlled ? isCollapsed : internalCollapsed;
|
|
81
|
+
const handleToggle = () => {
|
|
82
|
+
const next = !effectiveCollapsed;
|
|
83
|
+
if (!isControlled) {
|
|
84
|
+
setInternalCollapsed(next);
|
|
85
|
+
}
|
|
86
|
+
onToggleCollapse?.(next);
|
|
87
|
+
};
|
|
88
|
+
// Nothing to show
|
|
89
|
+
if (entries.length === 0) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return (_jsxs("div", { className: cn('mb-4 rounded-md border border-border bg-muted/30', className), "data-testid": "frontmatter-header", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-2", children: [_jsx("h4", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: "Frontmatter" }), _jsx("button", { type: "button", onClick: handleToggle, "aria-expanded": !effectiveCollapsed, "aria-controls": "frontmatter-header-content", className: cn('inline-flex items-center gap-1 rounded px-2 py-0.5', 'text-xs text-muted-foreground', 'hover:bg-accent hover:text-accent-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'transition-colors'), children: effectiveCollapsed ? (_jsxs(_Fragment, { children: [_jsx(ChevronDown, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), _jsx("span", { children: "Show" })] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronUp, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), _jsx("span", { children: "Hide" })] })) })] }), !effectiveCollapsed && (_jsx("div", { id: "frontmatter-header-content", className: "max-h-64 overflow-y-auto border-t border-border px-3 py-2", children: _jsx("dl", { className: "space-y-1.5", children: entries.map(([key, value]) => (_jsxs("div", { className: "flex flex-wrap items-baseline gap-x-1.5 text-sm", children: [_jsx("dt", { className: "shrink-0 font-medium text-foreground", children: key }), _jsx("dd", { className: "min-w-0 break-words text-muted-foreground", children: renderValue(value) })] }, key))) }) }))] }));
|
|
93
|
+
}
|
|
94
|
+
export default FrontmatterHeader;
|
|
95
|
+
//# sourceMappingURL=FrontmatterHeader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FrontmatterHeader.js","sourceRoot":"","sources":["../../../src/components/content-viewer/FrontmatterHeader.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Callout component and default callout variants.
|
|
3
|
+
*
|
|
4
|
+
* Each variant renders with:
|
|
5
|
+
* - A left accent border (type-specific color via Tailwind + CSS variable hooks)
|
|
6
|
+
* - A type label / icon prefix
|
|
7
|
+
* - Semantic HTML structure
|
|
8
|
+
* - `children` forwarded as-is (ReactMarkdown passes rendered JSX)
|
|
9
|
+
*
|
|
10
|
+
* CSS variable hooks (applied in Phase 2):
|
|
11
|
+
* - `--callout-note-border` / `--callout-note-bg`
|
|
12
|
+
* - `--callout-reference-border` / `--callout-reference-bg`
|
|
13
|
+
* - `--callout-warning-border` / `--callout-warning-bg`
|
|
14
|
+
* - `--callout-info-border` / `--callout-info-bg`
|
|
15
|
+
*/
|
|
16
|
+
import type { CalloutProps } from '../types';
|
|
17
|
+
/**
|
|
18
|
+
* Base callout renderer used by all type-specific variants.
|
|
19
|
+
* Accepts an explicit `type` prop for cases where the component is used
|
|
20
|
+
* without a type-specific wrapper.
|
|
21
|
+
*/
|
|
22
|
+
export declare function Callout({ type, children, className }: CalloutProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
/**
|
|
24
|
+
* Note callout — informational content that supplements the main text.
|
|
25
|
+
* @example `::: note\n...\n:::`
|
|
26
|
+
*/
|
|
27
|
+
export declare function NoteCallout({ children, className }: Omit<CalloutProps, 'type'>): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* Reference callout — citations, sources, or cross-references.
|
|
30
|
+
* @example `::: reference\n...\n:::`
|
|
31
|
+
*/
|
|
32
|
+
export declare function ReferenceCallout({ children, className }: Omit<CalloutProps, 'type'>): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
/**
|
|
34
|
+
* Warning callout — potentially destructive or dangerous information.
|
|
35
|
+
* @example `::: warning\n...\n:::`
|
|
36
|
+
*/
|
|
37
|
+
export declare function WarningCallout({ children, className }: Omit<CalloutProps, 'type'>): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
/**
|
|
39
|
+
* Info callout — supplementary information or tips.
|
|
40
|
+
* @example `::: info\n...\n:::`
|
|
41
|
+
*/
|
|
42
|
+
export declare function InfoCallout({ children, className }: Omit<CalloutProps, 'type'>): import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
//# sourceMappingURL=Callout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Callout.d.ts","sourceRoot":"","sources":["../../../../src/components/content-viewer/callouts/Callout.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,UAAU,CAAC;AAuD1D;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,YAAY,2CAqClE;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,2CAM9E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,2CAMnF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,2CAMjF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,2CAM9E"}
|