/ lionsmane-fe / src / components / ui / item.tsx
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  };