CoffinsStep: rewrite to grid-sidebar ecommerce layout

- Switch from wide-form to grid-sidebar (viewport-locked, independent scroll)
- Sidebar: heading, allowance info bubble (conditional), category menu with
  expandable subcategories, dual-knob price slider with editable inputs,
  sort by dropdown, clear all filters, save-and-exit link
- Grid: coffin cards with thumbnail hover preview, equal-height cards,
  subtle bg for white-bg product photos, colour count, Most Popular badge
- Card click navigates to CoffinDetailsStep (no Continue button)
- 20 coffins per page max before pagination
- WizardLayout grid-sidebar: wider sidebar (28%), overflowX hidden,
  vertical scroll at all breakpoints, viewport-lock on desktop only

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 10:36:23 +11:00
parent 6041ca2504
commit 34cd87c85a
4 changed files with 600 additions and 306 deletions

View File

@@ -230,31 +230,64 @@ const ListDetailLayout: React.FC<{
</Container>
);
/** Grid + Sidebar: ~25% filter sidebar (left) / ~75% card grid (right) */
/** Grid + Sidebar: ~25% filter sidebar (left) / ~75% card grid (right).
* Viewport-locked on desktop — both panels scroll independently.
* On mobile, stacks vertically and scrolls as a single page. */
const GridSidebarLayout: React.FC<{
children: React.ReactNode;
secondaryPanel?: React.ReactNode;
}> = ({ children, secondaryPanel }) => (
<Container maxWidth="lg" sx={{ flex: 1, py: 3 }}>
<Box
sx={{
display: 'flex',
flex: 1,
overflow: { md: 'hidden' },
flexDirection: { xs: 'column', md: 'row' },
maxWidth: 1200,
mx: 'auto',
width: '100%',
}}
>
{/* Left sidebar — scrollbar visible on hover */}
<Box
component="aside"
sx={{
display: 'flex',
gap: { xs: 0, md: 3 },
flexDirection: { xs: 'column', md: 'row' },
width: { xs: '100%', md: '28%' },
flexShrink: 0,
overflowY: 'auto',
overflowX: 'hidden',
px: { xs: 2, md: 3 },
py: 3,
scrollbarWidth: 'thin',
scrollbarColor: 'transparent transparent',
'&:hover': {
scrollbarColor: 'rgba(0,0,0,0.25) transparent',
},
'&::-webkit-scrollbar': { width: 6 },
'&::-webkit-scrollbar-thumb': {
background: 'transparent',
borderRadius: 3,
},
'&:hover::-webkit-scrollbar-thumb': {
background: 'rgba(0,0,0,0.25)',
},
}}
>
<Box
component="aside"
sx={{
width: { xs: '100%', md: '25%' },
flexShrink: 0,
}}
>
{children}
</Box>
<Box sx={{ flex: 1 }}>{secondaryPanel}</Box>
{children}
</Box>
</Container>
{/* Right panel — always scrollable */}
<Box
sx={{
flex: 1,
overflowY: { md: 'auto' },
px: { xs: 2, md: 3 },
py: 3,
scrollbarWidth: 'thin',
}}
>
{secondaryPanel}
</Box>
</Box>
);
/** Detail + Toggles: scrollable left (image/desc) / sticky right (info/CTA) */
@@ -401,6 +434,10 @@ export const WizardLayout = React.forwardRef<HTMLDivElement, WizardLayoutProps>(
height: '100vh',
overflow: 'hidden',
}),
...(variant === 'grid-sidebar' && {
height: { xs: 'auto', md: '100vh' },
overflow: { xs: 'visible', md: 'hidden' },
}),
},
...(Array.isArray(sx) ? sx : [sx]),
]}