Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | 132x 132x 132x 132x 148x 265x 1x 25x 15x 14x 1x 1x 528x 528x | import { type ReactNode, useRef, useState } from "react";
import { useFocusTrap } from "../hooks";
import { InboxIcon, SendIcon, SettingsIcon, ShieldIcon, XIcon } from "./Icons";
import { InboundConnectorsTab, OutboundConnectorsTab } from "./settings/ConnectorsTab";
import { GeneralTab } from "./settings/GeneralTab";
import { SecurityTab } from "./settings/SecurityTab";
interface SettingsProps {
onClose: () => void;
}
type SettingsTab = "inbound" | "outbound" | "general" | "security";
export function Settings({ onClose }: SettingsProps) {
const [tab, setTab] = useState<SettingsTab>("inbound");
const dialogRef = useRef<HTMLDivElement>(null);
useFocusTrap(dialogRef);
return (
<div
ref={dialogRef}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
role="dialog"
aria-modal="true"
aria-label="Settings"
onClick={(e) => {
// Close when clicking the backdrop (outside the modal panel)
if (e.target === e.currentTarget) onClose();
}}
onKeyDown={(e) => {
// useFocusTrap keeps focus inside the dialog, so this handler
// fires even when inputs are focused (unlike the app-level
// useKeyboardShortcuts which skips inputs).
if (e.key === "Escape") onClose();
}}
>
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-full max-w-[800px] h-[85vh] mx-4 flex flex-col overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between px-4 sm:px-6 py-4 border-b border-gray-200 dark:border-gray-800">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Settings</h2>
<button
type="button"
onClick={onClose}
aria-label="Close settings"
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 p-0.5"
>
<XIcon className="w-5 h-5" />
</button>
</div>
{/* Mobile tab bar — horizontal, shown below md */}
<nav className="sm:hidden flex border-b border-gray-200 dark:border-gray-800 overflow-x-auto">
<MobileTabButton
active={tab === "inbound"}
onClick={() => setTab("inbound")}
label="Inbound"
/>
<MobileTabButton
active={tab === "outbound"}
onClick={() => setTab("outbound")}
label="Outbound"
/>
<MobileTabButton
active={tab === "general"}
onClick={() => setTab("general")}
label="General"
/>
<MobileTabButton
active={tab === "security"}
onClick={() => setTab("security")}
label="Security"
/>
</nav>
<div className="flex flex-1 min-h-0">
{/* Sidebar tabs — hidden on mobile */}
<nav className="hidden sm:block w-48 flex-shrink-0 border-r border-gray-200 dark:border-gray-800 p-3 space-y-1">
<TabButton
active={tab === "inbound"}
onClick={() => setTab("inbound")}
label="Inbound"
icon={<InboxIcon className="w-4 h-4" />}
/>
<TabButton
active={tab === "outbound"}
onClick={() => setTab("outbound")}
label="Outbound"
icon={<SendIcon className="w-4 h-4" />}
/>
<TabButton
active={tab === "general"}
onClick={() => setTab("general")}
label="General"
icon={<SettingsIcon className="w-4 h-4" />}
/>
<TabButton
active={tab === "security"}
onClick={() => setTab("security")}
label="Security"
icon={<ShieldIcon className="w-4 h-4" />}
/>
</nav>
{/* Content */}
<div className="flex-1 overflow-y-auto">
{tab === "inbound" && <InboundConnectorsTab />}
{tab === "outbound" && <OutboundConnectorsTab />}
{tab === "general" && <GeneralTab />}
{tab === "security" && <SecurityTab />}
</div>
</div>
</div>
</div>
);
}
function MobileTabButton({
active,
onClick,
label,
}: {
active: boolean;
onClick: () => void;
label: string;
}) {
return (
<button
type="button"
onClick={onClick}
className={`flex-1 py-2.5 text-sm font-medium text-center transition-colors ${
active
? "text-stork-600 dark:text-stork-400 border-b-2 border-stork-500"
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
}`}
>
{label}
</button>
);
}
function TabButton({
active,
onClick,
label,
icon,
}: {
active: boolean;
onClick: () => void;
label: string;
icon: ReactNode;
}) {
return (
<button
type="button"
onClick={onClick}
className={`w-full flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors ${
active
? "bg-stork-100 dark:bg-stork-950 text-stork-700 dark:text-stork-300 font-medium"
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"
}`}
>
{icon}
{label}
</button>
);
}
|