fix(frontend): extend h5 tabbar safe-area coverage (#197)

Ensure the PWA bottom tab bar and tab hit areas fully cover the safe-area inset while keeping related frontend formatting updates consistent.
This commit is contained in:
sagit
2026-02-23 15:45:14 +08:00
committed by GitHub
parent e121dadb90
commit 191aface2e
4 changed files with 173 additions and 121 deletions
+49 -19
View File
@@ -50,7 +50,9 @@ export default function AdminLayout({
const { isOpen, onOpen, onOpenChange } = useDisclosure(); const { isOpen, onOpen, onOpenChange } = useDisclosure();
const [mobileMenuVisible, setMobileMenuVisible] = useState(false); const [mobileMenuVisible, setMobileMenuVisible] = useState(false);
const [isCollapsed, setIsCollapsed] = useState(() => localStorage.getItem("sidebar_collapsed") === "true"); const [isCollapsed, setIsCollapsed] = useState(
() => localStorage.getItem("sidebar_collapsed") === "true",
);
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [passwordLoading, setPasswordLoading] = useState(false); const [passwordLoading, setPasswordLoading] = useState(false);
@@ -208,6 +210,7 @@ export default function AdminLayout({
// 切换折叠状态 // 切换折叠状态
const toggleCollapse = () => { const toggleCollapse = () => {
const newCollapsed = !isCollapsed; const newCollapsed = !isCollapsed;
setIsCollapsed(newCollapsed); setIsCollapsed(newCollapsed);
localStorage.setItem("sidebar_collapsed", newCollapsed.toString()); localStorage.setItem("sidebar_collapsed", newCollapsed.toString());
}; };
@@ -328,11 +331,15 @@ export default function AdminLayout({
<div className="flex-shrink-0 flex items-center justify-center w-10"> <div className="flex-shrink-0 flex items-center justify-center w-10">
<Logo size={28} /> <Logo size={28} />
</div> </div>
<div className={`transition-all duration-300 overflow-hidden ${isCollapsed ? "max-w-0 opacity-0 ml-0" : "max-w-[180px] opacity-100 ml-2"}`}> <div
className={`transition-all duration-300 overflow-hidden ${isCollapsed ? "max-w-0 opacity-0 ml-0" : "max-w-[180px] opacity-100 ml-2"}`}
>
<h1 className="text-sm font-bold text-foreground overflow-hidden whitespace-nowrap text-ellipsis"> <h1 className="text-sm font-bold text-foreground overflow-hidden whitespace-nowrap text-ellipsis">
{siteConfig.name} {siteConfig.name}
</h1> </h1>
<p className="text-xs text-default-500 overflow-hidden whitespace-nowrap text-ellipsis">v{siteConfig.version}</p> <p className="text-xs text-default-500 overflow-hidden whitespace-nowrap text-ellipsis">
v{siteConfig.version}
</p>
</div> </div>
</div> </div>
@@ -345,15 +352,16 @@ export default function AdminLayout({
return ( return (
<li key={item.path}> <li key={item.path}>
<motion.button <motion.button
title={isCollapsed ? item.label : undefined}
className={` className={`
w-full flex items-center p-2 rounded-lg text-left w-full flex items-center p-2 rounded-lg text-left
relative min-h-[44px] overflow-hidden transition-colors relative min-h-[44px] overflow-hidden transition-colors
${isActive ${
? "text-primary-600 dark:text-primary-300" isActive
: "text-gray-700 dark:text-gray-200" ? "text-primary-600 dark:text-primary-300"
} : "text-gray-700 dark:text-gray-200"
}
`} `}
title={isCollapsed ? item.label : undefined}
transition={{ duration: 0.15 }} transition={{ duration: 0.15 }}
onClick={() => handleMenuClick(item.path)} onClick={() => handleMenuClick(item.path)}
> >
@@ -378,7 +386,9 @@ export default function AdminLayout({
<div className="flex-shrink-0 w-10 h-10 flex items-center justify-center relative z-10"> <div className="flex-shrink-0 w-10 h-10 flex items-center justify-center relative z-10">
{item.icon} {item.icon}
</div> </div>
<div className={`transition-all duration-300 overflow-hidden flex items-center ${isCollapsed ? "max-w-0 opacity-0 ml-0" : "max-w-[200px] opacity-100 ml-2"}`}> <div
className={`transition-all duration-300 overflow-hidden flex items-center ${isCollapsed ? "max-w-0 opacity-0 ml-0" : "max-w-[200px] opacity-100 ml-2"}`}
>
<span className="font-medium text-sm relative z-10 whitespace-nowrap"> <span className="font-medium text-sm relative z-10 whitespace-nowrap">
{item.label} {item.label}
</span> </span>
@@ -392,7 +402,9 @@ export default function AdminLayout({
{/* 底部版权信息和折叠按钮 */} {/* 底部版权信息和折叠按钮 */}
<div className="px-5 py-2 pb-4 mt-auto flex-shrink-0 flex items-center justify-between overflow-hidden whitespace-nowrap box-border"> <div className="px-5 py-2 pb-4 mt-auto flex-shrink-0 flex items-center justify-between overflow-hidden whitespace-nowrap box-border">
<div className={`transition-all duration-300 overflow-hidden flex items-center ${isCollapsed ? "max-w-0 opacity-0" : "max-w-[200px] opacity-100"}`}> <div
className={`transition-all duration-300 overflow-hidden flex items-center ${isCollapsed ? "max-w-0 opacity-0" : "max-w-[200px] opacity-100"}`}
>
<p className="text-xs text-gray-400 dark:text-gray-500"> <p className="text-xs text-gray-400 dark:text-gray-500">
Powered by{" "} Powered by{" "}
<a <a
@@ -410,20 +422,40 @@ export default function AdminLayout({
{!isMobile && ( {!isMobile && (
<Button <Button
isIconOnly isIconOnly
variant="light"
size="sm"
className="flex-shrink-0 text-gray-400 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-300 min-w-0 w-10 h-10 rounded-full ml-auto" className="flex-shrink-0 text-gray-400 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-300 min-w-0 w-10 h-10 rounded-full ml-auto"
size="sm"
variant="light"
onPress={toggleCollapse} onPress={toggleCollapse}
> >
{isCollapsed ? ( {isCollapsed ? (
// 向右扩展的提示 // 向右扩展的提示
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 5l7 7-7 7M5 5l7 7-7 7" /> className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
d="M13 5l7 7-7 7M5 5l7 7-7 7"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
/>
</svg> </svg>
) : ( ) : (
// 向左收起的提示 // 向左收起的提示
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 19l-7-7 7-7m8 14l-7-7 7-7" /> className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
/>
</svg> </svg>
)} )}
</Button> </Button>
@@ -532,9 +564,7 @@ export default function AdminLayout({
</header> </header>
{/* 主内容 */} {/* 主内容 */}
<main <main className="flex-1 bg-gray-100 dark:bg-black overflow-y-auto">
className="flex-1 bg-gray-100 dark:bg-black overflow-y-auto"
>
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
key={location.pathname} key={location.pathname}
+3 -3
View File
@@ -115,10 +115,10 @@ export default function H5Layout({ children }: { children: React.ReactNode }) {
<main className="flex-1 bg-gray-100 dark:bg-black">{children}</main> <main className="flex-1 bg-gray-100 dark:bg-black">{children}</main>
{/* 用于给固定 Tabbar 腾出空间的占位元素 */} {/* 用于给固定 Tabbar 腾出空间的占位元素 */}
<div aria-hidden className="h-16 safe-bottom" /> <div aria-hidden className="h-[calc(4rem+var(--safe-area-bottom))]" />
{/* 底部Tabbar */} {/* 底部Tabbar */}
<nav className="bg-white dark:bg-black border-t border-gray-200 dark:border-gray-600 h-16 safe-bottom flex-shrink-0 flex items-center justify-around px-2 fixed bottom-0 left-0 right-0 z-30"> <nav className="bg-white dark:bg-black border-t border-gray-200 dark:border-gray-600 h-[calc(4rem+var(--safe-area-bottom))] flex-shrink-0 flex items-center justify-around px-2 fixed bottom-0 left-0 right-0 z-30">
{filteredTabItems.map((item) => { {filteredTabItems.map((item) => {
const isActive = location.pathname === item.path; const isActive = location.pathname === item.path;
@@ -126,7 +126,7 @@ export default function H5Layout({ children }: { children: React.ReactNode }) {
<button <button
key={item.path} key={item.path}
className={` className={`
flex flex-col items-center justify-center flex-1 h-full flex flex-col items-center justify-center flex-1 h-full pb-[var(--safe-area-bottom)]
transition-colors duration-200 min-h-[44px] transition-colors duration-200 min-h-[44px]
${ ${
isActive isActive
+111 -97
View File
@@ -230,7 +230,7 @@ export default function ForwardPage() {
setViewMode(newMode); setViewMode(newMode);
try { try {
localStorage.setItem("forward-view-mode", newMode); localStorage.setItem("forward-view-mode", newMode);
} catch { } } catch {}
}; };
// 加载所有数据 // 加载所有数据
@@ -1196,10 +1196,10 @@ export default function ForwardPage() {
const style: React.CSSProperties = { const style: React.CSSProperties = {
transform: transform transform: transform
? CSS.Transform.toString({ ? CSS.Transform.toString({
...transform, ...transform,
x: Math.round(transform.x), x: Math.round(transform.x),
y: Math.round(transform.y), y: Math.round(transform.y),
}) })
: undefined, : undefined,
transition: isDragging ? undefined : transition || undefined, transition: isDragging ? undefined : transition || undefined,
opacity: isDragging ? 0.5 : 1, opacity: isDragging ? 0.5 : 1,
@@ -1257,10 +1257,10 @@ export default function ForwardPage() {
const style = { const style = {
transform: transform transform: transform
? CSS.Transform.toString({ ? CSS.Transform.toString({
...transform, ...transform,
x: Math.round(transform.x), x: Math.round(transform.x),
y: Math.round(transform.y), y: Math.round(transform.y),
}) })
: undefined, : undefined,
transition: isDragging ? undefined : transition || undefined, transition: isDragging ? undefined : transition || undefined,
opacity: isDragging ? 0.5 : 1, opacity: isDragging ? 0.5 : 1,
@@ -1317,10 +1317,11 @@ export default function ForwardPage() {
</TableCell> </TableCell>
<TableCell className="max-w-[220px]"> <TableCell className="max-w-[220px]">
<button <button
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${hasMultipleAddresses(forward.inIp) className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${
? "hover:bg-default-200 hover:shadow-sm" hasMultipleAddresses(forward.inIp)
: "" ? "hover:bg-default-200 hover:shadow-sm"
}`} : ""
}`}
title={formatInAddress(forward.inIp, forward.inPort)} title={formatInAddress(forward.inIp, forward.inPort)}
type="button" type="button"
onClick={() => onClick={() =>
@@ -1332,10 +1333,11 @@ export default function ForwardPage() {
</TableCell> </TableCell>
<TableCell className="max-w-[240px]"> <TableCell className="max-w-[240px]">
<button <button
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${hasMultipleAddresses(forward.remoteAddr) className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${
? "hover:bg-default-200 hover:shadow-sm" hasMultipleAddresses(forward.remoteAddr)
: "" ? "hover:bg-default-200 hover:shadow-sm"
}`} : ""
}`}
title={formatRemoteAddress(forward.remoteAddr)} title={formatRemoteAddress(forward.remoteAddr)}
type="button" type="button"
onClick={() => onClick={() =>
@@ -1472,10 +1474,11 @@ export default function ForwardPage() {
<div className="flex items-center gap-1.5 ml-2"> <div className="flex items-center gap-1.5 ml-2">
{viewMode === "direct" && ( {viewMode === "direct" && (
<div <div
className={`cursor-grab active:cursor-grabbing p-2 text-default-400 hover:text-default-600 transition-colors touch-manipulation ${isMobile className={`cursor-grab active:cursor-grabbing p-2 text-default-400 hover:text-default-600 transition-colors touch-manipulation ${
? "opacity-100" // 移动端始终显示 isMobile
: "opacity-0 group-hover:opacity-100 sm:opacity-0 sm:group-hover:opacity-100" ? "opacity-100" // 移动端始终显示
}`} : "opacity-0 group-hover:opacity-100 sm:opacity-0 sm:group-hover:opacity-100"
}`}
{...listeners} {...listeners}
style={{ touchAction: "none" }} style={{ touchAction: "none" }}
title={isMobile ? "长按拖拽排序" : "拖拽排序"} title={isMobile ? "长按拖拽排序" : "拖拽排序"}
@@ -1513,10 +1516,11 @@ export default function ForwardPage() {
{/* 地址信息 */} {/* 地址信息 */}
<div className="space-y-1"> <div className="space-y-1">
<button <button
className={`cursor-pointer px-2 py-1 bg-default-50 dark:bg-default-100/50 rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${hasMultipleAddresses(forward.inIp) className={`cursor-pointer px-2 py-1 bg-default-50 dark:bg-default-100/50 rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${
? "hover:bg-default-100 dark:hover:bg-default-200/50" hasMultipleAddresses(forward.inIp)
: "" ? "hover:bg-default-100 dark:hover:bg-default-200/50"
}`} : ""
}`}
title={formatInAddress(forward.inIp, forward.inPort)} title={formatInAddress(forward.inIp, forward.inPort)}
type="button" type="button"
onClick={() => onClick={() =>
@@ -1552,10 +1556,11 @@ export default function ForwardPage() {
</button> </button>
<button <button
className={`cursor-pointer px-2 py-1 bg-default-50 dark:bg-default-100/50 rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${hasMultipleAddresses(forward.remoteAddr) className={`cursor-pointer px-2 py-1 bg-default-50 dark:bg-default-100/50 rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${
? "hover:bg-default-100 dark:hover:bg-default-200/50" hasMultipleAddresses(forward.remoteAddr)
: "" ? "hover:bg-default-100 dark:hover:bg-default-200/50"
}`} : ""
}`}
title={formatRemoteAddress(forward.remoteAddr)} title={formatRemoteAddress(forward.remoteAddr)}
type="button" type="button"
onClick={() => onClick={() =>
@@ -1973,39 +1978,39 @@ export default function ForwardPage() {
</Card> </Card>
) )
) : /* 直接显示模式 */ ) : /* 直接显示模式 */
forwards.length > 0 ? ( forwards.length > 0 ? (
<DndContext <DndContext
collisionDetection={closestCenter} collisionDetection={closestCenter}
sensors={sensors} sensors={sensors}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
onDragStart={() => { }} // 添加空的 onDragStart 处理器 onDragStart={() => {}} // 添加空的 onDragStart 处理器
>
<SortableContext
items={sortableForwardIds}
strategy={rectSortingStrategy}
> >
<SortableContext <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
items={sortableForwardIds} {sortedForwards.map((forward) =>
strategy={rectSortingStrategy} forward && forward.id ? (
> <SortableForwardCard key={forward.id} forward={forward} />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4"> ) : null,
{sortedForwards.map((forward) => )}
forward && forward.id ? ( </div>
<SortableForwardCard key={forward.id} forward={forward} /> </SortableContext>
) : null, </DndContext>
)} ) : (
</div> /* 空状态 */
</SortableContext> <Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50">
</DndContext> <CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
) : ( <h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
/* 空状态 */ 暂无转发配置
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50"> </h3>
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]"> <p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed">
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2"> 还没有创建任何转发配置,点击上方按钮开始创建
暂无转发配置 </p>
</h3> </CardBody>
<p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed"> </Card>
还没有创建任何转发配置,点击上方按钮开始创建 )}
</p>
</CardBody>
</Card>
)}
{/* 新增/编辑模态框 */} {/* 新增/编辑模态框 */}
<Modal <Modal
@@ -2474,10 +2479,11 @@ export default function ForwardPage() {
{importResults.map((result, index) => ( {importResults.map((result, index) => (
<div <div
key={index} key={index}
className={`p-2 rounded border ${result.success className={`p-2 rounded border ${
? "bg-success-50 dark:bg-success-100/10 border-success-200 dark:border-success-300/20" result.success
: "bg-danger-50 dark:bg-danger-100/10 border-danger-200 dark:border-danger-300/20" ? "bg-success-50 dark:bg-success-100/10 border-success-200 dark:border-success-300/20"
}`} : "bg-danger-50 dark:bg-danger-100/10 border-danger-200 dark:border-danger-300/20"
}`}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{result.success ? ( {result.success ? (
@@ -2510,10 +2516,11 @@ export default function ForwardPage() {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-0.5"> <div className="flex items-center gap-2 mb-0.5">
<span <span
className={`text-xs font-medium ${result.success className={`text-xs font-medium ${
? "text-success-700 dark:text-success-300" result.success
: "text-danger-700 dark:text-danger-300" ? "text-success-700 dark:text-success-300"
}`} : "text-danger-700 dark:text-danger-300"
}`}
> >
{result.success ? "成功" : "失败"} {result.success ? "成功" : "失败"}
</span> </span>
@@ -2525,10 +2532,11 @@ export default function ForwardPage() {
</code> </code>
</div> </div>
<div <div
className={`text-xs ${result.success className={`text-xs ${
? "text-success-600 dark:text-success-400" result.success
: "text-danger-600 dark:text-danger-400" ? "text-success-600 dark:text-success-400"
}`} : "text-danger-600 dark:text-danger-400"
}`}
> >
{result.message} {result.message}
</div> </div>
@@ -2711,18 +2719,20 @@ export default function ForwardPage() {
return ( return (
<tr <tr
key={index} key={index}
className={`hover:bg-default-50 dark:hover:bg-gray-700/50 ${result.success className={`hover:bg-default-50 dark:hover:bg-gray-700/50 ${
? "bg-white dark:bg-gray-800" result.success
: "bg-danger-50 dark:bg-danger-900/30" ? "bg-white dark:bg-gray-800"
}`} : "bg-danger-50 dark:bg-danger-900/30"
}`}
> >
<td className="px-3 py-2"> <td className="px-3 py-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span <span
className={`w-5 h-5 rounded-full flex items-center justify-center text-xs ${result.success className={`w-5 h-5 rounded-full flex items-center justify-center text-xs ${
? "bg-success text-white" result.success
: "bg-danger text-white" ? "bg-success text-white"
}`} : "bg-danger text-white"
}`}
> >
{result.success ? "✓" : "✗"} {result.success ? "✓" : "✗"}
</span> </span>
@@ -2764,10 +2774,11 @@ export default function ForwardPage() {
<td className="px-3 py-2 text-center"> <td className="px-3 py-2 text-center">
{result.success ? ( {result.success ? (
<span <span
className={`font-semibold ${(result.packetLoss || 0) > 0 className={`font-semibold ${
? "text-warning" (result.packetLoss || 0) > 0
: "text-success" ? "text-warning"
}`} : "text-success"
}`}
> >
{result.packetLoss?.toFixed(1)}% {result.packetLoss?.toFixed(1)}%
</span> </span>
@@ -2881,17 +2892,19 @@ export default function ForwardPage() {
return ( return (
<div <div
key={index} key={index}
className={`border rounded-lg p-3 ${result.success className={`border rounded-lg p-3 ${
? "border-divider bg-white dark:bg-gray-800" result.success
: "border-danger-200 dark:border-danger-300/30 bg-danger-50 dark:bg-danger-900/30" ? "border-divider bg-white dark:bg-gray-800"
}`} : "border-danger-200 dark:border-danger-300/30 bg-danger-50 dark:bg-danger-900/30"
}`}
> >
<div className="flex items-start gap-2 mb-2"> <div className="flex items-start gap-2 mb-2">
<span <span
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs flex-shrink-0 ${result.success className={`w-6 h-6 rounded-full flex items-center justify-center text-xs flex-shrink-0 ${
? "bg-success text-white" result.success
: "bg-danger text-white" ? "bg-success text-white"
}`} : "bg-danger text-white"
}`}
> >
{result.success ? "✓" : "✗"} {result.success ? "✓" : "✗"}
</span> </span>
@@ -2927,10 +2940,11 @@ export default function ForwardPage() {
</div> </div>
<div className="text-center"> <div className="text-center">
<div <div
className={`text-lg font-bold ${(result.packetLoss || 0) > 0 className={`text-lg font-bold ${
? "text-warning" (result.packetLoss || 0) > 0
: "text-success" ? "text-warning"
}`} : "text-success"
}`}
> >
{result.packetLoss?.toFixed(1)}% {result.packetLoss?.toFixed(1)}%
</div> </div>
+10 -2
View File
@@ -674,7 +674,11 @@ export default function PanelSharingPage() {
</Tabs> </Tabs>
{/* Create Share Modal */} {/* Create Share Modal */}
<Modal isOpen={createShareOpen} onClose={() => setCreateShareOpen(false)} scrollBehavior="inside"> <Modal
isOpen={createShareOpen}
scrollBehavior="inside"
onClose={() => setCreateShareOpen(false)}
>
<ModalContent> <ModalContent>
<ModalHeader>创建分享</ModalHeader> <ModalHeader>创建分享</ModalHeader>
<ModalBody> <ModalBody>
@@ -777,7 +781,11 @@ export default function PanelSharingPage() {
</Modal> </Modal>
{/* Edit Share Modal */} {/* Edit Share Modal */}
<Modal isOpen={editShareOpen} onClose={() => setEditShareOpen(false)} scrollBehavior="inside"> <Modal
isOpen={editShareOpen}
scrollBehavior="inside"
onClose={() => setEditShareOpen(false)}
>
<ModalContent> <ModalContent>
<ModalHeader>编辑分享</ModalHeader> <ModalHeader>编辑分享</ModalHeader>
<ModalBody> <ModalBody>