@connectycube/react-ui-kit 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/configs/dependencies.json +21 -0
  2. package/configs/imports.json +7 -0
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/types/components/avatar.d.ts +2 -2
  6. package/dist/types/components/avatar.d.ts.map +1 -1
  7. package/dist/types/components/badge.d.ts +2 -2
  8. package/dist/types/components/badge.d.ts.map +1 -1
  9. package/dist/types/components/dialog-item.d.ts +46 -0
  10. package/dist/types/components/dialog-item.d.ts.map +1 -0
  11. package/dist/types/components/file-picker.d.ts +22 -0
  12. package/dist/types/components/file-picker.d.ts.map +1 -0
  13. package/dist/types/components/formatted-date.d.ts +8 -0
  14. package/dist/types/components/formatted-date.d.ts.map +1 -0
  15. package/dist/types/components/input.d.ts.map +1 -1
  16. package/dist/types/components/label.d.ts +5 -0
  17. package/dist/types/components/label.d.ts.map +1 -0
  18. package/dist/types/components/link-preview.d.ts +21 -0
  19. package/dist/types/components/link-preview.d.ts.map +1 -0
  20. package/dist/types/components/linkify-text.d.ts +9 -0
  21. package/dist/types/components/linkify-text.d.ts.map +1 -0
  22. package/dist/types/components/spinner.d.ts +3 -1
  23. package/dist/types/components/spinner.d.ts.map +1 -1
  24. package/dist/types/components/status-sent.d.ts +7 -0
  25. package/dist/types/components/status-sent.d.ts.map +1 -0
  26. package/dist/types/components/stream-view.d.ts.map +1 -1
  27. package/dist/types/components/utils.d.ts +0 -2
  28. package/dist/types/components/utils.d.ts.map +1 -1
  29. package/gen/components/avatar.jsx +13 -3
  30. package/gen/components/badge.jsx +3 -3
  31. package/gen/components/button.jsx +2 -2
  32. package/gen/components/dialog-item.jsx +149 -0
  33. package/gen/components/file-picker.jsx +200 -0
  34. package/gen/components/formatted-date.jsx +57 -0
  35. package/gen/components/label.jsx +22 -0
  36. package/gen/components/link-preview.jsx +131 -0
  37. package/gen/components/linkify-text.jsx +31 -0
  38. package/gen/components/spinner.jsx +29 -5
  39. package/gen/components/status-sent.jsx +21 -0
  40. package/gen/components/stream-view.jsx +5 -1
  41. package/gen/components/utils.js +0 -11
  42. package/package.json +4 -1
  43. package/src/components/avatar.tsx +15 -5
  44. package/src/components/badge.tsx +6 -6
  45. package/src/components/button.tsx +2 -2
  46. package/src/components/connectycube-ui/avatar.jsx +54 -0
  47. package/src/components/connectycube-ui/avatar.tsx +77 -0
  48. package/src/components/connectycube-ui/badge.jsx +45 -0
  49. package/src/components/connectycube-ui/badge.tsx +42 -0
  50. package/src/components/connectycube-ui/dialog-item.jsx +149 -0
  51. package/src/components/connectycube-ui/dialog-item.tsx +188 -0
  52. package/src/components/connectycube-ui/file-picker.jsx +200 -0
  53. package/src/components/connectycube-ui/file-picker.tsx +231 -0
  54. package/src/components/connectycube-ui/formatted-date.jsx +57 -0
  55. package/src/components/connectycube-ui/formatted-date.tsx +57 -0
  56. package/src/components/connectycube-ui/label.jsx +22 -0
  57. package/src/components/connectycube-ui/label.tsx +23 -0
  58. package/src/components/connectycube-ui/linkify-text.tsx +40 -0
  59. package/src/components/connectycube-ui/presence.jsx +81 -0
  60. package/src/components/connectycube-ui/presence.tsx +96 -0
  61. package/src/components/connectycube-ui/status-sent.jsx +21 -0
  62. package/src/components/connectycube-ui/status-sent.tsx +25 -0
  63. package/src/components/connectycube-ui/utils.js +10 -0
  64. package/src/components/connectycube-ui/utils.ts +10 -0
  65. package/src/components/dialog-item.tsx +188 -0
  66. package/src/components/file-picker.tsx +231 -0
  67. package/src/components/formatted-date.tsx +57 -0
  68. package/src/components/input.tsx +1 -1
  69. package/src/components/label.tsx +23 -0
  70. package/src/components/link-preview.tsx +149 -0
  71. package/src/components/linkify-text.tsx +41 -0
  72. package/src/components/spinner.tsx +31 -5
  73. package/src/components/status-sent.tsx +25 -0
  74. package/src/components/stream-view.tsx +5 -1
  75. package/src/components/utils.ts +0 -11
