style: align modal close button across apps

This commit is contained in:
KodekaTeam committed 2026-06-21 23:08:11 +07:00
1 parent ad14f97d90
commit 7a66ed9578
30 files changed
+535 -482

No files matched your search

+16 -14
View File
@@ -22,6 +22,7 @@ import {
RefreshCw,
Shield,
Trash2,
X,
} from "lucide-react";
import { useEffect, useMemo, useState } from "react";
@@ -855,6 +856,19 @@ export default function ApiKeysPage() {
{showCreate && (
<div className="modal-overlay" onClick={closeCreateModal}>
<div
className="modal-shell"
style={{ maxWidth: 520 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={closeCreateModal}
className="modal-close"
aria-label="Close API key modal"
>
<X size={22} />
</button>
<div
className="modal"
style={{
@@ -863,14 +877,13 @@ export default function ApiKeysPage() {
overflowY: "auto",
padding: "clamp(18px, 3vw, 24px)",
}}
onClick={(event) => event.stopPropagation()}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -895,18 +908,6 @@ export default function ApiKeysPage() {
: "Create a new API key for scripts, bots, and CI/CD jobs."}
</p>
</div>
<button
onClick={closeCreateModal}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
fontSize: 18,
}}
>
×
</button>
</div>
{generatedKey ? (
@@ -1197,6 +1198,7 @@ export default function ApiKeysPage() {
)}
</div>
</div>
</div>
)}
</GuardedPage>
);
+11 -14
View File
@@ -483,6 +483,15 @@ function InstallOperationsModal({
padding: 16,
}}
>
<div className="modal-shell" style={{ maxWidth: 1100 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close installed app details modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -498,10 +507,10 @@ function InstallOperationsModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
gap: 16,
padding: "18px 22px",
paddingRight: 56,
borderBottom: "1px solid var(--border)",
}}
>
@@ -527,19 +536,6 @@ function InstallOperationsModal({
{install.containerName ? ` • ${install.containerName}` : ""}
</p>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
</div>
<div
@@ -1216,6 +1212,7 @@ function InstallOperationsModal({
</div>
</div>
</div>
</div>
);
}
+16 -15
View File
@@ -1002,12 +1002,24 @@ function InstallModal({
return createPortal(
<div className="modal-overlay">
<div
className="modal-shell"
style={{ width: "min(1100px, calc(100% - 48px))", maxWidth: 1100 }}
>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close install modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
width: "min(1100px, 100%)",
width: "100%",
maxWidth: 1100,
maxHeight: "calc(100vh - 40px)",
maxHeight: "calc(100vh - 64px)",
overflow: "auto",
padding: 24,
}}
@@ -1015,10 +1027,10 @@ function InstallModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
gap: 16,
alignItems: "flex-start",
marginBottom: 20,
paddingRight: 28,
}}
>
<div style={{ display: "flex", gap: 14, alignItems: "center" }}>
@@ -1043,18 +1055,6 @@ function InstallModal({
</p>
</div>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
<div
@@ -1676,6 +1676,7 @@ function InstallModal({
</div>
</form>
</div>
</div>
</div>,
document.body,
);
+22 -24
View File
@@ -259,6 +259,15 @@ function CreateBackupModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 440 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close create backup modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{ width: "100%", maxWidth: 440, padding: 28 }}
@@ -266,9 +275,9 @@ function CreateBackupModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -287,17 +296,6 @@ function CreateBackupModal({
Backup a server&apos;s data to local or S3
</p>
</div>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
{error && (
@@ -628,6 +626,7 @@ function CreateBackupModal({
</form>
</div>
</div>
</div>
);
}
@@ -919,6 +918,15 @@ export default function BackupsPage() {
/>
{downloadDialog && (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 720 }}>
<button
type="button"
onClick={() => setDownloadDialog(null)}
className="modal-close"
aria-label="Close download backup modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{ width: "100%", maxWidth: 720, padding: 24 }}
@@ -926,10 +934,10 @@ export default function BackupsPage() {
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 18,
gap: 12,
paddingRight: 36,
}}
>
<div>
@@ -952,17 +960,6 @@ export default function BackupsPage() {
Pilih format download untuk {downloadDialog.name}
</p>
</div>
<button
onClick={() => setDownloadDialog(null)}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
<div
style={{
@@ -1045,6 +1042,7 @@ export default function BackupsPage() {
</div>
</div>
</div>
</div>
)}
<ToastViewport toasts={toasts} onClose={dismissToast} />
{showCreate && (
@@ -58,6 +58,15 @@ export default function ContainerDetailsModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 1080 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close container details modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -73,9 +82,9 @@ export default function ContainerDetailsModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "18px 22px",
paddingRight: 56,
borderBottom: "1px solid var(--border)",
}}
>
@@ -103,18 +112,6 @@ export default function ContainerDetailsModal({
: ""}
</p>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
<div
style={{
@@ -589,5 +586,6 @@ export default function ContainerDetailsModal({
</div>
</div>
</div>
</div>
);
}
@@ -491,6 +491,18 @@ export default function ContainerFileManagerModal({
current?.onConfirm();
}}
/>
<div className="modal-shell" style={{ maxWidth: 1240 }}>
<button
type="button"
onClick={async () => {
if (!(await confirmLoseChanges())) return;
onClose();
}}
className="modal-close"
aria-label="Close file manager modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -507,8 +519,8 @@ export default function ContainerFileManagerModal({
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "18px 22px",
paddingRight: 56,
borderBottom: "1px solid var(--border)",
gap: 16,
}}
@@ -535,21 +547,6 @@ export default function ContainerFileManagerModal({
: ""}
</p>
</div>
<button
type="button"
onClick={async () => {
if (!(await confirmLoseChanges())) return;
onClose();
}}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
<div
@@ -1497,5 +1494,6 @@ export default function ContainerFileManagerModal({
</div>
</div>
</div>
</div>
);
}
@@ -48,6 +48,15 @@ export default function ContainerLogsModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 760 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close logs modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -62,9 +71,9 @@ export default function ContainerLogsModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "16px 20px",
paddingRight: 52,
borderBottom: "1px solid var(--border)",
}}
>
@@ -80,18 +89,6 @@ export default function ContainerLogsModal({
Logs - {container.name}
</h3>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
<div
ref={viewportRef}
@@ -116,5 +113,6 @@ export default function ContainerLogsModal({
</div>
</div>
</div>
</div>
);
}
@@ -989,9 +989,7 @@ export default function DeployContainerModal({
},
});
void containersApi
.cancelJob(jobId)
.catch(() => undefined);
void containersApi.cancelJob(jobId).catch(() => undefined);
},
},
});
@@ -1079,6 +1077,15 @@ export default function DeployContainerModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 640 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close deploy container modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -1091,10 +1098,10 @@ export default function DeployContainerModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
gap: 12,
paddingRight: 36,
}}
>
<div>
@@ -1140,27 +1147,6 @@ export default function DeployContainerModal({
: "Select a source to deploy from. You can choose from pre-built app templates, connect your Git provider, or manually enter deployment details."}
</p>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
marginTop: -35,
marginRight: -20,
// backgroundColor: "#acacac",
// borderRadius: "50%",
width: 28,
height: 28,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<X size={18} />
</button>
</div>
{error ? (
<div
@@ -2099,5 +2085,6 @@ export default function DeployContainerModal({
)}
</div>
</div>
</div>
);
}
@@ -467,6 +467,15 @@ export default function AddDatabaseModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 540 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close deploy database modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{ width: "100%", maxWidth: 540, padding: 28 }}
@@ -474,9 +483,9 @@ export default function AddDatabaseModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -514,17 +523,6 @@ export default function AddDatabaseModal({
: "Deploy a new database container on your server"}
</p>
</div>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
{error && (
@@ -1054,5 +1052,6 @@ export default function AddDatabaseModal({
)}
</div>
</div>
</div>
);
}
+12 -13
View File
@@ -478,6 +478,15 @@ export default function AddDomainModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 580 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label={isEditMode ? "Close edit domain modal" : "Close add domain modal"}
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -489,9 +498,9 @@ export default function AddDomainModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -512,17 +521,6 @@ export default function AddDomainModal({
: "Configure domain with optional auto-SSL"}
</p>
</div>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
{error && (
@@ -744,7 +742,7 @@ export default function AddDomainModal({
}}
>
Isolated config means the Nginx config for this domain will be
created separately and won't be shared with any other domain.
created separately and won&apos;t be shared with any other domain.
This allows you to reuse target containers and ports that are
already in use by other domains, as long as those domains are
not using Shared mode on the same server.
@@ -805,5 +803,6 @@ export default function AddDomainModal({
</form>
</div>
</div>
</div>
);
}
+16 -8
View File
@@ -16,6 +16,7 @@ import {
ToggleRight,
Loader2,
Server as ServerIcon,
X,
} from "lucide-react";
import {
security as securityApi,
@@ -1043,16 +1044,28 @@ export default function FirewallPage() {
{showAddRule && (
<div className="modal-overlay" onClick={() => setShowAddRule(false)}>
<div
className="modal-content card"
className="modal-shell"
style={{ maxWidth: 420 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={() => setShowAddRule(false)}
className="modal-close"
aria-label="Close add firewall rule modal"
>
<X size={22} />
</button>
<div
className="modal-content card"
style={{ width: "100%", maxWidth: 420, padding: 20 }}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 16,
paddingRight: 36,
}}
>
<div>
@@ -1069,12 +1082,6 @@ export default function FirewallPage() {
Configure a new UFW rule for this server.
</p>
</div>
<button
className="btn btn-ghost"
onClick={() => setShowAddRule(false)}
>
Close
</button>
</div>
<div
@@ -1157,6 +1164,7 @@ export default function FirewallPage() {
</div>
</div>
</div>
</div>
)}
</div>
</DashboardLayout>
+38 -43
View File
@@ -492,8 +492,21 @@ export default function GitProvidersPanel({
const renderProviderSelectorModal = () => (
<div className="modal-overlay" onClick={closeProviderSelector}>
<div
className="modal animate-slide-in"
className="modal-shell"
style={{ maxWidth: 640 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={closeProviderSelector}
className="modal-close"
aria-label="Close modal"
title="Close"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
width: "100%",
maxWidth: 640,
@@ -504,10 +517,10 @@ export default function GitProvidersPanel({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
marginBottom: 20,
gap: 12,
paddingRight: 36,
}}
>
<div>
@@ -531,32 +544,12 @@ export default function GitProvidersPanel({
and repository access.
</p>
</div>
<button
type="button"
onClick={closeProviderSelector}
aria-label="Close modal"
title="Close"
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
marginTop: -8,
marginRight: -8,
width: 28,
height: 28,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<X size={18} />
</button>
</div>
{renderProviderSelectionRow()}
</div>
</div>
</div>
);
const renderSharedCredentialFooter = () => (
@@ -1221,8 +1214,27 @@ export default function GitProvidersPanel({
{draft ? (
<div className="modal-overlay" onClick={closeModal}>
<div
className="modal animate-slide-in"
className="modal-shell"
style={{
width:
draft.provider === "github"
? "min(640px, calc(100% - 48px))"
: "min(720px, calc(100% - 48px))",
maxWidth: 720,
}}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={closeModal}
className="modal-close"
aria-label="Close modal"
title="Close"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
width:
draft.provider === "github"
@@ -1237,10 +1249,10 @@ export default function GitProvidersPanel({
<div
style={{
display: "flex",
justifyContent: "space-between",
gap: 12,
alignItems: "flex-start",
marginBottom: 22,
paddingRight: 36,
}}
>
<div>
@@ -1265,24 +1277,6 @@ export default function GitProvidersPanel({
flow.
</p>
</div>
<button
type="button"
onClick={closeModal}
aria-label="Close modal"
title="Close"
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
padding: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<X size={18} />
</button>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
@@ -1343,6 +1337,7 @@ export default function GitProvidersPanel({
)}
</div>
</div>
</div>
) : null}
</>
);
+68
View File
@@ -822,6 +822,7 @@ body {
contain: layout paint style;
}
.modal {
position: relative;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
@@ -838,6 +839,73 @@ body {
contain: content;
}
.modal-shell {
position: relative;
width: 100%;
max-width: 480px;
--modal-close-size: 28px;
}
.modal-close {
position: absolute;
top: 5px;
right: 5px;
z-index: 5;
display: inline-flex;
width: var(--modal-close-size);
height: var(--modal-close-size);
min-width: var(--modal-close-size);
flex: 0 0 var(--modal-close-size);
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
border: 2px solid rgba(255, 255, 255, 0.82) !important;
border-radius: 50%;
background: #2c2c2c !important;
color: #ffffff !important;
/* box-shadow:
0 14px 30px rgba(220, 38, 38, 0.34),
0 4px 14px rgba(2, 6, 23, 0.26); */
cursor: pointer;
line-height: 1;
transition:
background 0.18s ease,
box-shadow 0.18s ease,
transform 0.18s ease;
transform: translate(50%, -50%);
transition: all 0.4s ease;
}
.modal-close:hover {
background: #b91c1c !important;
/* box-shadow:
0 16px 36px rgba(220, 38, 38, 0.42),
0 6px 18px rgba(2, 6, 23, 0.28); */
transform: translate(50%, calc(-50% - 1px)) rotate(180deg);
}
.modal-close:disabled {
cursor: not-allowed;
opacity: 0.55;
transform: translate(50%, -50%);
}
.modal-close:focus-visible {
outline: 3px solid rgba(239, 68, 68, 0.3);
outline-offset: 3px;
}
.modal-close svg {
stroke-width: 2.25;
}
@media (max-width: 640px) {
.modal-close {
--modal-close-size: 28px;
}
}
.modal::-webkit-scrollbar {
width: 12px;
}
+22 -24
View File
@@ -111,6 +111,15 @@ function AddNetworkModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 520 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close create network modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{ width: "100%", maxWidth: 520, padding: 24 }}
@@ -118,9 +127,9 @@ function AddNetworkModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 18,
paddingRight: 36,
}}
>
<div>
@@ -140,17 +149,6 @@ function AddNetworkModal({
database.
</p>
</div>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
{error && (
@@ -358,6 +356,7 @@ function AddNetworkModal({
</form>
</div>
</div>
</div>
);
}
@@ -439,6 +438,15 @@ function NetworkDetailsModal({
padding: 16,
}}
>
<div className="modal-shell" style={{ maxWidth: 1040 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close network detail modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -454,9 +462,9 @@ function NetworkDetailsModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "18px 22px",
paddingRight: 56,
borderBottom: "1px solid var(--border)",
}}
>
@@ -495,17 +503,6 @@ function NetworkDetailsModal({
: "Unknown server"}
</p>
</div>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
<div
@@ -978,6 +975,7 @@ function NetworkDetailsModal({
</div>
</div>
</div>
</div>
);
}
@@ -596,6 +596,19 @@ export default function NotificationsSettingsPanel({
{draft ? (
<div className="modal-overlay">
<div
className="modal-shell"
style={{ width: "min(780px, calc(100% - 48px))", maxWidth: 780 }}
>
<button
type="button"
onClick={closeModal}
className="modal-close"
aria-label="Close modal"
title="Close"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -609,10 +622,10 @@ export default function NotificationsSettingsPanel({
<div
style={{
display: "flex",
justifyContent: "space-between",
gap: 12,
alignItems: "flex-start",
marginBottom: 22,
paddingRight: 36,
}}
>
<div>
@@ -637,24 +650,6 @@ export default function NotificationsSettingsPanel({
: "Set up a new notification provider and configure which events will trigger notifications."}
</p>
</div>
<button
type="button"
onClick={closeModal}
aria-label="Close modal"
title="Close"
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
padding: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<X size={18} />
</button>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
@@ -1426,6 +1421,7 @@ export default function NotificationsSettingsPanel({
</div>
</div>
</div>
</div>
) : null}
<style jsx>{`
@@ -126,7 +126,11 @@ export default function ImportFromSyncModal({
);
setServerContainers((current) =>
current.map((item) =>
item.id === container.id ? { ...item, environmentId } : item,
item.id === container.id
? { ...item, environmentId }
: item.environmentId === environmentId
? item
: item,
),
);
} catch (assignError) {
@@ -143,17 +147,29 @@ export default function ImportFromSyncModal({
return (
<div className="modal-overlay" onClick={onClose}>
<div
className="modal animate-slide-in"
className="modal-shell"
style={{ maxWidth: 760 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close import from sync modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{ width: "100%", maxWidth: 760, padding: 24 }}
>
<div
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 12,
marginBottom: 16,
paddingRight: 36,
}}
>
<div style={{ minWidth: 0 }}>
@@ -178,18 +194,6 @@ export default function ImportFromSyncModal({
{environmentName}.
</p>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
<section
@@ -252,7 +256,9 @@ export default function ImportFromSyncModal({
>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<CloudSyncIcon size={14} style={{ color: "var(--text-muted)" }} />
<strong style={{ fontSize: 13 }}>Services / Containers</strong>
<strong style={{ fontSize: 13 }}>
Services / Containers
</strong>
</div>
<button
type="button"
@@ -402,7 +408,9 @@ export default function ImportFromSyncModal({
className="btn btn-ghost"
onClick={() => void handleAssign(container)}
disabled={
assigningContainerId !== null || syncing || loading
assigningContainerId !== null ||
syncing ||
loading
}
style={{
color: assignedElsewhere ? "#f59e0b" : "#93c5fd",
@@ -459,5 +467,6 @@ export default function ImportFromSyncModal({
) : null}
</div>
</div>
</div>
);
}
@@ -61,9 +61,22 @@ export default function EnvironmentFormModal({
return (
<div className="modal-overlay" onClick={submitting ? undefined : onClose}>
<div
className="modal-shell"
style={{ maxWidth: 560 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
disabled={submitting}
className="modal-close"
aria-label="Close environment modal"
>
<X size={22} />
</button>
<form
className="modal animate-slide-in"
onClick={(event) => event.stopPropagation()}
onSubmit={handleSubmit}
style={{
width: "100%",
@@ -75,9 +88,9 @@ export default function EnvironmentFormModal({
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 12,
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -96,19 +109,6 @@ export default function EnvironmentFormModal({
Tambahkan environment baru ke project {projectName}.
</p>
</div>
<button
type="button"
onClick={onClose}
disabled={submitting}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
<div style={{ display: "grid", gap: 14 }}>
@@ -251,5 +251,6 @@ export default function EnvironmentFormModal({
</div>
</form>
</div>
</div>
);
}
@@ -79,9 +79,22 @@ export default function EnvironmentOpsModal({
return (
<div className="modal-overlay" onClick={submitting ? undefined : onClose}>
<div
className="modal-shell"
style={{ maxWidth: 760 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
disabled={submitting}
className="modal-close"
aria-label="Close environment detail modal"
>
<X size={22} />
</button>
<form
className="modal animate-slide-in"
onClick={(event) => event.stopPropagation()}
onSubmit={handleSubmit}
style={{ width: "100%", maxWidth: 760, padding: 28 }}
>
@@ -89,9 +102,9 @@ export default function EnvironmentOpsModal({
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 12,
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -110,19 +123,6 @@ export default function EnvironmentOpsModal({
Informasi dan edit metadata environment.
</p>
</div>
<button
type="button"
onClick={onClose}
disabled={submitting}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
<section
@@ -332,5 +332,6 @@ export default function EnvironmentOpsModal({
</div>
</form>
</div>
</div>
);
}
@@ -44,9 +44,22 @@ export default function ProjectFormModal({
return (
<div className="modal-overlay" onClick={submitting ? undefined : onClose}>
<div
className="modal-shell"
style={{ maxWidth: 560 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
disabled={submitting}
className="modal-close"
aria-label="Close project modal"
>
<X size={22} />
</button>
<form
className="modal animate-slide-in"
onClick={(event) => event.stopPropagation()}
onSubmit={handleSubmit}
style={{
width: "100%",
@@ -58,9 +71,9 @@ export default function ProjectFormModal({
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 12,
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -84,19 +97,6 @@ export default function ProjectFormModal({
: "Create a new project to manage your environments and deployments."}
</p>
</div>
<button
type="button"
onClick={onClose}
disabled={submitting}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
<div style={{ display: "grid", gap: 14, marginBottom: 20 }}>
@@ -196,5 +196,6 @@ export default function ProjectFormModal({
</div>
</form>
</div>
</div>
);
}
@@ -599,6 +599,19 @@ export default function S3StorageSettingsPanel({
{draft ? (
<div className="modal-overlay">
<div
className="modal-shell"
style={{ width: "min(780px, calc(100% - 48px))", maxWidth: 1100 }}
>
<button
type="button"
onClick={closeModal}
className="modal-close"
aria-label="Close modal"
title="Close"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -612,10 +625,10 @@ export default function S3StorageSettingsPanel({
<div
style={{
display: "flex",
justifyContent: "space-between",
gap: 12,
alignItems: "flex-start",
marginBottom: 22,
paddingRight: 36,
}}
>
<div>
@@ -639,24 +652,6 @@ export default function S3StorageSettingsPanel({
use it.
</p>
</div>
<button
type="button"
onClick={closeModal}
aria-label="Close modal"
title="Close"
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
padding: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<X size={18} />
</button>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
@@ -1150,6 +1145,7 @@ export default function S3StorageSettingsPanel({
</div>
</div>
</div>
</div>
) : null}
</>
);
@@ -1,4 +1,4 @@
import { Loader2, Plus } from "lucide-react";
import { Loader2, Plus, X } from "lucide-react";
import type { RuleFormState } from "./security-types";
interface AddFirewallRuleModalProps {
@@ -19,16 +19,28 @@ export default function AddFirewallRuleModal({
return (
<div className="modal-overlay" onClick={onClose}>
<div
className="modal"
className="modal-shell"
style={{ maxWidth: 400 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close add firewall rule modal"
>
<X size={22} />
</button>
<div
className="modal"
style={{ maxWidth: 400 }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<h3
@@ -40,19 +52,6 @@ export default function AddFirewallRuleModal({
>
Add Firewall Rule
</h3>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
fontSize: 18,
}}
>
x
</button>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<div>
@@ -176,5 +175,6 @@ export default function AddFirewallRuleModal({
</div>
</div>
</div>
</div>
);
}
@@ -628,6 +628,15 @@ export default function ServerConfigModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 920 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close server config modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -946,7 +955,7 @@ export default function ServerConfigModal({
Refresh.
</p>
</div>
<div style={{ display: "flex", gap: 8 }}>
<div style={{ display: "flex", gap: 8, paddingRight: 36 }}>
<button
className="btn btn-ghost btn-sm"
onClick={() => void loadSnapshot()}
@@ -954,17 +963,6 @@ export default function ServerConfigModal({
>
<RefreshCw size={14} /> Refresh
</button>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
</div>
@@ -1158,5 +1156,6 @@ export default function ServerConfigModal({
)}
</div>
</div>
</div>
);
}
+11 -13
View File
@@ -92,6 +92,15 @@ export default function ServerFormModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 520 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label={isEdit ? "Close edit server modal" : "Close add server modal"}
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{
@@ -103,9 +112,9 @@ export default function ServerFormModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<h3
@@ -117,18 +126,6 @@ export default function ServerFormModal({
>
{isEdit ? "Edit Server" : "Add Server"}
</h3>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
}}
>
<X size={18} />
</button>
</div>
{error && (
<div
@@ -430,5 +427,6 @@ export default function ServerFormModal({
</form>
</div>
</div>
</div>
);
}
+21 -17
View File
@@ -458,22 +458,39 @@ export default function ServersPage() {
onClick={closeDeleteModal}
>
<div
className="card"
className="modal-shell"
style={{
width: "min(100%, 520px)",
maxWidth: 520,
}}
onClick={(event) => event.stopPropagation()}
>
{!confirmDeleteStep ? (
<button
type="button"
onClick={closeDeleteModal}
disabled={deleting !== null}
className="modal-close"
aria-label="Close delete server dialog"
>
<X size={22} />
</button>
) : null}
<div
className="card"
style={{
padding: 0,
overflow: "hidden",
boxShadow: "0 28px 70px rgba(2, 6, 23, 0.46)",
}}
onClick={(event) => event.stopPropagation()}
>
{!confirmDeleteStep ? (
<div style={{ padding: 28, display: "grid", gap: 18 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
gap: 12,
paddingRight: 28,
}}
>
<div style={{ display: "grid", gap: 8 }}>
@@ -515,20 +532,6 @@ export default function ServersPage() {
server access assignments.
</p>
</div>
<button
onClick={closeDeleteModal}
disabled={deleting !== null}
style={{
background: "none",
border: "none",
cursor: deleting ? "not-allowed" : "pointer",
color: "var(--text-muted)",
alignSelf: "flex-start",
}}
aria-label="Close delete server dialog"
>
<X size={18} />
</button>
</div>
<div
@@ -648,6 +651,7 @@ export default function ServersPage() {
)}
</div>
</div>
</div>
) : null}
<ToastViewport
toasts={toasts}
@@ -40,6 +40,15 @@ export default function IssueCertificateModal({
return (
<div className="modal-overlay">
<div className="modal-shell" style={{ maxWidth: 500 }}>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close issue certificate modal"
>
<X size={22} />
</button>
<div
className="modal animate-slide-in"
style={{ width: "100%", maxWidth: 500, padding: 24 }}
@@ -47,9 +56,9 @@ export default function IssueCertificateModal({
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 18,
paddingRight: 36,
}}
>
<div>
@@ -68,18 +77,6 @@ export default function IssueCertificateModal({
Create a new SSL record from an existing domain.
</p>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
{error ? (
@@ -190,5 +187,6 @@ export default function IssueCertificateModal({
</form>
</div>
</div>
</div>
);
}
+15 -13
View File
@@ -771,17 +771,29 @@ function TerminalPageContent() {
onClick={() => setShowPicker(false)}
style={{ zIndex: 200 }}
>
<div
className="modal-shell"
style={{ maxWidth: 480 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={() => setShowPicker(false)}
className="modal-close"
aria-label="Close terminal session picker"
>
<X size={22} />
</button>
<div
className="modal"
style={{ maxWidth: 480, width: "100%" }}
onClick={(event) => event.stopPropagation()}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 16,
paddingRight: 36,
}}
>
<div>
@@ -804,17 +816,6 @@ function TerminalPageContent() {
Choose a server to open or restore an SSH session.
</p>
</div>
<button
onClick={() => setShowPicker(false)}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
}}
>
<X size={18} />
</button>
</div>
{pickerError && (
@@ -928,6 +929,7 @@ function TerminalPageContent() {
</div>
</div>
</div>
</div>
)}
</DashboardLayout>
);
+16 -16
View File
@@ -1,6 +1,6 @@
"use client";
import { AlertCircle, Edit3, Loader2 } from "lucide-react";
import { AlertCircle, Edit3, Loader2, X } from "lucide-react";
import { useState } from "react";
import type { UserRecord, UserRole } from "@/lib/api";
import { manageableRoles } from "@/app/users/components/user-role-config";
@@ -27,16 +27,28 @@ export default function EditRoleModal({
return (
<div className="modal-overlay" onClick={onClose}>
<div
className="modal"
className="modal-shell"
style={{ maxWidth: 420 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close edit role modal"
>
<X size={22} />
</button>
<div
className="modal"
style={{ maxWidth: 420 }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -53,19 +65,6 @@ export default function EditRoleModal({
{user.email}
</p>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
fontSize: 18,
}}
>
x
</button>
</div>
{error ? (
@@ -154,5 +153,6 @@ export default function EditRoleModal({
</div>
</div>
</div>
</div>
);
}
+16 -16
View File
@@ -1,6 +1,6 @@
"use client";
import { AlertCircle, CheckCircle, Copy, Key, Loader2 } from "lucide-react";
import { AlertCircle, CheckCircle, Copy, Key, Loader2, X } from "lucide-react";
import { useState } from "react";
import type {
CreateUserInvitationBody,
@@ -58,16 +58,28 @@ export default function InviteUserModal({
return (
<div className="modal-overlay" onClick={onClose}>
<div
className="modal"
className="modal-shell"
style={{ maxWidth: 560 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close invite user modal"
>
<X size={22} />
</button>
<div
className="modal"
style={{ maxWidth: 560 }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -86,19 +98,6 @@ export default function InviteUserModal({
Generate an onboarding link so the user sets their own password.
</p>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
fontSize: 18,
}}
>
x
</button>
</div>
{result ? (
@@ -381,5 +380,6 @@ export default function InviteUserModal({
)}
</div>
</div>
</div>
);
}
+16 -16
View File
@@ -1,6 +1,6 @@
"use client";
import { AlertCircle, Loader2, Shield } from "lucide-react";
import { AlertCircle, Loader2, Shield, X } from "lucide-react";
import { useState } from "react";
import type { Server as ServerRecord, UserRecord } from "@/lib/api";
import ServerAccessSelector from "@/app/users/components/ServerAccessSelector";
@@ -35,16 +35,28 @@ export default function ServerAccessModal({
return (
<div className="modal-overlay" onClick={onClose}>
<div
className="modal"
className="modal-shell"
style={{ maxWidth: 600 }}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close server access modal"
>
<X size={22} />
</button>
<div
className="modal"
style={{ maxWidth: 600 }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
paddingRight: 36,
}}
>
<div>
@@ -61,19 +73,6 @@ export default function ServerAccessModal({
{user.email}
</p>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none",
border: "none",
color: "var(--text-muted)",
cursor: "pointer",
fontSize: 18,
}}
>
x
</button>
</div>
{error ? (
@@ -142,5 +141,6 @@ export default function ServerAccessModal({
</div>
</div>
</div>
</div>
);
}
+18 -16
View File
@@ -73,21 +73,35 @@ export default function ConfirmActionDialog({
onClick={onClose}
>
<div
className="card"
className="modal-shell"
style={{
width: "min(100%, 520px)",
maxWidth: 520,
}}
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
onClick={onClose}
className="modal-close"
aria-label="Close confirmation dialog"
>
<X size={22} />
</button>
<div
className="card"
style={{
padding: 0,
overflow: "hidden",
boxShadow: "0 28px 70px rgba(2, 6, 23, 0.46)",
}}
onClick={(event) => event.stopPropagation()}
>
<div style={{ padding: 28, display: "grid", gap: 18 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
gap: 12,
paddingRight: 28,
}}
>
<div style={{ display: "grid", gap: 8 }}>
@@ -121,19 +135,6 @@ export default function ConfirmActionDialog({
{description}
</p>
</div>
<button
onClick={onClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-muted)",
alignSelf: "flex-start",
}}
aria-label="Close confirmation dialog"
>
<X size={18} />
</button>
</div>
{note ? (
@@ -178,5 +179,6 @@ export default function ConfirmActionDialog({
</div>
</div>
</div>
</div>
);
}