Files
Dramlog-Prod/src/components/FloatingScannerButton.tsx
robin 096daffb3e feat: Upgrade to Tailwind CSS v4.1.18
- Migrate from tailwindcss v3.3 to v4.1.18
- Replace @tailwind directives with @import 'tailwindcss'
- Move custom colors to @theme block in globals.css
- Convert custom utilities to @utility syntax
- Update PostCSS config to use @tailwindcss/postcss
- Remove autoprefixer (now built-in)
2026-01-19 22:26:21 +01:00

144 lines
6.1 KiB
TypeScript

'use client';
import React, { useState } from 'react';
import { Camera, Zap } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { useSession } from '@/context/SessionContext';
import BulkScanSheet from './BulkScanSheet';
interface FloatingScannerButtonProps {
onImageSelected: (base64Image: string) => void;
}
export default function FloatingScannerButton({ onImageSelected }: FloatingScannerButtonProps) {
const fileInputRef = React.useRef<HTMLInputElement>(null);
const { activeSession } = useSession();
const [isBulkOpen, setIsBulkOpen] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onloadend = () => {
const base64String = reader.result as string;
onImageSelected(base64String);
};
reader.readAsDataURL(file);
};
const handleMainClick = () => {
if (activeSession) {
setIsExpanded(!isExpanded);
} else {
fileInputRef.current?.click();
}
};
return (
<>
<div className="fixed bottom-8 left-1/2 -translate-x-1/2 z-50">
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept="image/*"
className="hidden"
/>
{/* Expanded Options */}
<AnimatePresence>
{isExpanded && activeSession && (
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.8 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.8 }}
className="absolute bottom-20 left-1/2 -translate-x-1/2 flex gap-3"
>
{/* Single Scan */}
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => {
setIsExpanded(false);
fileInputRef.current?.click();
}}
className="flex flex-col items-center gap-1.5 px-4 py-3 bg-zinc-900 border border-zinc-700 rounded-2xl shadow-xl"
>
<Camera size={24} className="text-white" />
<span className="text-[10px] font-bold text-zinc-400 uppercase tracking-widest">Einzel</span>
</motion.button>
{/* Bulk Scan */}
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => {
setIsExpanded(false);
setIsBulkOpen(true);
}}
className="flex flex-col items-center gap-1.5 px-4 py-3 bg-orange-600 rounded-2xl shadow-xl shadow-orange-950/30"
>
<Zap size={24} className="text-white" />
<span className="text-[10px] font-bold text-white/80 uppercase tracking-widest">Bulk</span>
</motion.button>
</motion.div>
)}
</AnimatePresence>
{/* Main Button */}
<motion.button
onClick={handleMainClick}
whileHover={{ scale: 1.1, translateY: -4 }}
whileTap={{ scale: 0.9 }}
initial={{ y: 100, opacity: 0 }}
animate={{ y: 0, opacity: 1, rotate: isExpanded ? 45 : 0 }}
className="relative group p-6 rounded-full bg-orange-600 text-black shadow-lg shadow-orange-950/40 hover:shadow-orange-950/60 transition-all overflow-hidden"
>
{/* Shine Animation */}
{!isExpanded && (
<motion.div
animate={{
x: ['-100%', '100%'],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut",
repeatDelay: 3
}}
className="absolute inset-0 bg-linear-to-r from-transparent via-white/40 to-transparent skew-x-12 z-0"
/>
)}
<Camera size={32} strokeWidth={2.5} className="relative z-10" />
{/* Active Session Indicator */}
{activeSession && !isExpanded && (
<div className="absolute -top-1 -right-1 w-4 h-4 bg-green-500 rounded-full border-2 border-orange-600 flex items-center justify-center">
<Zap size={10} className="text-white" />
</div>
)}
{/* Pulse ring */}
{!isExpanded && (
<span className="absolute inset-0 rounded-full border-4 border-orange-600 animate-ping opacity-20" />
)}
</motion.button>
</div>
{/* Bulk Scan Sheet */}
{activeSession && (
<BulkScanSheet
isOpen={isBulkOpen}
onClose={() => setIsBulkOpen(false)}
sessionId={activeSession.id}
sessionName={activeSession.name}
onSuccess={() => setIsBulkOpen(false)}
/>
)}
</>
);
}