mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
112 lines
3.5 KiB
React
112 lines
3.5 KiB
React
// components/generic/CMS/blocks/TextImageBlock.jsx
|
|
|
|
import { useState } from "react";
|
|
import { ImageIcon } from "lucide-react";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { AssetPickerSheet } from "../AssetPickerSheet";
|
|
|
|
export function TextImageBlock({ content, onUpdate }) {
|
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
|
|
{/* ── Layout ── */}
|
|
<div className="space-y-1.5 max-w-[200px]">
|
|
<Label>Image Position</Label>
|
|
<Select
|
|
value={content.image_position ?? "right"}
|
|
onValueChange={(v) => onUpdate({ ...content, image_position: v })}
|
|
>
|
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="left">Left</SelectItem>
|
|
<SelectItem value="right">Right</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className={[
|
|
"grid gap-4",
|
|
"grid-cols-1 md:grid-cols-2",
|
|
].join(" ")}>
|
|
|
|
{/* ── Text side ── */}
|
|
<div className="space-y-1.5">
|
|
<Label>Content</Label>
|
|
<Textarea
|
|
placeholder="Enter text content..."
|
|
rows={5}
|
|
value={content.body ?? ""}
|
|
onChange={(e) => onUpdate({ ...content, body: e.target.value })}
|
|
/>
|
|
</div>
|
|
|
|
{/* ── Image side ── */}
|
|
<div className={[
|
|
"space-y-1.5",
|
|
content.image_position === "left" ? "md:order-first" : "",
|
|
].join(" ")}>
|
|
<Label>Image</Label>
|
|
|
|
{content.url ? (
|
|
<div
|
|
className="relative rounded-lg overflow-hidden cursor-pointer group"
|
|
onClick={() => setPickerOpen(true)}
|
|
>
|
|
<img
|
|
src={content.url}
|
|
alt={content.alt ?? ""}
|
|
className="w-full aspect-video object-cover"
|
|
/>
|
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
|
<p className="text-white text-sm font-medium">Change Image</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => setPickerOpen(true)}
|
|
className="w-full aspect-video rounded-lg border-2 border-dashed border-muted-foreground/30 hover:border-primary/50 hover:bg-muted/20 transition-colors flex flex-col items-center justify-center gap-2"
|
|
>
|
|
<ImageIcon className="h-8 w-8 text-muted-foreground/50" />
|
|
<p className="text-sm text-muted-foreground">Click to select an image</p>
|
|
</button>
|
|
)}
|
|
|
|
{content.url && (
|
|
<div className="space-y-1.5">
|
|
<Label>Alt Text</Label>
|
|
<Input
|
|
placeholder="Describe the image..."
|
|
value={content.alt ?? ""}
|
|
onChange={(e) => onUpdate({ ...content, alt: e.target.value })}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<AssetPickerSheet
|
|
open={pickerOpen}
|
|
onOpenChange={setPickerOpen}
|
|
fileType="image"
|
|
onSelect={(asset) => onUpdate({
|
|
...content,
|
|
asset_id: asset.asset_id,
|
|
url: asset.file_url,
|
|
})}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|