Skip to content

Commit 8fc4fb3

Browse files
committed
refactor: replace Radix UI components with simple React components
- Removed all Radix UI dependencies from package.json - Created simple replacements for Dialog, Tooltip, Dropdown, Label, Slot, etc. - Created a compatibility layer to minimize code changes - Updated all component imports to use the new non-Radix components - This should fix the useLayoutEffect and createContext errors in production
1 parent f3b2f62 commit 8fc4fb3

File tree

11 files changed

+215
-15
lines changed

11 files changed

+215
-15
lines changed

package.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@
1212
"deploy": "npm run build && gh-pages -d dist"
1313
},
1414
"dependencies": {
15-
"@radix-ui/react-dialog": "^1.1.6",
16-
"@radix-ui/react-dropdown-menu": "^2.1.6",
17-
"@radix-ui/react-label": "^2.1.2",
18-
"@radix-ui/react-popover": "^1.1.6",
19-
"@radix-ui/react-slot": "^1.1.2",
20-
"@radix-ui/react-tooltip": "^1.1.8",
2115
"better-auth": "^1.2.3",
2216
"class-variance-authority": "^0.7.1",
2317
"clsx": "^2.1.1",

src/components/modals/ShareEmailModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import React, { useState } from 'react';
4-
import { Dialog, DialogContent, DialogDescription } from '../ui/dialog';
4+
import { Dialog, DialogContent, DialogDescription } from '../ui/radix-compatibility';
55
import { Button } from '../ui/button';
66
import { useEntries } from '../../features/statements/hooks/useEntries';
77
import { sendEmail } from '../../features/email/api/emailApi';

src/components/ui/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { Slot } from '@radix-ui/react-slot';
2+
import { Slot } from './radix-compatibility';
33
import type { VariantProps } from 'class-variance-authority';
44

55
import { cn } from '@/lib/utils';

src/components/ui/confirmation-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
DialogContent,
55
DialogFooter,
66
DialogDescription,
7-
} from './dialog';
7+
} from './radix-compatibility';
88

99
interface ConfirmationDialogProps {
1010
isOpen: boolean;

src/components/ui/radix-compatibility.tsx

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ import {
1919
} from './simple-tooltip';
2020

2121
import { SimpleLabel } from './simple-label';
22+
import { Slot } from './simple-slot';
23+
24+
import {
25+
SimpleDropdownMenu,
26+
SimpleDropdownMenuTrigger,
27+
SimpleDropdownMenuContent,
28+
SimpleDropdownMenuItem,
29+
SimpleDropdownMenuSeparator,
30+
} from './simple-dropdown';
2231

2332
// Create compatibility layer
2433
interface DialogRootProps {
@@ -43,8 +52,20 @@ const Dialog: React.FC<DialogRootProps> = ({ children, open, onOpenChange }) =>
4352
);
4453
};
4554

46-
// Re-export with Radix-compatible names
47-
// Custom Tooltip components compatible with Radix API
55+
// Simple Popover implementation
56+
const Popover: React.FC<{children: React.ReactNode}> = ({ children }) => {
57+
return <>{children}</>;
58+
};
59+
60+
const PopoverTrigger: React.FC<{children: React.ReactNode, asChild?: boolean}> = ({ children }) => {
61+
return <>{children}</>;
62+
};
63+
64+
const PopoverContent: React.FC<{children: React.ReactNode, className?: string}> = ({ children, className }) => {
65+
return <div className={className}>{children}</div>;
66+
};
67+
68+
// Simple Tooltip implementation
4869
const Tooltip: React.FC<{children: React.ReactNode}> = ({ children }) => {
4970
return <>{children}</>;
5071
};
@@ -57,7 +78,9 @@ const TooltipContent: React.FC<{children: React.ReactNode, className?: string}>
5778
return <div className={className}>{children}</div>;
5879
};
5980

