From 0cd85ae2870ed6f037d268eb7a5cd9e6d9fa87ba Mon Sep 17 00:00:00 2001 From: kang Date: Thu, 21 May 2026 17:28:06 +0800 Subject: [PATCH] feat: enhance UI consistency and improve spacing across components - Added styles for player-side toast notifications to improve user feedback. - Adjusted padding and spacing in various components for a more cohesive layout. - Updated card and dialog components to streamline visual hierarchy and enhance readability. - Refactored player panel and navigation elements for better alignment and user experience. --- .../(player)/(main)/results/[drawNo]/page.tsx | 2 +- src/app/(player)/(main)/results/page.tsx | 2 +- src/app/globals.css | 28 +++++++ src/components/layout/player-app-shell.tsx | 2 +- src/components/layout/player-bottom-nav.tsx | 11 +-- src/components/layout/player-panel.tsx | 79 +++++++++++-------- src/components/ui/card.tsx | 8 +- src/components/ui/sonner.tsx | 38 +++++---- src/features/hall/hall-bet-preview-dialog.tsx | 12 +-- src/features/hall/hall-bet-result-dialog.tsx | 14 ++-- src/features/hall/hall-betting-grid.tsx | 25 ++++-- src/features/hall/hall-draw-panel.tsx | 8 +- src/features/hall/hall-play-catalog-panel.tsx | 2 +- src/features/hall/hall-screen.tsx | 75 ++++++++++-------- src/features/hall/hall-wallet-strip.tsx | 6 +- src/features/hall/jackpot-burst-overlay.tsx | 6 +- .../orders/ticket-order-detail-screen.tsx | 14 +--- .../orders/ticket-orders-list-screen.tsx | 8 +- src/features/player/entry-gate.tsx | 4 +- src/features/results/check-winning-screen.tsx | 6 +- .../results/draw-result-detail-screen.tsx | 14 +--- .../results/draw-results-list-screen.tsx | 8 +- .../results/twenty-three-results-grid.tsx | 2 +- src/features/rules/play-rules-screen.tsx | 3 +- src/features/wallet/transfer-in-screen.tsx | 2 +- src/features/wallet/transfer-out-screen.tsx | 2 +- src/features/wallet/wallet-logs-block.tsx | 2 +- src/features/wallet/wallet-logs-screen.tsx | 6 +- src/features/wallet/wallet-screen.tsx | 8 +- .../wallet/wallet-transfer-dialogs.tsx | 8 +- src/features/wallet/wallet-transfer-forms.tsx | 15 ++-- src/lib/player-spacing.ts | 11 +++ src/lib/utils.ts | 12 +++ 33 files changed, 253 insertions(+), 190 deletions(-) create mode 100644 src/lib/player-spacing.ts diff --git a/src/app/(player)/(main)/results/[drawNo]/page.tsx b/src/app/(player)/(main)/results/[drawNo]/page.tsx index db354ca..f8c170b 100644 --- a/src/app/(player)/(main)/results/[drawNo]/page.tsx +++ b/src/app/(player)/(main)/results/[drawNo]/page.tsx @@ -9,7 +9,7 @@ export default async function DrawResultByNoPage(props: PageProps) { const drawNo = decodeURIComponent(raw); return ( -
+
); diff --git a/src/app/(player)/(main)/results/page.tsx b/src/app/(player)/(main)/results/page.tsx index 2abd1d7..2c963c2 100644 --- a/src/app/(player)/(main)/results/page.tsx +++ b/src/app/(player)/(main)/results/page.tsx @@ -2,7 +2,7 @@ import { DrawResultsListScreen } from "@/features/results/draw-results-list-scre export default function DrawResultsHistoryPage() { return ( -
+
); diff --git a/src/app/globals.css b/src/app/globals.css index c56032b..b441295 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -127,4 +127,32 @@ html { @apply font-sans; } +} + +/* 玩家端 Toast:顶部居中、紧凑尺寸(位置见 components/ui/sonner.tsx) */ +[data-sonner-toaster] { + --width: min(280px, calc(100vw - 24px)); +} + +[data-sonner-toast][data-styled="true"] { + padding: 8px 12px; + font-size: 12px; + line-height: 1.35; + box-shadow: 0 4px 14px rgba(15, 23, 42, 0.1); +} + +[data-sonner-toast][data-styled="true"] [data-title] { + font-size: 12px; + font-weight: 600; + line-height: 1.35; +} + +[data-sonner-toast][data-styled="true"] [data-description] { + font-size: 11px; + line-height: 1.35; +} + +[data-sonner-toast][data-styled="true"] [data-icon] svg { + width: 14px; + height: 14px; } \ No newline at end of file diff --git a/src/components/layout/player-app-shell.tsx b/src/components/layout/player-app-shell.tsx index 0315d63..47f81fe 100644 --- a/src/components/layout/player-app-shell.tsx +++ b/src/components/layout/player-app-shell.tsx @@ -20,7 +20,7 @@ export function PlayerAppShell({ children }: PlayerAppShellProps): ReactNode { return (
-
+
{children}
diff --git a/src/components/layout/player-bottom-nav.tsx b/src/components/layout/player-bottom-nav.tsx index 41ecab7..12adfa5 100644 --- a/src/components/layout/player-bottom-nav.tsx +++ b/src/components/layout/player-bottom-nav.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import { BarChart3, BookOpen, ClipboardList, Home, Wallet } from "lucide-react"; +import { BarChart3, ClipboardList, Home, Wallet } from "lucide-react"; import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; @@ -30,13 +30,6 @@ const tabs = [ icon: ClipboardList, match: (p: string) => p === "/orders" || p.startsWith("/orders/"), }, - { - href: "/rules", - labelKey: "nav.rules", - labelDefault: "规则", - icon: BookOpen, - match: (p: string) => p === "/rules", - }, { href: "/wallet", labelKey: "nav.wallet", @@ -58,7 +51,7 @@ export function PlayerBottomNav() { className="fixed bottom-0 left-0 right-0 z-50 border-t border-[#e4ebf5] bg-white/96 pb-[env(safe-area-inset-bottom,0px)] shadow-[0_-10px_30px_rgba(15,23,42,0.08)] backdrop-blur-md" aria-label={t("nav.aria")} > -
+
{tabs.map(({ href, labelKey, labelDefault, icon: Icon, match }) => { const active = match(pathname); const label = t(labelKey, { defaultValue: labelDefault }); diff --git a/src/components/layout/player-panel.tsx b/src/components/layout/player-panel.tsx index d2a7680..426b87f 100644 --- a/src/components/layout/player-panel.tsx +++ b/src/components/layout/player-panel.tsx @@ -7,12 +7,15 @@ import { Bell, ChevronLeft } from "lucide-react"; import { useTranslation } from "react-i18next"; import { LanguageSwitcher } from "@/components/language-switcher"; +import { + playerHeaderControl, + playerPageHeader, + playerPageInset, +} from "@/lib/player-spacing"; import { cn } from "@/lib/utils"; type PlayerPanelProps = { title: string; - subtitle?: string; - eyebrow?: string; children: ReactNode; backHref?: string; backLabel?: string; @@ -22,7 +25,6 @@ type PlayerPanelProps = { export function PlayerPanel({ title, - subtitle, children, backHref = "/hall", backLabel, @@ -37,42 +39,53 @@ export function PlayerPanel({
-
- - - {resolvedBackLabel} - -
-

+
+
+ + + {resolvedBackLabel} + +
+ +
+

{title}

- {subtitle ? ( -

- {subtitle} -

- ) : null}
- - -

+ +
+ + +
+ {children}
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 40cac5f..1ebe9c0 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -12,7 +12,7 @@ function Card({ data-slot="card" data-size={size} className={cn( - "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + "group/card flex flex-col gap-3 overflow-hidden rounded-xl bg-card py-3 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-2 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", className )} {...props} @@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
) { return (
) @@ -84,7 +84,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
{ return ( - ), - info: ( - - ), - warning: ( - - ), - error: ( - - ), - loading: ( - - ), + success: , + info: , + warning: , + error: , + loading: , }} style={ { "--normal-bg": "var(--popover)", "--normal-text": "var(--popover-foreground)", "--normal-border": "var(--border)", - "--border-radius": "var(--radius)", + "--border-radius": "calc(var(--radius) * 0.8)", + "--width": "min(280px, calc(100vw - 24px))", } as React.CSSProperties } toastOptions={{ classNames: { - toast: "cn-toast", + toast: + "cn-toast !min-h-0 !w-full !max-w-[min(280px,calc(100vw-24px))] !items-center !gap-2 !rounded-lg !border !px-3 !py-2 !text-xs !shadow-md", + title: "!text-xs !font-semibold !leading-snug", + description: "!text-[11px] !leading-snug !text-muted-foreground", + icon: "!mr-0 !size-3.5", }, }} {...props} diff --git a/src/features/hall/hall-bet-preview-dialog.tsx b/src/features/hall/hall-bet-preview-dialog.tsx index 3e5600e..d9c4655 100644 --- a/src/features/hall/hall-bet-preview-dialog.tsx +++ b/src/features/hall/hall-bet-preview-dialog.tsx @@ -4,6 +4,7 @@ import { AlertTriangleIcon, CheckCircle2, LoaderCircle, + Lock, WalletCards, XIcon, } from "lucide-react"; @@ -137,7 +138,7 @@ export function HallBetPreviewDialog({ showCloseButton={false} className="flex max-h-[calc(100dvh-24px)] flex-col gap-0 overflow-hidden rounded-2xl border-[#e4ebf6] bg-white p-0 shadow-[0_18px_60px_rgba(15,23,42,0.18)] ring-[#e8eef7] sm:max-h-[min(92vh,760px)] sm:max-w-lg" > -
+
-
+
{!data ? (

{t("hall.preview.empty")}

) : ( @@ -291,7 +292,7 @@ export function HallBetPreviewDialog({
-
+
-
+
{!data ? (

{t("hall.result.empty")} @@ -92,7 +92,7 @@ export function HallBetResultDialog({

-
+
@@ -106,7 +106,7 @@ export function HallBetResultDialog({
-
+

{t("hall.result.orderNo")}:{" "} {data.order_no} @@ -198,7 +198,7 @@ export function HallBetResultDialog({

-
+
-
+
{t("hall.table.draftTotal")} @@ -1255,9 +1255,18 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot } type="button" disabled={!canSubmit || previewLoading} onClick={() => void handlePreview()} - className="h-12 w-full rounded-xl border-0 bg-[#e5002c] text-base font-bold text-white shadow-[0_8px_20px_rgba(229,0,44,0.26)] hover:bg-[#d10028]" + className={cn( + "h-12 w-full gap-2 rounded-xl border-0 text-base font-bold text-white", + !isBettable + ? "bg-slate-500 shadow-none hover:bg-slate-500 disabled:opacity-100" + : "bg-[#e5002c] shadow-[0_8px_20px_rgba(229,0,44,0.26)] hover:bg-[#d10028]", + )} > - + {!isBettable ? ( + + ) : ( + + )} {previewLoading ? t("hall.table.previewing") : !isBettable diff --git a/src/features/hall/hall-draw-panel.tsx b/src/features/hall/hall-draw-panel.tsx index 77997d8..d2ddef0 100644 --- a/src/features/hall/hall-draw-panel.tsx +++ b/src/features/hall/hall-draw-panel.tsx @@ -90,7 +90,7 @@ export function HallDrawPanel({ drawLive }: { drawLive: HallDrawLiveSnapshot }) if (error) { return ( -
+

{t(error, { defaultValue: error })}

-
+
+ + + {tp("nav.rules")} + + +
+ diff --git a/src/features/hall/hall-wallet-strip.tsx b/src/features/hall/hall-wallet-strip.tsx index d067cfe..5f195ed 100644 --- a/src/features/hall/hall-wallet-strip.tsx +++ b/src/features/hall/hall-wallet-strip.tsx @@ -86,10 +86,10 @@ export function HallWalletStrip() { const availableMinor = Number(balance?.available_balance ?? 0); return ( -
+
-
+
-
+
{t("hall.jackpotBurst.amount")} {amount}
-
+
{t("hall.jackpotBurst.winners")} {event.winner_count}
-
+
{t("hall.jackpotBurst.triggerLabel")} diff --git a/src/features/orders/ticket-order-detail-screen.tsx b/src/features/orders/ticket-order-detail-screen.tsx index 2cdc1bf..74cf0f8 100644 --- a/src/features/orders/ticket-order-detail-screen.tsx +++ b/src/features/orders/ticket-order-detail-screen.tsx @@ -99,12 +99,10 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) { return ( -
+
@@ -116,12 +114,10 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) { return ( -
+

{error ?? t("orders.noData")}

) : items.length === 0 ? ( -
+

{t("orders.empty")}

-
+
{phase === "loading" ? (
@@ -279,7 +279,7 @@ export function EntryGate() {
-
+
{steps.map((step) => (
-
+
@@ -104,7 +104,7 @@ export function CheckWinningScreen() {

-
+