Files
local-cal/src/components/image-picker.tsx
Dmytro Stanchiev 513aafcebc feat: support multiple image uploads for AI event generation 🖼️
- Updated OpenRouter integration to accept an array of image URLs
- Updated ImagePicker to use the `multiple` attribute natively
- Added `appendImagesDeduped` for handling client-side image deduplication
- Enhanced clipboard pasting to extract multiple images at once
- Rendered multiple images in a horizontal thumbnail strip in the AIToolbar
- Added tests to cover multi-image logic and AI request mapping
2026-04-08 20:46:43 -04:00

80 lines
2.0 KiB
TypeScript

"use client";
import type { VariantProps } from "class-variance-authority";
import { ImageIcon } from "lucide-react";
import type React from "react";
import { useImperativeHandle, useRef } from "react";
import { Button, type buttonVariants } from "@/components/ui/button";
import { IMAGE_ACCEPT } from "@/lib/constants";
interface ImagePickerProps extends VariantProps<typeof buttonVariants> {
/** Called with ALL selected files (array of 1..N). */
onFilesSelect?: (files: File[]) => void;
className?: string;
children?: React.ReactNode;
disabled?: boolean;
/** Allow selecting multiple images at once (native OS picker multi-select). */
multiple?: boolean;
/** Expose an imperative trigger so parents can open the file dialog via ref */
triggerRef?: React.Ref<{ open: () => void }>;
}
export function ImagePicker({
onFilesSelect,
className,
children,
variant = "ghost",
size = "icon",
disabled = false,
multiple = false,
triggerRef,
}: ImagePickerProps) {
const fileInputRef = useRef<HTMLInputElement>(null);
// Expose `.open()` to parent through triggerRef
useImperativeHandle(triggerRef, () => ({
open() {
fileInputRef.current?.click();
},
}));
const handleButtonClick = () => {
fileInputRef.current?.click();
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const fileList = event.target.files;
if (fileList && fileList.length > 0 && onFilesSelect) {
onFilesSelect(Array.from(fileList));
}
// Reset so the same file(s) can be re-selected
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
};
return (
<>
<input
ref={fileInputRef}
type="file"
name="image-upload"
accept={IMAGE_ACCEPT}
multiple={multiple}
onChange={handleFileChange}
className="hidden"
/>
<Button
onClick={handleButtonClick}
variant={variant}
size={size}
className={className}
disabled={disabled}
aria-label="Attach image"
>
{children || <ImageIcon className="h-4 w-4" />}
</Button>
</>
);
}