fix(wardrobe): strip route-idiom wrapper from ListView so it fits both shells

ListView wrapped itself in `mx-auto max-w-5xl p-4 sm:p-6`, which is the
route-page idiom. Problem: wherever the component renders, that wrapper
is wrong.

- In the workbench homepage the AppPage card is a ~480px-wide ModuleShell
  with its own padding; max-w-5xl is a no-op, but the extra p-4/sm:p-6
  stacks on the shell's header padding and the inner GridView hero
  padding, pushing content off-centre and wasting vertical space.
- On /wardrobe the (app)/+layout already supplies
  `mx-auto max-w-7xl px-3 sm:px-6 lg:px-8`, so ListView was doubling the
  centring and tripling the padding.

Replace the Tailwind wrapper with a scoped `.wardrobe-root` using theme
tokens (hsl(var(--color-border)) etc.) and `container-type: inline-size`
— same pattern picture/ListView uses to adapt to its shell. Tabs move
into a scoped .wardrobe-tab style so the active underline uses a real
`border-bottom` instead of an absolute span (cleaner and no overflow
clipping on narrow cards).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-23 21:53:19 +02:00
parent c404db5b6e
commit 25314200b2

View file

@ -3,6 +3,13 @@
Outfits (OutfitsView). Keep the tab state local; SvelteKit keeps
ListView mounted across navigations within /wardrobe/, so scrolling
back to "/wardrobe" preserves which tab the user last opened.
No mx-auto / max-w wrapper here: the workbench AppPage renders us
inside a width-sized ModuleShell (~480px by default), and the /wardrobe
RoutePage is wrapped by (app)/+layout.svelte's `mx-auto max-w-7xl`.
Adding our own cap stacks paddings and looks wrong in both places.
container-type: inline-size lets inner views react to the card width
the same way picture/ListView does.
-->
<script lang="ts">
import GridView from './views/GridView.svelte';
@ -18,28 +25,76 @@
];
</script>
<div class="mx-auto max-w-5xl p-4 sm:p-6">
<nav class="mb-6 flex gap-1 border-b border-border">
<div class="wardrobe-root">
<nav class="wardrobe-tabs" aria-label="Ansicht wechseln">
{#each TABS as tab (tab.key)}
<button
type="button"
class="wardrobe-tab"
class:active={activeTab === tab.key}
aria-pressed={activeTab === tab.key}
onclick={() => (activeTab = tab.key)}
class="relative px-3 py-2 text-sm font-medium transition-colors {activeTab === tab.key
? 'text-primary'
: 'text-muted-foreground hover:text-foreground'}"
>
{tab.label}
{#if activeTab === tab.key}
<span class="absolute -bottom-px left-0 right-0 h-0.5 bg-primary" aria-hidden="true"
></span>
{/if}
</button>
{/each}
</nav>
{#if activeTab === 'garments'}
<GridView />
{:else}
<OutfitsView />
{/if}
<div class="wardrobe-body">
{#if activeTab === 'garments'}
<GridView />
{:else}
<OutfitsView />
{/if}
</div>
</div>
<style>
.wardrobe-root {
display: flex;
flex-direction: column;
gap: 0.75rem;
height: 100%;
padding: 0.5rem 0.75rem 0.75rem;
container-type: inline-size;
}
.wardrobe-tabs {
display: flex;
gap: 0.25rem;
border-bottom: 1px solid hsl(var(--color-border));
flex-shrink: 0;
}
.wardrobe-tab {
position: relative;
padding: 0.5rem 0.75rem;
margin-bottom: -1px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
font: inherit;
font-size: 0.8125rem;
font-weight: 500;
color: hsl(var(--color-muted-foreground));
cursor: pointer;
transition:
color 0.15s,
border-color 0.15s;
}
.wardrobe-tab:hover {
color: hsl(var(--color-foreground));
}
.wardrobe-tab.active {
color: hsl(var(--color-foreground));
border-bottom-color: hsl(var(--color-primary));
}
.wardrobe-body {
flex: 1;
min-height: 0;
}
@container (min-width: 640px) {
.wardrobe-root {
padding: 0.75rem 1rem 1rem;
gap: 1rem;
}
}
</style>