tembro 4.0.1 → 4.2.0
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.
- package/CHANGELOG.md +15 -0
- package/dist/components/display/chat.cjs +1 -0
- package/dist/components/display/chat.d.ts +94 -0
- package/dist/components/display/chat.js +1 -0
- package/dist/components/display/index.d.ts +1 -0
- package/dist/components/display/kanban.d.ts +33 -1
- package/dist/components/inputs/combobox.cjs +1 -0
- package/dist/components/inputs/combobox.d.ts +46 -0
- package/dist/components/inputs/combobox.js +1 -0
- package/dist/components/inputs/index.d.ts +1 -0
- package/dist/components/inputs/public.d.ts +1 -0
- package/dist/components/layout/sidebar.d.ts +24 -2
- package/dist/components/modern/rich-text-editor.d.ts +13 -1
- package/dist/components/theme-provider.d.ts +19 -6
- package/dist/components/ui/badge/index.d.ts +8 -2
- package/dist/src/components/display/activity-feed.cjs +1 -1
- package/dist/src/components/display/activity-feed.js +4 -1
- package/dist/src/components/display/chat.cjs +1 -0
- package/dist/src/components/display/chat.js +372 -0
- package/dist/src/components/display/index.cjs +1 -1
- package/dist/src/components/display/index.js +17 -16
- package/dist/src/components/display/kanban.cjs +1 -1
- package/dist/src/components/display/kanban.js +321 -176
- package/dist/src/components/display/timeline.cjs +1 -1
- package/dist/src/components/display/timeline.js +5 -5
- package/dist/src/components/inputs/combobox.cjs +1 -0
- package/dist/src/components/inputs/combobox.js +162 -0
- package/dist/src/components/inputs/index.cjs +1 -1
- package/dist/src/components/inputs/index.js +10 -9
- package/dist/src/components/inputs/public.cjs +1 -1
- package/dist/src/components/inputs/public.js +10 -9
- package/dist/src/components/inputs/tag-input.cjs +1 -1
- package/dist/src/components/inputs/tag-input.js +6 -2
- package/dist/src/components/layout/index.cjs +1 -1
- package/dist/src/components/layout/index.js +4 -4
- package/dist/src/components/layout/public.cjs +1 -1
- package/dist/src/components/layout/public.js +4 -4
- package/dist/src/components/layout/sidebar.cjs +1 -1
- package/dist/src/components/layout/sidebar.js +326 -228
- package/dist/src/components/modern/rich-text-editor.cjs +1 -1
- package/dist/src/components/modern/rich-text-editor.js +216 -89
- package/dist/src/components/theme-provider.cjs +1 -1
- package/dist/src/components/theme-provider.js +58 -36
- package/dist/src/components/ui/badge/index.cjs +1 -1
- package/dist/src/components/ui/badge/index.js +59 -47
- package/dist/src/components/ui/input/clearable.cjs +1 -1
- package/dist/src/components/ui/input/clearable.js +5 -2
- package/dist/src/components/ui/input/primitive.cjs +1 -1
- package/dist/src/components/ui/input/primitive.js +1 -1
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.js +88 -86
- package/dist/src/public-component-surface.cjs +1 -1
- package/dist/src/public-component-surface.js +107 -41
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +37 -37
- package/packages/cli/vendor/src/components/display/activity-feed.tsx +1 -1
- package/packages/cli/vendor/src/components/display/chat.tsx +237 -0
- package/packages/cli/vendor/src/components/display/index.ts +1 -0
- package/packages/cli/vendor/src/components/display/kanban.tsx +219 -72
- package/packages/cli/vendor/src/components/display/timeline.tsx +10 -10
- package/packages/cli/vendor/src/components/inputs/combobox.tsx +303 -0
- package/packages/cli/vendor/src/components/inputs/index.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/public.ts +1 -0
- package/packages/cli/vendor/src/components/inputs/tag-input.tsx +3 -2
- package/packages/cli/vendor/src/components/layout/sidebar.tsx +212 -51
- package/packages/cli/vendor/src/components/modern/rich-text-editor.tsx +115 -32
- package/packages/cli/vendor/src/components/theme-provider.tsx +82 -36
- package/packages/cli/vendor/src/components/ui/badge/index.tsx +29 -8
- package/packages/cli/vendor/src/components/ui/input/clearable.tsx +2 -2
- package/packages/cli/vendor/src/components/ui/input/primitive.tsx +1 -1
- package/packages/cli/vendor/src/public-component-surface.ts +36 -22
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
- package/packages/cli/vendor/templates/styles/globals.css +91 -10
- package/registry.json +28 -11
|
@@ -117,7 +117,7 @@ function ActivityFeed({
|
|
|
117
117
|
className={cn("relative flex gap-3 pb-4 last:pb-0", isCompact && "gap-2 pb-3", itemClassName, item.className)}
|
|
118
118
|
>
|
|
119
119
|
{showConnector && index < visibleItems.length - 1 && (
|
|
120
|
-
<div className={cn("absolute left-
|
|
120
|
+
<div data-slot="activity-feed-connector" className={cn("absolute left-5 top-8 h-[calc(100%-2rem)] w-px bg-border", connectorClassName)} />
|
|
121
121
|
)}
|
|
122
122
|
{item.href ? (
|
|
123
123
|
<a
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
CheckCheckIcon,
|
|
6
|
+
CheckIcon,
|
|
7
|
+
FileIcon,
|
|
8
|
+
MoreHorizontalIcon,
|
|
9
|
+
PaperclipIcon,
|
|
10
|
+
SearchIcon,
|
|
11
|
+
SendIcon,
|
|
12
|
+
SmileIcon,
|
|
13
|
+
XIcon,
|
|
14
|
+
} from "lucide-react"
|
|
15
|
+
|
|
16
|
+
import { Avatar } from "@/components/display/avatar"
|
|
17
|
+
import { Badge } from "@/components/ui/badge"
|
|
18
|
+
import { Button } from "@/components/ui/button"
|
|
19
|
+
import { Input } from "@/components/ui/input"
|
|
20
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
21
|
+
import { cn } from "@/lib/utils"
|
|
22
|
+
|
|
23
|
+
export type ChatMessageStatus = "sending" | "sent" | "delivered" | "read" | "failed"
|
|
24
|
+
|
|
25
|
+
export type ChatAttachmentData = {
|
|
26
|
+
key: string
|
|
27
|
+
name: string
|
|
28
|
+
size?: React.ReactNode
|
|
29
|
+
type?: React.ReactNode
|
|
30
|
+
href?: string
|
|
31
|
+
preview?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type ChatParticipant = {
|
|
35
|
+
name: string
|
|
36
|
+
avatar?: string
|
|
37
|
+
fallback?: React.ReactNode
|
|
38
|
+
status?: "online" | "offline" | "busy" | "away"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ChatShellProps = React.ComponentProps<"section"> & {
|
|
42
|
+
sidebar?: React.ReactNode
|
|
43
|
+
details?: React.ReactNode
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ChatShell({ sidebar, details, children, className, ...props }: ChatShellProps) {
|
|
47
|
+
return (
|
|
48
|
+
<section data-slot="chat-shell" className={cn("grid min-h-[34rem] overflow-hidden rounded-lg border bg-background shadow-sm lg:grid-cols-[18rem_minmax(0,1fr)]", details && "xl:grid-cols-[18rem_minmax(0,1fr)_17rem]", className)} {...props}>
|
|
49
|
+
{sidebar ? <aside data-slot="chat-sidebar" className="min-h-0 border-b lg:border-b-0 lg:border-r">{sidebar}</aside> : null}
|
|
50
|
+
<div data-slot="chat-main" className="grid min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)_auto]">{children}</div>
|
|
51
|
+
{details ? <aside data-slot="chat-details" className="hidden min-h-0 border-l xl:block">{details}</aside> : null}
|
|
52
|
+
</section>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type ChatHeaderProps = React.ComponentProps<"header"> & {
|
|
57
|
+
participant: ChatParticipant
|
|
58
|
+
description?: React.ReactNode
|
|
59
|
+
actions?: React.ReactNode
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ChatHeader({ participant, description, actions, className, ...props }: ChatHeaderProps) {
|
|
63
|
+
return (
|
|
64
|
+
<header data-slot="chat-header" className={cn("flex min-h-16 items-center gap-3 border-b px-4 py-2.5", className)} {...props}>
|
|
65
|
+
<Avatar size="sm" name={participant.name} src={participant.avatar} fallback={participant.fallback} status={participant.status} />
|
|
66
|
+
<div className="min-w-0 flex-1">
|
|
67
|
+
<div className="truncate text-sm font-semibold">{participant.name}</div>
|
|
68
|
+
<div className="truncate text-xs text-muted-foreground">{description ?? (participant.status === "online" ? "Online" : participant.status)}</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="flex shrink-0 items-center gap-1">{actions}</div>
|
|
71
|
+
</header>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type ConversationItem = {
|
|
76
|
+
key: string
|
|
77
|
+
participant: ChatParticipant
|
|
78
|
+
preview?: React.ReactNode
|
|
79
|
+
time?: React.ReactNode
|
|
80
|
+
unread?: number
|
|
81
|
+
muted?: boolean
|
|
82
|
+
pinned?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type ConversationListProps = Omit<React.ComponentProps<"div">, "onSelect"> & {
|
|
86
|
+
items: ConversationItem[]
|
|
87
|
+
selectedKey?: string
|
|
88
|
+
defaultSelectedKey?: string
|
|
89
|
+
onSelect?: (item: ConversationItem) => void
|
|
90
|
+
searchable?: boolean
|
|
91
|
+
searchPlaceholder?: string
|
|
92
|
+
empty?: React.ReactNode
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ConversationList({ items, selectedKey, defaultSelectedKey, onSelect, searchable = true, searchPlaceholder = "Search conversations...", empty = "No conversations found.", className, ...props }: ConversationListProps) {
|
|
96
|
+
const [internalKey, setInternalKey] = React.useState(defaultSelectedKey)
|
|
97
|
+
const [query, setQuery] = React.useState("")
|
|
98
|
+
const activeKey = selectedKey ?? internalKey
|
|
99
|
+
const filtered = items.filter((item) => `${item.participant.name} ${String(item.preview ?? "")}`.toLowerCase().includes(query.trim().toLowerCase()))
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div data-slot="conversation-list" className={cn("grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)]", className)} {...props}>
|
|
103
|
+
{searchable ? <div className="border-b p-3"><Input type="search" value={query} onValueChange={setQuery} placeholder={searchPlaceholder} leading={<SearchIcon />} clearable aria-label={searchPlaceholder} /></div> : <span />}
|
|
104
|
+
<div role="listbox" aria-label="Conversations" className="min-h-0 overflow-y-auto p-2">
|
|
105
|
+
{filtered.length ? filtered.map((item) => {
|
|
106
|
+
const selected = item.key === activeKey
|
|
107
|
+
return (
|
|
108
|
+
<button key={item.key} type="button" role="option" aria-selected={selected} data-selected={selected || undefined} className="flex w-full items-start gap-3 rounded-md px-2.5 py-2.5 text-left outline-none transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring data-[selected=true]:bg-accent" onClick={() => { if (selectedKey === undefined) setInternalKey(item.key); onSelect?.(item) }}>
|
|
109
|
+
<Avatar size="sm" name={item.participant.name} src={item.participant.avatar} fallback={item.participant.fallback} status={item.participant.status} />
|
|
110
|
+
<span className="min-w-0 flex-1">
|
|
111
|
+
<span className="flex items-center gap-2"><strong className="min-w-0 flex-1 truncate text-sm font-medium">{item.participant.name}</strong><span className="shrink-0 text-[11px] text-muted-foreground">{item.time}</span></span>
|
|
112
|
+
<span className="mt-0.5 flex items-center gap-2"><span className="min-w-0 flex-1 truncate text-xs text-muted-foreground">{item.preview}</span>{item.unread ? <Badge size="sm" label={item.unread} /> : null}</span>
|
|
113
|
+
</span>
|
|
114
|
+
</button>
|
|
115
|
+
)
|
|
116
|
+
}) : <div className="grid min-h-32 place-items-center px-4 text-center text-sm text-muted-foreground">{empty}</div>}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type ChatMessageListProps = React.ComponentProps<"div"> & {
|
|
123
|
+
autoScroll?: boolean
|
|
124
|
+
empty?: React.ReactNode
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ChatMessageList({ autoScroll = true, empty = "No messages yet.", children, className, ...props }: ChatMessageListProps) {
|
|
128
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
129
|
+
React.useEffect(() => {
|
|
130
|
+
const element = ref.current
|
|
131
|
+
if (!autoScroll || !element) return
|
|
132
|
+
if (typeof element.scrollTo === "function") element.scrollTo({ top: element.scrollHeight, behavior: "smooth" })
|
|
133
|
+
else element.scrollTop = element.scrollHeight
|
|
134
|
+
}, [autoScroll, children])
|
|
135
|
+
return <div ref={ref} data-slot="chat-message-list" role="log" aria-live="polite" className={cn("min-h-0 overflow-y-auto bg-muted/15 px-4 py-5", className)} {...props}>{React.Children.count(children) ? <div className="mx-auto grid w-full max-w-3xl gap-4">{children}</div> : <div className="grid h-full place-items-center text-sm text-muted-foreground">{empty}</div>}</div>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type ChatAttachmentProps = React.ComponentProps<"div"> & Omit<ChatAttachmentData, "key"> & {
|
|
139
|
+
attachmentKey?: string
|
|
140
|
+
onRemove?: () => void
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ChatAttachment({ name, size, type, href, preview, onRemove, attachmentKey: _attachmentKey, className, ...props }: ChatAttachmentProps) {
|
|
144
|
+
const content = <><span className="grid size-9 shrink-0 place-items-center overflow-hidden rounded-md bg-muted">{preview ? <img src={preview} alt="" className="size-full object-cover" /> : <FileIcon className="size-4" />}</span><span className="min-w-0 flex-1"><span className="block truncate text-xs font-medium text-foreground">{name}</span><span className="block truncate text-[11px] text-muted-foreground">{[type, size].filter(Boolean).join(" · ")}</span></span></>
|
|
145
|
+
return <div data-slot="chat-attachment" className={cn("flex min-w-0 items-center gap-2 rounded-md border bg-background p-2", className)} {...props}>{href ? <a href={href} className="flex min-w-0 flex-1 items-center gap-2 outline-none focus-visible:ring-2 focus-visible:ring-ring">{content}</a> : content}{onRemove ? <Button type="button" size="icon-xs" variant="ghost" iconOnly aria-label={`Remove ${name}`} onClick={onRemove}><XIcon /></Button> : null}</div>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type ChatMessageProps = React.ComponentProps<"article"> & {
|
|
149
|
+
participant?: ChatParticipant
|
|
150
|
+
outgoing?: boolean
|
|
151
|
+
time?: React.ReactNode
|
|
152
|
+
status?: ChatMessageStatus
|
|
153
|
+
replyTo?: React.ReactNode
|
|
154
|
+
attachments?: ChatAttachmentData[]
|
|
155
|
+
reactions?: Array<{ key: string; label: React.ReactNode; count?: number; active?: boolean }>
|
|
156
|
+
onReaction?: (key: string) => void
|
|
157
|
+
actions?: React.ReactNode
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function ChatMessage({ participant, outgoing = false, time, status, replyTo, attachments, reactions, onReaction, actions, children, className, ...props }: ChatMessageProps) {
|
|
161
|
+
const StatusIcon = status === "delivered" || status === "read" ? CheckCheckIcon : CheckIcon
|
|
162
|
+
return (
|
|
163
|
+
<article data-slot="chat-message" data-outgoing={outgoing || undefined} className={cn("group flex max-w-[88%] items-end gap-2 justify-self-start data-[outgoing=true]:justify-self-end data-[outgoing=true]:flex-row-reverse sm:max-w-[75%]", className)} {...props}>
|
|
164
|
+
{participant ? <Avatar size="xs" name={participant.name} src={participant.avatar} fallback={participant.fallback} /> : null}
|
|
165
|
+
<div className="min-w-0">
|
|
166
|
+
{participant && !outgoing ? <div className="mb-1 px-1 text-[11px] font-medium text-muted-foreground">{participant.name}</div> : null}
|
|
167
|
+
<div className={cn("rounded-lg rounded-bl-sm border bg-background px-3 py-2 text-sm leading-5 shadow-sm", outgoing && "rounded-bl-lg rounded-br-sm border-primary bg-primary text-primary-foreground")}>
|
|
168
|
+
{replyTo ? <div className={cn("mb-2 border-l-2 pl-2 text-xs opacity-75", outgoing ? "border-primary-foreground/60" : "border-primary")}>{replyTo}</div> : null}
|
|
169
|
+
<div className="whitespace-pre-wrap break-words">{children}</div>
|
|
170
|
+
{attachments?.length ? <div className="mt-2 grid gap-1.5">{attachments.map(({ key, ...attachment }) => <ChatAttachment key={key} attachmentKey={key} {...attachment} className={outgoing ? "border-primary-foreground/20 bg-primary-foreground/10 text-primary-foreground" : undefined} />)}</div> : null}
|
|
171
|
+
<div className={cn("mt-1 flex items-center justify-end gap-1 text-[10px] text-muted-foreground", outgoing && "text-primary-foreground/75")}><span>{time}</span>{status && status !== "failed" && status !== "sending" ? <StatusIcon className={cn("size-3", status === "read" && "text-sky-300")} /> : null}{status === "sending" ? <span>Sending...</span> : null}{status === "failed" ? <span className="font-medium text-destructive">Failed</span> : null}</div>
|
|
172
|
+
</div>
|
|
173
|
+
{reactions?.length ? <div className={cn("mt-1 flex flex-wrap gap-1", outgoing && "justify-end")}>{reactions.map((reaction) => <button key={reaction.key} type="button" aria-pressed={reaction.active} className="rounded-full border bg-background px-2 py-0.5 text-[11px] shadow-sm outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring aria-pressed:border-primary aria-pressed:bg-primary/10" onClick={() => onReaction?.(reaction.key)}>{reaction.label}{reaction.count ? ` ${reaction.count}` : ""}</button>)}</div> : null}
|
|
174
|
+
</div>
|
|
175
|
+
{actions ? <div className="self-center opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">{actions}</div> : null}
|
|
176
|
+
</article>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function ChatTypingIndicator({ participant, className, ...props }: React.ComponentProps<"div"> & { participant?: ChatParticipant }) {
|
|
181
|
+
return <div data-slot="chat-typing" className={cn("flex items-center gap-2 text-xs text-muted-foreground", className)} {...props}>{participant ? <Avatar size="xs" name={participant.name} src={participant.avatar} fallback={participant.fallback} /> : null}<span className="flex items-center gap-1 rounded-full border bg-background px-3 py-2"><span className="size-1.5 animate-bounce rounded-full bg-current [animation-delay:-0.2s]" /><span className="size-1.5 animate-bounce rounded-full bg-current [animation-delay:-0.1s]" /><span className="size-1.5 animate-bounce rounded-full bg-current" /></span></div>
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type ChatComposerProps = Omit<React.ComponentProps<"form">, "onSubmit"> & {
|
|
185
|
+
value?: string
|
|
186
|
+
defaultValue?: string
|
|
187
|
+
onValueChange?: (value: string) => void
|
|
188
|
+
onSend: (value: string) => void | Promise<void>
|
|
189
|
+
placeholder?: string
|
|
190
|
+
disabled?: boolean
|
|
191
|
+
sending?: boolean
|
|
192
|
+
attachments?: ChatAttachmentData[]
|
|
193
|
+
onRemoveAttachment?: (key: string) => void
|
|
194
|
+
onAttachmentClick?: () => void
|
|
195
|
+
onEmojiClick?: () => void
|
|
196
|
+
maxLength?: number
|
|
197
|
+
submitLabel?: string
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function ChatComposer({ value, defaultValue = "", onValueChange, onSend, placeholder = "Write a message...", disabled = false, sending = false, attachments, onRemoveAttachment, onAttachmentClick, onEmojiClick, maxLength, submitLabel = "Send message", className, ...props }: ChatComposerProps) {
|
|
201
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue)
|
|
202
|
+
const currentValue = value ?? internalValue
|
|
203
|
+
const setValue = (next: string) => { if (value === undefined) setInternalValue(next); onValueChange?.(next) }
|
|
204
|
+
const send = async () => {
|
|
205
|
+
const next = currentValue.trim()
|
|
206
|
+
if (!next || disabled || sending) return
|
|
207
|
+
await onSend(next)
|
|
208
|
+
setValue("")
|
|
209
|
+
}
|
|
210
|
+
return (
|
|
211
|
+
<form data-slot="chat-composer" className={cn("border-t bg-background p-3", className)} onSubmit={(event) => { event.preventDefault(); void send() }} {...props}>
|
|
212
|
+
{attachments?.length ? <div className="mb-2 grid gap-2 sm:grid-cols-2">{attachments.map(({ key, ...attachment }) => <ChatAttachment key={key} attachmentKey={key} {...attachment} onRemove={onRemoveAttachment ? () => onRemoveAttachment(key) : undefined} />)}</div> : null}
|
|
213
|
+
<div className="flex items-end gap-2 rounded-lg border bg-background p-1.5 shadow-sm transition-[border-color,box-shadow] focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/20">
|
|
214
|
+
{onAttachmentClick ? <Button type="button" size="icon-sm" variant="ghost" iconOnly aria-label="Attach file" onClick={onAttachmentClick}><PaperclipIcon /></Button> : null}
|
|
215
|
+
<Textarea value={currentValue} onValueChange={setValue} rows={1} maxLength={maxLength} disabled={disabled} placeholder={placeholder} aria-label={placeholder} className="max-h-36 min-h-9 resize-none border-0 bg-transparent px-2 py-2 font-normal shadow-none focus-visible:shadow-none" onKeyDown={(event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); void send() } }} />
|
|
216
|
+
{onEmojiClick ? <Button type="button" size="icon-sm" variant="ghost" iconOnly aria-label="Choose emoji" onClick={onEmojiClick}><SmileIcon /></Button> : null}
|
|
217
|
+
<Button type="submit" size="icon-sm" iconOnly aria-label={submitLabel} loading={sending} disabled={disabled || !currentValue.trim()}><SendIcon /></Button>
|
|
218
|
+
</div>
|
|
219
|
+
</form>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function ChatHeaderActions() {
|
|
224
|
+
return <><Button size="icon-sm" variant="ghost" iconOnly aria-label="Search conversation"><SearchIcon /></Button><Button size="icon-sm" variant="ghost" iconOnly aria-label="More conversation actions"><MoreHorizontalIcon /></Button></>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export {
|
|
228
|
+
ChatAttachment,
|
|
229
|
+
ChatComposer,
|
|
230
|
+
ChatHeader,
|
|
231
|
+
ChatHeaderActions,
|
|
232
|
+
ChatMessage,
|
|
233
|
+
ChatMessageList,
|
|
234
|
+
ChatShell,
|
|
235
|
+
ChatTypingIndicator,
|
|
236
|
+
ConversationList,
|
|
237
|
+
}
|