@@ -0,0 +1,54 @@
1
+ import { memo, forwardRef } from 'react';
2
+ import * as AvatarPrimitive from '@radix-ui/react-avatar';
3
+ import { PresenceBadge } from './presence';
4
+ import { cn } from './utils';
5
+
6
+ function getInitialsFromName(name) {
7
+ const words = name?.trim().split(/\s+/).filter(Boolean) ?? [];
8
+ const result = words.length > 1 ? `${words[0]?.[0]}${words[1]?.[0]}` : (words[0]?.slice(0, 2) ?? 'NA');
9
+
10
+ return result.toUpperCase();
11
+ }
12
+
13
+ function AvatarBase(
14
+ { src, name = 'NA', online, presence, className, onlineProps, presenceProps, imageProps, fallbackProps, ...props },
15
+ ref
16
+ ) {
17
+ const initials = getInitialsFromName(name);
18
+
19
+ return (
20
+ <AvatarPrimitive.Root ref={ref} {...props} className={cn('relative flex size-8 shrink-0 rounded-full', className)}>
21
+ <AvatarPrimitive.Image
22
+ {...imageProps}
23
+ src={src}
24
+ className={cn('aspect-square size-full rounded-full overflow-hidden object-cover', imageProps?.className)}
25
+ />
26
+ <AvatarPrimitive.Fallback
27
+ {...fallbackProps}
28
+ className={cn('bg-muted size-full rounded-full flex items-center justify-center', fallbackProps?.className)}
29
+ >
30
+ {initials}
31
+ </AvatarPrimitive.Fallback>
32
+ {online && (
33
+ <div
34
+ {...onlineProps}
35
+ className={cn(
36
+ 'absolute top-0 right-0 rounded-full border-2 bg-green-600 border-green-200 size-3.5',
37
+ onlineProps?.className
38
+ )}
39
+ />
40
+ )}
41
+ <PresenceBadge
42
+ status={presence}
43
+ {...presenceProps}
44
+ className={cn('absolute bottom-0 right-0', presenceProps?.className)}
45
+ />
46
+ </AvatarPrimitive.Root>
47
+ );
48
+ }
49
+
50
+ const Avatar = memo(forwardRef(AvatarBase));
51
+
52
+ Avatar.displayName = 'Avatar';
53
+
54
+ export { Avatar };
@@ -0,0 +1,77 @@
1
+ import type React from 'react';
2
+ import { memo, forwardRef } from 'react';
3
+ import * as AvatarPrimitive from '@radix-ui/react-avatar';
4
+ import { PresenceBadge, type PresenceStatus, type PresenceBadgeProps } from './presence';
5
+ import { cn } from './utils';
6
+
7
+ interface AvatarProps extends AvatarPrimitive.AvatarProps {
8
+ src?: string;
9
+ name?: string;
10
+ online?: boolean;
11
+ presence?: PresenceStatus;
12
+ onlineProps?: React.ComponentProps<'div'>;
13
+ presenceProps?: PresenceBadgeProps;
14
+ imageProps?: AvatarPrimitive.AvatarImageProps;
15
+ fallbackProps?: AvatarPrimitive.AvatarFallbackProps;
16
+ }
17
+
18
+ function getInitialsFromName(name?: string): string {
19
+ const words = name?.trim().split(/\s+/).filter(Boolean) ?? [];
20
+ const result = words.length > 1 ? `${words[0]?.[0]}${words[1]?.[0]}` : (words[0]?.slice(0, 2) ?? 'NA');
21
+
22
+ return result.toUpperCase();
23
+ }
24
+
25
+ function AvatarBase(
26
+ {
27
+ src,
28
+ name = 'NA',
29
+ online,
30
+ presence,
31
+ className,
32
+ onlineProps,
33
+ presenceProps,
34
+ imageProps,
35
+ fallbackProps,
36
+ ...props
37
+ }: AvatarProps,
38
+ ref: React.ForwardedRef<HTMLDivElement>
39
+ ) {
40
+ const initials = getInitialsFromName(name);
41
+
42
+ return (
43
+ <AvatarPrimitive.Root ref={ref} {...props} className={cn('relative flex size-8 shrink-0 rounded-full', className)}>
44
+ <AvatarPrimitive.Image
45
+ {...imageProps}
46
+ src={src}
47
+ className={cn('aspect-square size-full rounded-full overflow-hidden object-cover', imageProps?.className)}
48
+ />
49
+ <AvatarPrimitive.Fallback
50
+ {...fallbackProps}
51
+ className={cn('bg-muted size-full rounded-full flex items-center justify-center', fallbackProps?.className)}
52
+ >
53
+ {initials}
54
+ </AvatarPrimitive.Fallback>
55
+ {online && (
56
+ <div
57
+ {...onlineProps}
58
+ className={cn(
59
+ 'absolute top-0 right-0 rounded-full border-2 bg-green-600 border-green-200 size-3.5',
60
+ onlineProps?.className
61
+ )}
62
+ />
63
+ )}
64
+ <PresenceBadge
65
+ status={presence}
66
+ {...presenceProps}
67
+ className={cn('absolute bottom-0 right-0', presenceProps?.className)}
68
+ />
69
+ </AvatarPrimitive.Root>
70
+ );
71
+ }
72
+
73
+ const Avatar = memo(forwardRef<HTMLDivElement, AvatarProps>(AvatarBase));
74
+
75
+ Avatar.displayName = 'Avatar';
76
+
77
+ export { Avatar, type AvatarProps };
@@ -0,0 +1,45 @@
1
+ import { forwardRef } from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva } from 'class-variance-authority';
4
+ import { cn } from './utils';
5
+
6
+ const badgeVariants = cva(
7
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
12
+ secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
13
+ destructive:
14
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
15
+ outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ variant: 'default',
20
+ },
21
+ }
22
+ );
23
+
24
+ function BadgeBase({ className, variant, asChild = false, ...props }, ref) {
25
+ const Comp = asChild ? Slot : 'span';
26
+
27
+ return (
28
+ <Comp
29
+ ref={ref}
30
+ {...props}
31
+ className={cn(
32
+ badgeVariants({
33
+ variant,
34
+ }),
35
+ className
36
+ )}
37
+ />
38
+ );
39
+ }
40
+
41
+ const Badge = forwardRef(BadgeBase);
42
+
43
+ Badge.displayName = 'Badge';
44
+
45
+ export { Badge };
@@ -0,0 +1,42 @@
1
+ import type React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import { Slot } from '@radix-ui/react-slot';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { cn } from './utils';
6
+
7
+ interface BadgeProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof badgeVariants> {
8
+ asChild?: boolean;
9
+ }
10
+
11
+ const badgeVariants = cva(
12
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
13
+ {
14
+ variants: {
15
+ variant: {
16
+ default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
17
+ secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
18
+ destructive:
19
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
20
+ outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: 'default',
25
+ },
26
+ }
27
+ );
28
+
29
+ function BadgeBase(
30
+ { className, variant, asChild = false, ...props }: BadgeProps,
31
+ ref?: React.ForwardedRef<HTMLElement>
32
+ ) {
33
+ const Comp = asChild ? Slot : 'span';
34
+
35
+ return <Comp ref={ref} {...props} className={cn(badgeVariants({ variant }), className)} />;
36
+ }
37
+
38
+ const Badge = forwardRef<HTMLElement, BadgeProps>(BadgeBase);
39
+
40
+ Badge.displayName = 'Badge';
41
+
42
+ export { Badge, type BadgeProps };
@@ -0,0 +1,149 @@
1
+ import { forwardRef, memo } from 'react';
2
+ import { Users } from 'lucide-react';
3
+ import { cn } from './utils';
4
+ import { Avatar } from './avatar';
5
+ import { StatusSent } from './status-sent';
6
+ import { FormattedDate } from './formatted-date';
7
+ import { Badge } from './badge';
8
+
9
+ function DialogItemBase(
10
+ {
11
+ index = -1,
12
+ isPrivateDialog,
13
+ selected,
14
+ onSelect = () => {},
15
+ name,
16
+ photo,
17
+ userOnline,
18
+ userPresence,
19
+ hasGroupIcon,
20
+ lastSentStatus,
21
+ lastSentDate,
22
+ lastSenderName,
23
+ lastMessage,
24
+ typingStatusText,
25
+ draft,
26
+ draftLabel = 'Draft',
27
+ unreadCount = 0,
28
+ language = 'en',
29
+ divider = 'bottom',
30
+ avatarProps,
31
+ contentProps,
32
+ headerProps,
33
+ headerLeftProps,
34
+ groupIconProps,
35
+ nameProps,
36
+ headerRightProps,
37
+ statusSentIconProps,
38
+ formattedDateProps,
39
+ footerProps,
40
+ lastMessageProps,
41
+ draftLabelProps,
42
+ lastSenderNameProps,
43
+ unreadBadgeProps,
44
+ dividerProps,
45
+ ...props
46
+ },
47
+ ref
48
+ ) {
49
+ const avatarSource = photo || avatarProps?.src;
50
+ const avatarFallback = name || avatarProps?.name;
51
+ const avatarOnline = isPrivateDialog ? userOnline || avatarProps?.online : false;
52
+ const avatarPresence = isPrivateDialog ? userPresence || avatarProps?.presence : undefined;
53
+ const showTopDivider = divider === 'top' || (divider === 'both' && index === 0);
54
+ const showBottomDivider = divider === 'bottom' || divider === 'both';
55
+
56
+ return (
57
+ <div
58
+ ref={ref}
59
+ {...props}
60
+ onClick={onSelect}
61
+ className={cn(
62
+ 'flex items-start gap-2 px-2 flex-1 cursor-pointer',
63
+ 'transition-colors duration-200 ease-linear',
64
+ `${selected ? 'border-l-[0.25em] pl-1 border-l-ring bg-ring/20' : 'hover:bg-ring/5'}`,
65
+ props?.className
66
+ )}
67
+ >
68
+ <Avatar
69
+ src={avatarSource}
70
+ name={avatarFallback}
71
+ online={avatarOnline}
72
+ presence={avatarPresence}
73
+ {...avatarProps}
74
+ className={cn(`size-13 my-2 ${photo ? 'bg-ring/10' : 'bg-ring/30'}`, avatarProps?.className)}
75
+ />
76
+
77
+ <div
78
+ {...contentProps}
79
+ className={cn('relative self-stretch flex-1 flex-col text-foreground', contentProps?.className)}
80
+ >
81
+ <div {...headerProps} className={cn('flex items-center justify-between gap-1', headerProps?.className)}>
82
+ <div {...headerLeftProps} className={cn('flex items-center gap-1', headerLeftProps?.className)}>
83
+ {!isPrivateDialog && hasGroupIcon && (
84
+ <Users
85
+ {...groupIconProps}
86
+ className={cn('text-muted-foreground size-4 min-w-4', groupIconProps?.className)}
87
+ />
88
+ )}
89
+ <span {...nameProps} className={cn('font-medium text-left line-clamp-1', nameProps?.className)}>
90
+ {name}
91
+ </span>
92
+ </div>
93
+ <div {...headerRightProps} className={cn('flex items-center gap-1', headerRightProps?.className)}>
94
+ <StatusSent status={lastSentStatus} {...statusSentIconProps} />
95
+ <FormattedDate date={lastSentDate} language={language} {...formattedDateProps} />
96
+ </div>
97
+ </div>
98
+ <div {...footerProps} className={cn('flex items-start justify-between gap-1', footerProps?.className)}>
99
+ <span
100
+ {...lastMessageProps}
101
+ className={cn('text-sm text-left text-muted-foreground line-clamp-2', lastMessageProps?.className)}
102
+ >
103
+ {typingStatusText ||
104
+ (draft ? (
105
+ <span>
106
+ <span
107
+ {...draftLabelProps}
108
+ className={cn('text-red-500 font-medium', draftLabelProps?.className)}
109
+ >{`${draftLabel}: `}</span>
110
+ {draft}
111
+ </span>
112
+ ) : (
113
+ <span>
114
+ {lastSenderName && (
115
+ <span
116
+ {...lastSenderNameProps}
117
+ className={cn('font-semibold', lastSenderNameProps?.className)}
118
+ >{`${lastSenderName}: `}</span>
119
+ )}
120
+ {lastMessage}
121
+ </span>
122
+ ))}
123
+ </span>
124
+ {unreadCount > 0 && (
125
+ <Badge
126
+ {...unreadBadgeProps}
127
+ className={cn(
128
+ 'bg-ring text-background text-xs rounded-full p-1 h-6 min-w-6 mt-0.5',
129
+ unreadBadgeProps?.className
130
+ )}
131
+ >
132
+ {unreadCount}
133
+ </Badge>
134
+ )}
135
+ </div>
136
+ {showTopDivider && (
137
+ <div {...dividerProps} className={cn('absolute -top-px left-0 right-0 border-t', dividerProps?.className)} />
138
+ )}
139
+ {showBottomDivider && <div className="absolute bottom-0 left-0 right-0 border-b" />}
140
+ </div>
141
+ </div>
142
+ );
143
+ }
144
+
145
+ const DialogItem = memo(forwardRef(DialogItemBase));
146
+
147
+ DialogItem.displayName = 'DialogItem';
148
+
149
+ export { DialogItem };
@@ -0,0 +1,188 @@
1
+ import type React from 'react';
2
+ import { forwardRef, memo } from 'react';
3
+ import { Users, type LucideProps } from 'lucide-react';
4
+ import { cn } from './utils';
5
+ import { Avatar, type AvatarProps } from './avatar';
6
+ import { type PresenceStatus } from './presence';
7
+ import { StatusSent, type StatusSentProps } from './status-sent';
8
+ import { FormattedDate, type FormattedDateProps } from './formatted-date';
9
+ import { Badge, type BadgeProps } from './badge';
10
+
11
+ interface DialogItemProps extends React.ComponentProps<'div'> {
12
+ index?: number;
13
+ isPrivateDialog?: boolean;
14
+ selected?: boolean;
15
+ onSelect?: () => void;
16
+ name: string;
17
+ photo?: string;
18
+ userOnline?: boolean;
19
+ userPresence?: PresenceStatus;
20
+ hasGroupIcon?: boolean;
21
+ lastSentStatus?: StatusSentProps['status'];
22
+ lastSentDate?: FormattedDateProps['date'];
23
+ lastSenderName?: string;
24
+ lastMessage?: string;
25
+ typingStatusText?: string;
26
+ draft?: string;
27
+ draftLabel?: string;
28
+ unreadCount?: number;
29
+ language?: string;
30
+ divider?: 'top' | 'bottom' | 'both' | 'none';
31
+ avatarProps?: AvatarProps;
32
+ contentProps?: React.ComponentProps<'div'>;
33
+ headerProps?: React.ComponentProps<'div'>;
34
+ headerLeftProps?: React.ComponentProps<'div'>;
35
+ groupIconProps?: LucideProps;
36
+ nameProps?: React.ComponentProps<'span'>;
37
+ headerRightProps?: React.ComponentProps<'div'>;
38
+ statusSentIconProps?: StatusSentProps;
39
+ formattedDateProps?: FormattedDateProps;
40
+ footerProps?: React.ComponentProps<'div'>;
41
+ lastMessageProps?: React.ComponentProps<'span'>;
42
+ draftLabelProps?: React.ComponentProps<'span'>;
43
+ lastSenderNameProps?: React.ComponentProps<'span'>;
44
+ unreadBadgeProps?: BadgeProps;
45
+ dividerProps?: React.ComponentProps<'div'>;
46
+ }
47
+
48
+ function DialogItemBase(
49
+ {
50
+ index = -1,
51
+ isPrivateDialog,
52
+ selected,
53
+ onSelect = () => {},
54
+ name,
55
+ photo,
56
+ userOnline,
57
+ userPresence,
58
+ hasGroupIcon,
59
+ lastSentStatus,
60
+ lastSentDate,
61
+ lastSenderName,
62
+ lastMessage,
63
+ typingStatusText,
64
+ draft,
65
+ draftLabel = 'Draft',
66
+ unreadCount = 0,
67
+ language = 'en',
68
+ divider = 'bottom',
69
+ avatarProps,
70
+ contentProps,
71
+ headerProps,
72
+ headerLeftProps,
73
+ groupIconProps,
74
+ nameProps,
75
+ headerRightProps,
76
+ statusSentIconProps,
77
+ formattedDateProps,
78
+ footerProps,
79
+ lastMessageProps,
80
+ draftLabelProps,
81
+ lastSenderNameProps,
82
+ unreadBadgeProps,
83
+ dividerProps,
84
+ ...props
85
+ }: DialogItemProps,
86
+ ref: React.ForwardedRef<HTMLDivElement>
87
+ ) {
88
+ const avatarSource = photo || avatarProps?.src;
89
+ const avatarFallback = name || avatarProps?.name;
90
+ const avatarOnline = isPrivateDialog ? userOnline || avatarProps?.online : false;
91
+ const avatarPresence = isPrivateDialog ? userPresence || avatarProps?.presence : undefined;
92
+ const showTopDivider = divider === 'top' || (divider === 'both' && index === 0);
93
+ const showBottomDivider = divider === 'bottom' || divider === 'both';
94
+
95
+ return (
96
+ <div
97
+ ref={ref}
98
+ {...props}
99
+ onClick={onSelect}
100
+ className={cn(
101
+ 'flex items-start gap-2 px-2 flex-1 cursor-pointer',
102
+ 'transition-colors duration-200 ease-linear',
103
+ `${selected ? 'border-l-[0.25em] pl-1 border-l-ring bg-ring/20' : 'hover:bg-ring/5'}`,
104
+ props?.className
105
+ )}
106
+ >
107
+ <Avatar
108
+ src={avatarSource}
109
+ name={avatarFallback}
110
+ online={avatarOnline}
111
+ presence={avatarPresence}
112
+ {...avatarProps}
113
+ className={cn(`size-13 my-2 ${photo ? 'bg-ring/10' : 'bg-ring/30'}`, avatarProps?.className)}
114
+ />
115
+
116
+ <div
117
+ {...contentProps}
118
+ className={cn('relative self-stretch flex-1 flex-col text-foreground', contentProps?.className)}
119
+ >
120
+ <div {...headerProps} className={cn('flex items-center justify-between gap-1', headerProps?.className)}>
121
+ <div {...headerLeftProps} className={cn('flex items-center gap-1', headerLeftProps?.className)}>
122
+ {!isPrivateDialog && hasGroupIcon && (
123
+ <Users
124
+ {...groupIconProps}
125
+ className={cn('text-muted-foreground size-4 min-w-4', groupIconProps?.className)}
126
+ />
127
+ )}
128
+ <span {...nameProps} className={cn('font-medium text-left line-clamp-1', nameProps?.className)}>
129
+ {name}
130
+ </span>
131
+ </div>
132
+ <div {...headerRightProps} className={cn('flex items-center gap-1', headerRightProps?.className)}>
133
+ <StatusSent status={lastSentStatus} {...statusSentIconProps} />
134
+ <FormattedDate date={lastSentDate} language={language} {...formattedDateProps} />
135
+ </div>
136
+ </div>
137
+ <div {...footerProps} className={cn('flex items-start justify-between gap-1', footerProps?.className)}>
138
+ <span
139
+ {...lastMessageProps}
140
+ className={cn('text-sm text-left text-muted-foreground line-clamp-2', lastMessageProps?.className)}
141
+ >
142
+ {typingStatusText ||
143
+ (draft ? (
144
+ <span>
145
+ <span
146
+ {...draftLabelProps}
147
+ className={cn('text-red-500 font-medium', draftLabelProps?.className)}
148
+ >{`${draftLabel}: `}</span>
149
+ {draft}
150
+ </span>
151
+ ) : (
152
+ <span>
153
+ {lastSenderName && (
154
+ <span
155
+ {...lastSenderNameProps}
156
+ className={cn('font-semibold', lastSenderNameProps?.className)}
157
+ >{`${lastSenderName}: `}</span>
158
+ )}
159
+ {lastMessage}
160
+ </span>
161
+ ))}
162
+ </span>
163
+ {unreadCount > 0 && (
164
+ <Badge
165
+ {...unreadBadgeProps}
166
+ className={cn(
167
+ 'bg-ring text-background text-xs rounded-full p-1 h-6 min-w-6 mt-0.5',
168
+ unreadBadgeProps?.className
169
+ )}
170
+ >
171
+ {unreadCount}
172
+ </Badge>
173
+ )}
174
+ </div>
175
+ {showTopDivider && (
176
+ <div {...dividerProps} className={cn('absolute -top-px left-0 right-0 border-t', dividerProps?.className)} />
177
+ )}
178
+ {showBottomDivider && <div className="absolute bottom-0 left-0 right-0 border-b" />}
179
+ </div>
180
+ </div>
181
+ );
182
+ }
183
+
184
+ const DialogItem = memo(forwardRef<HTMLDivElement, DialogItemProps>(DialogItemBase));
185
+
186
+ DialogItem.displayName = 'DialogItem';
187
+
188
+ export { DialogItem };