games: per-game pages, games.library table, games group in header/tiles/start
All checks were successful
Deploy production / deploy (push) Successful in 1m7s
All checks were successful
Deploy production / deploy (push) Successful in 1m7s
This commit is contained in:
@@ -184,16 +184,22 @@
|
||||
// into the flyout (crossing a neighbouring tile) does not close it.
|
||||
let flyout = $state<string | null>(null);
|
||||
let flyoutSide = $state<'left' | 'right'>('right');
|
||||
let flyoutUp = $state(false);
|
||||
let flyoutTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
const FLYOUT_WIDTH = 240;
|
||||
function openFlyout(name: string, el: HTMLElement) {
|
||||
// Big groups (games) get a two-column flyout; it opens upwards when it would not fit below.
|
||||
const WIDE_FLYOUT = 6;
|
||||
function openFlyout(name: string, el: HTMLElement, count: number) {
|
||||
clearTimeout(flyoutTimer);
|
||||
if (flyout === name) return;
|
||||
const wide = count > WIDE_FLYOUT;
|
||||
const width = wide ? 432 : 240;
|
||||
const height = (wide ? Math.ceil(count / 2) : count) * 46 + 20;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const panel = el.closest('[data-menu-panel]')?.getBoundingClientRect() ?? rect;
|
||||
const leftColumn = rect.left + rect.width / 2 < panel.left + panel.width / 2;
|
||||
const roomLeft = rect.left - FLYOUT_WIDTH >= 8;
|
||||
const roomRight = rect.right + FLYOUT_WIDTH <= window.innerWidth - 8;
|
||||
const roomLeft = rect.left - width >= 8;
|
||||
const roomRight = rect.right + width <= window.innerWidth - 8;
|
||||
flyoutUp = rect.top + height > window.innerHeight - 8 && rect.bottom - height >= 8;
|
||||
flyoutSide = leftColumn ? (roomLeft ? 'left' : 'right') : roomRight ? 'right' : 'left';
|
||||
flyout = name;
|
||||
}
|
||||
@@ -308,12 +314,21 @@
|
||||
aria-label={`${project.name} trackers`}
|
||||
onpointerenter={(event) => {
|
||||
if (event.pointerType !== 'touch')
|
||||
openFlyout(project.name, event.currentTarget);
|
||||
openFlyout(
|
||||
project.name,
|
||||
event.currentTarget,
|
||||
project.children?.length ?? 0
|
||||
);
|
||||
}}
|
||||
onpointerleave={(event) => {
|
||||
if (event.pointerType !== 'touch') closeFlyoutSoon();
|
||||
}}
|
||||
onfocusin={(event) => openFlyout(project.name, event.currentTarget)}
|
||||
onfocusin={(event) =>
|
||||
openFlyout(
|
||||
project.name,
|
||||
event.currentTarget,
|
||||
project.children?.length ?? 0
|
||||
)}
|
||||
onfocusout={(event) => {
|
||||
if (!event.currentTarget.contains(event.relatedTarget as Node | null))
|
||||
flyout = null;
|
||||
@@ -345,23 +360,30 @@
|
||||
<!-- The padding facing the tile is a hover bridge into the panel. -->
|
||||
<div
|
||||
class={[
|
||||
'absolute -top-2 w-60',
|
||||
'absolute',
|
||||
flyoutUp ? '-bottom-2' : '-top-2',
|
||||
project.children.length > WIDE_FLYOUT ? 'w-[27rem]' : 'w-60',
|
||||
flyoutSide === 'right' ? 'left-full pl-3' : 'right-full pr-3'
|
||||
]}
|
||||
>
|
||||
<div
|
||||
class="flyout grid gap-1 rounded-md border border-border bg-popover p-1.5 shadow-lg"
|
||||
class={[
|
||||
'flyout grid gap-1 rounded-md border border-border bg-popover p-1.5 shadow-lg',
|
||||
project.children.length > WIDE_FLYOUT && 'grid-cols-2'
|
||||
]}
|
||||
class:from-left={flyoutSide === 'left'}
|
||||
>
|
||||
{#each project.children as child (child.name)}
|
||||
<a
|
||||
href={resolve(child.href as '/')}
|
||||
class="tile flex items-center gap-2.5 p-1.5 text-sm"
|
||||
class="tile flex min-w-0 items-center gap-2.5 p-1.5 text-sm"
|
||||
title={child.subtitle}
|
||||
aria-current={current(child.href) ? 'page' : undefined}
|
||||
onkeydown={closeFlyoutOnEscape}
|
||||
><TrackerIcon name={child.name} class="tile-icon size-8" /><span
|
||||
class="font-medium">{child.name}</span
|
||||
></a
|
||||
><TrackerIcon
|
||||
name={child.icon ?? child.name}
|
||||
class="tile-icon size-8"
|
||||
/><span class="truncate font-medium">{child.name}</span></a
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -520,9 +542,10 @@
|
||||
href={resolve(child.href as '/')}
|
||||
class="tile flex min-w-0 items-center gap-2 p-1.5 text-sm"
|
||||
onclick={() => (menuOpen = false)}
|
||||
><TrackerIcon name={child.name} class="tile-icon size-6" /><span
|
||||
class="truncate">{child.name}</span
|
||||
></a
|
||||
><TrackerIcon
|
||||
name={child.icon ?? child.name}
|
||||
class="tile-icon size-6"
|
||||
/><span class="truncate">{child.name}</span></a
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user