|
1 | 1 | import React from "react";
|
2 | 2 | import { twJoin } from "tailwind-merge";
|
| 3 | +import { Portal } from "../Portal"; |
3 | 4 |
|
4 | 5 | interface MenuDrawerProps {
|
5 | 6 | isOpen: boolean;
|
@@ -36,73 +37,102 @@ export const MenuDrawer: React.FC<MenuDrawerProps> = ({
|
36 | 37 | right: "text-right",
|
37 | 38 | };
|
38 | 39 |
|
39 |
| - return ( |
40 |
| - <> |
41 |
| - {fullHeight && isOpen && onBackdropClick && ( |
42 |
| - <div className="fixed inset-0 z-40 bg-black/50 transition-opacity duration-300" onClick={onBackdropClick} /> |
43 |
| - )} |
44 |
| - |
| 40 | + const renderHeader = () => { |
| 41 | + if (!(showBackButton || title)) return null; |
| 42 | + return ( |
45 | 43 | <div
|
46 |
| - className={twJoin( |
47 |
| - "transform transition-transform duration-300 ease-in-out", |
48 |
| - "bg-[#FFFFFF] dark:bg-[#252525]", |
49 |
| - fullHeight |
50 |
| - ? twJoin("fixed inset-y-0 right-0 z-50", fullWidth ? "w-full" : "w-full max-w-sm") |
51 |
| - : "absolute inset-0 rounded-lg", |
52 |
| - isOpen ? "translate-x-0" : "translate-x-full", |
53 |
| - className, |
54 |
| - )} |
| 44 | + className={twJoin("flex items-center p-4", showDivider && "border-b border-[#38708533] dark:border-[#404040]")} |
55 | 45 | >
|
56 |
| - {/* Header */} |
57 |
| - {(showBackButton || title) && ( |
58 |
| - <div |
59 |
| - className={twJoin( |
60 |
| - "flex items-center p-4", |
61 |
| - showDivider && "border-b border-[#38708533] dark:border-[#404040]", |
62 |
| - )} |
| 46 | + {showBackButton && ( |
| 47 | + <button |
| 48 | + onClick={onClose} |
| 49 | + className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded transition-colors hover:bg-accent-secondary/10" |
| 50 | + aria-label="Go back" |
63 | 51 | >
|
64 |
| - {showBackButton && ( |
65 |
| - <button |
66 |
| - onClick={onClose} |
67 |
| - className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded transition-colors hover:bg-accent-secondary/10" |
68 |
| - aria-label="Go back" |
| 52 | + {backIcon || ( |
| 53 | + <svg |
| 54 | + width="16" |
| 55 | + height="16" |
| 56 | + viewBox="0 0 16 16" |
| 57 | + fill="none" |
| 58 | + xmlns="http://www.w3.org/2000/svg" |
| 59 | + className="text-accent-primary" |
69 | 60 | >
|
70 |
| - {backIcon || ( |
71 |
| - <svg |
72 |
| - width="16" |
73 |
| - height="16" |
74 |
| - viewBox="0 0 16 16" |
75 |
| - fill="none" |
76 |
| - xmlns="http://www.w3.org/2000/svg" |
77 |
| - className="text-accent-primary" |
78 |
| - > |
79 |
| - <path |
80 |
| - d="M10 12L6 8L10 4" |
81 |
| - stroke="currentColor" |
82 |
| - strokeWidth="2" |
83 |
| - strokeLinecap="round" |
84 |
| - strokeLinejoin="round" |
85 |
| - /> |
86 |
| - </svg> |
87 |
| - )} |
88 |
| - </button> |
| 61 | + <path |
| 62 | + d="M10 12L6 8L10 4" |
| 63 | + stroke="currentColor" |
| 64 | + strokeWidth="2" |
| 65 | + strokeLinecap="round" |
| 66 | + strokeLinejoin="round" |
| 67 | + /> |
| 68 | + </svg> |
89 | 69 | )}
|
90 |
| - {title && ( |
91 |
| - <h3 |
92 |
| - className={twJoin( |
93 |
| - "flex-1 text-sm font-medium text-accent-primary", |
94 |
| - showBackButton ? "ml-3" : "", |
95 |
| - titleAlignment[titleAlign], |
96 |
| - )} |
97 |
| - > |
98 |
| - {title} |
99 |
| - </h3> |
| 70 | + </button> |
| 71 | + )} |
| 72 | + {title && ( |
| 73 | + <h3 |
| 74 | + className={twJoin( |
| 75 | + "flex-1 text-sm font-medium text-accent-primary", |
| 76 | + showBackButton ? "ml-3" : "", |
| 77 | + titleAlignment[titleAlign], |
100 | 78 | )}
|
101 |
| - </div> |
| 79 | + > |
| 80 | + {title} |
| 81 | + </h3> |
102 | 82 | )}
|
103 |
| - |
104 |
| - <div className="flex-1 overflow-y-auto">{children}</div> |
105 | 83 | </div>
|
| 84 | + ); |
| 85 | + }; |
| 86 | + |
| 87 | + const renderContent = () => ( |
| 88 | + <> |
| 89 | + {renderHeader()} |
| 90 | + <div className="flex-1 overflow-y-auto">{children}</div> |
106 | 91 | </>
|
107 | 92 | );
|
| 93 | + |
| 94 | + // For fullHeight drawers (mobile), use Portal to ensure proper theme sync |
| 95 | + if (fullHeight) { |
| 96 | + return ( |
| 97 | + <Portal mounted={true} rootClassName="menu-drawer-portal"> |
| 98 | + {onBackdropClick && ( |
| 99 | + <div |
| 100 | + className={twJoin( |
| 101 | + "fixed inset-0 z-40 bg-black/50 transition-opacity duration-300", |
| 102 | + isOpen ? "opacity-100" : "pointer-events-none opacity-0", |
| 103 | + )} |
| 104 | + onClick={onBackdropClick} |
| 105 | + /> |
| 106 | + )} |
| 107 | + |
| 108 | + <div |
| 109 | + className={twJoin( |
| 110 | + "transform transition-transform duration-300 ease-in-out", |
| 111 | + "bg-[#FFFFFF] dark:bg-[#252525]", |
| 112 | + "fixed inset-y-0 right-0 z-50", |
| 113 | + fullWidth ? "w-full" : "w-full max-w-sm", |
| 114 | + isOpen ? "translate-x-0" : "translate-x-full", |
| 115 | + className, |
| 116 | + )} |
| 117 | + > |
| 118 | + {renderContent()} |
| 119 | + </div> |
| 120 | + </Portal> |
| 121 | + ); |
| 122 | + } |
| 123 | + |
| 124 | + // For non-fullHeight drawers (desktop), render normally |
| 125 | + return ( |
| 126 | + <div |
| 127 | + className={twJoin( |
| 128 | + "transform transition-transform duration-300 ease-in-out", |
| 129 | + "bg-[#FFFFFF] dark:bg-[#252525]", |
| 130 | + "absolute inset-0 rounded-lg", |
| 131 | + isOpen ? "translate-x-0" : "translate-x-full", |
| 132 | + className, |
| 133 | + )} |
| 134 | + > |
| 135 | + {renderContent()} |
| 136 | + </div> |
| 137 | + ); |
108 | 138 | };
|
0 commit comments