{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "post-image",
  "type": "registry:block",
  "title": "Post Image",
  "description": "A component for displaying images in a post, with support for bento grids and carousels.",
  "files": [
    {
      "path": "src/components/deso/post-image.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselNext,\n  CarouselPrevious,\n  type CarouselApi,\n} from '@/components/ui/carousel';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogClose } from '@/components/ui/dialog';\nimport { cn } from '@/lib/utils/deso';\nimport { PostEngagement } from './post-engagement';\nimport { Button } from '../ui/button';\nimport { Blurhash } from 'react-blurhash';\nimport { Lock } from 'lucide-react';\n\nexport interface PostImageActions {\n  likes: { count: number; active: boolean };\n  reposts: { count: number; active: boolean };\n  diamonds: { count: number; value: string; active: boolean };\n  comments: { count: number };\n  views?: { count: number };\n  onLike: () => void;\n  onRepost: () => void;\n  onDiamond: () => void;\n  onComment: () => void;\n}\n\nexport interface PostImageProps {\n  images: string[];\n  variant?: 'default' | 'carousel' | 'bento' | 'blurred' | 'unlockable';\n  className?: string;\n  onImageClick?: (index: number) => void;\n  withModal?: boolean;\n  withModalActions?: PostImageActions;\n  blurhash?: string;\n  onUnlock?: () => void;\n}\n\nexport function PostImage({\n  images,\n  variant,\n  className,\n  onImageClick,\n  withModal = true,\n  withModalActions,\n  blurhash,\n  onUnlock,\n}: PostImageProps) {\n  const [modalOpen, setModalOpen] = React.useState(false);\n  const [selectedIndex, setSelectedIndex] = React.useState(0);\n  const [api, setApi] = React.useState<CarouselApi>();\n  const [current, setCurrent] = React.useState(0);\n  const [isUnlocked, setIsUnlocked] = React.useState(variant !== 'unlockable');\n\n  if (!images || images.length === 0) {\n    return null;\n  }\n\n  const handleUnlockClick = () => {\n    onUnlock?.();\n    setIsUnlocked(true);\n  };\n\n  const handleImageClick = (index: number) => {\n    onImageClick?.(index);\n    if (withModal) {\n      setSelectedIndex(index);\n      setModalOpen(true);\n    }\n  };\n\n  React.useEffect(() => {\n    if (!api) return;\n    setCurrent(api.selectedScrollSnap());\n    api.on('select', () => setCurrent(api.selectedScrollSnap()));\n  }, [api]);\n\n  const imageCount = images.length;\n\n  const ImageOrBlurComponent = ({\n    src,\n    index,\n    className: imgClassName,\n  }: {\n    src: string;\n    index: number;\n    className?: string;\n  }) => {\n    if (isUnlocked) {\n      return (\n        <img\n          src={src}\n          alt={`Post image ${index + 1}`}\n          className={cn(\n            'w-full h-full object-cover',\n            withModal && 'cursor-pointer',\n            imgClassName\n          )}\n          onClick={() => handleImageClick(index)}\n        />\n      );\n    }\n    if (blurhash) {\n      return <Blurhash hash={blurhash} width=\"100%\" height=\"100%\" />;\n    }\n    return <div className=\"w-full h-full bg-muted\" />;\n  };\n\n  const modalContent = (\n    <DialogContent data-no-default-close className=\"[&>button:last-child]:hidden bg-black/70 rounded-none backdrop-blur-sm !max-w-none !w-screen !h-screen border-0 p-0 inset-0 translate-x-0 translate-y-0\">\n      <DialogHeader>\n        <DialogTitle className=\"sr-only\">Image Modal</DialogTitle>\n        <DialogDescription className=\"sr-only\">\n          Image modal with carousel and actions\n        </DialogDescription>\n      </DialogHeader>\n      <div className=\"relative w-full h-full\">       \n        <Carousel\n          setApi={setApi}\n          opts={{ startIndex: selectedIndex, loop: true }}\n          className=\"w-full h-full relative\"\n        >\n          <CarouselContent className=\"h-full relative\">           \n            {images.map((src, index) => (\n              <CarouselItem key={index} className=\"h-full\">\n                <div\n                  className=\"w-full h-full flex items-center justify-center p-8\"\n                  onClick={() => setModalOpen(false)}\n                >\n                  <img\n                    src={src}\n                    alt={`Post image ${index + 1}`}\n                    className=\"max-w-full max-h-full object-contain border border-border\"\n                    onClick={(e) => e.stopPropagation()}\n                  />\n                </div>\n              </CarouselItem>\n            ))}\n          </CarouselContent>\n          <CarouselPrevious className=\"left-4 bg-background/50 border-none hover:bg-background/75 backdrop-blur-sm\" />\n          <CarouselNext className=\"right-4 bg-background/50 border-none hover:bg-background/75 backdrop-blur-sm\" />\n        </Carousel>\n        {withModalActions && (\n          <div className=\"absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-x-6 bg-black/50 text-white p-2 px-4 rounded-full border border-border\">\n            <PostEngagement\n              variant=\"comment\"\n              count={withModalActions.comments.count}\n              onClick={withModalActions.onComment}\n              className=\"hover:text-white\"\n            />\n            <PostEngagement\n              variant=\"repost\"\n              count={withModalActions.reposts.count}\n              active={withModalActions.reposts.active}\n              onClick={withModalActions.onRepost}\n              className=\"hover:text-white\"\n            />\n            <PostEngagement\n              variant=\"like\"\n              count={withModalActions.likes.count}\n              active={withModalActions.likes.active}\n              onClick={withModalActions.onLike}\n              className=\"hover:text-white\"\n            />\n            <PostEngagement\n              variant=\"diamond\"\n              count={withModalActions.diamonds.count}\n              value={withModalActions.diamonds.value}\n              active={withModalActions.diamonds.active}\n              onClick={withModalActions.onDiamond}\n              className=\"hover:text-white\"\n            />\n          </div>\n        )}\n      </div>\n      <DialogClose asChild>\n        <Button type=\"button\" variant=\"secondary\" size=\"icon\" className=\"absolute top-4 right-4 text-lg bg-white/10 text-white hover:opacity-75 cursor-pointer\">\n          &times;\n        </Button>\n      </DialogClose>\n    </DialogContent>\n  );\n\n  const renderContent = () => {\n    let determinedVariant = variant;\n    if (!variant || variant === 'unlockable') {\n      if (imageCount === 1) determinedVariant = 'default';\n      else if (imageCount <= 4) determinedVariant = 'bento';\n      else determinedVariant = 'carousel';\n    }\n\n    const layoutContent = () => {\n      if (determinedVariant === 'default') {\n        return (\n          <div className=\"rounded-xl overflow-hidden border border-border max-h-[512px] aspect-video w-full\">\n            <ImageOrBlurComponent src={images[0]} index={0} />\n          </div>\n        );\n      }\n\n      if (determinedVariant === 'bento') {\n        const gridClasses = {\n          2: 'grid grid-cols-2 gap-0.5',\n          3: 'grid grid-cols-2 grid-rows-2 gap-0.5',\n          4: 'grid grid-cols-2 grid-rows-2 gap-0.5',\n        };\n        return (\n          <div className=\"mt-2 rounded-xl overflow-hidden border border-border aspect-video max-h-[512px]\">\n            <div className={cn('h-full', gridClasses[imageCount as 2 | 3 | 4])}>\n              {imageCount === 2 && (\n                <>\n                  <ImageOrBlurComponent src={images[0]} index={0} />\n                  <ImageOrBlurComponent src={images[1]} index={1} />\n                </>\n              )}\n              {imageCount === 3 && (\n                <>\n                  <div className=\"col-span-1 row-span-2\">\n                    <ImageOrBlurComponent src={images[0]} index={0} />\n                  </div>\n                  <ImageOrBlurComponent src={images[1]} index={1} />\n                  <ImageOrBlurComponent src={images[2]} index={2} />\n                </>\n              )}\n              {imageCount === 4 && (\n                <>\n                  <ImageOrBlurComponent src={images[0]} index={0} />\n                  <ImageOrBlurComponent src={images[1]} index={1} />\n                  <ImageOrBlurComponent src={images[2]} index={2} />\n                  <ImageOrBlurComponent src={images[3]} index={3} />\n                </>\n              )}\n            </div>\n          </div>\n        );\n      }\n\n      if (determinedVariant === 'carousel') {\n        return (\n          <div>\n            <Carousel setApi={setApi} className=\"w-full\">\n              {imageCount > 1 && (\n                <div className=\"absolute top-4 right-4 z-10 rounded-full bg-black/20 px-2 py-1 text-xs text-white\">\n                  {current + 1} / {imageCount}\n                </div>\n              )}\n              <CarouselContent>\n                {images.map((src, index) => (\n                  <CarouselItem key={index}>\n                    <div className=\"rounded-xl overflow-hidden border border-border aspect-video\">\n                      <ImageOrBlurComponent src={src} index={index} />\n                    </div>\n                  </CarouselItem>\n                ))}\n              </CarouselContent>\n              <CarouselPrevious className=\"left-4 bg-background/50 border-none hover:bg-background/75 backdrop-blur-sm\" />\n              <CarouselNext className=\"right-4 bg-background/50 border-none hover:bg-background/75 backdrop-blur-sm\" />\n            </Carousel>\n            <div className=\"absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2\">\n              {images.map((_, index) => (\n                <button\n                  key={index}\n                  onClick={() => api?.scrollTo(index)}\n                  aria-label={`Go to slide ${index + 1}`}\n                  className={cn(\n                    'w-2 h-2 rounded-full bg-background/50 transition-colors',\n                    current === index ? 'bg-background' : 'hover:bg-background/75'\n                  )}\n                />\n              ))}\n            </div>\n          </div>\n        );\n      }\n\n      if (determinedVariant === 'blurred' && blurhash) {\n        return (\n          <div className=\"mt-2 rounded-xl overflow-hidden border border-border relative aspect-video\">\n            <Blurhash hash={blurhash} width=\"100%\" height=\"100%\" />\n          </div>\n        );\n      }\n\n      return null;\n    };\n\n    return (\n      <div className={cn('relative', className)}>\n        {layoutContent()}\n        {variant === 'unlockable' && !isUnlocked && (\n          <div className=\"absolute inset-0 flex items-center justify-center bg-black/30 rounded-xl border border-border\">\n            <Button onClick={handleUnlockClick}>\n              <Lock className=\"h-4 w-4 mr-2\" />\n              Unlock All Content\n            </Button>\n          </div>\n        )}\n      </div>\n    );\n  };\n\n  if (withModal) {\n    return (\n      <Dialog open={modalOpen} onOpenChange={setModalOpen}>\n        {renderContent()}\n        {modalContent}\n      </Dialog>\n    );\n  }\n\n  return renderContent();\n} ",
      "type": "registry:component",
      "target": "src/components/deso-ui/post-image.tsx"
    }
  ]
}