6b506a9ca8
* feat(ui): frame-layout shell, pill buttons, 24px page titles (UI migration PR 1) The visual shell from the concept, zero behavior change: - New --frame token pair (40 18% 96% light / 0 0% 5% dark); the dashboard wrapper is bg-frame and <main> becomes a rounded 12px panel with its own inner scroll (md-gated; mobile keeps document flow + bottom nav) - Sidebar goes borderless/transparent on the frame - Buttons are pills app-wide (radius 99px, default 7px/16px padding, 13px text, icon buttons become circles), set once in components/ui/button.tsx - PageHeader locked at exactly 24px/32px Hedvig serif - MainContainer resets panel scroll on route change (Next's window scroll-to-top never fires for an inner scroll container) - chat layout + extension workspaces switch viewport-height formulas to h-full so they fill the panel instead of overflowing it by 20px - .claude/rules/design.md rewritten with the 14 locked UI-migration conventions from dev_docs/ui_migration_plan.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): clamp hand-rolled page titles to the locked 24px/32px Transaktioner (TransactionStatusBar) and 13 other pages hand-roll their h1 instead of using PageHeader, so they kept text-3xl/4xl after the shell change. Clamp them all to font-display text-2xl leading-8. Onboarding heroes and headline numbers are intentionally untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): keep the frame strip above the panel on banner-less accounts <main>'s 10px top margin was the first in-flow margin inside the shell wrapper, so it collapsed through the wrapper and pushed the whole shell down, showing white body background above the panel instead of the warm frame strip (only visible on real accounts: the sandbox banner blocked the collapse). Flex containers never collapse child margins, so the shell wrappers become md:flex md:flex-col. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
'use client'
|
|
|
|
import { usePathname } from 'next/navigation'
|
|
import { useEffect } from 'react'
|
|
import type { ReactNode } from 'react'
|
|
|
|
/**
|
|
* Picks the dashboard chrome container based on route. Extension workspaces
|
|
* (/e/*) and the /chat app shell want the full viewport for their own
|
|
* multi-pane layouts; everything else gets the centered max-w-5xl card.
|
|
*
|
|
* Lives in a client component because the parent (dashboard) layout is
|
|
* shared across all dashboard routes. Server-side pathname checks done in
|
|
* the layout don't re-evaluate reliably on soft navigation between sibling
|
|
* routes, so the wrapper class would otherwise stick on whichever branch
|
|
* the first render picked.
|
|
*/
|
|
export function MainContainer({
|
|
companyId,
|
|
children,
|
|
}: {
|
|
companyId: string | null
|
|
children: ReactNode
|
|
}) {
|
|
const pathname = usePathname()
|
|
|
|
// The panel (<main>) is its own scroll container on desktop, so Next's
|
|
// built-in scroll-to-top on navigation (which targets the window) never
|
|
// fires for it. Reset the panel scroll on every route change; hash-anchor
|
|
// scrolling still works because pages call scrollIntoView themselves.
|
|
useEffect(() => {
|
|
document.getElementById('main-content')?.scrollTo(0, 0)
|
|
}, [pathname])
|
|
|
|
// Full-bleed routes own their own padding + multi-pane layout. They
|
|
// shouldn't sit inside max-w-5xl or any horizontal padding: that's what
|
|
// causes a visible gap between the dashboard sidebar and the chat-sidebar
|
|
// pane on wide viewports.
|
|
const isFullBleed = pathname.startsWith('/e/') || pathname.startsWith('/chat')
|
|
|
|
// The salary run detail page drives a wide, horizontal-flow layout (progress
|
|
// band + 5-up KPIs + full-width employee ledger) that the standard max-w-5xl
|
|
// column squeezes. It opts into a wider canvas — a deliberate, scoped
|
|
// exception to the locked container token. Match only /salary/runs/{id}, not
|
|
// its nested employee sub-pages.
|
|
const isWide = /^\/salary\/runs\/[^/]+$/.test(pathname)
|
|
|
|
if (isFullBleed) {
|
|
return <div key={companyId ?? ''} className="h-full">{children}</div>
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={companyId ?? ''}
|
|
className={
|
|
isWide
|
|
? 'max-w-7xl mx-auto px-5 py-8 md:px-8 md:py-10'
|
|
: 'max-w-5xl mx-auto px-5 py-8 md:px-8 md:py-10'
|
|
}
|
|
>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|