style: improve color contrast in various components and update prop types to readonly

This commit is contained in:
ctrlcat0x
2025-11-06 19:03:23 +05:30
parent 518a0e1cf4
commit cedf7e6e37
9 changed files with 115 additions and 101 deletions

View File

@@ -15,8 +15,7 @@
padding: 8px 16px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.05);
border: none;
color: rgba(255, 255, 255, 0.6);
color: rgba(255, 255, 255, 0.9);
cursor: pointer;
font-size: 13px;
font-weight: 500;
@@ -51,8 +50,8 @@
}
&__count {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.8);
background: rgba(255, 255, 255, 0.16);
color: rgba(255, 255, 255, 0.95);
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;

View File

@@ -19,7 +19,7 @@ export function FilterOptions({
favouritedCount,
newGamesCount,
top10Count,
}: FilterOptionsProps) {
}: Readonly<FilterOptionsProps>) {
const { t } = useTranslation("library");
return (

View File

@@ -104,7 +104,7 @@
justify-content: center;
cursor: pointer;
transition: all ease 0.2s;
color: rgba(255, 255, 255, 0.8);
color: rgba(255, 255, 255, 0.95);
padding: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
opacity: 0;
@@ -162,7 +162,7 @@
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
color: rgba(255, 255, 255, 0.8);
color: rgba(255, 255, 255, 0.95);
border: solid 1px rgba(255, 255, 255, 0.15);
border-radius: 4px;
display: flex;
@@ -242,7 +242,7 @@
&__achievement-percentage {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
color: rgba(255, 255, 255, 0.85);
white-space: nowrap;
}
@@ -254,7 +254,7 @@
border-radius: 6px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.9);
color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
cursor: pointer;

View File

@@ -30,7 +30,9 @@ const getImageWithCustomPriority = (
return customUrl || originalUrl || fallbackUrl || "";
};
export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) {
export function LibraryGameCardLarge({
game,
}: Readonly<LibraryGameCardLargeProps>) {
const { t } = useTranslation("library");
const { numberFormatter } = useFormat();
const navigate = useNavigate();
@@ -82,7 +84,7 @@ export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) {
try {
await handleCloseGame();
} catch (e) {
void e;
console.error(e);
}
return;
}
@@ -92,7 +94,7 @@ export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) {
try {
handleOpenDownloadOptions();
} catch (e) {
void e;
console.error(e);
}
}
};
@@ -223,30 +225,44 @@ export function LibraryGameCardLarge({ game }: LibraryGameCardLargeProps) {
className="library-game-card-large__action-button"
onClick={handleActionClick}
>
{isGameDownloading ? (
<>
<DownloadIcon
size={16}
className="library-game-card-large__action-icon--downloading"
/>
{t("downloading")}
</>
) : isGameRunning ? (
<>
<XIcon size={16} />
{t("close")}
</>
) : game.executablePath ? (
<>
<PlayIcon size={16} />
{t("play")}
</>
) : (
<>
<DownloadIcon size={16} />
{t("download")}
</>
)}
{(() => {
if (isGameDownloading) {
return (
<>
<DownloadIcon
size={16}
className="library-game-card-large__action-icon--downloading"
/>
{t("downloading")}
</>
);
}
if (isGameRunning) {
return (
<>
<XIcon size={16} />
{t("close")}
</>
);
}
if (game.executablePath) {
return (
<>
<PlayIcon size={16} />
{t("play")}
</>
);
}
return (
<>
<DownloadIcon size={16} />
{t("download")}
</>
);
})()}
</button>
</div>
</div>

View File

@@ -25,7 +25,7 @@ export function LibraryGameCard({
game,
onMouseEnter,
onMouseLeave,
}: LibraryGameCardProps) {
}: Readonly<LibraryGameCardProps>) {
const { t } = useTranslation("library");
const { numberFormatter } = useFormat();
const navigate = useNavigate();

View File

@@ -13,6 +13,10 @@ import "./library.scss";
export default function Library() {
const { library, updateLibrary } = useLibrary();
type ElectronAPI = {
refreshLibraryAssets?: () => Promise<unknown>;
onLibraryBatchComplete?: (cb: () => void) => () => void;
};
const [viewMode, setViewMode] = useState<ViewMode>("compact");
const [filterBy, setFilterBy] = useState<FilterOption>("all");
@@ -22,17 +26,22 @@ export default function Library() {
useEffect(() => {
dispatch(setHeaderTitle(t("library")));
// Refresh library assets from cloud, then update library display
window.electron
.refreshLibraryAssets()
.then(() => updateLibrary())
.catch(() => updateLibrary()); // Fallback to local cache on error
// Listen for library sync completion to refresh cover images
const unsubscribe = window.electron.onLibraryBatchComplete(() => {
const electron = (globalThis as unknown as { electron?: ElectronAPI })
.electron;
let unsubscribe: () => void = () => undefined;
if (electron?.refreshLibraryAssets) {
electron
.refreshLibraryAssets()
.then(() => updateLibrary())
.catch(() => updateLibrary());
if (electron.onLibraryBatchComplete) {
unsubscribe = electron.onLibraryBatchComplete(() => {
updateLibrary();
});
}
} else {
updateLibrary();
});
}
return () => {
unsubscribe();
@@ -47,31 +56,6 @@ export default function Library() {
// Optional: resume animations if needed
};
// Simple fuzzy search function
const fuzzySearch = (query: string, items: typeof library) => {
if (!query.trim()) return items;
const queryLower = query.toLowerCase();
return items.filter((game) => {
const titleLower = game.title.toLowerCase();
let matches = 0;
let queryIndex = 0;
for (
let i = 0;
i < titleLower.length && queryIndex < queryLower.length;
i++
) {
if (titleLower[i] === queryLower[queryIndex]) {
matches++;
queryIndex++;
}
}
return queryIndex === queryLower.length;
});
};
const filteredLibrary = useMemo(() => {
let filtered;
@@ -98,8 +82,25 @@ export default function Library() {
filtered = library;
}
// Apply search filter
return fuzzySearch(searchQuery, filtered);
if (!searchQuery.trim()) return filtered;
const queryLower = searchQuery.toLowerCase();
return filtered.filter((game) => {
const titleLower = game.title.toLowerCase();
let queryIndex = 0;
for (
let i = 0;
i < titleLower.length && queryIndex < queryLower.length;
i++
) {
if (titleLower[i] === queryLower[queryIndex]) {
queryIndex++;
}
}
return queryIndex === queryLower.length;
});
}, [library, filterBy, searchQuery]);
// No sorting for now — rely on filteredLibrary
@@ -118,30 +119,25 @@ export default function Library() {
return (
<section className="library__content">
{hasGames && (
<>
<div className="library__page-header">
<div className="library__controls-row">
<div className="library__controls-left">
<FilterOptions
filterBy={filterBy}
onFilterChange={setFilterBy}
allGamesCount={allGamesCount}
favouritedCount={favouritedCount}
newGamesCount={newGamesCount}
top10Count={top10Count}
/>
</div>
<div className="library__page-header">
<div className="library__controls-row">
<div className="library__controls-left">
<FilterOptions
filterBy={filterBy}
onFilterChange={setFilterBy}
allGamesCount={allGamesCount}
favouritedCount={favouritedCount}
newGamesCount={newGamesCount}
top10Count={top10Count}
/>
</div>
<div className="library__controls-right">
<SearchBar value={searchQuery} onChange={setSearchQuery} />
<ViewOptions
viewMode={viewMode}
onViewModeChange={setViewMode}
/>
</div>
<div className="library__controls-right">
<SearchBar value={searchQuery} onChange={setSearchQuery} />
<ViewOptions viewMode={viewMode} onViewModeChange={setViewMode} />
</div>
</div>
</>
</div>
)}
{!hasGames && (

View File

@@ -22,7 +22,7 @@
}
&__icon {
color: rgba(255, 255, 255, 0.5);
color: rgba(255, 255, 255, 0.75);
flex-shrink: 0;
transition: color 0.2s ease;
}
@@ -38,7 +38,7 @@
min-width: 0;
&::placeholder {
color: rgba(255, 255, 255, 0.4);
color: rgba(255, 255, 255, 0.6);
}
&:focus ~ .search-bar__icon {
@@ -50,7 +50,7 @@
flex-shrink: 0;
background: transparent;
border: none;
color: rgba(255, 255, 255, 0.5);
color: rgba(255, 255, 255, 0.65);
font-size: 18px;
font-weight: bold;
cursor: pointer;

View File

@@ -10,7 +10,7 @@
&__label {
font-size: 14px;
font-weight: 600;
color: rgba(255, 255, 255, 0.8);
color: rgba(255, 255, 255, 0.95);
white-space: nowrap;
}
@@ -30,7 +30,7 @@
border-radius: 6px;
background: rgba(255, 255, 255, 0.04);
border: none;
color: rgba(255, 255, 255, 0.6);
color: rgba(255, 255, 255, 0.9);
cursor: pointer;
font-size: 14px;
font-weight: 500;

View File

@@ -9,7 +9,10 @@ interface ViewOptionsProps {
onViewModeChange: (viewMode: ViewMode) => void;
}
export function ViewOptions({ viewMode, onViewModeChange }: ViewOptionsProps) {
export function ViewOptions({
viewMode,
onViewModeChange,
}: Readonly<ViewOptionsProps>) {
const { t } = useTranslation("library");
return (