Skip to content

Commit 4884af4

Browse files
committed
trying a table solution
1 parent 9df0dbd commit 4884af4

File tree

2 files changed

+210
-2
lines changed

2 files changed

+210
-2
lines changed

components/ResponsiveTable.tsx

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/* eslint-disable prefer-const */
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
import React, { useState, useEffect, ReactNode } from 'react';
4+
5+
interface ResponsiveTableProps {
6+
children: ReactNode;
7+
maxMobileColumns?: number;
8+
}
9+
10+
interface TableCellData {
11+
content: ReactNode;
12+
isHeader: boolean;
13+
}
14+
15+
interface TableRowData {
16+
cells: TableCellData[];
17+
}
18+
19+
const ResponsiveTable: React.FC<ResponsiveTableProps> = ({
20+
children,
21+
maxMobileColumns = 3,
22+
}) => {
23+
const [isMobile, setIsMobile] = useState(false);
24+
const [tableData, setTableData] = useState<{
25+
headers: ReactNode[];
26+
rows: ReactNode[][];
27+
columnCount: number;
28+
} | null>(null);
29+
30+
useEffect(() => {
31+
const checkIsMobile = () => {
32+
setIsMobile(window.innerWidth < 768);
33+
};
34+
35+
checkIsMobile();
36+
window.addEventListener('resize', checkIsMobile);
37+
return () => window.removeEventListener('resize', checkIsMobile);
38+
}, []);
39+
40+
useEffect(() => {
41+
const extractTableData = () => {
42+
try {
43+
let headers: ReactNode[] = [];
44+
let rows: ReactNode[][] = [];
45+
let columnCount = 0;
46+
47+
// Helper function to extract cell content recursively
48+
const extractCellContent = (element: any): ReactNode => {
49+
if (typeof element === 'string') return element;
50+
if (React.isValidElement(element)) {
51+
return element;
52+
}
53+
if (Array.isArray(element)) {
54+
return element.map((item, index) => (
55+
<span key={index}>{extractCellContent(item)}</span>
56+
));
57+
}
58+
return element;
59+
};
60+
61+
// Function to process table rows
62+
const processTableRows = (tableChildren: any[]) => {
63+
tableChildren.forEach((child: any) => {
64+
if (React.isValidElement(child)) {
65+
if (child.type === 'thead') {
66+
// Process header
67+
const theadChildren = React.Children.toArray(
68+
(child as React.ReactElement).props.children,
69+
);
70+
theadChildren.forEach((tr: any) => {
71+
if (
72+
React.isValidElement(tr) &&
73+
(tr as React.ReactElement).props.children
74+
) {
75+
const thElements = React.Children.toArray(
76+
(tr as React.ReactElement).props.children,
77+
);
78+
headers = thElements.map((th: any) =>
79+
extractCellContent(th.props?.children),
80+
);
81+
columnCount = Math.max(columnCount, headers.length);
82+
}
83+
});
84+
} else if (child.type === 'tbody') {
85+
// Process body rows
86+
const tbodyChildren = React.Children.toArray(
87+
(child as React.ReactElement).props.children,
88+
);
89+
tbodyChildren.forEach((tr: any) => {
90+
if (
91+
React.isValidElement(tr) &&
92+
(tr as React.ReactElement).props.children
93+
) {
94+
const tdElements = React.Children.toArray(
95+
(tr as React.ReactElement).props.children,
96+
);
97+
const rowData = tdElements.map((td: any) =>
98+
extractCellContent(td.props?.children),
99+
);
100+
rows.push(rowData);
101+
columnCount = Math.max(columnCount, rowData.length);
102+
}
103+
});
104+
} else if (child.type === 'tr') {
105+
// Direct tr elements (no tbody)
106+
const cellElements = React.Children.toArray(
107+
(child as React.ReactElement).props.children,
108+
);
109+
const rowData = cellElements.map((cell: any) =>
110+
extractCellContent(cell.props?.children),
111+
);
112+
113+
// If this is likely a header row (first row and no explicit thead)
114+
if (headers.length === 0 && rows.length === 0) {
115+
headers = rowData;
116+
} else {
117+
rows.push(rowData);
118+
}
119+
columnCount = Math.max(columnCount, rowData.length);
120+
}
121+
}
122+
});
123+
};
124+
125+
// Start processing from the table element
126+
const processChildren = (elements: any) => {
127+
React.Children.forEach(elements, (child: any) => {
128+
if (React.isValidElement(child)) {
129+
if (child.type === 'table') {
130+
processTableRows(
131+
React.Children.toArray(
132+
(child as React.ReactElement).props.children,
133+
),
134+
);
135+
} else if (
136+
child &&
137+
typeof child === 'object' &&
138+
child.props &&
139+
typeof child.props === 'object' &&
140+
'children' in child.props &&
141+
child.props.children
142+
) {
143+
processChildren(child.props.children);
144+
}
145+
}
146+
});
147+
};
148+
149+
processChildren(children);
150+
151+
setTableData({ headers, rows, columnCount });
152+
} catch (error) {
153+
console.warn('Error extracting table data:', error);
154+
setTableData(null);
155+
}
156+
};
157+
158+
extractTableData();
159+
}, [children]);
160+
161+
// Render as cards on mobile if table has too many columns
162+
if (isMobile && tableData && tableData.columnCount > maxMobileColumns) {
163+
return (
164+
<div className='mb-8 space-y-3'>
165+
{tableData.rows.map((row, rowIndex) => (
166+
<div
167+
key={rowIndex}
168+
className='border border-slate-200 dark:border-slate-600 rounded-lg p-4 bg-white dark:bg-slate-800 shadow-sm'
169+
>
170+
{tableData.headers.map((header, colIndex) => {
171+
const cellContent = row[colIndex];
172+
const hasContent =
173+
cellContent &&
174+
(typeof cellContent === 'string' ? cellContent.trim() : true);
175+
176+
return (
177+
<div key={colIndex} className='mb-3 last:mb-0'>
178+
<div className='font-semibold text-sm text-black dark:text-white mb-1 border-b border-slate-100 dark:border-slate-700 pb-1'>
179+
{header}
180+
</div>
181+
<div className='text-slate-600 dark:text-slate-300 leading-7'>
182+
{hasContent ? (
183+
cellContent
184+
) : (
185+
<span className='text-slate-400 dark:text-slate-500'>
186+
187+
</span>
188+
)}
189+
</div>
190+
</div>
191+
);
192+
})}
193+
</div>
194+
))}
195+
</div>
196+
);
197+
}
198+
199+
// Render normal table (default behavior)
200+
return (
201+
<div className='max-w-[100%] mx-auto mb-8 overflow-auto'>
202+
<table className='table-auto w-full'>{children}</table>
203+
</div>
204+
);
205+
};
206+
207+
export default ResponsiveTable;

components/StyledMarkdownBlock.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import classnames from 'classnames';
1818
import { checkHasContent } from '~/lib/markdownUtils';
1919
import { TableOfContent } from '~/components/TableOfContentMarkdown';
20+
import ResponsiveTable from '~/components/ResponsiveTable';
2021

2122
interface StyledMarkdownBlockProps {
2223
markdown: string;
@@ -124,9 +125,9 @@ export const StyledMarkdownBlock = ({ markdown }: StyledMarkdownBlockProps) => {
124125
},
125126
table: {
126127
component: ({ children }) => (
127-
<div className='max-w-[100%] mx-auto mb-8 overflow-auto'>
128+
<ResponsiveTable maxMobileColumns={3}>
128129
<table className='table-auto'>{children}</table>
129-
</div>
130+
</ResponsiveTable>
130131
),
131132
},
132133
thead: {

0 commit comments

Comments
 (0)