Skip to content

Commit 57424b3

Browse files
authored
Merge pull request #19 from objectql/copilot/add-markdown-component
2 parents 16b44d4 + 4ebe6c1 commit 57424b3

8 files changed

Lines changed: 895 additions & 1 deletion

File tree

examples/prototype/src/App.tsx

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,76 @@ const schema = {
365365
{
366366
value: 'analytics',
367367
label: 'Analytics',
368-
body: { type: 'text', content: 'Analytics Content' }
368+
body: {
369+
type: 'div',
370+
className: 'grid gap-6',
371+
body: [
372+
{
373+
type: 'card',
374+
className: 'shadow-sm',
375+
title: 'Markdown Component Demo',
376+
description: 'A fully-featured markdown renderer with GitHub Flavored Markdown support.',
377+
body: {
378+
type: 'div',
379+
className: 'p-6 pt-0',
380+
body: {
381+
type: 'markdown',
382+
content: `# Markdown Component
383+
384+
This is a new **Markdown** component for Object UI! It supports:
385+
386+
## Features
387+
388+
- **Bold** and *italic* text
389+
- [Links](https://objectui.org)
390+
- \`Inline code\`
391+
- Code blocks with syntax highlighting
392+
393+
\`\`\`javascript
394+
function hello() {
395+
console.log("Hello, Object UI!");
396+
}
397+
\`\`\`
398+
399+
## Lists
400+
401+
### Unordered Lists
402+
- Item 1
403+
- Item 2
404+
- Nested item 2.1
405+
- Nested item 2.2
406+
- Item 3
407+
408+
### Ordered Lists
409+
1. First item
410+
2. Second item
411+
3. Third item
412+
413+
## Tables (GitHub Flavored Markdown)
414+
415+
| Feature | Status |
416+
|---------|--------|
417+
| Headers | ✅ |
418+
| Lists | ✅ |
419+
| Tables | ✅ |
420+
| Code | ✅ |
421+
422+
## Blockquotes
423+
424+
> This is a blockquote. You can use it to highlight important information or quotes.
425+
426+
## Images
427+
428+
![Object UI Logo](/logo.svg)
429+
430+
---
431+
432+
*This markdown is rendered using react-markdown with remark-gfm support!*`
433+
}
434+
}
435+
}
436+
]
437+
}
369438
},
370439
{
371440
value: 'reports',

packages/components/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,11 @@
5757
"next-themes": "^0.4.6",
5858
"react-day-picker": "^9.13.0",
5959
"react-hook-form": "^7.71.0",
60+
"react-markdown": "^10.1.0",
6061
"react-resizable-panels": "^4.4.0",
6162
"recharts": "^3.6.0",
63+
"rehype-sanitize": "^6.0.0",
64+
"remark-gfm": "^4.0.1",
6265
"sonner": "^2.0.7",
6366
"tailwind-merge": "^2.6.0",
6467
"tailwindcss-animate": "^1.0.7",

packages/components/src/new-components.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ describe('New Components Registration', () => {
3333
expect(component).toBeDefined();
3434
expect(component?.label).toBe('Tree View');
3535
});
36+
37+
it('should register markdown component', () => {
38+
const component = ComponentRegistry.getConfig('markdown');
39+
expect(component).toBeDefined();
40+
expect(component?.label).toBe('Markdown');
41+
});
3642
});
3743

