@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 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
+ ```
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import './styles/base.css';
2
3
  interface MDXRendererProps {
3
4
  children: React.ReactNode;
4
5
  className?: string;
@@ -1,6 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { MDXProvider } from '@mdx-js/react';
3
- import { useThemeWithFallback } from './utils/useThemeWithFallback';
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
- const theme = useThemeWithFallback();
55
- return (_jsx("div", { className: className, "data-theme": theme.colors.background === '#212529' ? 'dark' : 'light', children: _jsx(MDXProvider, { components: components, children: children }) }));
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
- title: string;
4
- description: string;
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, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useState, useMemo } from 'react';
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
- // --- State for Data Manipulation ---
7
- const [viewMode, setViewMode] = useState('flow');
8
- const [filterText, setFilterText] = useState('');
9
- const [sortMode, setSortMode] = useState('original');
10
- const [isGrouped, setIsGrouped] = useState(false); // Group by First Letter
11
- const [isPivoted, setIsPivoted] = useState(false); // Only useful in Table view
12
- // --- Logic: Filter & Sort ---
13
- const processedData = useMemo(() => {
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 || '#ddd'}`,
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: theme.spacing.medium,
62
- borderBottom: `1px solid ${theme.colors.border || '#eee'}`,
63
- backgroundColor: theme.colors.background || '#f9f9f9'
64
- }, children: [_jsx("h2", { style: {
65
- color: theme.colors.text,
66
- margin: `0 0 ${theme.spacing.medium} 0`,
67
- textAlign: 'left'
68
- }, children: title }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: theme.spacing.medium, alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs("div", { style: { display: 'flex', gap: theme.spacing.small, flexWrap: 'wrap' }, children: [_jsx("input", { type: "text", placeholder: "Filter steps...", value: filterText, onChange: (e) => setFilterText(e.target.value), style: {
69
- padding: theme.spacing.small,
70
- borderRadius: theme.borderRadius.small,
71
- border: `1px solid ${theme.colors.border || '#ccc'}`
72
- } }), _jsxs("button", { onClick: () => setSortMode(prev => prev === 'original' ? 'asc' : prev === 'asc' ? 'desc' : 'original'), style: btnStyle(sortMode !== 'original'), children: ["Sort ", sortMode === 'original' ? 'Original' : sortMode === 'asc' ? '(A-Z)' : '(Z-A)'] }), _jsx("button", { onClick: () => setIsGrouped(!isGrouped), style: btnStyle(isGrouped), children: isGrouped ? 'Ungroup' : 'Group (A-Z)' })] }), _jsxs("div", { style: { display: 'flex', gap: theme.spacing.small }, children: [viewMode === 'table' && (_jsx("button", { onClick: () => setIsPivoted(!isPivoted), style: btnStyle(isPivoted), children: "Pivot Data" })), _jsx("div", { style: { display: 'flex', border: `1px solid ${theme.colors.primary}`, borderRadius: theme.borderRadius.small, overflow: 'hidden' }, children: ['flow', 'grid', 'list', 'table'].map((v) => (_jsx("button", { onClick: () => setViewMode(v), style: {
73
- ...btnStyle(viewMode === v),
74
- border: 'none',
75
- borderRadius: 0,
76
- textTransform: 'capitalize'
77
- }, children: v }, v))) })] })] })] }), _jsxs("div", { style: { padding: theme.spacing.medium, backgroundColor: theme.colors.background }, children: [Object.entries(groupedData).map(([groupTitle, groupSteps]) => (_jsxs("div", { style: { marginBottom: theme.spacing.large }, children: [isGrouped && (_jsxs("h4", { style: {
78
- color: theme.colors.textSecondary,
79
- borderBottom: `1px solid ${theme.colors.border || '#eee'}`,
80
- marginBottom: theme.spacing.medium
81
- }, children: ["Group: ", groupTitle] })), viewMode === 'flow' && (_jsxs("div", { style: {
82
- display: 'flex',
83
- alignItems: 'center',
84
- gap: theme.spacing.medium,
85
- overflowX: 'auto',
86
- padding: theme.spacing.small
87
- }, children: [groupSteps.map((step, i) => (_jsxs(React.Fragment, { children: [_jsxs("div", { style: {
88
- flex: '0 0 220px',
89
- padding: theme.spacing.medium,
90
- backgroundColor: theme.colors.cardBackground,
91
- border: `2px solid ${theme.colors.primary}`,
92
- borderRadius: theme.borderRadius.medium,
93
- boxShadow: '0 2px 5px rgba(0,0,0,0.1)'
94
- }, children: [_jsxs("div", { style: {
95
- fontWeight: 'bold',
96
- color: theme.colors.primary,
97
- marginBottom: theme.spacing.small,
98
- fontSize: theme.typography.bodySize
99
- }, children: [i + 1, ". ", step.title] }), _jsx("div", { style: {
100
- color: theme.colors.textSecondary,
101
- fontSize: theme.typography.smallSize,
102
- lineHeight: '1.5'
103
- }, children: step.description })] }), i < groupSteps.length - 1 && (_jsx("div", { style: {
104
- color: theme.colors.primary,
105
- fontSize: '28px',
106
- fontWeight: 'bold',
107
- minWidth: '20px'
108
- }, children: "\u2192" }))] }, i))), groupSteps.length === 0 && _jsx("div", { style: { color: theme.colors.textSecondary }, children: "No steps match filter." })] })), viewMode === 'grid' && (_jsx("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: theme.spacing.medium }, children: groupSteps.map((step, i) => (_jsxs("div", { style: {
109
- padding: theme.spacing.medium,
110
- backgroundColor: theme.colors.cardBackground,
111
- border: `1px solid ${theme.colors.border || '#ddd'}`,
112
- borderRadius: theme.borderRadius.medium
113
- }, children: [_jsx("div", { style: { fontWeight: 'bold', color: theme.colors.text }, children: step.title }), _jsx("hr", { style: { margin: '8px 0', border: '0', borderTop: `1px solid ${theme.colors.border || '#eee'}` } }), _jsx("div", { style: { color: theme.colors.textSecondary, fontSize: theme.typography.smallSize }, children: step.description })] }, i))) })), viewMode === 'list' && (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: theme.spacing.small }, children: groupSteps.map((step, i) => (_jsxs("div", { style: {
114
- padding: theme.spacing.medium,
115
- backgroundColor: theme.colors.cardBackground,
116
- borderLeft: `4px solid ${theme.colors.primary}`,
117
- borderRadius: theme.borderRadius.small,
118
- display: 'flex',
119
- alignItems: 'center',
120
- justifyContent: 'space-between'
121
- }, children: [_jsxs("span", { style: { fontWeight: 'bold', color: theme.colors.text }, children: [i + 1, ". ", step.title] }), _jsx("span", { style: { color: theme.colors.textSecondary }, children: step.description })] }, i))) })), viewMode === 'table' && (_jsx("div", { style: { overflowX: 'auto' }, children: _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: theme.typography.smallSize }, children: [!isPivoted && (_jsxs(_Fragment, { children: [_jsx("thead", { children: _jsxs("tr", { style: { backgroundColor: theme.colors.background, borderBottom: `2px solid ${theme.colors.primary}` }, children: [_jsx("th", { style: { padding: theme.spacing.small, textAlign: 'left' }, children: "Step #" }), _jsx("th", { style: { padding: theme.spacing.small, textAlign: 'left' }, children: "Title" }), _jsx("th", { style: { padding: theme.spacing.small, textAlign: 'left' }, children: "Description" })] }) }), _jsx("tbody", { children: groupSteps.map((step, i) => (_jsxs("tr", { style: { borderBottom: `1px solid ${theme.colors.border || '#eee'}` }, children: [_jsx("td", { style: { padding: theme.spacing.small }, children: i + 1 }), _jsx("td", { style: { padding: theme.spacing.small, fontWeight: 'bold' }, children: step.title }), _jsx("td", { style: { padding: theme.spacing.small, color: theme.colors.textSecondary }, children: step.description })] }, i))) })] })), isPivoted && (_jsxs("tbody", { children: [_jsxs("tr", { style: { borderBottom: `1px solid ${theme.colors.border || '#eee'}` }, children: [_jsx("th", { style: { padding: theme.spacing.small, textAlign: 'right', backgroundColor: theme.colors.background }, children: "Title" }), groupSteps.map((step, i) => (_jsx("td", { style: { padding: theme.spacing.small, fontWeight: 'bold', borderLeft: `1px solid ${theme.colors.border}` }, children: step.title }, i)))] }), _jsxs("tr", { children: [_jsx("th", { style: { padding: theme.spacing.small, textAlign: 'right', backgroundColor: theme.colors.background }, children: "Description" }), groupSteps.map((step, i) => (_jsx("td", { style: { padding: theme.spacing.small, color: theme.colors.textSecondary, borderLeft: `1px solid ${theme.colors.border}` }, children: step.description }, i)))] })] }))] }) }))] }, groupTitle))), processedData.length === 0 && (_jsxs("div", { style: { textAlign: 'center', padding: theme.spacing.large, color: theme.colors.textSecondary }, children: ["No results found for \"", filterText, "\""] }))] })] }));
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 { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, RadialBarChart, RadialBar, Treemap } from 'recharts';
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 chartData = views.chart.series[selected] || [];
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
- const renderChart = () => {
46
- switch (views.chart.chartType) {
47
- case 'radial_bar':
48
- return (_jsxs(RadialBarChart, { innerRadius: "30%", outerRadius: "90%", data: chartData, children: [_jsx(RadialBar, { dataKey: views.chart.yKey, fill: theme.colors.primary }), _jsx(Tooltip, {})] }));
49
- case 'treemap':
50
- return (_jsx(Treemap, { data: chartData, dataKey: views.chart.yKey, stroke: theme.colors.border, fill: theme.colors.primary, children: _jsx(Tooltip, {}) }));
51
- case 'line':
52
- default:
53
- return (_jsxs(LineChart, { data: chartData, children: [views.chart.xKey && _jsx(XAxis, { dataKey: views.chart.xKey, tick: { fill: theme.colors.textSecondary } }), _jsx(YAxis, { tick: { fill: theme.colors.textSecondary } }), _jsx(Tooltip, {}), _jsx(Line, { type: "monotone", dataKey: views.chart.yKey, stroke: theme.colors.primary, strokeWidth: 2, dot: false })] }));
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(ResponsiveContainer, { width: "100%", height: "100%", children: renderChart() }) })), (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: {
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,
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritanshu3011/mdx-renderer",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Reusable MDX visualization library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
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)