queue.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. 'use client'
  2. import type { ComponentProps } from 'react'
  3. import { ChevronDownIcon, PaperclipIcon } from 'lucide-react'
  4. import { cn } from '@/lib/utils'
  5. import { Button } from '@/components/ui/button'
  6. import {
  7. Collapsible,
  8. CollapsibleContent,
  9. CollapsibleTrigger,
  10. } from '@/components/ui/collapsible'
  11. import { ScrollArea } from '@/components/ui/scroll-area'
  12. export type QueueMessagePart = {
  13. type: string
  14. text?: string
  15. url?: string
  16. filename?: string
  17. mediaType?: string
  18. }
  19. export type QueueMessage = {
  20. id: string
  21. parts: QueueMessagePart[]
  22. }
  23. export type QueueTodo = {
  24. id: string
  25. title: string
  26. description?: string
  27. status?: 'pending' | 'completed'
  28. }
  29. export type QueueItemProps = ComponentProps<'li'>
  30. export const QueueItem = ({ className, ...props }: QueueItemProps) => (
  31. <li
  32. className={cn(
  33. 'group hover:bg-muted flex flex-col gap-1 rounded-md px-3 py-1 text-sm transition-colors',
  34. className
  35. )}
  36. {...props}
  37. />
  38. )
  39. export type QueueItemIndicatorProps = ComponentProps<'span'> & {
  40. completed?: boolean
  41. }
  42. export const QueueItemIndicator = ({
  43. completed = false,
  44. className,
  45. ...props
  46. }: QueueItemIndicatorProps) => (
  47. <span
  48. className={cn(
  49. 'mt-0.5 inline-block size-2.5 rounded-full border',
  50. completed
  51. ? 'border-muted-foreground/20 bg-muted-foreground/10'
  52. : 'border-muted-foreground/50',
  53. className
  54. )}
  55. {...props}
  56. />
  57. )
  58. export type QueueItemContentProps = ComponentProps<'span'> & {
  59. completed?: boolean
  60. }
  61. export const QueueItemContent = ({
  62. completed = false,
  63. className,
  64. ...props
  65. }: QueueItemContentProps) => (
  66. <span
  67. className={cn(
  68. 'line-clamp-1 grow break-words',
  69. completed
  70. ? 'text-muted-foreground/50 line-through'
  71. : 'text-muted-foreground',
  72. className
  73. )}
  74. {...props}
  75. />
  76. )
  77. export type QueueItemDescriptionProps = ComponentProps<'div'> & {
  78. completed?: boolean
  79. }
  80. export const QueueItemDescription = ({
  81. completed = false,
  82. className,
  83. ...props
  84. }: QueueItemDescriptionProps) => (
  85. <div
  86. className={cn(
  87. 'ml-6 text-xs',
  88. completed
  89. ? 'text-muted-foreground/40 line-through'
  90. : 'text-muted-foreground',
  91. className
  92. )}
  93. {...props}
  94. />
  95. )
  96. export type QueueItemActionsProps = ComponentProps<'div'>
  97. export const QueueItemActions = ({
  98. className,
  99. ...props
  100. }: QueueItemActionsProps) => (
  101. <div className={cn('flex gap-1', className)} {...props} />
  102. )
  103. export type QueueItemActionProps = Omit<
  104. ComponentProps<typeof Button>,
  105. 'variant' | 'size'
  106. >
  107. export const QueueItemAction = ({
  108. className,
  109. ...props
  110. }: QueueItemActionProps) => (
  111. <Button
  112. className={cn(
  113. 'text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground size-auto rounded p-1 opacity-0 transition-opacity group-hover:opacity-100',
  114. className
  115. )}
  116. size='icon'
  117. type='button'
  118. variant='ghost'
  119. {...props}
  120. />
  121. )
  122. export type QueueItemAttachmentProps = ComponentProps<'div'>
  123. export const QueueItemAttachment = ({
  124. className,
  125. ...props
  126. }: QueueItemAttachmentProps) => (
  127. <div className={cn('mt-1 flex flex-wrap gap-2', className)} {...props} />
  128. )
  129. export type QueueItemImageProps = ComponentProps<'img'>
  130. export const QueueItemImage = ({
  131. className,
  132. ...props
  133. }: QueueItemImageProps) => (
  134. <img
  135. alt=''
  136. className={cn('h-8 w-8 rounded border object-cover', className)}
  137. height={32}
  138. width={32}
  139. {...props}
  140. />
  141. )
  142. export type QueueItemFileProps = ComponentProps<'span'>
  143. export const QueueItemFile = ({
  144. children,
  145. className,
  146. ...props
  147. }: QueueItemFileProps) => (
  148. <span
  149. className={cn(
  150. 'bg-muted flex items-center gap-1 rounded border px-2 py-1 text-xs',
  151. className
  152. )}
  153. {...props}
  154. >
  155. <PaperclipIcon size={12} />
  156. <span className='max-w-[100px] truncate'>{children}</span>
  157. </span>
  158. )
  159. export type QueueListProps = ComponentProps<typeof ScrollArea>
  160. export const QueueList = ({
  161. children,
  162. className,
  163. ...props
  164. }: QueueListProps) => (
  165. <ScrollArea className={cn('mt-2 -mb-1', className)} {...props}>
  166. <div className='max-h-40 pr-4'>
  167. <ul>{children}</ul>
  168. </div>
  169. </ScrollArea>
  170. )
  171. // QueueSection - collapsible section container
  172. export type QueueSectionProps = ComponentProps<typeof Collapsible>
  173. export const QueueSection = ({
  174. className,
  175. defaultOpen = true,
  176. ...props
  177. }: QueueSectionProps) => (
  178. <Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
  179. )
  180. // QueueSectionTrigger - section header/trigger
  181. export type QueueSectionTriggerProps = ComponentProps<'button'>
  182. export const QueueSectionTrigger = ({
  183. children,
  184. className,
  185. ...props
  186. }: QueueSectionTriggerProps) => (
  187. <CollapsibleTrigger
  188. render={
  189. <Button
  190. variant='ghost'
  191. className={cn(
  192. 'group bg-muted/40 text-muted-foreground hover:bg-muted h-auto w-full justify-between px-3 py-2 text-left',
  193. className
  194. )}
  195. type='button'
  196. {...props}
  197. />
  198. }
  199. >
  200. {children}
  201. </CollapsibleTrigger>
  202. )
  203. // QueueSectionLabel - label content with icon and count
  204. export type QueueSectionLabelProps = ComponentProps<'span'> & {
  205. count?: number
  206. label: string
  207. icon?: React.ReactNode
  208. }
  209. export const QueueSectionLabel = ({
  210. count,
  211. label,
  212. icon,
  213. className,
  214. ...props
  215. }: QueueSectionLabelProps) => (
  216. <span className={cn('flex items-center gap-2', className)} {...props}>
  217. <ChevronDownIcon className='size-4 -rotate-90 transition-transform group-data-[panel-open]:rotate-0' />
  218. {icon}
  219. <span>
  220. {count} {label}
  221. </span>
  222. </span>
  223. )
  224. // QueueSectionContent - collapsible content area
  225. export type QueueSectionContentProps = ComponentProps<typeof CollapsibleContent>
  226. export const QueueSectionContent = ({
  227. className,
  228. ...props
  229. }: QueueSectionContentProps) => (
  230. <CollapsibleContent className={cn(className)} {...props} />
  231. )
  232. export type QueueProps = ComponentProps<'div'>
  233. export const Queue = ({ className, ...props }: QueueProps) => (
  234. <div
  235. className={cn(
  236. 'border-border bg-background flex flex-col gap-2 rounded-xl border px-3 pt-2 pb-2 shadow-xs',
  237. className
  238. )}
  239. {...props}
  240. />
  241. )