{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"ecommerce-ecommerce-customer-reviews-compact-review-list","type":"registry:block","title":"Compact Review List","files":[{"path":"components/blocks/ecommerce/ecommerce-customer-reviews/compact-review-list.tsx","content":"import { useState } from 'react';\nimport { Card, CardContent } from '@/components/ui/card';\nimport { Badge } from '@/components/ui/badge';\nimport { Button } from '@/components/ui/button';\nimport {\n  Star,\n  ChevronDown,\n  ChevronUp,\n  ThumbsUp,\n  MoreHorizontal,\n} from 'lucide-react';\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';\n\nexport default function CompactReviewList() {\n  const [expandedReviews, setExpandedReviews] = useState<number[]>([]);\n\n  const toggleExpand = (reviewId: number) => {\n    setExpandedReviews((prev) =>\n      prev.includes(reviewId)\n        ? prev.filter((id) => id !== reviewId)\n        : [...prev, reviewId]\n    );\n  };\n\n  const reviews = [\n    {\n      id: 1,\n      name: 'Jennifer Smith',\n      initials: 'JS',\n      avatar:\n        'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?q=80&w=400&auto=format&fit=crop&ixlib=rb-4.0.3',\n      rating: 5,\n      title: 'Exceeded expectations!',\n      preview:\n        'The quality is outstanding and the customer service was exceptional...',\n      fullReview:\n        \"The quality is outstanding and the customer service was exceptional. I ordered this product based on the reviews and I'm so glad I did. It arrived quickly, was packaged beautifully, and works exactly as described. The attention to detail is remarkable.\",\n      product: 'Premium Headphones',\n      helpful: 42,\n      date: '2 days ago',\n      verified: true,\n    },\n    {\n      id: 2,\n      name: 'Mark Johnson',\n      initials: 'MJ',\n      avatar:\n        'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?q=80&w=400&auto=format&fit=crop&ixlib=rb-4.0.3',\n      rating: 4,\n      title: 'Great product, minor issues',\n      preview:\n        'Overall very satisfied with my purchase. The build quality is solid...',\n      fullReview:\n        \"Overall very satisfied with my purchase. The build quality is solid and it performs well. My only complaint is that the battery life isn't quite as long as advertised, but it's still good enough for my daily use. Would recommend.\",\n      product: 'Wireless Mouse',\n      helpful: 28,\n      date: '5 days ago',\n      verified: true,\n    },\n    {\n      id: 3,\n      name: 'Lisa Chen',\n      initials: 'LC',\n      avatar:\n        'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=400&auto=format&fit=crop&ixlib=rb-4.0.3',\n      rating: 5,\n      title: 'Perfect for my needs',\n      preview:\n        'Exactly what I was looking for! Easy setup and works flawlessly...',\n      fullReview:\n        \"Exactly what I was looking for! Easy setup and works flawlessly with all my devices. The design is sleek and modern, and it doesn't take up much space on my desk. Customer support was also very responsive when I had a question.\",\n      product: 'USB-C Hub',\n      helpful: 15,\n      date: '1 week ago',\n      verified: true,\n    },\n    {\n      id: 4,\n      name: 'Robert Davis',\n      initials: 'RD',\n      avatar:\n        'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?q=80&w=400&auto=format&fit=crop&ixlib=rb-4.0.3',\n      rating: 5,\n      title: 'Best purchase this year',\n      preview:\n        \"I've been using this for a month now and it's been fantastic...\",\n      fullReview:\n        \"I've been using this for a month now and it's been fantastic. The quality is premium, the features are exactly what I needed, and the price point is very reasonable for what you get. Highly recommend to anyone looking for a reliable product.\",\n      product: 'Smart Watch',\n      helpful: 89,\n      date: '2 weeks ago',\n      verified: true,\n    },\n    {\n      id: 5,\n      name: 'Emma Wilson',\n      initials: 'EW',\n      avatar:\n        'https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=400&auto=format&fit=crop&ixlib=rb-4.0.3',\n      rating: 3,\n      title: 'Good but not great',\n      preview: \"It does what it's supposed to do, but I expected a bit more...\",\n      fullReview:\n        \"It does what it's supposed to do, but I expected a bit more for the price. The materials feel a bit cheap and the finish isn't as nice as shown in the photos. That said, it's functional and serves its purpose. Just manage your expectations.\",\n      product: 'Phone Case',\n      helpful: 12,\n      date: '3 weeks ago',\n      verified: false,\n    },\n    {\n      id: 6,\n      name: 'Michael Brown',\n      initials: 'MB',\n      avatar:\n        'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?q=80&w=400&auto=format&fit=crop&ixlib=rb-4.0.3',\n      rating: 5,\n      title: 'Highly recommend!',\n      preview:\n        'This product has exceeded all my expectations. The quality is top-notch...',\n      fullReview:\n        \"This product has exceeded all my expectations. The quality is top-notch, the design is beautiful, and it works perfectly. I've already recommended it to several friends. The company clearly puts a lot of thought into their products.\",\n      product: 'Desk Lamp',\n      helpful: 34,\n      date: '1 month ago',\n      verified: true,\n    },\n  ];\n\n  return (\n    <div className=\"container mx-auto px-4 py-24 md:px-6 lg:py-32 2xl:max-w-[1400px]\">\n      <div className=\"mb-12 flex flex-col items-center justify-between gap-4 md:flex-row\">\n        <div>\n          <h2 className=\"text-3xl font-bold tracking-tight sm:text-4xl\">\n            All Customer Reviews\n          </h2>\n          <p className=\"text-muted-foreground mt-2 text-lg\">\n            Showing {reviews.length} of 1,247 reviews\n          </p>\n        </div>\n\n        <div className=\"flex items-center gap-2\">\n          <Badge variant=\"secondary\">\n            <Star className=\"mr-1 size-3 fill-yellow-400 text-yellow-400\" />\n            4.8 Average\n          </Badge>\n          <Badge variant=\"outline\">1,247 Total</Badge>\n        </div>\n      </div>\n\n      <Card>\n        <CardContent className=\"p-0\">\n          <div className=\"divide-y\">\n            {reviews.map((review) => (\n              <div\n                key={review.id}\n                className=\"hover:bg-muted/50 p-4 transition-colors\"\n              >\n                <div className=\"flex items-start gap-3\">\n                  <Avatar className=\"size-10\">\n                    <AvatarImage src={review.avatar} alt={review.name} />\n                    <AvatarFallback>{review.initials}</AvatarFallback>\n                  </Avatar>\n\n                  <div className=\"flex-1\">\n                    <div className=\"mb-2 flex flex-wrap items-center gap-2\">\n                      <h3 className=\"font-semibold\">{review.name}</h3>\n                      <div className=\"flex items-center gap-1\">\n                        {[...Array(5)].map((_, i) => (\n                          <Star\n                            key={i}\n                            className={`size-3 ${\n                              i < review.rating\n                                ? 'fill-yellow-400 text-yellow-400'\n                                : 'text-muted-foreground'\n                            }`}\n                          />\n                        ))}\n                      </div>\n                      {review.verified && (\n                        <Badge variant=\"secondary\" className=\"text-xs\">\n                          Verified\n                        </Badge>\n                      )}\n                      <span className=\"text-muted-foreground text-xs\">\n                        {review.date}\n                      </span>\n                    </div>\n\n                    <h4 className=\"mb-1 font-medium\">{review.title}</h4>\n\n                    <p className=\"text-muted-foreground text-sm\">\n                      {expandedReviews.includes(review.id)\n                        ? review.fullReview\n                        : review.preview}\n                    </p>\n\n                    <div className=\"mt-3 flex flex-wrap items-center gap-3\">\n                      <button\n                        onClick={() => toggleExpand(review.id)}\n                        className=\"text-primary flex items-center gap-1 text-sm font-medium hover:underline\"\n                      >\n                        {expandedReviews.includes(review.id) ? (\n                          <>\n                            Show less\n                            <ChevronUp className=\"size-3\" />\n                          </>\n                        ) : (\n                          <>\n                            Read more\n                            <ChevronDown className=\"size-3\" />\n                          </>\n                        )}\n                      </button>\n\n                      <Badge variant=\"outline\" className=\"text-xs\">\n                        {review.product}\n                      </Badge>\n\n                      <Button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        className=\"h-7 gap-1 px-2\"\n                      >\n                        <ThumbsUp className=\"size-3\" />\n                        <span className=\"text-xs\">\n                          Helpful ({review.helpful})\n                        </span>\n                      </Button>\n\n                      <Button variant=\"ghost\" size=\"icon\" className=\"size-7\">\n                        <MoreHorizontal className=\"size-3\" />\n                      </Button>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            ))}\n          </div>\n        </CardContent>\n      </Card>\n\n      <div className=\"mt-8 text-center\">\n        <Button variant=\"outline\">Load More Reviews</Button>\n      </div>\n    </div>\n  );\n}\n","type":"registry:component","target":"components/blocks/ecommerce/ecommerce-customer-reviews/compact-review-list.tsx"},{"path":"components/ui/card.tsx","content":"import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Card({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"card\"\n      data-size={size}\n      className={cn(\n        \"group/card flex flex-col gap-6 overflow-hidden rounded-xl bg-card py-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\n        \"font-heading text-base leading-normal font-medium group-data-[size=sm]/card:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn(\"px-6 group-data-[size=sm]/card:px-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\n        \"flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n}\n","type":"registry:ui","target":"components/ui/card.tsx"},{"path":"components/ui/badge.tsx","content":"import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        secondary:\n          \"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80\",\n        destructive:\n          \"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20\",\n        outline:\n          \"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Badge({\n  className,\n  variant = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"span\"> &\n  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : \"span\"\n\n  return (\n    <Comp\n      data-slot=\"badge\"\n      data-variant={variant}\n      className={cn(badgeVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nexport { Badge, badgeVariants }\n","type":"registry:ui","target":"components/ui/badge.tsx"},{"path":"components/ui/button.tsx","content":"import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\n        outline:\n          \"border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n        destructive:\n          \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default:\n          \"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5\",\n        lg: \"h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        icon: \"size-9\",\n        \"icon-xs\":\n          \"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\":\n          \"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md\",\n        \"icon-lg\": \"size-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean\n  }) {\n  const Comp = asChild ? Slot.Root : \"button\"\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n","type":"registry:ui","target":"components/ui/button.tsx"},{"path":"components/ui/avatar.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Avatar as AvatarPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Avatar({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Root> & {\n  size?: \"default\" | \"sm\" | \"lg\"\n}) {\n  return (\n    <AvatarPrimitive.Root\n      data-slot=\"avatar\"\n      data-size={size}\n      className={cn(\n        \"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarImage({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Image>) {\n  return (\n    <AvatarPrimitive.Image\n      data-slot=\"avatar-image\"\n      className={cn(\n        \"aspect-square size-full rounded-full object-cover\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {\n  return (\n    <AvatarPrimitive.Fallback\n      data-slot=\"avatar-fallback\"\n      className={cn(\n        \"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"avatar-badge\"\n      className={cn(\n        \"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none\",\n        \"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden\",\n        \"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2\",\n        \"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"avatar-group\"\n      className={cn(\n        \"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"avatar-group-count\"\n      className={cn(\n        \"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n}\n","type":"registry:ui","target":"components/ui/avatar.tsx"}],"description":"A tight vertical list of reviews with star ratings and short quotes. Fits narrow sidebars or product pages where space for feedback is limited.","dependencies":["class-variance-authority","lucide-react","radix-ui"]}