Skip to content

Commit

Permalink
fix: improve mobile responsiveness and UI consistency
Browse files Browse the repository at this point in the history
- Enhance table layout for small screens
- Fix tab navigation counts
- Improve text wrapping and spacing
- Optimize settings modal for mobile
- Add responsive gallery grid
- Fix DOM nesting issues
  • Loading branch information
Ghost0159 committed Nov 21, 2024
1 parent 4a3c419 commit 3d4bdcf
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 75 deletions.
105 changes: 58 additions & 47 deletions src/components/ContentTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function ContentTable({
const { updates, dlcs } = getRelatedContent(allItems, baseId);

return (
<div className="flex gap-2">
<div className="flex flex-wrap gap-2">
{updates.length > 0 && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-500/10 text-blue-500">
{updates.length} Update{updates.length !== 1 ? 's' : ''}
Expand All @@ -53,41 +53,37 @@ export function ContentTable({
};

const renderSortHeader = (field: SortField, label: string, showForType?: 'base' | 'update' | 'dlc') => {
// Si showForType est spécifié et que tous les éléments ne sont pas de ce type, masquer la colonne
const shouldShow = !showForType || items.every(item => item.type === showForType);
if (!shouldShow) return null;

return (
<th className="px-6 py-3 text-left">
<button
onClick={() => onSort(field)}
className="flex items-center space-x-2 group w-full"
>
<span className={`
text-xs font-medium uppercase tracking-wider
${sortField === field ? 'text-primary' : 'text-muted-foreground'}
group-hover:text-primary transition-colors
`}>
{label}
</span>
<span className={`
transition-all duration-200
${sortField === field ? 'opacity-100' : 'opacity-0 group-hover:opacity-50'}
`}>
{sortField === field && (
sortDirection === 'asc' ? (
<ArrowUp className="h-4 w-4" />
) : (
<ArrowDown className="h-4 w-4" />
)
)}
</span>
</button>
</th>
<button
onClick={() => onSort(field)}
className="flex items-center space-x-2 group w-full"
>
<span className={`
text-xs font-medium uppercase tracking-wider
${sortField === field ? 'text-primary' : 'text-muted-foreground'}
group-hover:text-primary transition-colors
`}>
{label}
</span>
<span className={`
transition-all duration-200
${sortField === field ? 'opacity-100' : 'opacity-0 group-hover:opacity-50'}
`}>
{sortField === field && (
sortDirection === 'asc' ? (
<ArrowUp className="h-4 w-4" />
) : (
<ArrowDown className="h-4 w-4" />
)
)}
</span>
</button>
);
};

// Vérifier si nous affichons des jeux de base
const showingBaseGames = items.length > 0 && items[0].type === 'base';

return (
Expand All @@ -96,17 +92,29 @@ export function ContentTable({
<table className="min-w-full divide-y divide-border">
<thead className="bg-muted">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider w-24">
<th className="px-3 sm:px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider w-16 sm:w-24">
Icon
</th>
{renderSortHeader('id', 'Title ID')}
{renderSortHeader('name', 'Name')}
{renderSortHeader('size', 'Size')}
{showingBaseGames && renderSortHeader('releaseDate', 'Release Date', 'base')}
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
<th className="px-3 sm:px-6 py-3 text-left">
{renderSortHeader('id', 'Title ID')}
</th>
<th className="px-3 sm:px-6 py-3 text-left">
{renderSortHeader('name', 'Name')}
</th>
<th className="hidden sm:table-cell px-3 sm:px-6 py-3 text-left">
{renderSortHeader('size', 'Size')}
</th>
{showingBaseGames && (
<th className="hidden md:table-cell px-3 sm:px-6 py-3 text-left">
{renderSortHeader('releaseDate', 'Release Date', 'base')}
</th>
)}
<th className="hidden sm:table-cell px-3 sm:px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Related Content
</th>
<th className="px-6 py-3 text-right"></th>
<th className="px-3 sm:px-6 py-3 text-right">
<span className="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
Expand All @@ -116,7 +124,7 @@ export function ContentTable({
onClick={() => handleDetails(item.id)}
className="group hover:bg-muted/50 active:bg-muted transition-colors cursor-pointer"
>
<td className="px-6 py-4">
<td className="px-3 sm:px-6 py-4">
<div className="icon-container transform group-hover:scale-105 transition-transform duration-200">
<img
src={getIconUrl(item.id)}
Expand All @@ -129,8 +137,8 @@ export function ContentTable({
/>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="font-mono text-sm group-hover:text-primary transition-colors">
<td className="px-3 sm:px-6 py-4">
<div className="font-mono text-xs sm:text-sm group-hover:text-primary transition-colors">
{item.id}
</div>
{item.version && (
Expand All @@ -139,27 +147,30 @@ export function ContentTable({
</div>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm group-hover:text-primary transition-colors">
<td className="px-3 sm:px-6 py-4">
<div className="text-sm group-hover:text-primary transition-colors line-clamp-2 sm:line-clamp-1">
{item.name || 'Unknown Title'}
</div>
<div className="sm:hidden text-xs text-muted-foreground mt-1">
{formatFileSize(item.size)}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<td className="hidden sm:table-cell px-3 sm:px-6 py-4 whitespace-nowrap">
<div className="text-sm text-muted-foreground">
{formatFileSize(item.size)}
</div>
</td>
{showingBaseGames && (
<td className="px-6 py-4 whitespace-nowrap">
<td className="hidden md:table-cell px-3 sm:px-6 py-4 whitespace-nowrap">
<div className="text-sm text-muted-foreground">
{formatDate(item.releaseDate)}
</div>
</td>
)}
<td className="px-6 py-4 whitespace-nowrap">
<td className="hidden sm:table-cell px-3 sm:px-6 py-4">
{getContentBadges(item.id)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right">
<td className="px-3 sm:px-6 py-4 text-right">
<Info className="h-5 w-5 text-primary opacity-0 group-hover:opacity-100 transition-opacity" />
</td>
</tr>
Expand All @@ -181,8 +192,8 @@ export function ContentTable({
)}

{selectedId && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-card rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto border border-border">
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center p-2 sm:p-4 z-50">
<div className="bg-card rounded-lg shadow-xl w-full max-h-[95vh] overflow-y-auto border border-border sm:max-w-4xl">
<GameDetails
content={getRelatedContent(allItems, selectedId)}
onClose={() => setSelectedId(null)}
Expand Down
23 changes: 10 additions & 13 deletions src/components/GameDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ function ContentList({ items, maxVisible = 5, type }: ContentListProps) {
<div className="space-y-3">
{visibleItems.map(item => (
<div key={item.uniqueId} className="text-sm">
<p className="font-mono text-xs text-muted-foreground">
<p className="font-mono text-xs text-muted-foreground break-all">
{item.id}
</p>
{item.name && <p className="text-sm">{item.name}</p>}
{item.name && <p className="text-sm break-words">{item.name}</p>}
{item.version && type === 'update' && (
<p className="text-xs text-muted-foreground mt-1">
Version {item.version}
Expand Down Expand Up @@ -67,18 +67,18 @@ export function GameDetails({ content, onClose }: GameDetailsProps) {
const assets = getVisualAssets(base.id);

return (
<div className="p-6 space-y-6">
<div className="p-4 sm:p-6 space-y-4 sm:space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold">Content Details</h2>
<div className="flex items-center space-x-3">
<h2 className="text-lg sm:text-xl font-bold line-clamp-1">{base.name || 'Content Details'}</h2>
<div className="flex items-center space-x-2 sm:space-x-3">
<a
href={`https://stats.ghostland.at/${base.id}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center space-x-2 px-3 py-1.5 rounded-lg bg-primary/10 text-primary hover:bg-primary/20 transition-colors"
className="flex items-center space-x-1 sm:space-x-2 px-2 sm:px-3 py-1.5 rounded-lg bg-primary/10 text-primary hover:bg-primary/20 transition-colors text-sm"
>
<ExternalLink className="h-4 w-4" />
<span>View Stats</span>
<span className="hidden sm:inline">View Stats</span>
</a>
<button
onClick={onClose}
Expand All @@ -90,7 +90,6 @@ export function GameDetails({ content, onClose }: GameDetailsProps) {
</div>
</div>

{/* Banner */}
<div className="relative aspect-video rounded-lg overflow-hidden bg-muted">
<img
src={assets.banner}
Expand All @@ -103,8 +102,7 @@ export function GameDetails({ content, onClose }: GameDetailsProps) {
/>
</div>

{/* Content Info */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="bg-card border border-border p-4 rounded-lg">
<div className="flex items-center space-x-3 mb-3">
<img
Expand All @@ -118,10 +116,10 @@ export function GameDetails({ content, onClose }: GameDetailsProps) {
/>
<div>
<h3 className="text-sm font-medium">Base Game</h3>
<p className="text-xs text-muted-foreground font-mono">{base.id}</p>
<p className="text-xs text-muted-foreground font-mono break-all">{base.id}</p>
</div>
</div>
{base.name && <p className="text-sm mb-2">{base.name}</p>}
{base.name && <p className="text-sm mb-2 break-words">{base.name}</p>}
{base.size && (
<p className="text-sm text-muted-foreground">
Size: {formatFileSize(base.size)}
Expand Down Expand Up @@ -163,7 +161,6 @@ export function GameDetails({ content, onClose }: GameDetailsProps) {
)}
</div>

{/* Screenshots */}
<ScreenshotGallery screenshots={assets.screenshots} />
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/ScreenshotGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function ScreenshotGallery({ screenshots }: ScreenshotGalleryProps) {
<h3 className="text-sm font-medium text-foreground mb-3">Screenshots</h3>

<Gallery>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<div className="grid grid-cols-1 xs:grid-cols-2 md:grid-cols-3 gap-2 sm:gap-4">
{screenshots.map((url, index) => (
<Item
key={index}
Expand All @@ -36,7 +36,7 @@ export function ScreenshotGallery({ screenshots }: ScreenshotGalleryProps) {
}}
/>
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<span className="text-white text-sm font-medium">
<span className="text-white text-xs sm:text-sm font-medium">
View Fullscreen
</span>
</div>
Expand Down
23 changes: 10 additions & 13 deletions src/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export function Settings({ onClose }: SettingsProps) {
} = useUserPreferences();

return (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-card rounded-lg shadow-xl max-w-2xl w-full border border-border">
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center p-2 sm:p-4 z-50">
<div className="bg-card rounded-lg shadow-xl w-full max-w-lg border border-border">
<div className="flex items-center justify-between p-4 border-b border-border">
<div className="flex items-center space-x-2">
<SettingsIcon className="h-5 w-5 text-primary" />
Expand All @@ -47,7 +47,6 @@ export function Settings({ onClose }: SettingsProps) {
</div>

<div className="p-4 space-y-6 max-h-[calc(100vh-200px)] overflow-y-auto">
{/* Search Settings */}
<section className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground">Search Settings</h3>

Expand All @@ -64,7 +63,7 @@ export function Settings({ onClose }: SettingsProps) {
onChange={(e) => setNamePrecision(parseFloat(e.target.value))}
className="w-full accent-primary"
/>
<div className="flex justify-between text-sm text-muted-foreground mt-1">
<div className="flex justify-between text-xs sm:text-sm text-muted-foreground mt-1">
<span>Exact Match</span>
<span>Fuzzy Match</span>
</div>
Expand All @@ -83,14 +82,13 @@ export function Settings({ onClose }: SettingsProps) {
onChange={(e) => setTidPrecision(parseFloat(e.target.value))}
className="w-full accent-primary"
/>
<div className="flex justify-between text-sm text-muted-foreground mt-1">
<div className="flex justify-between text-xs sm:text-sm text-muted-foreground mt-1">
<span>Exact Match</span>
<span>Partial Match</span>
</div>
</div>
</section>

{/* Display Settings */}
<section className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground">Display Settings</h3>

Expand All @@ -101,7 +99,7 @@ export function Settings({ onClose }: SettingsProps) {
<select
value={itemsPerPage}
onChange={(e) => setItemsPerPage(Number(e.target.value))}
className="w-full bg-muted border border-border rounded-lg px-3 py-2"
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm"
>
<option value={10}>10 items</option>
<option value={25}>25 items</option>
Expand Down Expand Up @@ -157,7 +155,7 @@ export function Settings({ onClose }: SettingsProps) {
<select
value={maxDlcDisplay}
onChange={(e) => setMaxDlcDisplay(Number(e.target.value))}
className="w-full bg-muted border border-border rounded-lg px-3 py-2"
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm"
>
<option value={5}>5 DLCs</option>
<option value={10}>10 DLCs</option>
Expand All @@ -174,7 +172,7 @@ export function Settings({ onClose }: SettingsProps) {
<select
value={maxUpdateDisplay}
onChange={(e) => setMaxUpdateDisplay(Number(e.target.value))}
className="w-full bg-muted border border-border rounded-lg px-3 py-2"
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm"
>
<option value={5}>5 Updates</option>
<option value={10}>10 Updates</option>
Expand All @@ -191,7 +189,7 @@ export function Settings({ onClose }: SettingsProps) {
<select
value={autoRefreshInterval || ''}
onChange={(e) => setAutoRefreshInterval(e.target.value ? Number(e.target.value) : null)}
className="w-full bg-muted border border-border rounded-lg px-3 py-2"
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm"
>
<option value="">Disabled</option>
<option value="300000">5 minutes</option>
Expand All @@ -202,7 +200,6 @@ export function Settings({ onClose }: SettingsProps) {
</div>
</section>

{/* Advanced Settings */}
<section className="border-t border-border pt-4">
<button
onClick={() => setShowAdvanced(!showAdvanced)}
Expand All @@ -226,7 +223,7 @@ export function Settings({ onClose }: SettingsProps) {
type="url"
value={dataSources.workingContent}
onChange={(e) => setDataSource('workingContent', e.target.value)}
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm"
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-xs sm:text-sm break-all"
placeholder="Enter URL for working.txt"
/>
</div>
Expand All @@ -239,7 +236,7 @@ export function Settings({ onClose }: SettingsProps) {
type="url"
value={dataSources.titlesDb}
onChange={(e) => setDataSource('titlesDb', e.target.value)}
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm"
className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-xs sm:text-sm break-all"
placeholder="Enter URL for titles_db.txt"
/>
</div>
Expand Down

0 comments on commit 3d4bdcf

Please sign in to comment.