diff --git a/crates/lanspread-tauri-deno-ts/src/components/modals/SettingsDialog.tsx b/crates/lanspread-tauri-deno-ts/src/components/modals/SettingsDialog.tsx index 49e7083..da0bf50 100644 --- a/crates/lanspread-tauri-deno-ts/src/components/modals/SettingsDialog.tsx +++ b/crates/lanspread-tauri-deno-ts/src/components/modals/SettingsDialog.tsx @@ -14,6 +14,9 @@ import { interface Props { settings: UISettings; + gameDir: string; + hasGameDirectory: boolean; + onPickDirectory: () => void; onChange: (key: K, value: UISettings[K]) => void; onClose: () => void; } @@ -56,7 +59,37 @@ const SettingsTextInput = ({ value, placeholder, maxLength, onChange }: TextInpu ); -export const SettingsDialog = ({ settings, onChange, onClose }: Props) => ( +interface GameFolderFieldProps { + path: string; + isValid: boolean; + onPickDirectory: () => void; +} + +const GameFolderField = ({ path, isValid, onPickDirectory }: GameFolderFieldProps) => ( +
+
+); + +export const SettingsDialog = ({ + settings, + gameDir, + hasGameDirectory, + onPickDirectory, + onChange, + onClose, +}: Props) => (

Settings

@@ -110,6 +143,13 @@ export const SettingsDialog = ({ settings, onChange, onClose }: Props) => (
Library
+ + + void; -} - -export const DirectoryButton = ({ path, exists, onClick }: Props) => { - const isSet = !!(path && path.trim()); - const isValid = isSet && exists; - const label = isValid ? 'Game folder' : 'Set game folder'; - const tooltip = isValid ? (path as string) : 'Please select a game folder'; - const ariaLabel = isValid ? `Game folder: ${path}` : 'Set game folder'; - - return ( - - ); -}; diff --git a/crates/lanspread-tauri-deno-ts/src/components/topbar/TopBar.tsx b/crates/lanspread-tauri-deno-ts/src/components/topbar/TopBar.tsx index 33987f7..1acad27 100644 --- a/crates/lanspread-tauri-deno-ts/src/components/topbar/TopBar.tsx +++ b/crates/lanspread-tauri-deno-ts/src/components/topbar/TopBar.tsx @@ -2,7 +2,6 @@ import { Brand } from '../Brand'; import { SegmentedFilters } from './SegmentedFilters'; import { SearchField } from './SearchField'; import { SortMenu } from './SortMenu'; -import { DirectoryButton } from './DirectoryButton'; import { KebabMenu, KebabItem } from './KebabMenu'; import { FilterCounts } from '../../lib/gameState'; @@ -17,9 +16,6 @@ interface Props { setQuery: (value: string) => void; sort: GameSort; setSort: (value: GameSort) => void; - gameDir: string; - gameDirExists: boolean; - onPickDirectory: () => void; kebabItems: ReadonlyArray; } @@ -32,9 +28,6 @@ export const TopBar = ({ setQuery, sort, setSort, - gameDir, - gameDirExists, - onPickDirectory, kebabItems, }: Props) => (
@@ -52,7 +45,6 @@ export const TopBar = ({
-
diff --git a/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts b/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts index f65c8a2..cf9a0a1 100644 --- a/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts +++ b/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts @@ -64,6 +64,8 @@ export const useGameDirectory = () => { }; }, [gameDir]); + const hasGameDirectory = gameDir.trim() !== '' && gameDirExists; + const rescan = useCallback(() => { if (!gameDir.trim()) { setGameDirExists(false); @@ -86,5 +88,5 @@ export const useGameDirectory = () => { void sync(); }, [gameDir]); - return { gameDir, gameDirExists, setGameDir, rescan }; + return { gameDir, gameDirExists, hasGameDirectory, setGameDir, rescan }; }; diff --git a/crates/lanspread-tauri-deno-ts/src/styles/launcher.css b/crates/lanspread-tauri-deno-ts/src/styles/launcher.css index 48b9e12..1570ce6 100644 --- a/crates/lanspread-tauri-deno-ts/src/styles/launcher.css +++ b/crates/lanspread-tauri-deno-ts/src/styles/launcher.css @@ -394,44 +394,6 @@ color: var(--accent); } -/* Game-folder button: icon + short label. - Full path lives in tooltip + aria-label, not on-screen. */ -.dirbtn { - position: relative; - display: inline-flex; - align-items: center; - gap: 8px; - height: 36px; - padding: 0 14px 0 12px; - background: var(--bg-2); - border: 1px solid var(--bd-1); - border-radius: 8px; - color: var(--t-1); - font: inherit; - font-size: 12.5px; - font-weight: 600; - cursor: pointer; - white-space: nowrap; - flex-shrink: 0; - transition: - border-color 0.15s, - color 0.15s, - background 0.15s; -} -.dirbtn:hover { - border-color: var(--bd-2); -} -.dirbtn-label { - line-height: 1; -} -.dirbtn-unset { - border-color: color-mix(in srgb, var(--danger) 35%, var(--bd-1)); -} -.dirbtn-unset:hover { - border-color: color-mix(in srgb, var(--danger) 55%, var(--bd-2)); - background: color-mix(in srgb, var(--danger) 8%, var(--bg-2)); -} - /* Kebab menu */ .kebab { position: relative; @@ -1600,6 +1562,86 @@ font-weight: 500; } +/* Settings: game-folder field */ +.folder-field { + display: inline-flex; + align-items: center; + gap: 8px; + width: 340px; + height: 36px; + padding: 0 4px 0 12px; + background: var(--bg-3); + border: 1px solid var(--bd-1); + border-radius: 8px; + transition: + border-color 0.15s, + background 0.15s; +} +.folder-field:hover { + border-color: var(--bd-2); +} +.folder-field.is-unset { + border-color: color-mix(in srgb, var(--danger) 35%, var(--bd-1)); + background: color-mix(in srgb, var(--danger) 6%, var(--bg-3)); +} +.folder-field.is-unset:hover { + border-color: color-mix(in srgb, var(--danger) 55%, var(--bd-2)); +} +.folder-field-icon { + color: var(--t-3); + flex-shrink: 0; +} +.folder-field.is-unset .folder-field-icon { + color: #f87171; +} +.folder-field-path { + flex: 1; + min-width: 0; + font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; + font-size: 12px; + color: var(--t-1); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + direction: rtl; + text-align: left; + unicode-bidi: plaintext; +} +.folder-field-empty { + font-family: var(--font-ui); + font-size: 12.5px; + font-weight: 600; + color: #f87171; + letter-spacing: 0; +} +.folder-field-btn { + flex-shrink: 0; + height: 28px; + padding: 0 12px; + border: 0; + border-radius: 6px; + background: rgba(255, 255, 255, 0.06); + color: var(--t-1); + font: inherit; + font-size: 12.5px; + font-weight: 600; + letter-spacing: 0; + cursor: pointer; + transition: + background 0.15s, + color 0.15s; +} +.folder-field-btn:hover { + background: rgba(255, 255, 255, 0.12); +} +.folder-field.is-unset .folder-field-btn { + background: color-mix(in srgb, var(--accent) 85%, transparent); + color: #fff; +} +.folder-field.is-unset .folder-field-btn:hover { + background: var(--accent); +} + /* Settings: color swatches */ .swatch-row { display: inline-flex; diff --git a/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx b/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx index 947a84a..b028ff9 100644 --- a/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx +++ b/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx @@ -45,7 +45,7 @@ const openLogsWindow = async () => { export const MainWindow = () => { const { settings, set: setSetting } = useSettings(); - const { gameDir, gameDirExists, setGameDir, rescan } = useGameDirectory(); + const { gameDir, hasGameDirectory, setGameDir, rescan } = useGameDirectory(); const games = useGames(rescan); const actions = useGameActions(games, settings); const thumbnails = useThumbnails(); @@ -53,8 +53,6 @@ export const MainWindow = () => { const [openGameId, setOpenGameId] = useState(null); const [removeGameId, setRemoveGameId] = useState(null); const [settingsOpen, setSettingsOpen] = useState(false); - - const hasGameDirectory = !!gameDir.trim() && gameDirExists; const visibleGames = useMemo( () => hasGameDirectory ? games.games : [], [games.games, hasGameDirectory], @@ -130,9 +128,6 @@ export const MainWindow = () => { setQuery={setQuery} sort={settings.sort} setSort={(v) => setSetting('sort', v)} - gameDir={gameDir} - gameDirExists={gameDirExists} - onPickDirectory={() => void pickDirectory()} kebabItems={kebabItems} />
@@ -192,6 +187,9 @@ export const MainWindow = () => { {settingsOpen && ( void pickDirectory()} onChange={setSetting} onClose={() => setSettingsOpen(false)} />