@amritanshu3011/mdx-renderer 1.0.3 → 1.0.5
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/README.md +142 -0
- package/dist/MDXRenderer.d.ts +1 -0
- package/dist/MDXRenderer.js +28 -3
- package/dist/components/smart/FlowBlock.d.ts +11 -4
- package/dist/components/smart/FlowBlock.js +79 -104
- package/dist/components/smart/InteractiveCompositeBlock.js +135 -17
- package/dist/theme/themes.js +2 -2
- package/package.json +1 -1
- package/README.md +0 -102
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
```markdown
|
|
2
|
+
# MDX Renderer
|
|
3
|
+
|
|
4
|
+
A robust, theme-able visualization and MDX rendering system for React applications. It supports both direct MDX file rendering and programmatic visualization injection (Intent-based UI).
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @amritanshu3011/mdx-renderer
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 1. Intent-Based Usage (Programmatic)
|
|
14
|
+
|
|
15
|
+
Use this pattern when you want to render specific components based on backend data (e.g., from an LLM or API). This effectively turns JSON responses into React UI.
|
|
16
|
+
|
|
17
|
+
### Step 1: Wrap your App
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { VisualizationProvider } from '@amritanshu3011/mdx-renderer';
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return (
|
|
24
|
+
<VisualizationProvider>
|
|
25
|
+
<YourAppContent />
|
|
26
|
+
</VisualizationProvider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Step 2: Inject & Render
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { useVisualizations, visualizationRegistry } from '@amritanshu3011/mdx-renderer';
|
|
36
|
+
|
|
37
|
+
function ResultsPage() {
|
|
38
|
+
const { visualizations, addVisualization } = useVisualizations();
|
|
39
|
+
|
|
40
|
+
// Simulate receiving data from a backend
|
|
41
|
+
const handleLoadData = () => {
|
|
42
|
+
addVisualization({
|
|
43
|
+
type: 'pros_cons', // Key must match the registry (see table below)
|
|
44
|
+
data: {
|
|
45
|
+
title: 'React vs Angular',
|
|
46
|
+
pros: ['Virtual DOM', 'Huge Ecosystem'],
|
|
47
|
+
cons: ['Steep Learning Curve']
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<button onClick={handleLoadData}>Load Analysis</button>
|
|
55
|
+
|
|
56
|
+
{/* Render the Dynamic Components */}
|
|
57
|
+
<div className="results-container">
|
|
58
|
+
{visualizations.map((viz, index) => {
|
|
59
|
+
const Component = visualizationRegistry[viz.type];
|
|
60
|
+
|
|
61
|
+
if (!Component) return null;
|
|
62
|
+
|
|
63
|
+
return <Component key={index} {...viz.data} />;
|
|
64
|
+
})}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 2. Standard MDX Usage
|
|
73
|
+
|
|
74
|
+
Use this pattern if you are importing and rendering `.mdx` files directly.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { MDXRenderer, ThemeProvider } from '@amritanshu3011/mdx-renderer';
|
|
78
|
+
import Content from './document.mdx';
|
|
79
|
+
|
|
80
|
+
function DocumentPage() {
|
|
81
|
+
return (
|
|
82
|
+
<ThemeProvider>
|
|
83
|
+
<MDXRenderer>
|
|
84
|
+
<Content />
|
|
85
|
+
</MDXRenderer>
|
|
86
|
+
</ThemeProvider>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Component Registry & Data Structures
|
|
93
|
+
|
|
94
|
+
When using the intent-based approach, your backend must return the correct `type` string.
|
|
95
|
+
|
|
96
|
+
### Smart Analysis Blocks
|
|
97
|
+
|
|
98
|
+
| Component | Type Key | Required Data Structure |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| **Pros & Cons** | `pros_cons` | `{ title: string, pros: string[], cons: string[] }` |
|
|
101
|
+
| **Comparison** | `comparison` | `{ title: string, items: [{ name, price, features[] }] }` |
|
|
102
|
+
| **Flow Chart** | `flow` | `{ title: string, steps: [{ id, label, status }] }` |
|
|
103
|
+
| **Concept Tree** | `concept_tree` | `{ title: string, data: { name, children: [] } }` |
|
|
104
|
+
| **Sunburst** | `sunburst` | `{ title: string, data: { name, children: [{ name, value }] } }` |
|
|
105
|
+
| **Composite** | `interactive_composite` | `{ title: string, selector: object, views: object }` |
|
|
106
|
+
|
|
107
|
+
### Data Visualizations
|
|
108
|
+
|
|
109
|
+
| Component | Type Key | Required Data Structure |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| **Line Chart** | `line_chart` | `{ title: string, xAxisKey: string, data: object[], series: [{ key, color }] }` |
|
|
112
|
+
|
|
113
|
+
## Theme Customization
|
|
114
|
+
|
|
115
|
+
You can inject a custom theme to match your brand colors.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { ThemeProvider } from '@amritanshu3011/mdx-renderer';
|
|
119
|
+
|
|
120
|
+
const myTheme = {
|
|
121
|
+
colors: {
|
|
122
|
+
primary: '#007bff',
|
|
123
|
+
secondary: '#6c757d',
|
|
124
|
+
background: '#ffffff',
|
|
125
|
+
text: '#333333'
|
|
126
|
+
},
|
|
127
|
+
spacing: {
|
|
128
|
+
small: '8px',
|
|
129
|
+
medium: '16px',
|
|
130
|
+
large: '24px'
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
<ThemeProvider initialTheme={myTheme}>
|
|
135
|
+
<App />
|
|
136
|
+
</ThemeProvider>
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```
|
package/dist/MDXRenderer.d.ts
CHANGED
package/dist/MDXRenderer.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { MDXProvider } from '@mdx-js/react';
|
|
3
|
-
|
|
3
|
+
// 👇 CHANGE 1: Use the Context Hook (so updates happen live)
|
|
4
|
+
// import { useThemeWithFallback } from './utils/useThemeWithFallback';
|
|
5
|
+
import { useTheme } from './theme/ThemeContext';
|
|
6
|
+
// 👇 CHANGE 2: Import the CSS file
|
|
7
|
+
import './styles/base.css';
|
|
4
8
|
// Text components
|
|
5
9
|
import { Heading } from './components/text/Heading';
|
|
6
10
|
import { Paragraph } from './components/text/Paragraph';
|
|
@@ -51,6 +55,27 @@ const components = {
|
|
|
51
55
|
ComparisonGrid,
|
|
52
56
|
};
|
|
53
57
|
export const MDXRenderer = ({ children, className = 'mdx-content' }) => {
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
// 👇 CHANGE 3: Get theme from Context
|
|
59
|
+
const { theme } = useTheme();
|
|
60
|
+
// 👇 CHANGE 4: Create the Bridge (JS -> CSS Variables)
|
|
61
|
+
const themeStyles = {
|
|
62
|
+
// Colors
|
|
63
|
+
'--color-primary': theme.colors.primary,
|
|
64
|
+
'--color-secondary': theme.colors.secondary,
|
|
65
|
+
'--color-text': theme.colors.text,
|
|
66
|
+
'--color-text-secondary': theme.colors.textSecondary,
|
|
67
|
+
'--color-background': theme.colors.background,
|
|
68
|
+
'--color-card-bg': theme.colors.cardBackground,
|
|
69
|
+
'--color-border': theme.colors.border,
|
|
70
|
+
// Spacing
|
|
71
|
+
'--spacing-small': theme.spacing.small,
|
|
72
|
+
'--spacing-medium': theme.spacing.medium,
|
|
73
|
+
'--spacing-large': theme.spacing.large,
|
|
74
|
+
// Typography
|
|
75
|
+
'--font-family': theme.typography.fontFamily,
|
|
76
|
+
// Border Radius
|
|
77
|
+
'--radius-small': theme.borderRadius.small,
|
|
78
|
+
'--radius-medium': theme.borderRadius.medium,
|
|
79
|
+
};
|
|
80
|
+
return (_jsx("div", { className: className, style: themeStyles, children: _jsx(MDXProvider, { components: components, children: children }) }));
|
|
56
81
|
};
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
interface FlowStep {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export interface FlowStep {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
value: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
dropOff?: string;
|
|
8
|
+
breakdown?: {
|
|
9
|
+
label: string;
|
|
10
|
+
value: number;
|
|
11
|
+
}[];
|
|
5
12
|
}
|
|
6
13
|
export interface FlowBlockProps {
|
|
7
14
|
title: string;
|
|
15
|
+
total_candidates?: number;
|
|
8
16
|
steps: FlowStep[];
|
|
9
17
|
}
|
|
10
18
|
export declare const FlowBlock: React.FC<FlowBlockProps>;
|
|
11
|
-
export {};
|
|
@@ -1,46 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
-
import React, { useState
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
3
|
import { useThemeWithFallback } from '../../utils/useThemeWithFallback';
|
|
4
|
-
export const FlowBlock = ({ title, steps }) => {
|
|
4
|
+
export const FlowBlock = ({ title, total_candidates, steps }) => {
|
|
5
5
|
const theme = useThemeWithFallback();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const [
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let data = [...steps];
|
|
15
|
-
// 1. Filter
|
|
16
|
-
if (filterText) {
|
|
17
|
-
const lower = filterText.toLowerCase();
|
|
18
|
-
data = data.filter(s => s.title.toLowerCase().includes(lower) ||
|
|
19
|
-
s.description.toLowerCase().includes(lower));
|
|
20
|
-
}
|
|
21
|
-
// 2. Sort
|
|
22
|
-
if (sortMode !== 'original') {
|
|
23
|
-
data.sort((a, b) => {
|
|
24
|
-
return sortMode === 'asc'
|
|
25
|
-
? a.title.localeCompare(b.title)
|
|
26
|
-
: b.title.localeCompare(a.title);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
return data;
|
|
30
|
-
}, [steps, filterText, sortMode]);
|
|
31
|
-
// --- Logic: Grouping ---
|
|
32
|
-
const groupedData = useMemo(() => {
|
|
33
|
-
if (!isGrouped)
|
|
34
|
-
return { 'All Steps': processedData };
|
|
35
|
-
return processedData.reduce((acc, step) => {
|
|
36
|
-
const key = step.title.charAt(0).toUpperCase();
|
|
37
|
-
if (!acc[key])
|
|
38
|
-
acc[key] = [];
|
|
39
|
-
acc[key].push(step);
|
|
40
|
-
return acc;
|
|
41
|
-
}, {});
|
|
42
|
-
}, [processedData, isGrouped]);
|
|
43
|
-
// --- Styles Helper ---
|
|
6
|
+
const [viewMode, setViewMode] = useState('chart');
|
|
7
|
+
// Track expanded rows for both Chart and Table views
|
|
8
|
+
const [expandedSteps, setExpandedSteps] = useState({});
|
|
9
|
+
const toggleStep = (id) => {
|
|
10
|
+
setExpandedSteps(prev => ({ ...prev, [id]: !prev[id] }));
|
|
11
|
+
};
|
|
12
|
+
const maxValue = Math.max(...steps.map(s => s.value));
|
|
13
|
+
// --- Styles ---
|
|
44
14
|
const btnStyle = (active) => ({
|
|
45
15
|
padding: `${theme.spacing.small} ${theme.spacing.medium}`,
|
|
46
16
|
backgroundColor: active ? theme.colors.primary : 'transparent',
|
|
@@ -49,74 +19,79 @@ export const FlowBlock = ({ title, steps }) => {
|
|
|
49
19
|
borderRadius: theme.borderRadius.small,
|
|
50
20
|
cursor: 'pointer',
|
|
51
21
|
fontSize: theme.typography.smallSize,
|
|
52
|
-
fontWeight: 'bold'
|
|
22
|
+
fontWeight: 'bold',
|
|
23
|
+
transition: 'all 0.2s',
|
|
53
24
|
});
|
|
54
25
|
return (_jsxs("div", { style: {
|
|
55
26
|
margin: `${theme.spacing.large} 0`,
|
|
56
|
-
border: `1px solid ${theme.colors.border || '#
|
|
27
|
+
border: `1px solid ${theme.colors.border || '#e5e7eb'}`,
|
|
57
28
|
borderRadius: theme.borderRadius.medium,
|
|
29
|
+
backgroundColor: theme.colors.background,
|
|
58
30
|
boxShadow: '0 4px 12px rgba(0,0,0,0.05)',
|
|
59
31
|
overflow: 'hidden'
|
|
60
32
|
}, children: [_jsxs("div", { style: {
|
|
61
|
-
padding:
|
|
62
|
-
borderBottom: `1px solid ${theme.colors.border || '#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
33
|
+
padding: '16px 20px',
|
|
34
|
+
borderBottom: `1px solid ${theme.colors.border || '#e5e7eb'}`,
|
|
35
|
+
display: 'flex',
|
|
36
|
+
justifyContent: 'space-between',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
backgroundColor: theme.colors.cardBackground
|
|
39
|
+
}, children: [_jsxs("div", { children: [_jsx("h3", { style: { margin: 0, fontSize: '18px', color: theme.colors.text }, children: title }), total_candidates && (_jsxs("div", { style: { fontSize: '13px', color: theme.colors.textSecondary, marginTop: '4px' }, children: ["Total Volume: ", _jsx("strong", { children: total_candidates.toLocaleString() })] }))] }), _jsxs("div", { style: { display: 'flex', borderRadius: '6px', overflow: 'hidden', border: `1px solid ${theme.colors.primary}` }, children: [_jsx("button", { onClick: () => setViewMode('chart'), style: { ...btnStyle(viewMode === 'chart'), border: 'none', borderRadius: 0 }, children: "Funnel" }), _jsx("button", { onClick: () => setViewMode('table'), style: { ...btnStyle(viewMode === 'table'), border: 'none', borderRadius: 0 }, children: "Grid" })] })] }), _jsxs("div", { style: { padding: '24px' }, children: [viewMode === 'chart' && (_jsx("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }, children: steps.map((step, index) => {
|
|
40
|
+
const widthPercentage = Math.max(25, (step.value / maxValue) * 100);
|
|
41
|
+
const isExpanded = expandedSteps[step.id];
|
|
42
|
+
const hasBreakdown = step.breakdown && step.breakdown.length > 0;
|
|
43
|
+
return (_jsxs("div", { style: { width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }, children: [_jsxs("div", { onClick: () => hasBreakdown && toggleStep(step.id), style: {
|
|
44
|
+
width: `${widthPercentage}%`,
|
|
45
|
+
backgroundColor: step.color || theme.colors.primary,
|
|
46
|
+
borderRadius: '8px',
|
|
47
|
+
padding: '12px 16px',
|
|
48
|
+
color: '#fff',
|
|
49
|
+
display: 'flex',
|
|
50
|
+
justifyContent: 'space-between',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
cursor: hasBreakdown ? 'pointer' : 'default',
|
|
53
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
|
|
54
|
+
transition: 'width 0.3s ease, transform 0.1s',
|
|
55
|
+
position: 'relative'
|
|
56
|
+
}, onMouseEnter: (e) => e.currentTarget.style.transform = hasBreakdown ? 'scale(1.01)' : 'none', onMouseLeave: (e) => e.currentTarget.style.transform = 'none', children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [hasBreakdown && _jsx("span", { style: { fontSize: '10px' }, children: isExpanded ? '▼' : '▶' }), _jsxs("span", { style: { fontWeight: 600, fontSize: '15px' }, children: [index + 1, ". ", step.label] })] }), _jsx("span", { style: { fontWeight: 'bold', backgroundColor: 'rgba(255,255,255,0.2)', padding: '2px 8px', borderRadius: '4px' }, children: step.value.toLocaleString() })] }), isExpanded && hasBreakdown && (_jsx("div", { style: {
|
|
57
|
+
width: `${widthPercentage}%`,
|
|
58
|
+
backgroundColor: theme.colors.cardBackground,
|
|
59
|
+
border: `1px solid ${theme.colors.border}`,
|
|
60
|
+
borderTop: 'none',
|
|
61
|
+
borderRadius: '0 0 8px 8px',
|
|
62
|
+
padding: '12px',
|
|
63
|
+
marginBottom: '10px',
|
|
64
|
+
boxShadow: 'inset 0 4px 6px -4px rgba(0,0,0,0.1)'
|
|
65
|
+
}, children: _jsx("table", { style: { width: '100%', fontSize: '13px', color: theme.colors.text }, children: _jsx("tbody", { children: step.breakdown.map((item, idx) => (_jsxs("tr", { style: { borderBottom: idx !== step.breakdown.length - 1 ? `1px solid ${theme.colors.border}` : 'none' }, children: [_jsx("td", { style: { padding: '6px 0', color: theme.colors.textSecondary }, children: item.label }), _jsx("td", { style: { padding: '6px 0', textAlign: 'right', fontWeight: 'bold' }, children: item.value.toLocaleString() })] }, idx))) }) }) })), !isExpanded && index < steps.length - 1 && (_jsxs("div", { style: { height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative', width: '100%' }, children: [_jsx("div", { style: { width: '2px', height: '100%', backgroundColor: theme.colors.border } }), step.dropOff && (_jsxs("span", { style: {
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
fontSize: '11px',
|
|
68
|
+
color: '#dc2626',
|
|
69
|
+
backgroundColor: '#fef2f2',
|
|
70
|
+
padding: '2px 8px',
|
|
71
|
+
borderRadius: '10px',
|
|
72
|
+
border: '1px solid #fecaca',
|
|
73
|
+
fontWeight: 600
|
|
74
|
+
}, children: [step.dropOff, " Drop-off"] }))] }))] }, step.id));
|
|
75
|
+
}) })), viewMode === 'table' && (_jsx("div", { style: { overflowX: 'auto', border: `1px solid ${theme.colors.border}`, borderRadius: '8px' }, children: _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: '14px', backgroundColor: theme.colors.cardBackground }, children: [_jsx("thead", { children: _jsxs("tr", { style: { backgroundColor: theme.colors.background, borderBottom: `2px solid ${theme.colors.border}` }, children: [_jsx("th", { style: { padding: '12px 16px', textAlign: 'left', color: theme.colors.textSecondary, fontWeight: 600 }, children: "Stage" }), _jsx("th", { style: { padding: '12px 16px', textAlign: 'right', color: theme.colors.textSecondary, fontWeight: 600 }, children: "Candidates" }), _jsx("th", { style: { padding: '12px 16px', textAlign: 'right', color: theme.colors.textSecondary, fontWeight: 600 }, children: "Status" })] }) }), _jsx("tbody", { children: steps.map((step, i) => {
|
|
76
|
+
const hasBreakdown = step.breakdown && step.breakdown.length > 0;
|
|
77
|
+
const isExpanded = expandedSteps[step.id];
|
|
78
|
+
return (_jsxs(React.Fragment, { children: [_jsxs("tr", { onClick: () => hasBreakdown && toggleStep(step.id), style: {
|
|
79
|
+
borderBottom: isExpanded ? 'none' : `1px solid ${theme.colors.border}`,
|
|
80
|
+
cursor: hasBreakdown ? 'pointer' : 'default',
|
|
81
|
+
transition: 'background-color 0.2s'
|
|
82
|
+
}, onMouseEnter: (e) => e.currentTarget.style.backgroundColor = theme.colors.background, onMouseLeave: (e) => e.currentTarget.style.backgroundColor = 'transparent', children: [_jsx("td", { style: { padding: '14px 16px', fontWeight: 600, color: theme.colors.text }, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [hasBreakdown && (_jsx("span", { style: { color: theme.colors.primary, fontSize: '12px' }, children: isExpanded ? '▼' : '▶' })), i + 1, ". ", step.label] }) }), _jsx("td", { style: { padding: '14px 16px', textAlign: 'right', fontWeight: 'bold', fontSize: '15px' }, children: step.value.toLocaleString() }), _jsx("td", { style: { padding: '14px 16px', textAlign: 'right' }, children: step.dropOff ? (_jsxs("span", { style: {
|
|
83
|
+
color: '#dc2626',
|
|
84
|
+
backgroundColor: '#fef2f2',
|
|
85
|
+
padding: '4px 8px',
|
|
86
|
+
borderRadius: '4px',
|
|
87
|
+
fontSize: '12px',
|
|
88
|
+
fontWeight: 600
|
|
89
|
+
}, children: [step.dropOff, " Loss"] })) : (_jsx("span", { style: { color: '#16a34a', fontSize: '20px' }, children: "\u2022" })) })] }), isExpanded && hasBreakdown && (_jsx("tr", { style: { borderBottom: `1px solid ${theme.colors.border}`, backgroundColor: theme.colors.background }, children: _jsx("td", { colSpan: 3, style: { padding: '0 0 12px 0' }, children: _jsxs("div", { style: {
|
|
90
|
+
margin: '0 20px',
|
|
91
|
+
padding: '12px',
|
|
92
|
+
borderLeft: `3px solid ${theme.colors.primary}`,
|
|
93
|
+
backgroundColor: theme.colors.cardBackground,
|
|
94
|
+
borderRadius: '0 4px 4px 0'
|
|
95
|
+
}, children: [_jsx("h4", { style: { margin: '0 0 8px 0', fontSize: '12px', textTransform: 'uppercase', color: theme.colors.textSecondary }, children: "Breakdown" }), _jsx("table", { style: { width: '100%', fontSize: '13px' }, children: _jsx("tbody", { children: step.breakdown.map((b, idx) => (_jsxs("tr", { children: [_jsx("td", { style: { padding: '4px 0', color: theme.colors.text }, children: b.label }), _jsx("td", { style: { padding: '4px 0', textAlign: 'right', fontWeight: 600 }, children: b.value.toLocaleString() })] }, idx))) }) })] }) }) }))] }, step.id));
|
|
96
|
+
}) })] }) }))] })] }));
|
|
122
97
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useMemo } from 'react';
|
|
3
|
-
import
|
|
2
|
+
import { useState, useMemo, useEffect, useRef } from 'react';
|
|
3
|
+
import * as echarts from 'echarts';
|
|
4
4
|
import { useThemeWithFallback } from '../../utils/useThemeWithFallback';
|
|
5
5
|
export const InteractiveCompositeBlock = ({ title, selector, views }) => {
|
|
6
6
|
const theme = useThemeWithFallback();
|
|
@@ -9,18 +9,19 @@ export const InteractiveCompositeBlock = ({ title, selector, views }) => {
|
|
|
9
9
|
const [viewMode, setViewMode] = useState('split');
|
|
10
10
|
const [filterText, setFilterText] = useState('');
|
|
11
11
|
const [sortConfig, setSortConfig] = useState({ col: '', dir: null });
|
|
12
|
+
// --- Refs ---
|
|
13
|
+
const chartRef = useRef(null);
|
|
14
|
+
const chartInstance = useRef(null);
|
|
12
15
|
// --- Data Access ---
|
|
13
|
-
const
|
|
16
|
+
const rawChartData = views.chart.series[selected] || [];
|
|
14
17
|
const rawTableRows = views.table.rows[selected] || [];
|
|
15
18
|
// --- Logic: Process Table Data ---
|
|
16
19
|
const tableRows = useMemo(() => {
|
|
17
20
|
let data = [...rawTableRows];
|
|
18
|
-
// 1. Filter
|
|
19
21
|
if (filterText) {
|
|
20
22
|
const lower = filterText.toLowerCase();
|
|
21
23
|
data = data.filter(row => Object.values(row).some(val => String(val).toLowerCase().includes(lower)));
|
|
22
24
|
}
|
|
23
|
-
// 2. Sort
|
|
24
25
|
if (sortConfig.col && sortConfig.dir) {
|
|
25
26
|
data.sort((a, b) => {
|
|
26
27
|
const valA = a[sortConfig.col];
|
|
@@ -35,24 +36,141 @@ export const InteractiveCompositeBlock = ({ title, selector, views }) => {
|
|
|
35
36
|
}
|
|
36
37
|
return data;
|
|
37
38
|
}, [rawTableRows, filterText, sortConfig]);
|
|
38
|
-
// --- Handlers ---
|
|
39
39
|
const handleSort = (col) => {
|
|
40
40
|
setSortConfig(prev => ({
|
|
41
41
|
col,
|
|
42
42
|
dir: prev.col === col && prev.dir === 'asc' ? 'desc' : 'asc'
|
|
43
43
|
}));
|
|
44
44
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
// --- Logic: ECharts Option Generator ---
|
|
46
|
+
const chartOptions = useMemo(() => {
|
|
47
|
+
const { chartType, xKey, yKey } = views.chart;
|
|
48
|
+
const commonOption = {
|
|
49
|
+
tooltip: {
|
|
50
|
+
trigger: 'item',
|
|
51
|
+
backgroundColor: theme.colors.cardBackground,
|
|
52
|
+
borderColor: theme.colors.border,
|
|
53
|
+
textStyle: { color: theme.colors.text }
|
|
54
|
+
},
|
|
55
|
+
grid: { top: 30, right: 30, bottom: 20, left: 40, containLabel: true },
|
|
56
|
+
textStyle: { fontFamily: 'sans-serif' },
|
|
57
|
+
};
|
|
58
|
+
if (chartType === 'line') {
|
|
59
|
+
return {
|
|
60
|
+
...commonOption,
|
|
61
|
+
tooltip: { trigger: 'axis' },
|
|
62
|
+
xAxis: {
|
|
63
|
+
type: 'category',
|
|
64
|
+
// Fallback to empty string if xKey is missing
|
|
65
|
+
data: rawChartData.map(d => xKey ? d[xKey] : ''),
|
|
66
|
+
axisLine: { lineStyle: { color: theme.colors.border } },
|
|
67
|
+
axisLabel: { color: theme.colors.textSecondary }
|
|
68
|
+
},
|
|
69
|
+
yAxis: {
|
|
70
|
+
type: 'value',
|
|
71
|
+
splitLine: { lineStyle: { type: 'dashed', color: theme.colors.border } },
|
|
72
|
+
axisLabel: { color: theme.colors.textSecondary }
|
|
73
|
+
},
|
|
74
|
+
series: [{
|
|
75
|
+
data: rawChartData.map(d => d[yKey]),
|
|
76
|
+
type: 'line',
|
|
77
|
+
smooth: true,
|
|
78
|
+
symbol: 'circle',
|
|
79
|
+
symbolSize: 6,
|
|
80
|
+
itemStyle: { color: theme.colors.primary },
|
|
81
|
+
areaStyle: {
|
|
82
|
+
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
83
|
+
{ offset: 0, color: theme.colors.primary },
|
|
84
|
+
{ offset: 1, color: 'rgba(255, 255, 255, 0)' }
|
|
85
|
+
]),
|
|
86
|
+
opacity: 0.2
|
|
87
|
+
}
|
|
88
|
+
}]
|
|
89
|
+
};
|
|
54
90
|
}
|
|
55
|
-
|
|
91
|
+
if (chartType === 'radial_bar') {
|
|
92
|
+
return {
|
|
93
|
+
...commonOption,
|
|
94
|
+
polar: { radius: [30, '80%'] },
|
|
95
|
+
angleAxis: {
|
|
96
|
+
type: 'category',
|
|
97
|
+
data: rawChartData.map(d => xKey ? d[xKey] : ''),
|
|
98
|
+
startAngle: 75,
|
|
99
|
+
axisLine: { lineStyle: { color: theme.colors.border } },
|
|
100
|
+
axisLabel: { color: theme.colors.textSecondary }
|
|
101
|
+
},
|
|
102
|
+
radiusAxis: { type: 'value', show: false },
|
|
103
|
+
series: [{
|
|
104
|
+
type: 'bar',
|
|
105
|
+
data: rawChartData.map(d => d[yKey]),
|
|
106
|
+
coordinateSystem: 'polar',
|
|
107
|
+
itemStyle: { color: theme.colors.primary },
|
|
108
|
+
label: { show: true, position: 'middle', formatter: '{b}' }
|
|
109
|
+
}]
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (chartType === 'treemap') {
|
|
113
|
+
// FIX: Better logic to determine the label (name)
|
|
114
|
+
const treeData = rawChartData.map((d, index) => {
|
|
115
|
+
// 1. Try explicit xKey
|
|
116
|
+
if (xKey && d[xKey])
|
|
117
|
+
return { name: d[xKey], value: d[yKey] };
|
|
118
|
+
// 2. Try guessing common name keys
|
|
119
|
+
const nameCandidate = d.name || d.label || d.category || d.id;
|
|
120
|
+
if (nameCandidate)
|
|
121
|
+
return { name: nameCandidate, value: d[yKey] };
|
|
122
|
+
// 3. Fallback to index so it's unique (prevents "Node Node")
|
|
123
|
+
return { name: `Item ${index + 1}`, value: d[yKey] };
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
...commonOption,
|
|
127
|
+
series: [{
|
|
128
|
+
type: 'treemap',
|
|
129
|
+
data: treeData,
|
|
130
|
+
label: {
|
|
131
|
+
show: true,
|
|
132
|
+
formatter: '{b}' // Show name
|
|
133
|
+
},
|
|
134
|
+
itemStyle: {
|
|
135
|
+
borderColor: theme.colors.cardBackground,
|
|
136
|
+
borderWidth: 2,
|
|
137
|
+
gapWidth: 1
|
|
138
|
+
},
|
|
139
|
+
levels: [
|
|
140
|
+
{ itemStyle: { borderColor: '#555', borderWidth: 4, gapWidth: 4 } },
|
|
141
|
+
{ colorSaturation: [0.3, 0.6], itemStyle: { borderColorSaturation: 0.7, gapWidth: 2, borderWidth: 2 } }
|
|
142
|
+
]
|
|
143
|
+
}]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {};
|
|
147
|
+
}, [views.chart, rawChartData, theme]);
|
|
148
|
+
// --- Effect: Initialize & Update Chart ---
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (chartRef.current && !chartInstance.current) {
|
|
151
|
+
chartInstance.current = echarts.init(chartRef.current);
|
|
152
|
+
}
|
|
153
|
+
if (chartInstance.current) {
|
|
154
|
+
chartInstance.current.setOption(chartOptions, true);
|
|
155
|
+
}
|
|
156
|
+
const handleResize = () => chartInstance.current?.resize();
|
|
157
|
+
window.addEventListener('resize', handleResize);
|
|
158
|
+
return () => {
|
|
159
|
+
window.removeEventListener('resize', handleResize);
|
|
160
|
+
if (!chartRef.current && chartInstance.current) {
|
|
161
|
+
chartInstance.current.dispose();
|
|
162
|
+
chartInstance.current = null;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}, [chartOptions, viewMode]);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (viewMode === 'table') {
|
|
168
|
+
if (chartInstance.current) {
|
|
169
|
+
chartInstance.current.dispose();
|
|
170
|
+
chartInstance.current = null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}, [viewMode]);
|
|
56
174
|
const btnStyle = (active) => ({
|
|
57
175
|
padding: '4px 12px',
|
|
58
176
|
backgroundColor: active ? theme.colors.primary : 'transparent',
|
|
@@ -83,7 +201,7 @@ export const InteractiveCompositeBlock = ({ title, selector, views }) => {
|
|
|
83
201
|
borderRadius: theme.borderRadius.medium,
|
|
84
202
|
border: `1px solid ${theme.colors.border}`,
|
|
85
203
|
marginBottom: viewMode === 'split' ? theme.spacing.medium : 0
|
|
86
|
-
}, children: _jsx(
|
|
204
|
+
}, children: _jsx("div", { ref: chartRef, style: { width: '100%', height: '100%' } }) })), (viewMode === 'split' || viewMode === 'table') && (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: theme.spacing.small, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [_jsx("input", { type: "text", placeholder: "Filter table data...", value: filterText, onChange: (e) => setFilterText(e.target.value), style: { padding: '4px 8px', borderRadius: theme.borderRadius.small, border: `1px solid ${theme.colors.border}`, width: '200px' } }), _jsxs("span", { style: { fontSize: '11px', color: theme.colors.textSecondary }, children: [tableRows.length, " rows found"] })] }), _jsx("div", { style: {
|
|
87
205
|
overflowX: 'auto',
|
|
88
206
|
backgroundColor: theme.colors.cardBackground,
|
|
89
207
|
borderRadius: theme.borderRadius.medium,
|
package/dist/theme/themes.js
CHANGED
|
@@ -21,7 +21,7 @@ export const lightTheme = {
|
|
|
21
21
|
h2Size: '24px',
|
|
22
22
|
bodySize: '16px',
|
|
23
23
|
smallSize: '14px',
|
|
24
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
24
|
+
fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
25
25
|
},
|
|
26
26
|
borderRadius: {
|
|
27
27
|
small: '4px',
|
|
@@ -51,7 +51,7 @@ export const darkTheme = {
|
|
|
51
51
|
h2Size: '24px',
|
|
52
52
|
bodySize: '16px',
|
|
53
53
|
smallSize: '14px',
|
|
54
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
54
|
+
fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
55
55
|
},
|
|
56
56
|
borderRadius: {
|
|
57
57
|
small: '4px',
|
package/package.json
CHANGED
package/README.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# MDX Renderer
|
|
2
|
-
|
|
3
|
-
Reusable MDX rendering system with theme support and rich component library.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
```bash
|
|
7
|
-
# If published as package
|
|
8
|
-
npm install @yourorg/mdx-renderer
|
|
9
|
-
|
|
10
|
-
# If local
|
|
11
|
-
import { MDXRenderer, ThemeProvider } from './mdx-renderer';
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Quick Start
|
|
15
|
-
```tsx
|
|
16
|
-
import { MDXRenderer, ThemeProvider } from './mdx-renderer';
|
|
17
|
-
import Content from './content.mdx';
|
|
18
|
-
|
|
19
|
-
function App() {
|
|
20
|
-
return (
|
|
21
|
-
<ThemeProvider>
|
|
22
|
-
<MDXRenderer>
|
|
23
|
-
<Content />
|
|
24
|
-
</MDXRenderer>
|
|
25
|
-
</ThemeProvider>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Features
|
|
31
|
-
|
|
32
|
-
✅ **Theme Support** - Light/dark themes with fallbacks
|
|
33
|
-
✅ **Classless CSS** - Works without any class names
|
|
34
|
-
✅ **Default Props** - All widgets work with sensible defaults
|
|
35
|
-
✅ **Rich Components** - Text, data, interactive, and composite widgets
|
|
36
|
-
✅ **Zero Config** - Works out of the box
|
|
37
|
-
|
|
38
|
-
## Components
|
|
39
|
-
|
|
40
|
-
### Text Widgets
|
|
41
|
-
- `Heading` - Themed headings
|
|
42
|
-
- `Paragraph` - Themed paragraphs
|
|
43
|
-
- `Blockquote` - Quote blocks with optional author
|
|
44
|
-
- `List` - Ordered/unordered lists
|
|
45
|
-
|
|
46
|
-
### Data Widgets
|
|
47
|
-
- `SimpleChart` - JSON display
|
|
48
|
-
- `LineChart` - Line graphs (Recharts)
|
|
49
|
-
- `BarChart` - Bar graphs (Recharts)
|
|
50
|
-
|
|
51
|
-
### Interactive Widgets
|
|
52
|
-
- `Accordion` - Collapsible content
|
|
53
|
-
- `Tabs` - Tabbed interface
|
|
54
|
-
|
|
55
|
-
### Composite Widgets
|
|
56
|
-
- `Dashboard` - Grid layout for metrics
|
|
57
|
-
- `DataExplorer` - Searchable data viewer
|
|
58
|
-
|
|
59
|
-
## Usage in MDX
|
|
60
|
-
```mdx
|
|
61
|
-
import { Accordion, Tabs, Dashboard, DataExplorer } from './mdx-renderer';
|
|
62
|
-
|
|
63
|
-
# My Document
|
|
64
|
-
|
|
65
|
-
<Accordion title="Click to expand">
|
|
66
|
-
Hidden content here
|
|
67
|
-
</Accordion>
|
|
68
|
-
|
|
69
|
-
<Tabs tabs={[
|
|
70
|
-
{ label: 'Tab 1', content: <div>Content 1</div> },
|
|
71
|
-
{ label: 'Tab 2', content: <div>Content 2</div> }
|
|
72
|
-
]} />
|
|
73
|
-
|
|
74
|
-
<Dashboard title="Metrics" columns={3}>
|
|
75
|
-
<Card>Metric 1</Card>
|
|
76
|
-
<Card>Metric 2</Card>
|
|
77
|
-
<Card>Metric 3</Card>
|
|
78
|
-
</Dashboard>
|
|
79
|
-
|
|
80
|
-
<DataExplorer data={myData} searchable />
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Theme Customization
|
|
84
|
-
```tsx
|
|
85
|
-
const customTheme = {
|
|
86
|
-
colors: { primary: '#ff0000', ... },
|
|
87
|
-
spacing: { small: '4px', ... },
|
|
88
|
-
typography: { h1Size: '48px', ... }
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
<ThemeProvider initialTheme={customTheme}>
|
|
92
|
-
<MDXRenderer>
|
|
93
|
-
<Content />
|
|
94
|
-
</MDXRenderer>
|
|
95
|
-
</ThemeProvider>
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Dependencies
|
|
99
|
-
|
|
100
|
-
- `react` >= 18.0.0
|
|
101
|
-
- `@mdx-js/react` >= 2.0.0
|
|
102
|
-
- `recharts` >= 2.0.0 (for charts)
|