feat(ui): move game folder picker into settings

The design update moved game-folder configuration out of launcher chrome and
into Settings > Library. Follow that contract in the runtime UI without
changing the existing storage or Tauri directory commands.

The top bar now leaves its right edge for the kebab menu. Settings owns a new
Game folder row that shows a valid selected path with a neutral Change button,
or the red Not set state with a stronger Choose button when no accessible
directory is configured. Both the empty-library state and the Settings row
still use the existing native directory picker, so existing saved paths and
rescans keep their current behavior.

Keep useGameDirectory as the directory-state owner and expose the shared
hasGameDirectory boolean from that hook so the grid and Settings field agree on
what counts as configured.

Test Plan:
- git diff --cached --check
- just frontend-test
- just build

Refs: 62b409f4bfc4995c25461776107d28f52b24f30e
This commit is contained in:
2026-05-21 20:42:12 +02:00
parent eedfc0105d
commit 2e7a0cff2f
6 changed files with 128 additions and 82 deletions
@@ -1,28 +0,0 @@
import { Icon } from '../Icon';
interface Props {
path: string | null;
exists: boolean;
onClick: () => 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 (
<button
type="button"
className={`dirbtn ${isValid ? 'dirbtn-set' : 'dirbtn-unset'}`}
title={tooltip}
aria-label={ariaLabel}
onClick={onClick}
>
<Icon.folder />
<span className="dirbtn-label">{label}</span>
</button>
);
};
@@ -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<KebabItem>;
}
@@ -32,9 +28,6 @@ export const TopBar = ({
setQuery,
sort,
setSort,
gameDir,
gameDirExists,
onPickDirectory,
kebabItems,
}: Props) => (
<header className="topbar">
@@ -52,7 +45,6 @@ export const TopBar = ({
<SortMenu value={sort} onChange={setSort} />
</div>
<div className="topbar-right-tail">
<DirectoryButton path={gameDir} exists={gameDirExists} onClick={onPickDirectory} />
<KebabMenu items={kebabItems} />
</div>
</div>