81+
// Export all components with Radix-compatible names
6082
export {
83+
// Dialog components
6184
Dialog,
6285
SimpleDialogTrigger as DialogTrigger,
6386
SimpleDialogContent as DialogContent,
@@ -69,10 +92,25 @@ export {
6992
SimpleDialogOverlay as DialogOverlay,
7093
SimpleDialogPortal as DialogPortal,
7194

95+
// Tooltip components
7296
SimpleTooltipProvider as TooltipProvider,
7397
Tooltip,
7498
TooltipTrigger,
7599
TooltipContent,
76100

101+
// Popover components
102+
Popover,
103+
PopoverTrigger,
104+
PopoverContent,
105+
106+
// Dropdown menu components
107+
SimpleDropdownMenu as DropdownMenu,
108+
SimpleDropdownMenuTrigger as DropdownMenuTrigger,
109+
SimpleDropdownMenuContent as DropdownMenuContent,
110+
SimpleDropdownMenuItem as DropdownMenuItem,
111+
SimpleDropdownMenuSeparator as DropdownMenuSeparator,
112+
113+
// Other components
77114
SimpleLabel as Label,
115+
Slot,
78116
};
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import { cn } from '@/lib/utils';
3+
4+
interface SimpleDropdownMenuProps {
5+
children: React.ReactNode;
6+
}
7+
8+
interface SimpleDropdownMenuTriggerProps {
9+
children: React.ReactNode;
10+
asChild?: boolean;
11+
}
12+
13+
interface SimpleDropdownMenuContentProps {
14+
children: React.ReactNode;
15+
className?: string;
16+
sideOffset?: number;
17+
[key: string]: any;
18+
}
19+
20+
interface SimpleDropdownMenuItemProps {
21+
children: React.ReactNode;
22+
className?: string;
23+
[key: string]: any;
24+
}
25+
26+
interface SimpleDropdownMenuSeparatorProps {
27+
className?: string;
28+
[key: string]: any;
29+
}
30+
31+
// The root dropdown component
32+
const SimpleDropdownMenu: React.FC<SimpleDropdownMenuProps> = ({ children }) => {
33+
const [isOpen, setIsOpen] = useState(false);
34+
const dropdownRef = useRef<HTMLDivElement>(null);
35+
36+
// Close the dropdown when clicking outside
37+
useEffect(() => {
38+
if (!isOpen) return;
39+
40+
const handleClickOutside = (event: MouseEvent) => {
41+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
42+
setIsOpen(false);
43+
}
44+
};
45+
46+
document.addEventListener('mousedown', handleClickOutside);
47+
return () => document.removeEventListener('mousedown', handleClickOutside);
48+
}, [isOpen]);
49+
50+
return (
51+
<div ref={dropdownRef} className="relative">
52+
{React.Children.map(children, (child) => {
53+
if (!React.isValidElement(child)) return child;
54+
55+
// Pass the open state and toggle function to the children
56+
if (child.type === SimpleDropdownMenuTrigger) {
57+
return React.cloneElement(child as React.ReactElement<any>, {
58+
onClick: () => setIsOpen(!isOpen),
59+
});
60+
}
61+
62+
if (child.type === SimpleDropdownMenuContent) {
63+
return isOpen ? child : null;
64+
}
65+
66+
return child;
67+
})}
68+
</div>
69+
);
70+
};
71+
72+
// The trigger button
73+
const SimpleDropdownMenuTrigger: React.FC<SimpleDropdownMenuTriggerProps> = ({
74+
children,
75+
asChild,
76+
...props
77+
}) => {
78+
return (
79+
<div {...props} className="cursor-pointer">
80+
{children}
81+
</div>
82+
);
83+
};
84+
85+
// The dropdown content container
86+
const SimpleDropdownMenuContent = React.forwardRef<HTMLDivElement, SimpleDropdownMenuContentProps>(
87+
({ children, className, sideOffset = 4, ...props }, ref) => {
88+
return (
89+
<div
90+
ref={ref}
91+
className={cn(
92+
'absolute z-50 min-w-[8rem] overflow-hidden rounded-md border bg-white p-2 shadow-md',
93+
'top-full right-0 mt-1',
94+
className
95+
)}
96+
{...props}
97+
>
98+
{children}
99+
</div>
100+
);
101+
}
102+
);
103+
SimpleDropdownMenuContent.displayName = 'SimpleDropdownMenuContent';
104+
105+
// Individual dropdown items
106+
const SimpleDropdownMenuItem = React.forwardRef<HTMLDivElement, SimpleDropdownMenuItemProps>(
107+
({ children, className, ...props }, ref) => {
108+
return (
109+
<div
110+
ref={ref}
111+
className={cn(
112+
'flex cursor-pointer items-center rounded-sm px-2 py-1 text-sm text-gray-700 hover:bg-gray-100',
113+
className
114+
)}
115+
{...props}
116+
>
117+
{children}
118+
</div>
119+
);
120+
}
121+
);
122+
SimpleDropdownMenuItem.displayName = 'SimpleDropdownMenuItem';
123+
124+
// Separator line
125+
const SimpleDropdownMenuSeparator = React.forwardRef<HTMLDivElement, SimpleDropdownMenuSeparatorProps>(
126+
({ className, ...props }, ref) => {
127+
return (
128+
<div
129+
ref={ref}
130+
className={cn('my-1 h-px bg-gray-200', className)}
131+
{...props}
132+
/>
133+
);
134+
}
135+
);
136+
SimpleDropdownMenuSeparator.displayName = 'SimpleDropdownMenuSeparator';
137+
138+
export {
139+
SimpleDropdownMenu,
140+
SimpleDropdownMenuTrigger,
141+
SimpleDropdownMenuContent,
142+
SimpleDropdownMenuItem,
143+
SimpleDropdownMenuSeparator,
144+
};

