item.tsx
1 import { Slot } from '@radix-ui/react-slot'; 2 import { cva, type VariantProps } from 'class-variance-authority'; 3 import * as React from 'react'; 4 import { Separator } from '@/components/ui/separator'; 5 import { cn } from '@/lib/utils'; 6 7 function ItemGroup({ className, ...props }: React.ComponentProps<'div'>) { 8 return ( 9 <div 10 className={cn('group/item-group flex flex-col', className)} 11 data-slot="item-group" 12 role="list" 13 {...props} 14 /> 15 ); 16 } 17 18 function ItemSeparator({ 19 className, 20 ...props 21 }: React.ComponentProps<typeof Separator>) { 22 return ( 23 <Separator 24 className={cn('my-0', className)} 25 data-slot="item-separator" 26 orientation="horizontal" 27 {...props} 28 /> 29 ); 30 } 31 32 const itemVariants = cva( 33 'group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', 34 { 35 defaultVariants: { 36 size: 'default', 37 variant: 'default', 38 }, 39 variants: { 40 size: { 41 default: 'p-4 gap-4 ', 42 sm: 'py-3 px-4 gap-2.5', 43 }, 44 variant: { 45 default: 'bg-transparent', 46 muted: 'bg-muted/50', 47 outline: 'border-border', 48 }, 49 }, 50 }, 51 ); 52 53 function Item({ 54 className, 55 variant = 'default', 56 size = 'default', 57 asChild = false, 58 ...props 59 }: React.ComponentProps<'div'> & 60 VariantProps<typeof itemVariants> & { asChild?: boolean }) { 61 const Comp = asChild ? Slot : 'div'; 62 return ( 63 <Comp 64 className={cn(itemVariants({ className, size, variant }))} 65 data-size={size} 66 data-slot="item" 67 data-variant={variant} 68 {...props} 69 /> 70 ); 71 } 72 73 const itemMediaVariants = cva( 74 'flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5', 75 { 76 defaultVariants: { 77 variant: 'default', 78 }, 79 variants: { 80 variant: { 81 default: 'bg-transparent', 82 icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4", 83 image: 84 'size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover', 85 }, 86 }, 87 }, 88 ); 89 90 function ItemMedia({ 91 className, 92 variant = 'default', 93 ...props 94 }: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) { 95 return ( 96 <div 97 className={cn(itemMediaVariants({ className, variant }))} 98 data-slot="item-media" 99 data-variant={variant} 100 {...props} 101 /> 102 ); 103 } 104 105 function ItemContent({ className, ...props }: React.ComponentProps<'div'>) { 106 return ( 107 <div 108 className={cn( 109 'flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none', 110 className, 111 )} 112 data-slot="item-content" 113 {...props} 114 /> 115 ); 116 } 117 118 function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) { 119 return ( 120 <div 121 className={cn( 122 'flex w-fit items-center gap-2 text-sm leading-snug font-medium', 123 className, 124 )} 125 data-slot="item-title" 126 {...props} 127 /> 128 ); 129 } 130 131 function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) { 132 return ( 133 <p 134 className={cn( 135 'text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance', 136 '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4', 137 className, 138 )} 139 data-slot="item-description" 140 {...props} 141 /> 142 ); 143 } 144 145 function ItemActions({ className, ...props }: React.ComponentProps<'div'>) { 146 return ( 147 <div 148 className={cn('flex items-center gap-2', className)} 149 data-slot="item-actions" 150 {...props} 151 /> 152 ); 153 } 154 155 function ItemHeader({ className, ...props }: React.ComponentProps<'div'>) { 156 return ( 157 <div 158 className={cn( 159 'flex basis-full items-center justify-between gap-2', 160 className, 161 )} 162 data-slot="item-header" 163 {...props} 164 /> 165 ); 166 } 167 168 function ItemFooter({ className, ...props }: React.ComponentProps<'div'>) { 169 return ( 170 <div 171 className={cn( 172 'flex basis-full items-center justify-between gap-2', 173 className, 174 )} 175 data-slot="item-footer" 176 {...props} 177 /> 178 ); 179 } 180 181 export { 182 Item, 183 ItemMedia, 184 ItemContent, 185 ItemActions, 186 ItemGroup, 187 ItemSeparator, 188 ItemTitle, 189 ItemDescription, 190 ItemHeader, 191 ItemFooter, 192 };