- Add Floating Action Button (FAB) with 3 quick actions - Implement Asset Price Update dialog for bulk price updates - Add bulk price update API endpoint with transaction support - Optimize multiselect, calendar, and dropdown options for mobile (44px touch targets) - Add custom date range popover to save space in Overview header - Localize number format suffixes (k/m/b for EN, rb/jt/m for ID) - Localize date format in Financial Trend (Oct 8 vs 8 Okt) - Fix negative values in trend line chart (domain auto) - Improve Asset Price Update dialog layout (compact horizontal) - Add mobile-optimized calendar with responsive cells - Fix FAB overlay and close button position - Add translations for FAB and asset price updates
83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { Plus, TrendingUp, Wallet, Receipt } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
interface FABAction {
|
|
icon: React.ReactNode
|
|
label: string
|
|
onClick: () => void
|
|
variant?: "default" | "outline" | "secondary"
|
|
}
|
|
|
|
interface FloatingActionButtonProps {
|
|
actions: FABAction[]
|
|
className?: string
|
|
}
|
|
|
|
export function FloatingActionButton({ actions, className }: FloatingActionButtonProps) {
|
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
|
|
const toggleMenu = () => setIsOpen(!isOpen)
|
|
|
|
const handleActionClick = (action: FABAction) => {
|
|
action.onClick()
|
|
setIsOpen(false)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* Backdrop Overlay */}
|
|
{isOpen && (
|
|
<div
|
|
className="fixed inset-0 bg-black/40 backdrop-blur-xs z-40 animate-in fade-in duration-200"
|
|
onClick={() => setIsOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
{/* FAB Container */}
|
|
<div className={cn("fixed bottom-6 right-6 z-50 flex flex-col items-end gap-3", className)}>
|
|
{/* Main FAB Button - Always at bottom */}
|
|
<Button
|
|
size="lg"
|
|
onClick={toggleMenu}
|
|
className={cn(
|
|
"h-16 w-16 rounded-full shadow-2xl hover:scale-110 transition-all duration-200 order-last",
|
|
isOpen && "rotate-45"
|
|
)}
|
|
>
|
|
<Plus className="h-6 w-6" />
|
|
</Button>
|
|
|
|
{/* Action Menu - Above main button */}
|
|
{isOpen && (
|
|
<div className="flex flex-col gap-3 animate-in fade-in slide-in-from-bottom-2 duration-200">
|
|
{actions.map((action, index) => (
|
|
<div key={index} className="flex items-center gap-3 justify-end">
|
|
{/* Label */}
|
|
<span className="bg-background border shadow-lg rounded-lg px-3 py-2 text-sm font-medium whitespace-nowrap">
|
|
{action.label}
|
|
</span>
|
|
{/* Action Button */}
|
|
<Button
|
|
size="lg"
|
|
variant={action.variant || "secondary"}
|
|
onClick={() => handleActionClick(action)}
|
|
className="h-14 w-14 rounded-full shadow-lg hover:scale-110 transition-transform px-0"
|
|
>
|
|
{action.icon}
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
// Export icons for convenience
|
|
export { TrendingUp as FABTrendingUpIcon, Wallet as FABWalletIcon, Receipt as FABReceiptIcon }
|