Spaces:
Sleeping
Sleeping
| import { ReactNode } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { ChevronUp, ChevronDown } from "lucide-react"; | |
| import { cn } from "@/lib/utils"; | |
| import { useState } from "react"; | |
| interface StickyBottomBarProps { | |
| summary: ReactNode; | |
| actions: ReactNode; | |
| expandable?: boolean; | |
| expandedContent?: ReactNode; | |
| className?: string; | |
| } | |
| export function StickyBottomBar({ | |
| summary, | |
| actions, | |
| expandable = false, | |
| expandedContent, | |
| className | |
| }: StickyBottomBarProps) { | |
| const [isExpanded, setIsExpanded] = useState(false); | |
| return ( | |
| <div className={cn( | |
| "fixed bottom-0 left-0 right-0 z-30", | |
| "bg-card border-t shadow-2xl", | |
| "safe-area-inset-bottom", | |
| "md:relative md:shadow-none md:rounded-lg md:border", | |
| className | |
| )}> | |
| {/* Expanded Content */} | |
| {expandable && isExpanded && expandedContent && ( | |
| <div className="px-4 py-4 border-b max-h-64 overflow-auto"> | |
| {expandedContent} | |
| </div> | |
| )} | |
| {/* Main Bar */} | |
| <div className="px-4 py-3 flex items-center justify-between gap-4"> | |
| {/* Summary */} | |
| <div className="flex-1 min-w-0"> | |
| {summary} | |
| </div> | |
| {/* Actions */} | |
| <div className="flex items-center gap-2 shrink-0"> | |
| {actions} | |
| </div> | |
| {/* Expand Toggle */} | |
| {expandable && expandedContent && ( | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={() => setIsExpanded(!isExpanded)} | |
| className="h-10 w-10 shrink-0" | |
| > | |
| {isExpanded ? ( | |
| <ChevronDown className="h-5 w-5" /> | |
| ) : ( | |
| <ChevronUp className="h-5 w-5" /> | |
| )} | |
| </Button> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Preset variations for common use cases | |
| interface FormBottomBarProps { | |
| totalLabel: string; | |
| totalValue: string | number; | |
| saveLabel?: string; | |
| onSave: () => void; | |
| cancelLabel?: string; | |
| onCancel?: () => void; | |
| saveDisabled?: boolean; | |
| expandable?: boolean; | |
| breakdown?: Array<{ label: string; value: string | number }>; | |
| } | |
| export function FormBottomBar({ | |
| totalLabel, | |
| totalValue, | |
| saveLabel = "जतन करा", | |
| onSave, | |
| cancelLabel, | |
| onCancel, | |
| saveDisabled = false, | |
| expandable = false, | |
| breakdown | |
| }: FormBottomBarProps) { | |
| return ( | |
| <StickyBottomBar | |
| summary={ | |
| <div className="space-y-1"> | |
| <div className="text-xs text-muted-foreground">{totalLabel}</div> | |
| <div className="text-2xl font-bold truncate"> | |
| {typeof totalValue === 'number' | |
| ? `₹${totalValue.toLocaleString('en-IN')}` | |
| : totalValue | |
| } | |
| </div> | |
| </div> | |
| } | |
| actions={ | |
| <> | |
| {cancelLabel && onCancel && ( | |
| <Button | |
| variant="outline" | |
| size="lg" | |
| onClick={onCancel} | |
| className="h-12 px-6 md:flex" | |
| > | |
| {cancelLabel} | |
| </Button> | |
| )} | |
| <Button | |
| size="lg" | |
| onClick={onSave} | |
| disabled={saveDisabled} | |
| className="h-12 px-8 font-semibold" | |
| > | |
| {saveLabel} | |
| </Button> | |
| </> | |
| } | |
| expandable={expandable} | |
| expandedContent={ | |
| breakdown && ( | |
| <div className="space-y-2"> | |
| <div className="font-semibold text-sm mb-3">तपशील</div> | |
| {breakdown.map((item, index) => ( | |
| <div key={index} className="flex justify-between text-sm"> | |
| <span className="text-muted-foreground">{item.label}</span> | |
| <span className="font-semibold"> | |
| {typeof item.value === 'number' | |
| ? `₹${item.value.toLocaleString('en-IN')}` | |
| : item.value | |
| } | |
| </span> | |
| </div> | |
| ))} | |
| </div> | |
| ) | |
| } | |
| /> | |
| ); | |
| } | |
| // Simple action bar | |
| interface ActionBottomBarProps { | |
| primaryAction: { | |
| label: string; | |
| onClick: () => void; | |
| disabled?: boolean; | |
| variant?: "default" | "destructive"; | |
| }; | |
| secondaryAction?: { | |
| label: string; | |
| onClick: () => void; | |
| }; | |
| } | |
| export function ActionBottomBar({ | |
| primaryAction, | |
| secondaryAction | |
| }: ActionBottomBarProps) { | |
| return ( | |
| <div className={cn( | |
| "fixed bottom-0 left-0 right-0 z-30", | |
| "bg-card border-t shadow-2xl p-4", | |
| "safe-area-inset-bottom", | |
| "flex gap-3" | |
| )}> | |
| {secondaryAction && ( | |
| <Button | |
| variant="outline" | |
| size="lg" | |
| onClick={secondaryAction.onClick} | |
| className="flex-1 h-12 font-semibold" | |
| > | |
| {secondaryAction.label} | |
| </Button> | |
| )} | |
| <Button | |
| variant={primaryAction.variant || "default"} | |
| size="lg" | |
| onClick={primaryAction.onClick} | |
| disabled={primaryAction.disabled} | |
| className={cn( | |
| "h-12 font-semibold", | |
| secondaryAction ? "flex-1" : "w-full" | |
| )} | |
| > | |
| {primaryAction.label} | |
| </Button> | |
| </div> | |
| ); | |
| } | |
| // Info bar with single action | |
| interface InfoBottomBarProps { | |
| icon?: ReactNode; | |
| title: string; | |
| description?: string; | |
| action: { | |
| label: string; | |
| onClick: () => void; | |
| icon?: ReactNode; | |
| }; | |
| } | |
| export function InfoBottomBar({ | |
| icon, | |
| title, | |
| description, | |
| action | |
| }: InfoBottomBarProps) { | |
| return ( | |
| <div className={cn( | |
| "fixed bottom-0 left-0 right-0 z-30", | |
| "bg-card border-t shadow-2xl p-4", | |
| "safe-area-inset-bottom", | |
| "flex items-center gap-4" | |
| )}> | |
| {icon && ( | |
| <div className="shrink-0 text-primary"> | |
| {icon} | |
| </div> | |
| )} | |
| <div className="flex-1 min-w-0"> | |
| <div className="font-semibold truncate">{title}</div> | |
| {description && ( | |
| <div className="text-sm text-muted-foreground truncate"> | |
| {description} | |
| </div> | |
| )} | |
| </div> | |
| <Button | |
| size="lg" | |
| onClick={action.onClick} | |
| className="h-12 px-6 shrink-0" | |
| > | |
| {action.icon && <span className="mr-2">{action.icon}</span>} | |
| {action.label} | |
| </Button> | |
| </div> | |
| ); | |
| } | |