feat: add jackpot animations and enhance currency handling across components
- Introduced new CSS animations for jackpot effects to improve visual engagement. - Integrated CurrencySwitcher into PlayerPanel and HallScreen for better currency management. - Updated various components to utilize active player currency for consistent display. - Enhanced event handling for currency changes to ensure real-time updates across the application.
This commit is contained in:
151
src/components/currency-switcher.tsx
Normal file
151
src/components/currency-switcher.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
|
||||
import { Banknote, ChevronDown } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CurrencySwitcherProps = {
|
||||
variant?: "default" | "header" | "minimal";
|
||||
menuAlign?: "start" | "end";
|
||||
className?: string;
|
||||
showLabel?: boolean;
|
||||
};
|
||||
|
||||
export function CurrencySwitcher({
|
||||
variant = "minimal",
|
||||
menuAlign,
|
||||
className,
|
||||
showLabel = true,
|
||||
}: CurrencySwitcherProps) {
|
||||
const { t } = useTranslation("common");
|
||||
const { activeCurrency, bettableCurrencies, canSwitchCurrency, setActiveCurrency } =
|
||||
useActivePlayerCurrency();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
bettableCurrencies.map((row) => ({
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
label: t("currency.option", { code: row.code, name: row.name }),
|
||||
})),
|
||||
[bettableCurrencies, t],
|
||||
);
|
||||
|
||||
if (!canSwitchCurrency) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full border border-[#e4eaf4] bg-[#f8fafc] px-2 text-xs font-bold text-[#0b3f96]",
|
||||
className,
|
||||
)}
|
||||
aria-label={t("currency.current", { code: activeCurrency })}
|
||||
>
|
||||
<Banknote className="size-3.5 shrink-0" aria-hidden />
|
||||
{showLabel ? <span>{activeCurrency}</span> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const variantStyles = {
|
||||
default: {
|
||||
button: "border border-white/20 bg-white/10 text-white hover:bg-white/20",
|
||||
dropdown: "border border-gray-200 bg-white shadow-lg",
|
||||
item: "text-gray-800 hover:bg-gray-100",
|
||||
activeItem: "bg-red-50 text-red-600",
|
||||
},
|
||||
header: {
|
||||
button: "text-white/80 hover:bg-white/10 hover:text-white",
|
||||
dropdown: "border border-white/20 bg-white/95 shadow-xl backdrop-blur-sm",
|
||||
item: "text-gray-800 hover:bg-white/10",
|
||||
activeItem: "bg-red-500/10 text-red-600",
|
||||
},
|
||||
minimal: {
|
||||
button: "text-current hover:bg-black/5",
|
||||
dropdown: "border border-gray-200 bg-white shadow-lg",
|
||||
item: "text-gray-800 hover:bg-gray-100",
|
||||
activeItem: "bg-red-50 text-red-600",
|
||||
},
|
||||
} as const;
|
||||
|
||||
const styles = variantStyles[variant];
|
||||
const align = menuAlign ?? (variant === "header" || variant === "default" ? "start" : "end");
|
||||
|
||||
function handleSelect(code: string): void {
|
||||
setActiveCurrency(code);
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger
|
||||
className={cn("inline-flex", className)}
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
|
||||
styles.button,
|
||||
)}
|
||||
aria-expanded={isOpen}
|
||||
aria-haspopup="listbox"
|
||||
aria-label={t("currency.switchAria", { code: activeCurrency })}
|
||||
>
|
||||
<Banknote className="size-4 shrink-0" aria-hidden />
|
||||
{showLabel ? <span>{activeCurrency}</span> : null}
|
||||
<ChevronDown
|
||||
className={cn("size-4 transition-transform duration-200", isOpen && "rotate-180")}
|
||||
aria-hidden
|
||||
/>
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
<PopoverContent
|
||||
align={align}
|
||||
side="bottom"
|
||||
sideOffset={6}
|
||||
className={cn(
|
||||
"z-[200] w-auto min-w-[min(100vw-2rem,200px)] max-w-[min(100vw-2rem,280px)] p-1 text-gray-900",
|
||||
styles.dropdown,
|
||||
)}
|
||||
>
|
||||
<div className="max-h-[min(280px,50dvh)] overflow-y-auto" role="listbox">
|
||||
{options.map((option) => (
|
||||
<button
|
||||
key={option.code}
|
||||
type="button"
|
||||
onClick={() => handleSelect(option.code)}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors",
|
||||
activeCurrency === option.code ? styles.activeItem : styles.item,
|
||||
)}
|
||||
role="option"
|
||||
aria-selected={activeCurrency === option.code}
|
||||
>
|
||||
<div className="flex min-w-0 flex-col leading-tight">
|
||||
<span className="font-bold">{option.code}</span>
|
||||
<span className="truncate text-xs opacity-70">{option.name}</span>
|
||||
</div>
|
||||
{activeCurrency === option.code ? (
|
||||
<svg
|
||||
className="ml-auto size-4 shrink-0 text-red-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
aria-hidden
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
) : null}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import type { ReactNode } from "react";
|
||||
import { Bell, ChevronLeft } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { CurrencySwitcher } from "@/components/currency-switcher";
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import {
|
||||
playerHeaderControl,
|
||||
@@ -70,6 +71,15 @@ export function PlayerPanel({
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 items-center justify-end gap-1">
|
||||
<CurrencySwitcher
|
||||
variant="minimal"
|
||||
menuAlign="end"
|
||||
showLabel
|
||||
className={cn(
|
||||
playerHeaderControl,
|
||||
"rounded-full border border-[#e4eaf4] bg-[#f8fafc] [&_button]:h-8 [&_button]:gap-1 [&_button]:px-2 [&_button]:py-0 [&_button]:text-xs [&_button]:font-bold [&_button]:text-[#0b3f96]",
|
||||
)}
|
||||
/>
|
||||
<LanguageSwitcher
|
||||
variant="minimal"
|
||||
menuAlign="end"
|
||||
|
||||
Reference in New Issue
Block a user