Live picks: one shared card with animated banner header for desktop and phone popup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
simandClaude Sonnet 5.5 committed 2026-11-06 14:04:34 +01:00
1 parent 0dab935911
commit 269fe03a80
3 files changed
+19 -25

No files matched your search

+1 -1
View File
@@ -85,7 +85,7 @@ test("live picks on phones: bolt button with count opens a popup and minimizes a
await expect(page.getByText("1 person has ordered")).toBeVisible();
await page.reload();
await expect(page.getByText("1 person has ordered")).toBeVisible(); // expanded state is remembered
await page.getByRole("button", { name: "Minimize live picks" }).click();
await page.getByRole("button", { name: /1 ordered/ }).click();
await expect(page.getByText("1 person has ordered")).toBeHidden();
await expect(fab).toBeVisible();
await page.close();
+15 -23
View File
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { CloseOutlined, DownOutlined, LockOutlined, ThunderboltFilled } from "@ant-design/icons";
import { Button, Card, Flex, Progress, Typography } from "antd";
import { DownOutlined, LockOutlined, ThunderboltFilled } from "@ant-design/icons";
import { Card, Flex, Progress, Typography } from "antd";
import type { OrderStats } from "../../lib/services/ordersService";
import { getStoredValue, setStoredValue } from "../../lib/storage";
@@ -55,22 +55,17 @@ function summary(stats: OrderStats, closed: boolean) {
const icon = (closed: boolean) => (closed ? <LockOutlined /> : <ThunderboltFilled />);
/** Desktop: expandable banner above the order card; the bars unfold below it. */
export function LivePicksBanner({ stats, closed }: { stats: OrderStats; closed: boolean }) {
const [open, setOpen] = useStoredFlag(EXPANDED_KEY);
/** One card for both layouts: the animated banner is its header, the bars unfold below it. */
function LivePicksCard({ stats, closed, open, onToggle }: { stats: OrderStats; closed: boolean; open: boolean; onToggle: () => void }) {
return (
<div>
<button
className={closed ? "live-banner closed" : "live-banner"}
aria-expanded={open}
onClick={() => setOpen(!open)}
>
<div className={open ? "live-card open" : "live-card"}>
<button className={closed ? "live-banner closed" : "live-banner"} aria-expanded={open} onClick={onToggle}>
{icon(closed)}
<span className="live-banner-text">{summary(stats, closed)}</span>
<DownOutlined className={open ? "live-chevron open" : "live-chevron"} />
</button>
{open && (
<Card size="small" style={{ marginTop: 8 }} styles={{ body: { padding: "12px 16px" } }}>
<Card size="small" className="live-card-body" styles={{ body: { padding: "12px 16px" } }}>
<Bars stats={stats} />
</Card>
)}
@@ -78,21 +73,18 @@ export function LivePicksBanner({ stats, closed }: { stats: OrderStats; closed:
);
}
/** Phones: a bolt button with the order count; tap for the popup, tap again (or the X) to minimize. */
/** Desktop: inline above the order card. */
export function LivePicksBanner({ stats, closed }: { stats: OrderStats; closed: boolean }) {
const [open, setOpen] = useStoredFlag(EXPANDED_KEY);
return <LivePicksCard stats={stats} closed={closed} open={open} onToggle={() => setOpen(!open)} />;
}
/** Phones: a bolt button with the order count; tap for the card as a popup, tap its header to minimize. */
export function LivePicksFab({ stats, closed }: { stats: OrderStats; closed: boolean }) {
const [open, setOpen] = useStoredFlag(EXPANDED_KEY);
return (
<div className="live-fab-wrap">
{open && (
<Card
size="small"
className="live-sheet"
title={closed ? "Final picks" : "Live picks"}
extra={<Button type="text" size="small" aria-label="Minimize live picks" icon={<CloseOutlined />} onClick={() => setOpen(false)} />}
>
<Bars stats={stats} />
</Card>
)}
{open && <LivePicksCard stats={stats} closed={closed} open onToggle={() => setOpen(false)} />}
{!open && (
<button
className={closed ? "live-fab closed" : "live-fab"}
+3 -1
View File
@@ -211,7 +211,9 @@
pointer-events: none;
}
.live-fab-wrap > * { pointer-events: auto; }
.live-sheet { width: 100%; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); }
.live-fab-wrap .live-card { width: 100%; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); border-radius: 16px; }
.live-card.open .live-banner { border-radius: 16px 16px 0 0; }
.live-card-body { border-top: 0 !important; border-radius: 0 0 16px 16px !important; }
.live-fab {
position: relative;
width: 48px;