Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/Messaging/AttachmentPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ const AttachmentPicker = React.forwardRef<
type="button"
onClick={handleClick}
disabled={disabled}
data-slot="attachment-picker-button"
className={cn(
'inline-flex items-center justify-center',
'rounded-full p-2',
Expand Down
20 changes: 16 additions & 4 deletions src/components/Messaging/ConversationHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ const ConversationHeader = React.forwardRef<
return (
<header
ref={ref}
data-slot="conversation-header"
className={cn(headerVariants({ size }), className)}
{...props}
>
Expand Down Expand Up @@ -201,7 +202,10 @@ const ConversationHeader = React.forwardRef<
))}

{/* Avatar */}
<div className="relative shrink-0">
<div
data-slot="conversation-header-avatar"
className="relative shrink-0"
>
<div
className={cn(
'flex h-10 w-10 items-center justify-center rounded-full',
Expand Down Expand Up @@ -232,7 +236,7 @@ const ConversationHeader = React.forwardRef<
</div>

{/* Title and subtitle */}
<div className="min-w-0 flex-1">
<div data-slot="conversation-header-info" className="min-w-0 flex-1">
<h2 className="truncate font-semibold text-neutral-900 dark:text-neutral-100">
{displayTitle}
</h2>
Expand Down Expand Up @@ -325,6 +329,7 @@ const ConversationListItem = React.forwardRef<
ref={ref}
type="button"
onClick={() => onSelect?.(conversation)}
data-slot="conversation-list-item"
className={cn(
'flex w-full items-center gap-3 px-4 py-3',
'text-left transition-colors',
Expand All @@ -338,7 +343,10 @@ const ConversationListItem = React.forwardRef<
{...props}
>
{/* Avatar */}
<div className="relative shrink-0">
<div
data-slot="conversation-list-item-avatar"
className="relative shrink-0"
>
<div
className={cn(
'flex h-12 w-12 items-center justify-center rounded-full',
Expand Down Expand Up @@ -367,7 +375,10 @@ const ConversationListItem = React.forwardRef<
</div>

{/* Content */}
<div className="min-w-0 flex-1">
<div
data-slot="conversation-list-item-content"
className="min-w-0 flex-1"
>
<div className="flex items-center justify-between gap-2">
<h3
className={cn(
Expand Down Expand Up @@ -398,6 +409,7 @@ const ConversationListItem = React.forwardRef<
</p>
{isUnread && (
<span
data-slot="conversation-list-item-badge"
className={cn(
'flex shrink-0 items-center justify-center',
'h-5 min-w-[20px] rounded-full px-1.5',
Expand Down
13 changes: 11 additions & 2 deletions src/components/Messaging/MessageBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
return (
<div
ref={ref}
data-slot="message-system"
className={cn(bubbleVariants({ variant: 'system' }), className)}
role="status"
aria-live="polite"
Expand All @@ -482,6 +483,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
return (
<div
ref={ref}
data-slot="message-bubble"
className={cn(
'group flex items-end gap-2',
isOutgoing ? 'flex-row-reverse' : 'flex-row',
Expand All @@ -491,7 +493,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
>
{/* Avatar */}
{showAvatar && !isOutgoing && (
<div className="mb-1 shrink-0">
<div data-slot="message-avatar" className="mb-1 shrink-0">
<div
className={cn(
'flex h-8 w-8 items-center justify-center rounded-full',
Expand Down Expand Up @@ -520,14 +522,18 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
>
{/* Sender name (for group chats) */}
{showSenderName && !isOutgoing && (
<span className="mb-1 px-1 text-xs font-medium text-neutral-500 dark:text-neutral-400">
<span
data-slot="message-sender-name"
className="mb-1 px-1 text-xs font-medium text-neutral-500 dark:text-neutral-400"
>
{message.sender.name}
</span>
)}

{/* Reply preview */}
{message.replyTo && (
<div
data-slot="message-reply-preview"
className={cn(
'mb-1 max-w-full rounded-lg px-3 py-1.5 text-xs',
isOutgoing
Expand All @@ -542,6 +548,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(

{/* Bubble */}
<div
data-slot="message-bubble-content"
className={cn(bubbleVariants({ variant, status: message.status }))}
role="article"
aria-label={`Message from ${message.sender.name}`}
Expand Down Expand Up @@ -580,6 +587,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(

{/* Footer: timestamp, status, read receipts */}
<div
data-slot="message-footer"
className={cn(
'mt-1 flex items-center gap-2 px-1',
isOutgoing ? 'flex-row-reverse' : 'flex-row'
Expand Down Expand Up @@ -636,6 +644,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
{/* Reactions */}
{message.reactions && message.reactions.length > 0 && (
<div
data-slot="message-reactions"
className={cn(
'-mt-1 flex flex-wrap gap-1',
isOutgoing ? 'justify-end' : 'justify-start'
Expand Down
19 changes: 16 additions & 3 deletions src/components/Messaging/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const SendButton = React.forwardRef<HTMLButtonElement, SendButtonProps>(
ref={ref}
type="submit"
disabled={disabled || !canSend || isLoading}
data-slot="composer-send-button"
className={cn(sendButtonVariants({ variant, canSend }), className)}
aria-label={isLoading ? 'Sending message' : 'Send message'}
{...props}
Expand Down Expand Up @@ -420,10 +421,15 @@ const MessageComposer = React.forwardRef<
onError={onError}
className={cn('w-full', className)}
>
<form onSubmit={handleSubmit} className="w-full">
<form
onSubmit={handleSubmit}
data-slot="message-composer"
className="w-full"
>
{/* Reply preview */}
{replyTo && (
<div
data-slot="composer-reply-preview"
className={cn(
'flex items-center gap-2 px-4 py-2',
'bg-neutral-50 dark:bg-neutral-800/50',
Expand Down Expand Up @@ -469,6 +475,7 @@ const MessageComposer = React.forwardRef<
{/* Attachment previews */}
{attachments.length > 0 && (
<div
data-slot="composer-attachments"
className={cn(
'flex flex-wrap gap-2 p-3',
'border-t border-neutral-200 dark:border-neutral-700'
Expand All @@ -486,6 +493,7 @@ const MessageComposer = React.forwardRef<

{/* Input area */}
<div
data-slot="composer-input-area"
className={cn(
'flex items-center gap-2 p-3',
'bg-white dark:bg-neutral-900',
Expand Down Expand Up @@ -513,9 +521,10 @@ const MessageComposer = React.forwardRef<
)}

{/* Text input */}
<div className="relative flex-1">
<div data-slot="composer-input-wrapper" className="relative flex-1">
<textarea
ref={textareaRef}
data-slot="composer-input"
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleKeyDown}
Expand All @@ -538,7 +547,11 @@ const MessageComposer = React.forwardRef<

{/* Character count */}
{showCharacterCount && (
<div id="char-count" className="absolute right-3 bottom-1.5">
<div
data-slot="composer-char-count"
id="char-count"
className="absolute right-3 bottom-1.5"
>
<CharacterCounter current={content.length} max={maxLength} />
</div>
)}
Expand Down
30 changes: 25 additions & 5 deletions src/components/Messaging/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ function SkeletonMessage({
}: SkeletonMessageProps) {
return (
<div
data-slot="skeleton-message"
className={cn(
'flex items-end gap-2',
isOutgoing ? 'flex-row-reverse' : 'flex-row',
Expand All @@ -118,9 +119,13 @@ function SkeletonMessage({
aria-hidden="true"
>
{showAvatar && !isOutgoing && (
<div className="h-8 w-8 animate-pulse rounded-full bg-neutral-200 dark:bg-neutral-700" />
<div
data-slot="skeleton-avatar"
className="h-8 w-8 animate-pulse rounded-full bg-neutral-200 dark:bg-neutral-700"
/>
)}
<div
data-slot="skeleton-bubble"
className={cn(
'animate-pulse rounded-2xl',
isOutgoing
Expand All @@ -129,7 +134,9 @@ function SkeletonMessage({
'h-10 w-48'
)}
/>
{showAvatar && isOutgoing && <div className="w-8" />}
{showAvatar && isOutgoing && (
<div data-slot="skeleton-spacer" className="w-8" />
)}
</div>
);
}
Expand Down Expand Up @@ -162,6 +169,7 @@ function TypingIndicator({ typingState, className }: TypingIndicatorProps) {

return (
<div
data-slot="typing-indicator"
className={cn('flex items-center gap-2 px-4 py-2', className)}
role="status"
aria-live="polite"
Expand Down Expand Up @@ -206,6 +214,7 @@ export interface DateSeparatorProps {
function DateSeparator({ label, className }: DateSeparatorProps) {
return (
<div
data-slot="date-separator"
className={cn('flex items-center justify-center py-4', className)}
role="separator"
aria-label={label}
Expand Down Expand Up @@ -253,6 +262,7 @@ function EmptyState({
}: EmptyStateProps) {
return (
<div
data-slot="message-empty-state"
className={cn(
'flex flex-1 flex-col items-center justify-center p-8 text-center',
className
Expand All @@ -261,7 +271,10 @@ function EmptyState({
aria-label={title}
>
{icon || (
<div className="mb-4 rounded-full bg-neutral-100 p-4 dark:bg-neutral-800">
<div
data-slot="message-empty-state-icon"
className="mb-4 rounded-full bg-neutral-100 p-4 dark:bg-neutral-800"
>
<svg
className="h-12 w-12 text-neutral-500"
fill="none"
Expand Down Expand Up @@ -309,7 +322,10 @@ function LoadMoreButton({
className,
}: LoadMoreButtonProps) {
return (
<div className={cn('flex justify-center py-4', className)}>
<div
data-slot="load-more-button"
className={cn('flex justify-center py-4', className)}
>
<button
type="button"
onClick={onClick}
Expand Down Expand Up @@ -518,6 +534,7 @@ const MessageList = React.forwardRef<HTMLDivElement, MessageListProps>(
return (
<div
ref={scrollContainerRef}
data-slot="message-list"
className={cn(
'flex flex-1 flex-col overflow-y-auto',
'scroll-smooth',
Expand All @@ -534,7 +551,10 @@ const MessageList = React.forwardRef<HTMLDivElement, MessageListProps>(
)}

{/* Messages grouped by date */}
<div className="flex flex-col gap-1 p-4">
<div
data-slot="message-list-content"
className="flex flex-col gap-1 p-4"
>
{messageGroups.map((group) => (
<React.Fragment key={group.date}>
{/* Date separator */}
Expand Down
5 changes: 5 additions & 0 deletions src/components/Messaging/MessageThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function LightboxModal({ attachment, onClose }: LightboxModalProps) {

return (
<div
data-slot="message-lightbox"
className={cn('fixed inset-0 z-50', 'flex items-center justify-center')}
role="dialog"
aria-modal="true"
Expand Down Expand Up @@ -286,6 +287,7 @@ const MessageThread = React.forwardRef<HTMLDivElement, MessageThreadProps>(
return (
<div
ref={ref}
data-slot="message-thread"
className={cn(
'flex h-full flex-col',
'bg-white dark:bg-neutral-900',
Expand Down Expand Up @@ -421,6 +423,7 @@ function MessagingSplitView({

return (
<div
data-slot="messaging-split-view"
className={cn(
'h-full w-full',
breakpointClasses[mobileBreakpoint],
Expand All @@ -429,6 +432,7 @@ function MessagingSplitView({
>
{/* Conversation list */}
<div
data-slot="messaging-list-pane"
className={cn(
'h-full w-full flex-shrink-0',
'border-r border-neutral-200 dark:border-neutral-700',
Expand All @@ -443,6 +447,7 @@ function MessagingSplitView({

{/* Message thread */}
<div
data-slot="messaging-thread-pane"
className={cn(
'h-full min-w-0 flex-1',
showMobileClasses[mobileBreakpoint]
Expand Down
Loading
Loading