3844
describe('Layout Components', () => {

packages/components/src/renderers/data-display/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ import './alert';
44
import './chart';
55
import './list';
66
import './tree-view';
7+
import './markdown';
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { ComponentRegistry } from '@object-ui/core';
2+
import { Markdown } from '@/ui';
3+
4+
/**
5+
* Markdown Renderer Component
6+
*
7+
* A schema-driven renderer that displays markdown content in Object UI applications.
8+
* This component follows the "Schema First" principle, enabling markdown rendering
9+
* through pure JSON/YAML configuration without writing custom code.
10+
*
11+
* @example
12+
* ```json
13+
* {
14+
* "type": "markdown",
15+
* "content": "# Hello World\n\nThis is **markdown** text."
16+
* }
17+
* ```
18+
*
19+
* Features:
20+
* - GitHub Flavored Markdown support (tables, strikethrough, task lists)
21+
* - XSS protection via rehype-sanitize
22+
* - Dark mode support
23+
* - Tailwind CSS prose styling
24+
*/
25+
ComponentRegistry.register('markdown',
26+
({ schema, className, ...props }) => (
27+
<Markdown
28+
content={schema.content || ''}
29+
className={className}
30+
{...props}
31+
/>
32+
),
33+
{
34+
label: 'Markdown',
35+
inputs: [
36+
{
37+
name: 'content',
38+
type: 'string',
39+
label: 'Markdown Content',
40+
required: true,
41+
inputType: 'textarea'
42+
},
43+
{ name: 'className', type: 'string', label: 'CSS Class' }
44+
],
45+
defaultProps: {
46+
content: '# Hello World\n\nThis is a **markdown** component with *formatting* support.\n\n- Item 1\n- Item 2\n- Item 3',
47+
}
48+
}
49+
);

packages/components/src/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export * from './input';
3030
export * from './item';
3131
export * from './kbd';
3232
export * from './label';
33+
export * from './markdown';
3334
export * from './menubar';
3435
export * from './navigation-menu';
3536
export * from './pagination';
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as React from "react"
2+
import ReactMarkdown from "react-markdown"
3+
import remarkGfm from "remark-gfm"
4+
import rehypeSanitize from "rehype-sanitize"
5+
import { cn } from "@/lib/utils"
6+
7+
/**
8+
* Props for the Markdown component.
9+
*
10+
* This component renders markdown content using react-markdown with GitHub Flavored Markdown support.
11+
* All content is sanitized to prevent XSS attacks.
12+
*/
13+
export interface MarkdownProps {
14+
/**
15+
* The markdown content to render. Supports GitHub Flavored Markdown including:
16+
* - Headers (H1-H6)
17+
* - Bold, italic, and inline code
18+
* - Links and images
19+
* - Lists (ordered, unordered, and nested)
20+
* - Tables
21+
* - Blockquotes
22+
* - Code blocks
23+
*/
24+
content: string
25+
26+
/**
27+
* Optional CSS class name to apply custom styling to the markdown container.
28+
* The component uses Tailwind CSS prose classes for typography by default.
29+
*/
30+
className?: string
31+
}
32+
33+
function Markdown({ content, className }: MarkdownProps) {
34+
return (
35+
<div
36+
data-slot="markdown"
37+
className={cn(
38+
"prose prose-sm dark:prose-invert max-w-none",
39+
"prose-headings:font-semibold prose-headings:tracking-tight",
40+
"prose-h1:text-3xl prose-h2:text-2xl prose-h3:text-xl",
41+
"prose-p:leading-relaxed prose-p:text-foreground",
42+
"prose-a:text-primary prose-a:no-underline hover:prose-a:underline",
43+
"prose-code:text-foreground prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none",
44+
"prose-pre:bg-muted prose-pre:text-foreground prose-pre:border",
45+
"prose-blockquote:border-l-primary prose-blockquote:text-muted-foreground",
46+
"prose-strong:text-foreground prose-strong:font-semibold",
47+
"prose-ul:list-disc prose-ol:list-decimal",
48+
"prose-li:text-foreground prose-li:marker:text-muted-foreground",
49+
"prose-table:border prose-th:border prose-th:bg-muted prose-td:border",
50+
"prose-img:rounded-md prose-img:border",
51+
className
52+
)}
53+
>
54+
<ReactMarkdown
55+
remarkPlugins={[remarkGfm]}
56+
rehypePlugins={[rehypeSanitize]}
57+
>
58+
{content}
59+
</ReactMarkdown>
60+
</div>
61+
)
62+
}
63+
64+
export { Markdown }

0 commit comments

Comments
 (0)