src/components/ui/simple-slot.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
3+
// A simple Slot component that just renders its children
4+
// This is a simplified version that doesn't try to do ref forwarding
5+
const Slot: React.FC<{
6+
children?: React.ReactNode;
7+
className?: string;
8+
[key: string]: any;
9+
}> = ({
10+
children,
11+
...props
12+
}) => {
13+
if (!children || !React.isValidElement(children)) {
14+
return null;
15+
}
16+
17+
// Clone the element with merged props
18+
return React.cloneElement(children, {
19+
...props,
20+
// We don't handle refs in this simplified version
21+
});
22+
};
23+
24+
export { Slot };

src/features/statements/components/ActionLine.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
DropdownMenuTrigger,
77
DropdownMenuContent,
88
DropdownMenuItem,
9-
} from '../../../components/ui/dropdown-menu';
9+
} from '../../../components/ui/radix-compatibility';
1010
import ActionForm from './ActionForm';
1111
import { ConfirmationDialog } from '../../../components/ui/confirmation-dialog';
1212
import type { Action } from '../../../types/entries';

src/features/statements/components/QuestionCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React from 'react';
44
import type { SetQuestion } from '../../../types/entries';
55
import { HelpCircle, ChevronRight, BellOff, Bell } from 'lucide-react';
66
import { cn } from '../../../lib/utils';
7-
import { Tooltip, TooltipTrigger, TooltipContent } from '../../../components/ui/tooltip';
7+
import { Tooltip, TooltipTrigger, TooltipContent } from '../../../components/ui/radix-compatibility';
88

99
export interface QuestionCardProps {
1010
presetQuestion: SetQuestion;

src/layouts/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// src/components/Header.tsx
22
import React, { useState } from 'react';
33
import { useEntries } from '../../features/statements/hooks/useEntries';
4-
import { Dialog, DialogTrigger } from '../../components/ui/dialog';
4+
import { Dialog, DialogTrigger } from '../../components/ui/radix-compatibility';
55
import SmallCircularQuestionCounter from '../../components/ui/questionCounter/smallCircularQuestionCounter';
66
import UserDataModal from '../../components/modals/UserDataModal';
77

0 commit comments

Comments
 (0)