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:
1 parent
0dab935911
commit
269fe03a80
3 files changed
+19
-25
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user