Files
tabungin/apps/web/src/components/ui/floating-action-button.tsx
dwindown 49d60676d0 feat: Add FAB with asset price update, mobile optimizations, and localized financial trend
- 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
2025-10-12 23:30:54 +07:00

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 }