From 3f675966a6964ce71cd191ddac17fb60470838e7 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Fri, 10 Apr 2026 09:05:11 +0000 Subject: [PATCH] Extract modal to ui kit --- src/common/assets/modal.module.css | 125 ++++++++++++++++++ src/common/components/Modal.tsx | 55 ++++++++ src/common/components/modal/Modal.module.css | 42 ------ src/common/components/modal/Modal.tsx | 40 ------ src/games/horde/assets/app.module.css | 2 +- .../horde/assets/leaderboard-modal.module.css | 8 +- .../assets/manage-workers-modal.module.css | 12 +- src/games/horde/assets/modal.module.css | 2 +- src/games/horde/assets/navbar.module.css | 2 +- .../horde/assets/options-modal.module.css | 10 +- src/games/horde/assets/stats-panel.module.css | 8 +- src/games/horde/assets/style.css | 5 +- src/games/horde/assets/worker-card.module.css | 2 +- .../horde/assets/workers-panel.module.css | 2 +- .../components/modals/leaderboard-modal.tsx | 2 +- .../modals/manage-workers-modal.tsx | 2 +- .../horde/components/modals/options-modal.tsx | 2 +- .../assets/settings-modal.module.css | 113 ---------------- .../storywriter/components/settings-modal.tsx | 100 +++++--------- 19 files changed, 253 insertions(+), 281 deletions(-) create mode 100644 src/common/assets/modal.module.css create mode 100644 src/common/components/Modal.tsx delete mode 100644 src/common/components/modal/Modal.module.css delete mode 100644 src/common/components/modal/Modal.tsx diff --git a/src/common/assets/modal.module.css b/src/common/assets/modal.module.css new file mode 100644 index 0000000..6d9ce73 --- /dev/null +++ b/src/common/assets/modal.module.css @@ -0,0 +1,125 @@ +@import './global.css'; + +.dialog { + position: fixed; + inset: 0; + width: 100%; + max-width: 100%; + height: 100%; + max-height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + outline: none; + padding: 0; + + &::backdrop { + background: rgba(0, 0, 0, 0.5); + } +} + +.modal { + background: var(--bg); + color: var(--text); + border-radius: 8px; + width: 90%; + max-width: 960px; + height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.title { + font-size: 18px; + font-weight: bold; + color: var(--text); + margin: 0; +} + +.closeButton { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + color: var(--text-muted); + background: transparent; + border: none; + border-radius: var(--radius); + cursor: pointer; + + &:hover { + color: var(--text); + background: var(--bg-hover); + } +} + +.body { + display: flex; + flex: 1; + overflow: hidden; +} + +.sidebar { + display: flex; + flex-direction: column; + width: 160px; + flex-shrink: 0; + border-right: 1px solid var(--border); + padding: 8px; + gap: 2px; +} + +.content { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; +} + +.footer { + padding: 16px 20px; + border-top: 1px solid var(--border); + display: flex; + justify-content: flex-end; + gap: 8px; + flex-shrink: 0; +} + +@media (max-width: 600px) { + .body { + flex-direction: column; + } + + .sidebar { + flex-direction: row; + width: 100%; + overflow-x: auto; + border-right: none; + border-bottom: 1px solid var(--border); + padding: 4px 8px; + flex-shrink: 0; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } + + .footer { + padding: 12px 16px; + } +} \ No newline at end of file diff --git a/src/common/components/Modal.tsx b/src/common/components/Modal.tsx new file mode 100644 index 0000000..9b0927a --- /dev/null +++ b/src/common/components/Modal.tsx @@ -0,0 +1,55 @@ +import type { ComponentChildren } from "preact"; +import { useCallback, useEffect, useRef } from "preact/hooks"; +import { X } from "lucide-preact"; +import clsx from "clsx"; + +import styles from '../assets/modal.module.css'; + +interface IProps { + open: boolean; + title?: string; + onClose?: () => void; + sidebar?: ComponentChildren; + footer?: ComponentChildren; + class?: string; + className?: string; + children?: ComponentChildren; +} + +export const Modal = ({ children, open, title, onClose, sidebar, footer, ['class']: cls, className }: IProps) => { + const ref = useRef(null); + + useEffect(() => { + if (open) { + ref.current?.showModal(); + } else { + ref.current?.close(); + } + }, [open]); + + const handleBackdropMouseDown = useCallback((e: MouseEvent) => { + if (e.target === ref.current) onClose?.(); + }, [onClose]); + + return ( + +
e.stopPropagation()}> + {(title != null || onClose) && ( +
+ {title &&

{title}

} + {onClose && ( + + )} +
+ )} +
+ {sidebar &&
{sidebar}
} +
{children}
+
+ {footer &&
{footer}
} +
+
+ ); +}; diff --git a/src/common/components/modal/Modal.module.css b/src/common/components/modal/Modal.module.css deleted file mode 100644 index 1e40825..0000000 --- a/src/common/components/modal/Modal.module.css +++ /dev/null @@ -1,42 +0,0 @@ -.dialog { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - margin: auto; - width: 100%; - max-width: 1000px; - height: fit-content; - max-height: 80dvh; - overflow: hidden; - background-color: var(--backgroundColor, #333333); - color: var(--color, white); - border: var(--border, 1px solid white); - outline: none; - padding: 0; - border-radius: var(--border-radius, 0); - - &::backdrop { - backdrop-filter: blur(5px); - } - - >.content { - display: flex; - flex-direction: column; - max-height: 80dvh; - overflow: hidden; - - padding: 30px; - - >.close { - font-family: var(--emojiFont, sans-serif); - font-size: 12px; - position: absolute; - top: 10px; - right: 10px; - cursor: pointer; - user-select: none; - } - } -} \ No newline at end of file diff --git a/src/common/components/modal/Modal.tsx b/src/common/components/modal/Modal.tsx deleted file mode 100644 index 3e38681..0000000 --- a/src/common/components/modal/Modal.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { ComponentChildren } from "preact"; -import { useCallback, useEffect, useRef } from "preact/hooks"; - -import styles from './Modal.module.css'; - -interface IProps { - open: boolean; - class?: string; - className?: string; - onClose?: () => void; - children?: ComponentChildren; -} - -export const Modal = ({ children, open, onClose, ['class']: cls, className }: IProps) => { - const ref = useRef(null); - const handleClickWrapper = useCallback((e: MouseEvent) => { - if (e.currentTarget instanceof HTMLDialogElement) { - onClose?.(); - } else { - e.stopPropagation(); - } - }, [onClose]); - - useEffect(() => { - if (open) { - ref.current?.showModal(); - } else { - ref.current?.close(); - } - }, [open]); - - return ( - -
-
- {children} -
-
- ); -}; diff --git a/src/games/horde/assets/app.module.css b/src/games/horde/assets/app.module.css index ddee8b7..7f181fe 100644 --- a/src/games/horde/assets/app.module.css +++ b/src/games/horde/assets/app.module.css @@ -20,4 +20,4 @@ flex-direction: column; overflow-y: auto; } -} +} \ No newline at end of file diff --git a/src/games/horde/assets/leaderboard-modal.module.css b/src/games/horde/assets/leaderboard-modal.module.css index a7e6b35..47bbb09 100644 --- a/src/games/horde/assets/leaderboard-modal.module.css +++ b/src/games/horde/assets/leaderboard-modal.module.css @@ -2,7 +2,8 @@ width: 480px; } -.loading, .error { +.loading, +.error { color: var(--text-muted); font-size: 14px; text-align: center; @@ -18,7 +19,8 @@ border-collapse: collapse; font-size: 13px; - th, td { + th, + td { padding: 7px 10px; text-align: left; border-bottom: 1px solid var(--border); @@ -71,4 +73,4 @@ font-size: 12px; color: var(--text-muted); opacity: 0.7; -} +} \ No newline at end of file diff --git a/src/games/horde/assets/manage-workers-modal.module.css b/src/games/horde/assets/manage-workers-modal.module.css index 3a62fcf..9a7e674 100644 --- a/src/games/horde/assets/manage-workers-modal.module.css +++ b/src/games/horde/assets/manage-workers-modal.module.css @@ -65,7 +65,9 @@ color: var(--bg); transition: opacity var(--transition); - &:hover { opacity: 0.85; } + &:hover { + opacity: 0.85; + } } .deleteBtn { @@ -77,5 +79,9 @@ margin-left: auto; transition: all var(--transition); - &:hover { background: var(--accent); border-color: var(--accent); color: var(--bg); } -} + &:hover { + background: var(--accent); + border-color: var(--accent); + color: var(--bg); + } +} \ No newline at end of file diff --git a/src/games/horde/assets/modal.module.css b/src/games/horde/assets/modal.module.css index a04c66f..42d7c68 100644 --- a/src/games/horde/assets/modal.module.css +++ b/src/games/horde/assets/modal.module.css @@ -69,4 +69,4 @@ gap: 8px; flex-shrink: 0; align-items: center; -} +} \ No newline at end of file diff --git a/src/games/horde/assets/navbar.module.css b/src/games/horde/assets/navbar.module.css index eac228b..5a30825 100644 --- a/src/games/horde/assets/navbar.module.css +++ b/src/games/horde/assets/navbar.module.css @@ -20,4 +20,4 @@ display: flex; flex-direction: row; gap: 4px; -} +} \ No newline at end of file diff --git a/src/games/horde/assets/options-modal.module.css b/src/games/horde/assets/options-modal.module.css index 512896c..4e03a4b 100644 --- a/src/games/horde/assets/options-modal.module.css +++ b/src/games/horde/assets/options-modal.module.css @@ -22,7 +22,9 @@ color: var(--bg); transition: opacity var(--transition); - &:hover { opacity: 0.85; } + &:hover { + opacity: 0.85; + } } .clearBtn { @@ -33,5 +35,7 @@ color: var(--text); transition: background var(--transition); - &:hover { background: var(--bg-hover); } -} + &:hover { + background: var(--bg-hover); + } +} \ No newline at end of file diff --git a/src/games/horde/assets/stats-panel.module.css b/src/games/horde/assets/stats-panel.module.css index d3f369a..1a2bae1 100644 --- a/src/games/horde/assets/stats-panel.module.css +++ b/src/games/horde/assets/stats-panel.module.css @@ -59,7 +59,8 @@ } } -.userStats, .networkStats { +.userStats, +.networkStats { display: flex; flex-direction: column; } @@ -83,7 +84,8 @@ border-collapse: collapse; font-size: 12px; - th, td { + th, + td { padding: 5px 6px; text-align: left; border-bottom: 1px solid var(--border); @@ -122,4 +124,4 @@ width: 52px; text-align: right; white-space: nowrap; -} +} \ No newline at end of file diff --git a/src/games/horde/assets/style.css b/src/games/horde/assets/style.css index d255149..c0e598b 100644 --- a/src/games/horde/assets/style.css +++ b/src/games/horde/assets/style.css @@ -32,7 +32,8 @@ button { } } -input, textarea { +input, +textarea { background: var(--bg-active); border: 1px solid var(--border); border-radius: var(--radius); @@ -46,4 +47,4 @@ input, textarea { &:focus { border-color: var(--accent); } -} +} \ No newline at end of file diff --git a/src/games/horde/assets/worker-card.module.css b/src/games/horde/assets/worker-card.module.css index 536cd7a..fbb3345 100644 --- a/src/games/horde/assets/worker-card.module.css +++ b/src/games/horde/assets/worker-card.module.css @@ -130,4 +130,4 @@ font-size: 12px; color: var(--text-dim); } -} +} \ No newline at end of file diff --git a/src/games/horde/assets/workers-panel.module.css b/src/games/horde/assets/workers-panel.module.css index 368e7da..78e78d6 100644 --- a/src/games/horde/assets/workers-panel.module.css +++ b/src/games/horde/assets/workers-panel.module.css @@ -44,4 +44,4 @@ align-content: start; gap: 8px; padding: 10px; -} +} \ No newline at end of file diff --git a/src/games/horde/components/modals/leaderboard-modal.tsx b/src/games/horde/components/modals/leaderboard-modal.tsx index 445e9a5..aeac6cd 100644 --- a/src/games/horde/components/modals/leaderboard-modal.tsx +++ b/src/games/horde/components/modals/leaderboard-modal.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "preact/hooks"; import { useHordeState } from "../../contexts/state"; import { fetchLeaderboard, type LeaderboardEntry } from "../../utils/api"; import { formatNumber, formatTime } from "@common/utils"; -import { Modal } from "@common/components/modal/Modal"; +import { Modal } from "@common/components/Modal"; import modalStyles from "../../assets/modal.module.css"; import styles from "../../assets/leaderboard-modal.module.css"; diff --git a/src/games/horde/components/modals/manage-workers-modal.tsx b/src/games/horde/components/modals/manage-workers-modal.tsx index 734e744..05cb3e3 100644 --- a/src/games/horde/components/modals/manage-workers-modal.tsx +++ b/src/games/horde/components/modals/manage-workers-modal.tsx @@ -2,7 +2,7 @@ import clsx from "clsx"; import { useEffect, useState } from "preact/hooks"; import { useHordeState } from "../../contexts/state"; import { fetchWorker, updateWorker, deleteWorker, type WorkerData } from "../../utils/api"; -import { Modal } from "@common/components/modal/Modal"; +import { Modal } from "@common/components/Modal"; import modalStyles from "../../assets/modal.module.css"; import styles from "../../assets/manage-workers-modal.module.css"; diff --git a/src/games/horde/components/modals/options-modal.tsx b/src/games/horde/components/modals/options-modal.tsx index b5644c7..3293e60 100644 --- a/src/games/horde/components/modals/options-modal.tsx +++ b/src/games/horde/components/modals/options-modal.tsx @@ -2,7 +2,7 @@ import clsx from "clsx"; import { useEffect } from "preact/hooks"; import { useHordeState } from "../../contexts/state"; import { useInputState } from "@common/hooks/useInputState"; -import { Modal } from "@common/components/modal/Modal"; +import { Modal } from "@common/components/Modal"; import modalStyles from "../../assets/modal.module.css"; import styles from "../../assets/options-modal.module.css"; diff --git a/src/games/storywriter/assets/settings-modal.module.css b/src/games/storywriter/assets/settings-modal.module.css index d261703..3d17a55 100644 --- a/src/games/storywriter/assets/settings-modal.module.css +++ b/src/games/storywriter/assets/settings-modal.module.css @@ -1,77 +1,3 @@ -.overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -} - -.modal { - background: var(--bg); - border-radius: 8px; - width: 90%; - max-width: 960px; - height: 80vh; - display: flex; - flex-direction: column; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -} - -.header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-bottom: 1px solid var(--border); -} - -.title { - font-size: 18px; - font-weight: bold; - color: var(--text); - margin: 0; -} - -.closeButton { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - font-size: 20px; - color: var(--text-muted); - background: transparent; - border: none; - border-radius: 4px; - cursor: pointer; - - &:hover { - color: var(--text); - background: var(--bg-hover); - } -} - -.body { - display: flex; - flex: 1; - overflow: hidden; -} - -.sidebar { - display: flex; - flex-direction: column; - width: 160px; - flex-shrink: 0; - border-right: 1px solid var(--border); - padding: 8px; - gap: 2px; -} - .menuItem { display: flex; align-items: center; @@ -98,14 +24,6 @@ } } -.content { - flex: 1; - overflow-y: auto; - padding: 20px; - display: flex; - flex-direction: column; -} - .form { composes: form from '@common/assets/ui.module.css'; } @@ -153,14 +71,6 @@ min-height: unset; } -.footer { - padding: 16px 20px; - border-top: 1px solid var(--border); - display: flex; - justify-content: flex-end; - gap: 8px; -} - .button { composes: button from '@common/assets/ui.module.css'; } @@ -227,25 +137,6 @@ } @media (max-width: 600px) { - .body { - flex-direction: column; - } - - .sidebar { - flex-direction: row; - width: 100%; - overflow-x: auto; - border-right: none; - border-bottom: 1px solid var(--border); - padding: 4px 8px; - flex-shrink: 0; - scrollbar-width: none; - } - - .sidebar::-webkit-scrollbar { - display: none; - } - .menuItem { flex: 1; justify-content: center; @@ -254,8 +145,4 @@ padding: 8px; font-size: 13px; } - - .footer { - padding: 12px 16px; - } } diff --git a/src/games/storywriter/components/settings-modal.tsx b/src/games/storywriter/components/settings-modal.tsx index 720edf3..4b5ce5f 100644 --- a/src/games/storywriter/components/settings-modal.tsx +++ b/src/games/storywriter/components/settings-modal.tsx @@ -1,6 +1,6 @@ import clsx from "clsx"; -import { X } from "lucide-preact"; import { useState } from "preact/hooks"; +import { Modal } from "@common/components/Modal"; import styles from "../assets/settings-modal.module.css"; import { BannedTokensSettings } from "./settings/banned-tokens"; import { ChatSystemInstructionSettings } from "./settings/chat-system-instruction"; @@ -15,72 +15,44 @@ interface Props { type Tab = "banned-tokens" | "system-instruction" | "chat-system-instruction" | "continue-prompt" | "connection" | "user"; +const TABS: { id: Tab; label: string }[] = [ + { id: "connection", label: "Connection" }, + { id: "user", label: "User" }, + { id: "system-instruction", label: "System Instruction" }, + { id: "continue-prompt", label: "Continue Prompt" }, + { id: "chat-system-instruction", label: "Chat System Instruction" }, + { id: "banned-tokens", label: "Banned Tokens" }, +]; + export const SettingsModal = ({ onClose }: Props) => { const [activeTab, setActiveTab] = useState("connection"); return ( -
-
e.stopPropagation()}> -
-

Settings

- -
-
- -
- {activeTab === "user" && } - {activeTab === "banned-tokens" && } - {activeTab === "system-instruction" && } - {activeTab === "chat-system-instruction" && } - {activeTab === "continue-prompt" && } - {activeTab === "connection" && } -
-
-
- -
-
-
+ ( + + ))} + footer={ + + } + > + {activeTab === "user" && } + {activeTab === "banned-tokens" && } + {activeTab === "system-instruction" && } + {activeTab === "chat-system-instruction" && } + {activeTab === "continue-prompt" && } + {activeTab === "connection" && } + ); };