Skip to content

Commit a9bff5a

Browse files
committed
add Layout.tsx revisions
1 parent 3bd99df commit a9bff5a

4 files changed

Lines changed: 220 additions & 5 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// CREATE THIS FILE: client/src/components/FixedLucideIcon.tsx
2+
import React, { useEffect, useRef } from 'react';
3+
import { LucideProps } from 'lucide-react';
4+
5+
/**
6+
* Wrapper component that fixes Lucide React SVG className issues
7+
* Use this instead of importing Lucide icons directly
8+
*/
9+
export function FixedLucideIcon({
10+
children,
11+
className,
12+
...props
13+
}: {
14+
children: React.ReactElement,
15+
className?: string
16+
} & LucideProps) {
17+
const ref = useRef<HTMLElement>(null);
18+
19+
useEffect(() => {
20+
if (ref.current) {
21+
// Fix the SVG and all its children
22+
const svgElements = ref.current.querySelectorAll('svg, svg *');
23+
svgElements.forEach((el: Element) => {
24+
const htmlEl = el as HTMLElement;
25+
if (htmlEl.className && typeof htmlEl.className === 'object' && 'baseVal' in htmlEl.className) {
26+
const newClassName = (htmlEl.className as any).baseVal || '';
27+
htmlEl.setAttribute('class', newClassName);
28+
}
29+
});
30+
}
31+
});
32+
33+
// Clone the child element and add our ref
34+
const childWithRef = React.cloneElement(children, {
35+
ref,
36+
className: className || children.props.className,
37+
...props
38+
});
39+
40+
return childWithRef;
41+
}
42+
43+
// Alternative: Create a hook for existing components
44+
export function useLucideIconFix() {
45+
useEffect(() => {
46+
const fixSVGs = () => {
47+
const svgElements = document.querySelectorAll('svg.lucide, svg[class*="lucide"]');
48+
svgElements.forEach((el: Element) => {
49+
const htmlEl = el as HTMLElement;
50+
if (htmlEl.className && typeof htmlEl.className === 'object' && 'baseVal' in htmlEl.className) {
51+
const newClassName = (htmlEl.className as any).baseVal || '';
52+
htmlEl.setAttribute('class', newClassName);
53+
}
54+
55+
// Also fix children
56+
const children = htmlEl.querySelectorAll('*');
57+
children.forEach((child: Element) => {
58+
const childEl = child as HTMLElement;
59+
if (childEl.className && typeof childEl.className === 'object' && 'baseVal' in childEl.className) {
60+
const newClassName = (childEl.className as any).baseVal || '';
61+
childEl.setAttribute('class', newClassName);
62+
}
63+
});
64+
});
65+
};
66+
67+
// Fix immediately
68+
fixSVGs();
69+
70+
// Fix after any potential re-renders
71+
const timeoutId = setTimeout(fixSVGs, 100);
72+
73+
return () => clearTimeout(timeoutId);
74+
});
75+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// client/src/components/GlobalSVGMonitor.tsx
2+
import { useEffect } from 'react';
3+
4+
export function GlobalSVGMonitor() {
5+
useEffect(() => {
6+
console.log('[GlobalSVGMonitor] Starting SVG className monitoring...');
7+
8+
let totalFixed = 0;
9+
10+
// Function to fix SVG className objects
11+
const fixSVGClassNames = () => {
12+
const svgElements = document.querySelectorAll('svg, svg *');
13+
let batchFixed = 0;
14+
15+
svgElements.forEach((el) => {
16+
const htmlEl = el as HTMLElement;
17+
if (htmlEl.className &&
18+
typeof htmlEl.className === 'object' &&
19+
'baseVal' in htmlEl.className) {
20+
21+
const newClassName = (htmlEl.className as any).baseVal || '';
22+
htmlEl.setAttribute('class', newClassName);
23+
batchFixed++;
24+
totalFixed++;
25+
}
26+
});
27+
28+
if (batchFixed > 0) {
29+
console.log(`[GlobalSVGMonitor] Fixed ${batchFixed} SVG elements (total: ${totalFixed})`);
30+
}
31+
32+
return batchFixed;
33+
};
34+
35+
// Fix existing elements immediately
36+
const initialFixed = fixSVGClassNames();
37+
if (initialFixed > 0) {
38+
console.log(`[GlobalSVGMonitor] Initial fix: ${initialFixed} elements`);
39+
}
40+
41+
// Set up mutation observer for new elements
42+
const observer = new MutationObserver((mutations) => {
43+
let shouldFix = false;
44+
45+
mutations.forEach((mutation) => {
46+
if (mutation.type === 'childList') {
47+
mutation.addedNodes.forEach((node) => {
48+
if (node.nodeType === Node.ELEMENT_NODE) {
49+
const element = node as Element;
50+
51+
// Check if it's an SVG or contains SVGs
52+
if (element.tagName === 'SVG' ||
53+
element.querySelector?.('svg') ||
54+
element.classList?.contains('lucide')) {
55+
shouldFix = true;
56+
}
57+
}
58+
});
59+
}
60+
});
61+
62+
if (shouldFix) {
63+
// Small delay to let React finish rendering
64+
setTimeout(fixSVGClassNames, 10);
65+
}
66+
});
67+
68+
// Start observing
69+
observer.observe(document.body, {
70+
childList: true,
71+
subtree: true
72+
});
73+
74+
// Also run periodic checks to catch anything we missed
75+
const intervalId = setInterval(() => {
76+
fixSVGClassNames();
77+
}, 2000);
78+
79+
console.log('[GlobalSVGMonitor] SVG monitoring active');
80+
81+
// Cleanup function
82+
return () => {
83+
observer.disconnect();
84+
clearInterval(intervalId);
85+
console.log(`[GlobalSVGMonitor] Stopped. Total fixed: ${totalFixed}`);
86+
};
87+
}, []);
88+
89+
return null; // This component renders nothing
90+
}

client/src/components/Layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ export default function Layout({ children }: LayoutProps) {
3737
{/* Header */}
3838
<Header toggleMobileMenu={toggleMobileMenu} />
3939

40-
{/* Main Content Area */}
41-
<main className="flex-grow overflow-y-auto p-4 bg-slate-50 dark:bg-gray-800">
40+
{/* Main Content Area - FIXED */}
41+
<main className="flex-1 overflow-y-auto p-4 bg-slate-50 dark:bg-gray-800 min-h-0">
4242
{children}
4343
</main>
4444
</div>

client/src/lib/utils.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,58 @@
11
import { type ClassValue, clsx } from "clsx"
22
import { twMerge } from "tailwind-merge"
3-
4-
export function cn(...inputs: ClassValue[]) {
5-
return twMerge(clsx(inputs))
3+
4+
/**
5+
* Safely converts any className value to a string
6+
* Handles objects, arrays, and other non-string values
7+
*/
8+
export function safeClassName(className: any): string {
9+
if (typeof className === 'string') {
10+
return className;
11+
}
12+
13+
if (className === null || className === undefined) {
14+
return '';
15+
}
16+
17+
// Handle SVGAnimatedString (the main culprit)
18+
if (className && typeof className === 'object' && 'baseVal' in className) {
19+
return className.baseVal || '';
20+
}
21+
22+
// Handle arrays
23+
if (Array.isArray(className)) {
24+
return className.map(safeClassName).filter(Boolean).join(' ');
25+
}
26+
27+
// Handle objects (CSS modules, styled-components)
28+
if (typeof className === 'object') {
29+
return Object.keys(className)
30+
.filter(key => className[key])
31+
.join(' ');
32+
}
33+
34+
// Handle functions, numbers, booleans
35+
if (typeof className === 'function') {
36+
return '';
37+
}
38+
39+
return String(className);
40+
}
41+
42+
/**
43+
* Enhanced cn function that safely handles all className types
44+
* This prevents the "className.includes is not a function" error
45+
*/
46+
export function cn(...inputs: ClassValue[]): string {
47+
// First, make all inputs safe
48+
const safeInputs = inputs.map(input => {
49+
if (typeof input === 'string' || typeof input === 'undefined' || input === null) {
50+
return input;
51+
}
52+
return safeClassName(input);
53+
});
54+
55+
return twMerge(clsx(safeInputs));
656
}
757

858
export function debounce<T extends (...args: any[]) => any>(

0 commit comments

Comments
 (0)