@jcdecor/ui 0.1.0 → 0.1.6

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/dist/chat.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","names":["classes","classes","classes","classes","classes","classes","DAY","toDate","value","startOfDay","date","dayLabel","diff","now","shortTimeLabel","buildThread","items","lastDay","messages","message","day","prev","index","next","joinsPrev","groupWindowMs","joinsNext","position","initialsName","name","formatBytes","bytes","Chat_module_default","statusLabel","StatusIcon","status","Loader","IconCheck","IconAlertCircle","IconChecks","ChatMessage","others","system","Box","isLast","isFirst","time","createdAt","own","showAvatar","author","Avatar","showAuthor","attachments","file","IconFile","children","Tooltip","typingLabel","names","TypingIndicator","label","ChatThread","viewport","useRef","content","nearBottom","mounted","userMap","useMemo","users","u","groupWindow","lastMessage","useEffect","el","autoScroll","currentUserId","typing","inner","observer","ScrollArea","y","empty","item","showAuthors","renderContent","ChatComposer","text","setText","useUncontrolled","files","setFiles","useState","canSend","disabled","send","onSend","Pill","f","i","leftSection","allowAttachments","FileButton","selected","ActionIcon","props","IconPaperclip","Textarea","event","placeholder","sendLabel","IconSend2","ConversationList","query","setQuery","filtered","q","conversations","c","searchable","TextInput","searchPlaceholder","IconSearch","activeId","onSelect","Indicator","Badge","ChatHeader","onBack","IconArrowLeft","avatar","avatarName","online","title","subtitle","actions","ChatLayout","toCss","v","height","sidebarWidth","style","sidebar","mobileView","header","composer"],"sources":["../src/chat/utils.ts","../src/chat/Chat.module.css","../src/chat/ChatMessage.tsx","../src/chat/TypingIndicator.tsx","../src/chat/ChatThread.tsx","../src/chat/ChatComposer.tsx","../src/chat/ConversationList.tsx","../src/chat/ChatLayout.tsx"],"sourcesContent":["import type { ChatMessageData } from './types';\n\nconst DAY = 24 * 60 * 60 * 1000;\n\nexport function toDate(value: Date | string | number) {\n return value instanceof Date ? value : new Date(value);\n}\n\nfunction startOfDay(date: Date) {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();\n}\n\n/** \"Hoje\", \"Ontem\", \"segunda-feira\" (últimos 7 dias) ou \"12 de set. de 2026\". */\nexport function dayLabel(value: Date | string | number, now = new Date()) {\n const date = toDate(value);\n const diff = Math.round((startOfDay(now) - startOfDay(date)) / DAY);\n if (diff === 0) return 'Hoje';\n if (diff === 1) return 'Ontem';\n if (diff > 1 && diff < 7) return new Intl.DateTimeFormat('pt-BR', { weekday: 'long' }).format(date);\n return new Intl.DateTimeFormat('pt-BR', { day: 'numeric', month: 'short', year: 'numeric' }).format(date);\n}\n\n/** Horário curto para listas: \"14:32\" hoje, \"Ontem\", ou \"12/09\". */\nexport function shortTimeLabel(value: Date | string | number, now = new Date()) {\n const date = toDate(value);\n const diff = Math.round((startOfDay(now) - startOfDay(date)) / DAY);\n if (diff === 0) return new Intl.DateTimeFormat('pt-BR', { hour: '2-digit', minute: '2-digit' }).format(date);\n if (diff === 1) return 'Ontem';\n return new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit' }).format(date);\n}\n\nexport type GroupPosition = 'single' | 'first' | 'middle' | 'last';\n\nexport type ThreadItem =\n | { type: 'separator'; key: string; label: string }\n | { type: 'message'; key: string; message: ChatMessageData; position: GroupPosition };\n\n/**\n * Agrupa mensagens consecutivas do mesmo autor (janela de `groupWindowMs`)\n * e insere separadores de data.\n */\nexport function buildThread(messages: ChatMessageData[], groupWindowMs = 5 * 60 * 1000, now = new Date()): ThreadItem[] {\n const items: ThreadItem[] = [];\n let lastDay: number | null = null;\n\n messages.forEach((message, index) => {\n const date = toDate(message.createdAt);\n const day = startOfDay(date);\n if (day !== lastDay) {\n items.push({ type: 'separator', key: `sep-${day}`, label: dayLabel(date, now) });\n lastDay = day;\n }\n\n const prev = messages[index - 1];\n const next = messages[index + 1];\n const joinsPrev =\n !!prev && !prev.system && !message.system && prev.authorId === message.authorId &&\n startOfDay(toDate(prev.createdAt)) === day &&\n date.getTime() - toDate(prev.createdAt).getTime() <= groupWindowMs;\n const joinsNext =\n !!next && !next.system && !message.system && next.authorId === message.authorId &&\n startOfDay(toDate(next.createdAt)) === day &&\n toDate(next.createdAt).getTime() - date.getTime() <= groupWindowMs;\n\n const position: GroupPosition = joinsPrev && joinsNext ? 'middle' : joinsPrev ? 'last' : joinsNext ? 'first' : 'single';\n items.push({ type: 'message', key: message.id, message, position });\n });\n\n return items;\n}\n\n/** Remove símbolos/separadores para as iniciais do Avatar: \"Bruno · Instalação\" → \"Bruno Instalação\" (BI). */\nexport function initialsName(name: string) {\n return name.replace(/[^\\p{L}\\s]/gu, ' ').replace(/\\s+/g, ' ').trim() || name;\n}\n\nexport function formatBytes(bytes: number) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toLocaleString('pt-BR', { maximumFractionDigits: 1 })} KB`;\n return `${(bytes / 1024 / 1024).toLocaleString('pt-BR', { maximumFractionDigits: 1 })} MB`;\n}\n","/* ── ChatMessage ── */\n.message {\n display: flex;\n gap: var(--sp-8);\n align-items: flex-end;\n max-width: 100%;\n}\n.message[data-own] {\n flex-direction: row-reverse;\n}\n.avatarSlot {\n width: 32px;\n flex-shrink: 0;\n}\n.bubbleColumn {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n max-width: min(75%, 560px);\n min-width: 0;\n}\n.message[data-own] .bubbleColumn {\n align-items: flex-end;\n}\n.author {\n font-size: var(--type-disclaimer);\n font-weight: 600;\n color: var(--ds-text-3);\n margin: 0 var(--sp-12) var(--sp-4);\n}\n.bubble {\n --bubble-radius: 16px;\n --bubble-tail: 4px;\n position: relative;\n padding: var(--sp-8) var(--sp-12);\n border-radius: var(--bubble-radius);\n background: var(--ds-surface);\n color: var(--ds-text);\n border: 1px solid var(--ds-border-soft);\n font-size: var(--type-subheadline-sm);\n line-height: 1.45;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n box-shadow: var(--ds-shadow-sm);\n}\n.message[data-own] .bubble {\n /* azul fixo (#2663EB): branco 5,2:1 nos dois temas */\n background: var(--dc-horizon);\n color: #fff;\n border-color: transparent;\n}\n/* cantos agrupados (estilo mensageiro) */\n.message:not([data-own])[data-position='first'] .bubble { border-bottom-left-radius: var(--bubble-tail); }\n.message:not([data-own])[data-position='middle'] .bubble { border-top-left-radius: var(--bubble-tail); border-bottom-left-radius: var(--bubble-tail); }\n.message:not([data-own])[data-position='last'] .bubble { border-top-left-radius: var(--bubble-tail); }\n.message[data-own][data-position='first'] .bubble { border-bottom-right-radius: var(--bubble-tail); }\n.message[data-own][data-position='middle'] .bubble { border-top-right-radius: var(--bubble-tail); border-bottom-right-radius: var(--bubble-tail); }\n.message[data-own][data-position='last'] .bubble { border-top-right-radius: var(--bubble-tail); }\n.message[data-status='error'] .bubble {\n background: var(--ds-error-bg);\n color: var(--ds-error);\n border-color: var(--ds-error);\n}\n.meta {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n color: var(--ds-text-3);\n margin: 2px var(--sp-8) 0;\n font-variant-numeric: tabular-nums;\n}\n.meta[data-read] .status {\n color: var(--ds-primary);\n}\n.attachments {\n display: flex;\n flex-wrap: wrap;\n gap: var(--sp-4);\n margin-bottom: var(--sp-4);\n}\n.message[data-own] .attachments {\n justify-content: flex-end;\n}\n.imageAttachment {\n display: block;\n max-width: 240px;\n max-height: 180px;\n border-radius: 12px;\n object-fit: cover;\n border: 1px solid var(--ds-border-soft);\n}\n.fileAttachment {\n display: inline-flex;\n align-items: center;\n gap: var(--sp-8);\n padding: var(--sp-8) var(--sp-12);\n border-radius: 12px;\n background: var(--ds-surface-2);\n border: 1px solid var(--ds-border-soft);\n color: var(--ds-text);\n text-decoration: none;\n font-size: var(--type-disclaimer);\n max-width: 260px;\n}\n.fileName {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fileSize {\n flex-shrink: 0;\n white-space: nowrap;\n color: var(--ds-text-3);\n}\n.fileAttachment:hover {\n border-color: var(--ds-primary);\n}\n.system {\n align-self: center;\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n background: var(--ds-surface-2);\n padding: 4px var(--sp-12);\n border-radius: 99px;\n text-align: center;\n}\n\n/* ── ChatThread ── */\n.thread {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: var(--sp-16);\n}\n.thread > [data-group-start] {\n margin-top: var(--sp-12);\n}\n.separator {\n display: flex;\n align-items: center;\n gap: var(--sp-12);\n margin: var(--sp-16) 0 var(--sp-8);\n font-size: var(--type-disclaimer);\n font-weight: 600;\n color: var(--ds-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n.separator::before,\n.separator::after {\n content: '';\n flex: 1;\n height: 1px;\n background: var(--ds-border-soft);\n}\n\n/* ── TypingIndicator ── */\n.typing {\n display: inline-flex;\n align-items: center;\n gap: var(--sp-8);\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n}\n.dots {\n display: inline-flex;\n gap: 3px;\n padding: 10px 12px;\n border-radius: 16px;\n background: var(--ds-surface);\n border: 1px solid var(--ds-border-soft);\n}\n.dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--ds-text-3);\n animation: jc-typing 1.2s infinite ease-in-out;\n}\n.dot:nth-child(2) { animation-delay: 0.15s; }\n.dot:nth-child(3) { animation-delay: 0.3s; }\n@keyframes jc-typing {\n 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }\n 30% { transform: translateY(-4px); opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) {\n .dot { animation: none; opacity: 0.7; }\n}\n\n/* ── ChatComposer ── */\n.composer {\n display: flex;\n align-items: flex-end;\n gap: var(--sp-8);\n padding: var(--sp-12);\n border-top: 1px solid var(--ds-border-soft);\n background: var(--ds-surface);\n}\n.composerInput {\n flex: 1;\n}\n.composerFiles {\n display: flex;\n flex-wrap: wrap;\n gap: var(--sp-4);\n padding: var(--sp-8) var(--sp-12) 0;\n background: var(--ds-surface);\n border-top: 1px solid var(--ds-border-soft);\n}\n\n/* ── ConversationList ── */\n.conversations {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n}\n.conversationSearch {\n padding: var(--sp-12);\n border-bottom: 1px solid var(--ds-border-soft);\n}\n.conversation {\n all: unset;\n box-sizing: border-box;\n display: flex;\n gap: var(--sp-12);\n align-items: center;\n width: 100%;\n padding: var(--sp-12) var(--sp-16);\n cursor: pointer;\n border-bottom: 1px solid var(--ds-border-soft);\n transition: background 0.15s;\n}\n.conversation:hover {\n background: var(--ds-surface-2);\n}\n.conversation[data-active] {\n background: var(--ds-primary-soft);\n box-shadow: inset 3px 0 0 var(--ds-primary);\n}\n.conversation:focus-visible {\n outline: 2px solid var(--ds-primary);\n outline-offset: -2px;\n}\n.conversationBody {\n flex: 1;\n min-width: 0;\n}\n.conversationTop {\n display: flex;\n justify-content: space-between;\n gap: var(--sp-8);\n align-items: baseline;\n}\n.conversationName {\n font-weight: 600;\n font-size: var(--type-subheadline-sm);\n color: var(--ds-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.conversation[data-unread] .conversationName {\n font-weight: 700;\n}\n.conversationTime {\n font-size: 11px;\n color: var(--ds-text-3);\n flex-shrink: 0;\n}\n.conversationBottom {\n display: flex;\n justify-content: space-between;\n gap: var(--sp-8);\n align-items: center;\n margin-top: 2px;\n}\n.conversationPreview {\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.conversation[data-unread] .conversationPreview {\n color: var(--ds-text-2);\n font-weight: 500;\n}\n\n/* ── ChatHeader / ChatLayout ── */\n.header {\n display: flex;\n align-items: center;\n gap: var(--sp-12);\n padding: var(--sp-12) var(--sp-16);\n border-bottom: 1px solid var(--ds-border-soft);\n background: var(--ds-surface);\n min-height: 64px;\n box-sizing: border-box;\n}\n.headerBody {\n flex: 1;\n min-width: 0;\n}\n.headerTitle {\n font-weight: 600;\n color: var(--ds-text);\n}\n.headerSubtitle {\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n}\n.layoutContainer {\n container-type: inline-size;\n container-name: jc-chat;\n}\n.layout {\n display: grid;\n grid-template-columns: var(--chat-sidebar-width, 320px) 1fr;\n height: var(--chat-height, 600px);\n background: var(--ds-bg);\n border: 1px solid var(--ds-border-soft);\n border-radius: var(--ds-radius);\n overflow: hidden;\n}\n.layout[data-single] {\n grid-template-columns: 1fr;\n}\n.layoutSidebar {\n background: var(--ds-surface);\n border-right: 1px solid var(--ds-border-soft);\n min-height: 0;\n overflow: hidden;\n}\n.layoutMain {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n}\n.layoutMessages {\n flex: 1;\n min-height: 0;\n}\n/* Abaixo de 640px de largura útil: uma coluna (lista OU conversa) */\n@container jc-chat (max-width: 640px) {\n .layout {\n grid-template-columns: 1fr;\n }\n .layout[data-mobile-view='thread'] .layoutSidebar,\n .layout[data-mobile-view='list'] .layoutMain {\n display: none;\n }\n}\n@container jc-chat (min-width: 640.02px) {\n .backButton {\n display: none;\n }\n}\n/* fora de um ChatLayout o botão voltar segue a tela */\n@media (min-width: 48em) {\n :not(.layoutContainer *) > .backButton {\n display: none;\n }\n}\n","import { Avatar, Box, Loader, Tooltip, type BoxProps } from '@mantine/core';\nimport { IconAlertCircle, IconCheck, IconChecks, IconFile } from '@tabler/icons-react';\nimport { formatBytes, initialsName, toDate, type GroupPosition } from './utils';\nimport type { ChatAttachment, ChatMessageStatus, ChatUser } from './types';\nimport classes from './Chat.module.css';\n\nexport interface ChatMessageProps extends BoxProps, Omit<React.ComponentProps<'div'>, keyof BoxProps> {\n /** Mensagem do usuário atual (alinhada à direita, bolha azul) */\n own?: boolean;\n author?: ChatUser;\n /** Exibe o nome do autor acima da bolha (útil em grupos) */\n showAuthor?: boolean;\n /** Exibe avatar ao lado da bolha (só na última mensagem do grupo) @default true */\n showAvatar?: boolean;\n createdAt?: Date | string | number;\n status?: ChatMessageStatus;\n attachments?: ChatAttachment[];\n /** Posição dentro de um grupo de mensagens consecutivas @default 'single' */\n position?: GroupPosition;\n /** Mensagem de sistema (centralizada) */\n system?: boolean;\n children?: React.ReactNode;\n}\n\nconst statusLabel: Record<ChatMessageStatus, string> = {\n sending: 'Enviando…',\n sent: 'Enviada',\n delivered: 'Entregue',\n read: 'Lida',\n error: 'Falha no envio',\n};\n\nfunction StatusIcon({ status }: { status: ChatMessageStatus }) {\n if (status === 'sending') return <Loader size={10} color=\"gray\" />;\n if (status === 'sent') return <IconCheck size={14} />;\n if (status === 'error') return <IconAlertCircle size={14} color=\"var(--ds-error)\" />;\n return <IconChecks size={14} />;\n}\n\n/** Bolha de mensagem de chat. */\nexport function ChatMessage({\n own,\n author,\n showAuthor,\n showAvatar = true,\n createdAt,\n status,\n attachments,\n position = 'single',\n system,\n children,\n ...others\n}: ChatMessageProps) {\n if (system) {\n return (\n <Box className={classes.system} role=\"status\" {...others}>\n {children}\n </Box>\n );\n }\n\n const isLast = position === 'single' || position === 'last';\n const isFirst = position === 'single' || position === 'first';\n const time = createdAt\n ? new Intl.DateTimeFormat('pt-BR', { hour: '2-digit', minute: '2-digit' }).format(toDate(createdAt))\n : null;\n\n return (\n <Box className={classes.message} mod={{ own, position, status }} {...others}>\n {!own && (\n <div className={classes.avatarSlot}>\n {showAvatar && isLast && author && (\n <Avatar src={author.avatar} name={initialsName(author.name)} color={author.color ?? 'initials'} size={32} alt={author.name} />\n )}\n </div>\n )}\n <div className={classes.bubbleColumn}>\n {showAuthor && isFirst && author && !own && <div className={classes.author}>{author.name}</div>}\n {attachments && attachments.length > 0 && (\n <div className={classes.attachments}>\n {attachments.map((file, index) =>\n file.type === 'image' && file.url ? (\n <a key={index} href={file.url} target=\"_blank\" rel=\"noreferrer\">\n <img className={classes.imageAttachment} src={file.url} alt={file.name} />\n </a>\n ) : (\n <a key={index} className={classes.fileAttachment} href={file.url} target=\"_blank\" rel=\"noreferrer\" download={file.name}>\n <IconFile size={18} />\n <span className={classes.fileName}>{file.name}</span>\n {file.size !== undefined && <span className={classes.fileSize}>{formatBytes(file.size)}</span>}\n </a>\n ),\n )}\n </div>\n )}\n {children !== undefined && children !== null && children !== '' && <div className={classes.bubble}>{children}</div>}\n {isLast && (time || (own && status)) && (\n <div className={classes.meta} data-read={status === 'read' || undefined}>\n {time && createdAt && <time dateTime={toDate(createdAt).toISOString()}>{time}</time>}\n {own && status && (\n <Tooltip label={statusLabel[status]} withArrow>\n <span className={classes.status} aria-label={statusLabel[status]} style={{ display: 'inline-flex' }}>\n <StatusIcon status={status} />\n </span>\n </Tooltip>\n )}\n </div>\n )}\n </div>\n </Box>\n );\n}\nChatMessage.displayName = '@jcdecor/ui/ChatMessage';\n","import { Box, type BoxProps } from '@mantine/core';\nimport classes from './Chat.module.css';\n\nexport interface TypingIndicatorProps extends BoxProps {\n /** Nomes de quem está digitando. Vazio = só os pontinhos. */\n names?: string[];\n}\n\nfunction typingLabel(names: string[]) {\n if (names.length === 0) return '';\n if (names.length === 1) return `${names[0]} está digitando…`;\n if (names.length === 2) return `${names[0]} e ${names[1]} estão digitando…`;\n return `${names[0]} e mais ${names.length - 1} estão digitando…`;\n}\n\n/** Indicador animado de \"digitando…\". */\nexport function TypingIndicator({ names = [], ...others }: TypingIndicatorProps) {\n const label = typingLabel(names);\n return (\n <Box className={classes.typing} role=\"status\" aria-live=\"polite\" aria-label={label || 'Digitando'} {...others}>\n <span className={classes.dots} aria-hidden>\n <span className={classes.dot} />\n <span className={classes.dot} />\n <span className={classes.dot} />\n </span>\n {label && <span>{label}</span>}\n </Box>\n );\n}\nTypingIndicator.displayName = '@jcdecor/ui/TypingIndicator';\n","import { useEffect, useMemo, useRef } from 'react';\nimport { Box, ScrollArea, type ScrollAreaProps } from '@mantine/core';\nimport { ChatMessage } from './ChatMessage';\nimport { TypingIndicator } from './TypingIndicator';\nimport { buildThread } from './utils';\nimport type { ChatMessageData, ChatUser } from './types';\nimport classes from './Chat.module.css';\n\nexport interface ChatThreadProps extends Omit<ScrollAreaProps, 'children'> {\n messages: ChatMessageData[];\n /** Id do usuário atual — suas mensagens ficam à direita */\n currentUserId: string;\n users?: ChatUser[] | Record<string, ChatUser>;\n /** Nomes de quem está digitando agora */\n typing?: string[];\n /** Mostra nome do autor no início de cada grupo (conversas em grupo) */\n showAuthors?: boolean;\n /** Rola para a última mensagem quando chegam novas (se o usuário estiver perto do fim) @default true */\n autoScroll?: boolean;\n /** Janela (ms) para agrupar mensagens consecutivas do mesmo autor @default 300000 */\n groupWindow?: number;\n /** Conteúdo exibido quando não há mensagens */\n empty?: React.ReactNode;\n /** Renderização customizada do conteúdo da bolha */\n renderContent?: (message: ChatMessageData) => React.ReactNode;\n}\n\n/** Lista rolável de mensagens com separadores de data, agrupamento e auto-scroll. */\nexport function ChatThread({\n messages,\n currentUserId,\n users = [],\n typing,\n showAuthors,\n autoScroll = true,\n groupWindow,\n empty,\n renderContent,\n h = '100%',\n ...others\n}: ChatThreadProps) {\n const viewport = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const nearBottom = useRef(true);\n const mounted = useRef(false);\n\n const userMap = useMemo<Record<string, ChatUser>>(\n () => (Array.isArray(users) ? Object.fromEntries(users.map((u) => [u.id, u])) : users),\n [users],\n );\n const items = useMemo(() => buildThread(messages, groupWindow), [messages, groupWindow]);\n\n const lastMessage = messages[messages.length - 1];\n useEffect(() => {\n const el = viewport.current;\n if (!el || !autoScroll) return;\n if (nearBottom.current || lastMessage?.authorId === currentUserId) {\n // Primeira renderização: pula direto para o fim; depois, rolagem suave.\n el.scrollTo({ top: el.scrollHeight, behavior: mounted.current ? 'smooth' : 'auto' });\n }\n mounted.current = true;\n }, [messages.length, lastMessage?.id, typing?.length, autoScroll, currentUserId]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Conteúdo que cresce depois (imagens, fontes) mantém o fim visível se o usuário já estava lá.\n useEffect(() => {\n const el = viewport.current;\n const inner = content.current;\n if (!el || !inner || !autoScroll || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => {\n if (nearBottom.current) el.scrollTo({ top: el.scrollHeight });\n });\n observer.observe(inner);\n return () => observer.disconnect();\n }, [autoScroll]);\n\n return (\n <ScrollArea\n h={h}\n viewportRef={viewport}\n onScrollPositionChange={({ y }) => {\n const el = viewport.current;\n if (el) nearBottom.current = el.scrollHeight - el.clientHeight - y < 80;\n }}\n {...others}\n >\n <Box ref={content} className={classes.thread} role=\"log\" aria-live=\"polite\" aria-relevant=\"additions\">\n {messages.length === 0 && empty}\n {items.map((item) => {\n if (item.type === 'separator') {\n return (\n <div key={item.key} className={classes.separator} role=\"separator\">\n {item.label}\n </div>\n );\n }\n const { message, position } = item;\n const own = message.authorId === currentUserId;\n return (\n <ChatMessage\n key={item.key}\n own={own}\n system={message.system}\n author={userMap[message.authorId]}\n showAuthor={showAuthors}\n createdAt={message.createdAt}\n status={own ? message.status : undefined}\n attachments={message.attachments}\n position={position}\n data-group-start={position === 'single' || position === 'first' ? true : undefined}\n >\n {renderContent ? renderContent(message) : message.text}\n </ChatMessage>\n );\n })}\n {typing && typing.length > 0 && <TypingIndicator names={typing} mt=\"sm\" ml={40} />}\n </Box>\n </ScrollArea>\n );\n}\nChatThread.displayName = '@jcdecor/ui/ChatThread';\n","import { useState } from 'react';\nimport { ActionIcon, Box, FileButton, Pill, Textarea, Tooltip, type BoxProps } from '@mantine/core';\nimport { useUncontrolled } from '@mantine/hooks';\nimport { IconPaperclip, IconSend2 } from '@tabler/icons-react';\nimport classes from './Chat.module.css';\n\nexport interface ChatComposerSendPayload {\n text: string;\n files: File[];\n}\n\nexport interface ChatComposerProps extends Omit<BoxProps, 'onSubmit'> {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /** Chamado ao enviar (Enter ou botão). O campo é limpo em seguida. */\n onSend: (payload: ChatComposerSendPayload) => void;\n placeholder?: string;\n disabled?: boolean;\n /** Habilita anexos; `accept` segue o atributo HTML (ex.: \"image/*,.pdf\") */\n allowAttachments?: boolean;\n accept?: string;\n /** Máximo de linhas antes de rolar @default 5 */\n maxRows?: number;\n /** Elementos extras à esquerda (ex.: emoji picker, respostas rápidas) */\n leftSection?: React.ReactNode;\n sendLabel?: string;\n}\n\n/** Campo de mensagem: Enter envia, Shift+Enter quebra linha, anexos opcionais. */\nexport function ChatComposer({\n value,\n defaultValue,\n onChange,\n onSend,\n placeholder = 'Digite uma mensagem…',\n disabled,\n allowAttachments,\n accept,\n maxRows = 5,\n leftSection,\n sendLabel = 'Enviar',\n ...others\n}: ChatComposerProps) {\n const [text, setText] = useUncontrolled({ value, defaultValue, finalValue: '', onChange });\n const [files, setFiles] = useState<File[]>([]);\n const canSend = !disabled && (text.trim().length > 0 || files.length > 0);\n\n const send = () => {\n if (!canSend) return;\n onSend({ text: text.trim(), files });\n setText('');\n setFiles([]);\n };\n\n return (\n <Box {...others}>\n {files.length > 0 && (\n <div className={classes.composerFiles}>\n {files.map((file, index) => (\n <Pill key={`${file.name}-${index}`} withRemoveButton onRemove={() => setFiles((f) => f.filter((_, i) => i !== index))}>\n {file.name}\n </Pill>\n ))}\n </div>\n )}\n <div className={classes.composer}>\n {leftSection}\n {allowAttachments && (\n <FileButton multiple accept={accept} onChange={(selected) => setFiles((f) => [...f, ...(selected ?? [])])} disabled={disabled}>\n {(props) => (\n <Tooltip label=\"Anexar arquivo\">\n <ActionIcon {...props} size=\"lg\" variant=\"subtle\" aria-label=\"Anexar arquivo\">\n <IconPaperclip size={20} />\n </ActionIcon>\n </Tooltip>\n )}\n </FileButton>\n )}\n <Textarea\n className={classes.composerInput}\n value={text}\n onChange={(event) => setText(event.currentTarget.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) {\n event.preventDefault();\n send();\n }\n }}\n placeholder={placeholder}\n disabled={disabled}\n autosize\n minRows={1}\n maxRows={maxRows}\n radius=\"lg\"\n aria-label={placeholder}\n />\n <ActionIcon size={44} radius=\"xl\" variant=\"filled\" onClick={send} disabled={!canSend} aria-label={sendLabel}>\n <IconSend2 size={20} />\n </ActionIcon>\n </div>\n </Box>\n );\n}\nChatComposer.displayName = '@jcdecor/ui/ChatComposer';\n","import { useMemo, useState } from 'react';\nimport { Avatar, Badge, Box, Indicator, ScrollArea, TextInput, type BoxProps } from '@mantine/core';\nimport { IconSearch } from '@tabler/icons-react';\nimport { initialsName, shortTimeLabel } from './utils';\nimport type { Conversation } from './types';\nimport classes from './Chat.module.css';\n\nexport interface ConversationListProps extends Omit<BoxProps, 'onSelect'> {\n conversations: Conversation[];\n activeId?: string | null;\n onSelect?: (conversation: Conversation) => void;\n /** Exibe campo de busca @default true */\n searchable?: boolean;\n searchPlaceholder?: string;\n empty?: React.ReactNode;\n}\n\n/** Lista de conversas com busca, não lidas, presença e última mensagem. */\nexport function ConversationList({\n conversations,\n activeId,\n onSelect,\n searchable = true,\n searchPlaceholder = 'Buscar conversas',\n empty,\n ...others\n}: ConversationListProps) {\n const [query, setQuery] = useState('');\n const filtered = useMemo(() => {\n const q = query.trim().toLocaleLowerCase('pt-BR');\n if (!q) return conversations;\n return conversations.filter(\n (c) => c.name.toLocaleLowerCase('pt-BR').includes(q) || c.lastMessage?.toLocaleLowerCase('pt-BR').includes(q),\n );\n }, [conversations, query]);\n\n return (\n <Box className={classes.conversations} {...others}>\n {searchable && (\n <div className={classes.conversationSearch}>\n <TextInput\n size=\"sm\"\n placeholder={searchPlaceholder}\n leftSection={<IconSearch size={16} />}\n value={query}\n onChange={(e) => setQuery(e.currentTarget.value)}\n aria-label={searchPlaceholder}\n />\n </div>\n )}\n <ScrollArea style={{ flex: 1 }} type=\"auto\">\n <div role=\"list\">\n {filtered.length === 0 && (\n <Box p=\"lg\" ta=\"center\" c=\"var(--ds-text-3)\" fz=\"sm\">\n {empty ?? 'Nenhuma conversa encontrada'}\n </Box>\n )}\n {filtered.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n role=\"listitem\"\n className={classes.conversation}\n data-active={c.id === activeId || undefined}\n data-unread={(c.unread ?? 0) > 0 || undefined}\n aria-current={c.id === activeId ? 'true' : undefined}\n onClick={() => onSelect?.(c)}\n >\n <Indicator color=\"evergreen\" position=\"bottom-end\" offset={5} size={10} withBorder disabled={!c.online}>\n <Avatar src={c.avatar} name={initialsName(c.name)} color={c.color ?? 'initials'} size={40} />\n </Indicator>\n <div className={classes.conversationBody}>\n <div className={classes.conversationTop}>\n <span className={classes.conversationName}>{c.name}</span>\n {c.lastMessageAt !== undefined && <span className={classes.conversationTime}>{shortTimeLabel(c.lastMessageAt)}</span>}\n </div>\n <div className={classes.conversationBottom}>\n <span className={classes.conversationPreview}>{c.lastMessage}</span>\n {(c.unread ?? 0) > 0 && (\n <Badge size=\"sm\" variant=\"filled\" circle={(c.unread ?? 0) < 10} aria-label={`${c.unread} não lidas`}>\n {c.unread! > 99 ? '99+' : c.unread}\n </Badge>\n )}\n </div>\n {c.tag && (\n <Badge size=\"xs\" color=\"obsidian\" mt={6}>\n {c.tag}\n </Badge>\n )}\n </div>\n </button>\n ))}\n </div>\n </ScrollArea>\n </Box>\n );\n}\nConversationList.displayName = '@jcdecor/ui/ConversationList';\n","import { ActionIcon, Avatar, Box, Indicator, type BoxProps } from '@mantine/core';\nimport { IconArrowLeft } from '@tabler/icons-react';\nimport { initialsName } from './utils';\nimport classes from './Chat.module.css';\n\nexport interface ChatHeaderProps extends BoxProps {\n title: React.ReactNode;\n subtitle?: React.ReactNode;\n avatar?: string;\n /** Nome para iniciais do avatar */\n avatarName?: string;\n online?: boolean;\n actions?: React.ReactNode;\n /** Exibe botão voltar (mobile) */\n onBack?: () => void;\n}\n\n/** Cabeçalho da conversa: avatar, nome, status e ações. */\nexport function ChatHeader({ title, subtitle, avatar, avatarName, online, actions, onBack, ...others }: ChatHeaderProps) {\n return (\n <Box className={classes.header} {...others}>\n {onBack && (\n <ActionIcon className={classes.backButton} variant=\"subtle\" onClick={onBack} aria-label=\"Voltar\">\n <IconArrowLeft size={20} />\n </ActionIcon>\n )}\n {(avatar || avatarName) && (\n <Indicator color=\"evergreen\" position=\"bottom-end\" offset={5} size={10} withBorder disabled={!online}>\n <Avatar src={avatar} name={avatarName ? initialsName(avatarName) : undefined} color=\"initials\" size={40} />\n </Indicator>\n )}\n <div className={classes.headerBody}>\n <div className={classes.headerTitle}>{title}</div>\n {subtitle && <div className={classes.headerSubtitle}>{subtitle}</div>}\n </div>\n {actions}\n </Box>\n );\n}\nChatHeader.displayName = '@jcdecor/ui/ChatHeader';\n\nexport interface ChatLayoutProps extends BoxProps {\n /** Lista de conversas (coluna esquerda) — omita para um chat único */\n sidebar?: React.ReactNode;\n header?: React.ReactNode;\n /** Normalmente um <ChatThread /> */\n children: React.ReactNode;\n /** Normalmente um <ChatComposer /> */\n composer?: React.ReactNode;\n /** Altura total @default 600 */\n height?: number | string;\n sidebarWidth?: number | string;\n /** Em telas pequenas mostra só a lista ou só a conversa @default 'thread' */\n mobileView?: 'list' | 'thread';\n}\n\n/** Layout de chat: conversas + cabeçalho + mensagens + composer (responsivo). */\nexport function ChatLayout({\n sidebar,\n header,\n children,\n composer,\n height = 600,\n sidebarWidth = 320,\n mobileView = 'thread',\n style,\n ...others\n}: ChatLayoutProps) {\n const toCss = (v: number | string) => (typeof v === 'number' ? `${v}px` : v);\n // O wrapper é o contêiner das container queries: o layout se adapta ao espaço disponível, não à tela.\n return (\n <Box\n className={classes.layoutContainer}\n style={[\n {\n ['--chat-height' as string]: toCss(height),\n ['--chat-sidebar-width' as string]: toCss(sidebarWidth),\n },\n style,\n ]}\n {...others}\n >\n <div\n className={classes.layout}\n data-mobile-view={sidebar ? mobileView : undefined}\n data-single={sidebar ? undefined : true}\n >\n {sidebar && <aside className={classes.layoutSidebar}>{sidebar}</aside>}\n <section className={classes.layoutMain}>\n {header}\n <div className={classes.layoutMessages}>{children}</div>\n {composer}\n </section>\n </div>\n </Box>\n );\n}\nChatLayout.displayName = '@jcdecor/ui/ChatLayout';\n"],"mappings":"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"}
1
+ {"version":3,"file":"chat.js","names":["classes","classes","classes","classes","classes","classes","DAY","toDate","value","startOfDay","date","dayLabel","diff","now","shortTimeLabel","buildThread","items","lastDay","messages","message","day","prev","index","next","joinsPrev","groupWindowMs","joinsNext","position","initialsName","name","formatBytes","bytes","Chat_module_default","statusLabel","StatusIcon","status","Loader","IconCheck","IconAlertCircle","IconChecks","ChatMessage","others","system","Box","isLast","isFirst","time","createdAt","variant","own","showAvatar","avatar","author","Avatar","showAuthor","attachments","file","IconFile","children","Tooltip","typingLabel","names","TypingIndicator","label","ChatThread","viewport","useRef","content","nearBottom","mounted","userMap","useMemo","users","u","groupWindow","lastMessage","useEffect","el","autoScroll","currentUserId","typing","inner","observer","ScrollArea","y","empty","item","renderAvatar","showAuthors","renderContent","footer","ChatComposer","text","setText","useUncontrolled","files","setFiles","useState","canSend","disabled","sendDisabled","loading","send","onSend","Pill","f","i","leftSection","allowAttachments","FileButton","selected","ActionIcon","props","IconPaperclip","Textarea","event","placeholder","rightSection","onStop","stopLabel","IconPlayerStopFilled","sendLabel","IconSend2","ConversationList","query","setQuery","filtered","q","conversations","c","searchable","TextInput","searchPlaceholder","IconSearch","activeId","onSelect","Indicator","renderIcon","Badge","renderActions","ChatHeader","onBack","IconArrowLeft","avatarName","online","title","subtitle","actions","ChatLayout","toCss","v","height","sidebarWidth","style","sidebar","mobileView","header","composer"],"sources":["../src/chat/utils.ts","../src/chat/Chat.module.css","../src/chat/ChatMessage.tsx","../src/chat/TypingIndicator.tsx","../src/chat/ChatThread.tsx","../src/chat/ChatComposer.tsx","../src/chat/ConversationList.tsx","../src/chat/ChatLayout.tsx"],"sourcesContent":["import type { ChatMessageData } from './types';\n\nconst DAY = 24 * 60 * 60 * 1000;\n\nexport function toDate(value: Date | string | number) {\n return value instanceof Date ? value : new Date(value);\n}\n\nfunction startOfDay(date: Date) {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();\n}\n\n/** \"Hoje\", \"Ontem\", \"segunda-feira\" (últimos 7 dias) ou \"12 de set. de 2026\". */\nexport function dayLabel(value: Date | string | number, now = new Date()) {\n const date = toDate(value);\n const diff = Math.round((startOfDay(now) - startOfDay(date)) / DAY);\n if (diff === 0) return 'Hoje';\n if (diff === 1) return 'Ontem';\n if (diff > 1 && diff < 7) return new Intl.DateTimeFormat('pt-BR', { weekday: 'long' }).format(date);\n return new Intl.DateTimeFormat('pt-BR', { day: 'numeric', month: 'short', year: 'numeric' }).format(date);\n}\n\n/** Horário curto para listas: \"14:32\" hoje, \"Ontem\", ou \"12/09\". */\nexport function shortTimeLabel(value: Date | string | number, now = new Date()) {\n const date = toDate(value);\n const diff = Math.round((startOfDay(now) - startOfDay(date)) / DAY);\n if (diff === 0) return new Intl.DateTimeFormat('pt-BR', { hour: '2-digit', minute: '2-digit' }).format(date);\n if (diff === 1) return 'Ontem';\n return new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit' }).format(date);\n}\n\nexport type GroupPosition = 'single' | 'first' | 'middle' | 'last';\n\nexport type ThreadItem =\n | { type: 'separator'; key: string; label: string }\n | { type: 'message'; key: string; message: ChatMessageData; position: GroupPosition };\n\n/**\n * Agrupa mensagens consecutivas do mesmo autor (janela de `groupWindowMs`)\n * e insere separadores de data.\n */\nexport function buildThread(messages: ChatMessageData[], groupWindowMs = 5 * 60 * 1000, now = new Date()): ThreadItem[] {\n const items: ThreadItem[] = [];\n let lastDay: number | null = null;\n\n messages.forEach((message, index) => {\n const date = toDate(message.createdAt);\n const day = startOfDay(date);\n if (day !== lastDay) {\n items.push({ type: 'separator', key: `sep-${day}`, label: dayLabel(date, now) });\n lastDay = day;\n }\n\n const prev = messages[index - 1];\n const next = messages[index + 1];\n const joinsPrev =\n !!prev && !prev.system && !message.system && prev.authorId === message.authorId &&\n startOfDay(toDate(prev.createdAt)) === day &&\n date.getTime() - toDate(prev.createdAt).getTime() <= groupWindowMs;\n const joinsNext =\n !!next && !next.system && !message.system && next.authorId === message.authorId &&\n startOfDay(toDate(next.createdAt)) === day &&\n toDate(next.createdAt).getTime() - date.getTime() <= groupWindowMs;\n\n const position: GroupPosition = joinsPrev && joinsNext ? 'middle' : joinsPrev ? 'last' : joinsNext ? 'first' : 'single';\n items.push({ type: 'message', key: message.id, message, position });\n });\n\n return items;\n}\n\n/** Remove símbolos/separadores para as iniciais do Avatar: \"Bruno · Instalação\" → \"Bruno Instalação\" (BI). */\nexport function initialsName(name: string) {\n return name.replace(/[^\\p{L}\\s]/gu, ' ').replace(/\\s+/g, ' ').trim() || name;\n}\n\nexport function formatBytes(bytes: number) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toLocaleString('pt-BR', { maximumFractionDigits: 1 })} KB`;\n return `${(bytes / 1024 / 1024).toLocaleString('pt-BR', { maximumFractionDigits: 1 })} MB`;\n}\n","/* ── ChatMessage ── */\n.message {\n display: flex;\n gap: var(--sp-8);\n align-items: flex-end;\n max-width: 100%;\n}\n.message[data-own] {\n flex-direction: row-reverse;\n}\n.avatarSlot {\n width: 32px;\n flex-shrink: 0;\n}\n.bubbleColumn {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n max-width: min(75%, 560px);\n min-width: 0;\n}\n.message[data-own] .bubbleColumn {\n align-items: flex-end;\n}\n.author {\n font-size: var(--type-disclaimer);\n font-weight: 600;\n color: var(--ds-text-3);\n margin: 0 var(--sp-12) var(--sp-4);\n}\n.bubble {\n --bubble-radius: 16px;\n --bubble-tail: 4px;\n position: relative;\n padding: var(--sp-8) var(--sp-12);\n border-radius: var(--bubble-radius);\n background: var(--ds-surface);\n color: var(--ds-text);\n border: 1px solid var(--ds-border-soft);\n font-size: var(--type-subheadline-sm);\n line-height: 1.45;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n box-shadow: var(--ds-shadow-sm);\n}\n.message[data-own] .bubble {\n /* azul fixo (#2663EB): branco 5,2:1 nos dois temas */\n background: var(--dc-horizon);\n color: #fff;\n border-color: transparent;\n}\n/* cantos agrupados (estilo mensageiro) */\n.message:not([data-own])[data-position='first'] .bubble { border-bottom-left-radius: var(--bubble-tail); }\n.message:not([data-own])[data-position='middle'] .bubble { border-top-left-radius: var(--bubble-tail); border-bottom-left-radius: var(--bubble-tail); }\n.message:not([data-own])[data-position='last'] .bubble { border-top-left-radius: var(--bubble-tail); }\n.message[data-own][data-position='first'] .bubble { border-bottom-right-radius: var(--bubble-tail); }\n.message[data-own][data-position='middle'] .bubble { border-top-right-radius: var(--bubble-tail); border-bottom-right-radius: var(--bubble-tail); }\n.message[data-own][data-position='last'] .bubble { border-top-right-radius: var(--bubble-tail); }\n/* variante plain: sem bolha, largura total (respostas longas com markdown/tabelas) */\n.message[data-variant='plain'] {\n align-items: flex-start;\n}\n.message[data-variant='plain'] .bubbleColumn {\n flex: 1;\n max-width: 100%;\n}\n.message[data-variant='plain'] .bubble {\n width: 100%;\n padding: 0;\n background: none;\n border: 0;\n box-shadow: none;\n white-space: normal;\n overflow-wrap: break-word;\n}\n.message[data-status='error'] .bubble {\n background: var(--ds-error-bg);\n color: var(--ds-error);\n border-color: var(--ds-error);\n}\n.meta {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n color: var(--ds-text-3);\n margin: 2px var(--sp-8) 0;\n font-variant-numeric: tabular-nums;\n}\n.meta[data-read] .status {\n color: var(--ds-primary);\n}\n.attachments {\n display: flex;\n flex-wrap: wrap;\n gap: var(--sp-4);\n margin-bottom: var(--sp-4);\n}\n.message[data-own] .attachments {\n justify-content: flex-end;\n}\n.imageAttachment {\n display: block;\n max-width: 240px;\n max-height: 180px;\n border-radius: 12px;\n object-fit: cover;\n border: 1px solid var(--ds-border-soft);\n}\n.fileAttachment {\n display: inline-flex;\n align-items: center;\n gap: var(--sp-8);\n padding: var(--sp-8) var(--sp-12);\n border-radius: 12px;\n background: var(--ds-surface-2);\n border: 1px solid var(--ds-border-soft);\n color: var(--ds-text);\n text-decoration: none;\n font-size: var(--type-disclaimer);\n max-width: 260px;\n}\n.fileName {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fileSize {\n flex-shrink: 0;\n white-space: nowrap;\n color: var(--ds-text-3);\n}\n.fileAttachment:hover {\n border-color: var(--ds-primary);\n}\n.system {\n align-self: center;\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n background: var(--ds-surface-2);\n padding: 4px var(--sp-12);\n border-radius: 99px;\n text-align: center;\n}\n\n/* ── ChatThread ── */\n.thread {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: var(--sp-16);\n}\n.thread > [data-group-start] {\n margin-top: var(--sp-12);\n}\n.separator {\n display: flex;\n align-items: center;\n gap: var(--sp-12);\n margin: var(--sp-16) 0 var(--sp-8);\n font-size: var(--type-disclaimer);\n font-weight: 600;\n color: var(--ds-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n.separator::before,\n.separator::after {\n content: '';\n flex: 1;\n height: 1px;\n background: var(--ds-border-soft);\n}\n\n/* ── TypingIndicator ── */\n.typing {\n display: inline-flex;\n align-items: center;\n gap: var(--sp-8);\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n}\n.dots {\n display: inline-flex;\n gap: 3px;\n padding: 10px 12px;\n border-radius: 16px;\n background: var(--ds-surface);\n border: 1px solid var(--ds-border-soft);\n}\n.dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--ds-text-3);\n animation: jc-typing 1.2s infinite ease-in-out;\n}\n.dot:nth-child(2) { animation-delay: 0.15s; }\n.dot:nth-child(3) { animation-delay: 0.3s; }\n@keyframes jc-typing {\n 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }\n 30% { transform: translateY(-4px); opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) {\n .dot { animation: none; opacity: 0.7; }\n}\n\n/* ── ChatComposer ── */\n.composer {\n display: flex;\n align-items: flex-end;\n gap: var(--sp-8);\n padding: var(--sp-12);\n border-top: 1px solid var(--ds-border-soft);\n background: var(--ds-surface);\n}\n.composerInput {\n flex: 1;\n}\n.composerSection {\n display: flex;\n align-items: center;\n gap: var(--sp-4);\n flex-shrink: 0;\n}\n.composerFiles {\n display: flex;\n flex-wrap: wrap;\n gap: var(--sp-4);\n padding: var(--sp-8) var(--sp-12) 0;\n background: var(--ds-surface);\n border-top: 1px solid var(--ds-border-soft);\n}\n\n/* ── ConversationList ── */\n.conversations {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n}\n.conversationSearch {\n padding: var(--sp-12);\n border-bottom: 1px solid var(--ds-border-soft);\n}\n.conversationItem {\n display: flex;\n align-items: center;\n border-bottom: 1px solid var(--ds-border-soft);\n transition: background 0.15s;\n}\n.conversationItem:hover {\n background: var(--ds-surface-2);\n}\n.conversationItem[data-active] {\n background: var(--ds-primary-soft);\n box-shadow: inset 3px 0 0 var(--ds-primary);\n}\n.conversation {\n all: unset;\n box-sizing: border-box;\n display: flex;\n gap: var(--sp-12);\n align-items: center;\n flex: 1;\n min-width: 0;\n padding: var(--sp-12) var(--sp-16);\n cursor: pointer;\n}\n.conversation:focus-visible {\n outline: 2px solid var(--ds-primary);\n outline-offset: -2px;\n}\n.conversationIcon {\n display: grid;\n place-items: center;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: var(--ds-surface-2);\n color: var(--ds-text-2);\n}\n.conversationItem[data-active] .conversationIcon {\n background: var(--ds-surface);\n color: var(--ds-primary);\n}\n.conversationActions {\n flex-shrink: 0;\n padding-right: var(--sp-8);\n}\n.conversationBody {\n flex: 1;\n min-width: 0;\n}\n.conversationTop {\n display: flex;\n justify-content: space-between;\n gap: var(--sp-8);\n align-items: baseline;\n}\n.conversationName {\n font-weight: 600;\n font-size: var(--type-subheadline-sm);\n color: var(--ds-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.conversationItem[data-unread] .conversationName {\n font-weight: 700;\n}\n.conversationTime {\n font-size: 11px;\n color: var(--ds-text-3);\n flex-shrink: 0;\n}\n.conversationBottom {\n display: flex;\n justify-content: space-between;\n gap: var(--sp-8);\n align-items: center;\n margin-top: 2px;\n}\n.conversationPreview {\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.conversationItem[data-unread] .conversationPreview {\n color: var(--ds-text-2);\n font-weight: 500;\n}\n\n/* ── ChatHeader / ChatLayout ── */\n.header {\n display: flex;\n align-items: center;\n gap: var(--sp-12);\n padding: var(--sp-12) var(--sp-16);\n border-bottom: 1px solid var(--ds-border-soft);\n background: var(--ds-surface);\n min-height: 64px;\n box-sizing: border-box;\n}\n.headerBody {\n flex: 1;\n min-width: 0;\n}\n.headerTitle {\n font-weight: 600;\n color: var(--ds-text);\n}\n.headerSubtitle {\n font-size: var(--type-disclaimer);\n color: var(--ds-text-3);\n}\n.layoutContainer {\n container-type: inline-size;\n container-name: jc-chat;\n}\n.layout {\n display: grid;\n grid-template-columns: var(--chat-sidebar-width, 320px) 1fr;\n height: var(--chat-height, 600px);\n background: var(--ds-bg);\n border: 1px solid var(--ds-border-soft);\n border-radius: var(--ds-radius);\n overflow: hidden;\n}\n.layout[data-single] {\n grid-template-columns: 1fr;\n}\n.layoutSidebar {\n background: var(--ds-surface);\n border-right: 1px solid var(--ds-border-soft);\n min-height: 0;\n overflow: hidden;\n}\n.layoutMain {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n}\n.layoutMessages {\n flex: 1;\n min-height: 0;\n}\n/* Abaixo de 640px de largura útil: uma coluna (lista OU conversa) */\n@container jc-chat (max-width: 640px) {\n .layout {\n grid-template-columns: 1fr;\n }\n .layout[data-mobile-view='thread'] .layoutSidebar,\n .layout[data-mobile-view='list'] .layoutMain {\n display: none;\n }\n}\n@container jc-chat (min-width: 640.02px) {\n .backButton {\n display: none;\n }\n}\n/* fora de um ChatLayout o botão voltar segue a tela */\n@media (min-width: 48em) {\n :not(.layoutContainer *) > .backButton {\n display: none;\n }\n}\n","import { Avatar, Box, Loader, Tooltip, type BoxProps } from '@mantine/core';\nimport { IconAlertCircle, IconCheck, IconChecks, IconFile } from '@tabler/icons-react';\nimport { formatBytes, initialsName, toDate, type GroupPosition } from './utils';\nimport type { ChatAttachment, ChatMessageStatus, ChatUser } from './types';\nimport classes from './Chat.module.css';\n\nexport interface ChatMessageProps extends BoxProps, Omit<React.ComponentProps<'div'>, keyof BoxProps> {\n /** Mensagem do usuário atual (alinhada à direita, bolha azul) */\n own?: boolean;\n author?: ChatUser;\n /** Exibe o nome do autor acima da bolha (útil em grupos) */\n showAuthor?: boolean;\n /** Exibe avatar ao lado da bolha (só na última mensagem do grupo) @default true */\n showAvatar?: boolean;\n createdAt?: Date | string | number;\n status?: ChatMessageStatus;\n attachments?: ChatAttachment[];\n /** Posição dentro de um grupo de mensagens consecutivas @default 'single' */\n position?: GroupPosition;\n /** Mensagem de sistema (centralizada) */\n system?: boolean;\n /** `plain` = sem bolha e em largura total (respostas de assistente com markdown/tabelas) @default 'bubble' */\n variant?: 'bubble' | 'plain';\n /** Avatar customizado (ex.: ícone do assistente); substitui o avatar do `author` */\n avatar?: React.ReactNode;\n children?: React.ReactNode;\n}\n\nconst statusLabel: Record<ChatMessageStatus, string> = {\n sending: 'Enviando…',\n sent: 'Enviada',\n delivered: 'Entregue',\n read: 'Lida',\n error: 'Falha no envio',\n};\n\nfunction StatusIcon({ status }: { status: ChatMessageStatus }) {\n if (status === 'sending') return <Loader size={10} color=\"gray\" />;\n if (status === 'sent') return <IconCheck size={14} />;\n if (status === 'error') return <IconAlertCircle size={14} color=\"var(--ds-error)\" />;\n return <IconChecks size={14} />;\n}\n\n/** Bolha de mensagem de chat. */\nexport function ChatMessage({\n own,\n author,\n showAuthor,\n showAvatar = true,\n createdAt,\n status,\n attachments,\n position = 'single',\n system,\n variant = 'bubble',\n avatar,\n children,\n ...others\n}: ChatMessageProps) {\n if (system) {\n return (\n <Box className={classes.system} role=\"status\" {...others}>\n {children}\n </Box>\n );\n }\n\n const isLast = position === 'single' || position === 'last';\n const isFirst = position === 'single' || position === 'first';\n const time = createdAt\n ? new Intl.DateTimeFormat('pt-BR', { hour: '2-digit', minute: '2-digit' }).format(toDate(createdAt))\n : null;\n\n return (\n <Box className={classes.message} mod={{ own, position, status, variant: variant === 'plain' ? 'plain' : undefined }} {...others}>\n {!own && (\n <div className={classes.avatarSlot}>\n {showAvatar && (variant === 'plain' ? isFirst : isLast) &&\n (avatar ??\n (author && (\n <Avatar src={author.avatar} name={initialsName(author.name)} color={author.color ?? 'initials'} size={32} alt={author.name} />\n )))}\n </div>\n )}\n <div className={classes.bubbleColumn}>\n {showAuthor && isFirst && author && !own && <div className={classes.author}>{author.name}</div>}\n {attachments && attachments.length > 0 && (\n <div className={classes.attachments}>\n {attachments.map((file, index) =>\n file.type === 'image' && file.url ? (\n <a key={index} href={file.url} target=\"_blank\" rel=\"noreferrer\">\n <img className={classes.imageAttachment} src={file.url} alt={file.name} />\n </a>\n ) : (\n <a key={index} className={classes.fileAttachment} href={file.url} target=\"_blank\" rel=\"noreferrer\" download={file.name}>\n <IconFile size={18} />\n <span className={classes.fileName}>{file.name}</span>\n {file.size !== undefined && <span className={classes.fileSize}>{formatBytes(file.size)}</span>}\n </a>\n ),\n )}\n </div>\n )}\n {children !== undefined && children !== null && children !== '' && <div className={classes.bubble}>{children}</div>}\n {isLast && (time || (own && status)) && (\n <div className={classes.meta} data-read={status === 'read' || undefined}>\n {time && createdAt && <time dateTime={toDate(createdAt).toISOString()}>{time}</time>}\n {own && status && (\n <Tooltip label={statusLabel[status]} withArrow>\n <span className={classes.status} aria-label={statusLabel[status]} style={{ display: 'inline-flex' }}>\n <StatusIcon status={status} />\n </span>\n </Tooltip>\n )}\n </div>\n )}\n </div>\n </Box>\n );\n}\nChatMessage.displayName = '@jcdecor/ui/ChatMessage';\n","import { Box, type BoxProps } from '@mantine/core';\nimport classes from './Chat.module.css';\n\nexport interface TypingIndicatorProps extends BoxProps {\n /** Nomes de quem está digitando. Vazio = só os pontinhos. */\n names?: string[];\n}\n\nfunction typingLabel(names: string[]) {\n if (names.length === 0) return '';\n if (names.length === 1) return `${names[0]} está digitando…`;\n if (names.length === 2) return `${names[0]} e ${names[1]} estão digitando…`;\n return `${names[0]} e mais ${names.length - 1} estão digitando…`;\n}\n\n/** Indicador animado de \"digitando…\". */\nexport function TypingIndicator({ names = [], ...others }: TypingIndicatorProps) {\n const label = typingLabel(names);\n return (\n <Box className={classes.typing} role=\"status\" aria-live=\"polite\" aria-label={label || 'Digitando'} {...others}>\n <span className={classes.dots} aria-hidden>\n <span className={classes.dot} />\n <span className={classes.dot} />\n <span className={classes.dot} />\n </span>\n {label && <span>{label}</span>}\n </Box>\n );\n}\nTypingIndicator.displayName = '@jcdecor/ui/TypingIndicator';\n","import { useEffect, useMemo, useRef } from 'react';\nimport { Box, ScrollArea, type ScrollAreaProps } from '@mantine/core';\nimport { ChatMessage } from './ChatMessage';\nimport { TypingIndicator } from './TypingIndicator';\nimport { buildThread } from './utils';\nimport type { ChatMessageData, ChatUser } from './types';\nimport classes from './Chat.module.css';\n\nexport interface ChatThreadProps extends Omit<ScrollAreaProps, 'children'> {\n messages: ChatMessageData[];\n /** Id do usuário atual — suas mensagens ficam à direita */\n currentUserId: string;\n users?: ChatUser[] | Record<string, ChatUser>;\n /** Nomes de quem está digitando agora */\n typing?: string[];\n /** Mostra nome do autor no início de cada grupo (conversas em grupo) */\n showAuthors?: boolean;\n /** Rola para a última mensagem quando chegam novas (se o usuário estiver perto do fim) @default true */\n autoScroll?: boolean;\n /** Janela (ms) para agrupar mensagens consecutivas do mesmo autor @default 300000 */\n groupWindow?: number;\n /** Conteúdo exibido quando não há mensagens */\n empty?: React.ReactNode;\n /** Renderização customizada do conteúdo da bolha */\n renderContent?: (message: ChatMessageData) => React.ReactNode;\n /** Avatar customizado por mensagem (ex.: ícone do assistente) */\n renderAvatar?: (message: ChatMessageData) => React.ReactNode;\n /** Conteúdo ao fim da conversa, antes do \"digitando\" (ex.: status de uma resposta em andamento) */\n footer?: React.ReactNode;\n}\n\n/** Lista rolável de mensagens com separadores de data, agrupamento e auto-scroll. */\nexport function ChatThread({\n messages,\n currentUserId,\n users = [],\n typing,\n showAuthors,\n autoScroll = true,\n groupWindow,\n empty,\n renderContent,\n renderAvatar,\n footer,\n h = '100%',\n ...others\n}: ChatThreadProps) {\n const viewport = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const nearBottom = useRef(true);\n const mounted = useRef(false);\n\n const userMap = useMemo<Record<string, ChatUser>>(\n () => (Array.isArray(users) ? Object.fromEntries(users.map((u) => [u.id, u])) : users),\n [users],\n );\n const items = useMemo(() => buildThread(messages, groupWindow), [messages, groupWindow]);\n\n const lastMessage = messages[messages.length - 1];\n useEffect(() => {\n const el = viewport.current;\n if (!el || !autoScroll) return;\n if (nearBottom.current || lastMessage?.authorId === currentUserId) {\n // Primeira renderização: pula direto para o fim; depois, rolagem suave.\n el.scrollTo({ top: el.scrollHeight, behavior: mounted.current ? 'smooth' : 'auto' });\n }\n mounted.current = true;\n }, [messages.length, lastMessage?.id, typing?.length, autoScroll, currentUserId]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Conteúdo que cresce depois (imagens, fontes) mantém o fim visível se o usuário já estava lá.\n useEffect(() => {\n const el = viewport.current;\n const inner = content.current;\n if (!el || !inner || !autoScroll || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => {\n if (nearBottom.current) el.scrollTo({ top: el.scrollHeight });\n });\n observer.observe(inner);\n return () => observer.disconnect();\n }, [autoScroll]);\n\n return (\n <ScrollArea\n h={h}\n viewportRef={viewport}\n onScrollPositionChange={({ y }) => {\n const el = viewport.current;\n if (el) nearBottom.current = el.scrollHeight - el.clientHeight - y < 80;\n }}\n {...others}\n >\n <Box ref={content} className={classes.thread} role=\"log\" aria-live=\"polite\" aria-relevant=\"additions\">\n {messages.length === 0 && empty}\n {items.map((item) => {\n if (item.type === 'separator') {\n return (\n <div key={item.key} className={classes.separator} role=\"separator\">\n {item.label}\n </div>\n );\n }\n const { message, position } = item;\n const own = message.authorId === currentUserId;\n return (\n <ChatMessage\n key={item.key}\n own={own}\n system={message.system}\n variant={message.variant}\n avatar={renderAvatar?.(message)}\n author={userMap[message.authorId]}\n showAuthor={showAuthors}\n createdAt={message.createdAt}\n status={own ? message.status : undefined}\n attachments={message.attachments}\n position={position}\n data-group-start={position === 'single' || position === 'first' ? true : undefined}\n >\n {renderContent ? renderContent(message) : message.text}\n </ChatMessage>\n );\n })}\n {footer}\n {typing && typing.length > 0 && <TypingIndicator names={typing} mt=\"sm\" ml={40} />}\n </Box>\n </ScrollArea>\n );\n}\nChatThread.displayName = '@jcdecor/ui/ChatThread';\n","import { useState } from 'react';\nimport { ActionIcon, Box, FileButton, Pill, Textarea, Tooltip, type BoxProps } from '@mantine/core';\nimport { useUncontrolled } from '@mantine/hooks';\nimport { IconPaperclip, IconPlayerStopFilled, IconSend2 } from '@tabler/icons-react';\nimport classes from './Chat.module.css';\n\nexport interface ChatComposerSendPayload {\n text: string;\n files: File[];\n}\n\nexport interface ChatComposerProps extends Omit<BoxProps, 'onSubmit'> {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /** Chamado ao enviar (Enter ou botão). O campo é limpo em seguida. */\n onSend: (payload: ChatComposerSendPayload) => void;\n placeholder?: string;\n disabled?: boolean;\n /** Habilita anexos; `accept` segue o atributo HTML (ex.: \"image/*,.pdf\") */\n allowAttachments?: boolean;\n accept?: string;\n /** Máximo de linhas antes de rolar @default 5 */\n maxRows?: number;\n /** Elementos extras à esquerda (ex.: emoji picker, respostas rápidas) */\n leftSection?: React.ReactNode;\n /** Elementos extras à direita do campo, antes do botão de enviar (ex.: biblioteca de prompts) */\n rightSection?: React.ReactNode;\n sendLabel?: string;\n /**\n * Uma resposta está em andamento: o envio fica bloqueado (o texto digitado é mantido e o campo\n * continua editável). Com `onStop`, o botão de enviar vira o botão de interromper.\n */\n loading?: boolean;\n /** Chamado pelo botão de interromper (visível enquanto `loading`) */\n onStop?: () => void;\n stopLabel?: string;\n /** Bloqueia o envio sem desabilitar o campo (ex.: integração ainda não conectada) */\n sendDisabled?: boolean;\n}\n\n/** Campo de mensagem: Enter envia, Shift+Enter quebra linha, anexos opcionais. */\nexport function ChatComposer({\n value,\n defaultValue,\n onChange,\n onSend,\n placeholder = 'Digite uma mensagem…',\n disabled,\n allowAttachments,\n accept,\n maxRows = 5,\n leftSection,\n rightSection,\n sendLabel = 'Enviar',\n loading,\n onStop,\n stopLabel = 'Interromper',\n sendDisabled,\n ...others\n}: ChatComposerProps) {\n const [text, setText] = useUncontrolled({ value, defaultValue, finalValue: '', onChange });\n const [files, setFiles] = useState<File[]>([]);\n const canSend = !disabled && !sendDisabled && !loading && (text.trim().length > 0 || files.length > 0);\n\n const send = () => {\n if (!canSend) return;\n onSend({ text: text.trim(), files });\n setText('');\n setFiles([]);\n };\n\n return (\n <Box {...others}>\n {files.length > 0 && (\n <div className={classes.composerFiles}>\n {files.map((file, index) => (\n <Pill key={`${file.name}-${index}`} withRemoveButton onRemove={() => setFiles((f) => f.filter((_, i) => i !== index))}>\n {file.name}\n </Pill>\n ))}\n </div>\n )}\n <div className={classes.composer}>\n {leftSection && <div className={classes.composerSection}>{leftSection}</div>}\n {allowAttachments && (\n <FileButton multiple accept={accept} onChange={(selected) => setFiles((f) => [...f, ...(selected ?? [])])} disabled={disabled}>\n {(props) => (\n <Tooltip label=\"Anexar arquivo\">\n <ActionIcon {...props} size=\"lg\" variant=\"subtle\" aria-label=\"Anexar arquivo\">\n <IconPaperclip size={20} />\n </ActionIcon>\n </Tooltip>\n )}\n </FileButton>\n )}\n <Textarea\n className={classes.composerInput}\n value={text}\n onChange={(event) => setText(event.currentTarget.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) {\n event.preventDefault();\n send();\n }\n }}\n placeholder={placeholder}\n disabled={disabled}\n autosize\n minRows={1}\n maxRows={maxRows}\n radius=\"lg\"\n aria-label={placeholder}\n />\n {rightSection && <div className={classes.composerSection}>{rightSection}</div>}\n {loading && onStop ? (\n <Tooltip label={stopLabel}>\n <ActionIcon size={44} radius=\"xl\" variant=\"light\" color=\"danger\" onClick={onStop} aria-label={stopLabel}>\n <IconPlayerStopFilled size={18} />\n </ActionIcon>\n </Tooltip>\n ) : (\n <ActionIcon size={44} radius=\"xl\" variant=\"filled\" onClick={send} disabled={!canSend} aria-label={sendLabel}>\n <IconSend2 size={20} />\n </ActionIcon>\n )}\n </div>\n </Box>\n );\n}\nChatComposer.displayName = '@jcdecor/ui/ChatComposer';\n","import { useMemo, useState } from 'react';\nimport { Avatar, Badge, Box, Indicator, ScrollArea, TextInput, type BoxProps } from '@mantine/core';\nimport { IconSearch } from '@tabler/icons-react';\nimport { initialsName, shortTimeLabel } from './utils';\nimport type { Conversation } from './types';\nimport classes from './Chat.module.css';\n\nexport interface ConversationListProps extends Omit<BoxProps, 'onSelect'> {\n conversations: Conversation[];\n activeId?: string | null;\n onSelect?: (conversation: Conversation) => void;\n /** Exibe campo de busca @default true */\n searchable?: boolean;\n searchPlaceholder?: string;\n empty?: React.ReactNode;\n /** Ícone no lugar do avatar com iniciais (ex.: conversas com um assistente) */\n renderIcon?: (conversation: Conversation) => React.ReactNode;\n /** Ações por conversa (ex.: menu renomear/excluir), à direita do item */\n renderActions?: (conversation: Conversation) => React.ReactNode;\n}\n\n/** Lista de conversas com busca, não lidas, presença e última mensagem. */\nexport function ConversationList({\n conversations,\n activeId,\n onSelect,\n searchable = true,\n searchPlaceholder = 'Buscar conversas',\n empty,\n renderIcon,\n renderActions,\n ...others\n}: ConversationListProps) {\n const [query, setQuery] = useState('');\n const filtered = useMemo(() => {\n const q = query.trim().toLocaleLowerCase('pt-BR');\n if (!q) return conversations;\n return conversations.filter(\n (c) => c.name.toLocaleLowerCase('pt-BR').includes(q) || c.lastMessage?.toLocaleLowerCase('pt-BR').includes(q),\n );\n }, [conversations, query]);\n\n return (\n <Box className={classes.conversations} {...others}>\n {searchable && (\n <div className={classes.conversationSearch}>\n <TextInput\n size=\"sm\"\n placeholder={searchPlaceholder}\n leftSection={<IconSearch size={16} />}\n value={query}\n onChange={(e) => setQuery(e.currentTarget.value)}\n aria-label={searchPlaceholder}\n />\n </div>\n )}\n <ScrollArea style={{ flex: 1 }} type=\"auto\">\n <div role=\"list\">\n {filtered.length === 0 && (\n <Box p=\"lg\" ta=\"center\" c=\"var(--ds-text-3)\" fz=\"sm\">\n {empty ?? 'Nenhuma conversa encontrada'}\n </Box>\n )}\n {filtered.map((c) => (\n <div\n key={c.id}\n role=\"listitem\"\n className={classes.conversationItem}\n data-active={c.id === activeId || undefined}\n data-unread={(c.unread ?? 0) > 0 || undefined}\n >\n <button\n type=\"button\"\n className={classes.conversation}\n aria-current={c.id === activeId ? 'true' : undefined}\n onClick={() => onSelect?.(c)}\n >\n <Indicator color=\"evergreen\" position=\"bottom-end\" offset={5} size={10} withBorder disabled={!c.online}>\n {renderIcon ? (\n <span className={classes.conversationIcon}>{renderIcon(c)}</span>\n ) : (\n <Avatar src={c.avatar} name={initialsName(c.name)} color={c.color ?? 'initials'} size={40} />\n )}\n </Indicator>\n <div className={classes.conversationBody}>\n <div className={classes.conversationTop}>\n <span className={classes.conversationName}>{c.name}</span>\n {c.lastMessageAt !== undefined && <span className={classes.conversationTime}>{shortTimeLabel(c.lastMessageAt)}</span>}\n </div>\n <div className={classes.conversationBottom}>\n <span className={classes.conversationPreview}>{c.lastMessage}</span>\n {(c.unread ?? 0) > 0 && (\n <Badge size=\"sm\" variant=\"filled\" circle={(c.unread ?? 0) < 10} aria-label={`${c.unread} não lidas`}>\n {c.unread! > 99 ? '99+' : c.unread}\n </Badge>\n )}\n </div>\n {c.tag && (\n <Badge size=\"xs\" color=\"obsidian\" mt={6}>\n {c.tag}\n </Badge>\n )}\n </div>\n </button>\n {renderActions && <div className={classes.conversationActions}>{renderActions(c)}</div>}\n </div>\n ))}\n </div>\n </ScrollArea>\n </Box>\n );\n}\nConversationList.displayName = '@jcdecor/ui/ConversationList';\n","import { ActionIcon, Avatar, Box, Indicator, type BoxProps } from '@mantine/core';\nimport { IconArrowLeft } from '@tabler/icons-react';\nimport { initialsName } from './utils';\nimport classes from './Chat.module.css';\n\nexport interface ChatHeaderProps extends BoxProps {\n title: React.ReactNode;\n subtitle?: React.ReactNode;\n avatar?: string;\n /** Nome para iniciais do avatar */\n avatarName?: string;\n online?: boolean;\n actions?: React.ReactNode;\n /** Exibe botão voltar (mobile) */\n onBack?: () => void;\n}\n\n/** Cabeçalho da conversa: avatar, nome, status e ações. */\nexport function ChatHeader({ title, subtitle, avatar, avatarName, online, actions, onBack, ...others }: ChatHeaderProps) {\n return (\n <Box className={classes.header} {...others}>\n {onBack && (\n <ActionIcon className={classes.backButton} variant=\"subtle\" onClick={onBack} aria-label=\"Voltar\">\n <IconArrowLeft size={20} />\n </ActionIcon>\n )}\n {(avatar || avatarName) && (\n <Indicator color=\"evergreen\" position=\"bottom-end\" offset={5} size={10} withBorder disabled={!online}>\n <Avatar src={avatar} name={avatarName ? initialsName(avatarName) : undefined} color=\"initials\" size={40} />\n </Indicator>\n )}\n <div className={classes.headerBody}>\n <div className={classes.headerTitle}>{title}</div>\n {subtitle && <div className={classes.headerSubtitle}>{subtitle}</div>}\n </div>\n {actions}\n </Box>\n );\n}\nChatHeader.displayName = '@jcdecor/ui/ChatHeader';\n\nexport interface ChatLayoutProps extends BoxProps {\n /** Lista de conversas (coluna esquerda) — omita para um chat único */\n sidebar?: React.ReactNode;\n header?: React.ReactNode;\n /** Normalmente um <ChatThread /> */\n children: React.ReactNode;\n /** Normalmente um <ChatComposer /> */\n composer?: React.ReactNode;\n /** Altura total @default 600 */\n height?: number | string;\n sidebarWidth?: number | string;\n /** Em telas pequenas mostra só a lista ou só a conversa @default 'thread' */\n mobileView?: 'list' | 'thread';\n}\n\n/** Layout de chat: conversas + cabeçalho + mensagens + composer (responsivo). */\nexport function ChatLayout({\n sidebar,\n header,\n children,\n composer,\n height = 600,\n sidebarWidth = 320,\n mobileView = 'thread',\n style,\n ...others\n}: ChatLayoutProps) {\n const toCss = (v: number | string) => (typeof v === 'number' ? `${v}px` : v);\n // O wrapper é o contêiner das container queries: o layout se adapta ao espaço disponível, não à tela.\n return (\n <Box\n className={classes.layoutContainer}\n style={[\n {\n ['--chat-height' as string]: toCss(height),\n ['--chat-sidebar-width' as string]: toCss(sidebarWidth),\n },\n style,\n ]}\n {...others}\n >\n <div\n className={classes.layout}\n data-mobile-view={sidebar ? mobileView : undefined}\n data-single={sidebar ? undefined : true}\n >\n {sidebar && <aside className={classes.layoutSidebar}>{sidebar}</aside>}\n <section className={classes.layoutMain}>\n {header}\n <div className={classes.layoutMessages}>{children}</div>\n {composer}\n </section>\n </div>\n </Box>\n );\n}\nChatLayout.displayName = '@jcdecor/ui/ChatLayout';\n"],"mappings":"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"}
@@ -1,12 +1,20 @@
1
- import { type BoxProps } from '@mantine/core';
1
+ import { type BoxProps, type ElementProps, type Factory, type StylesApiProps } from '@mantine/core';
2
2
  export interface PageHeaderBreadcrumb {
3
3
  label: React.ReactNode;
4
4
  href?: string;
5
5
  }
6
- export interface PageHeaderProps extends BoxProps, Omit<React.ComponentProps<'div'>, keyof BoxProps | 'title'> {
6
+ export type PageHeaderStylesNames = 'root' | 'breadcrumbs' | 'header' | 'main' | 'icon' | 'body' | 'kicker' | 'title' | 'description' | 'actions';
7
+ export type PageHeaderCssVariables = {
8
+ root: '--page-header-icon-size';
9
+ };
10
+ export interface PageHeaderProps extends BoxProps, StylesApiProps<PageHeaderFactory>, Omit<ElementProps<'div'>, 'title'> {
7
11
  kicker?: React.ReactNode;
8
12
  title: React.ReactNode;
9
13
  description?: React.ReactNode;
14
+ /** Ícone à esquerda do título (ex.: `<IconUsers />`), num quadro suave na cor primária */
15
+ icon?: React.ReactNode;
16
+ /** Tamanho do quadro do ícone (px ou CSS) @default 48 */
17
+ iconSize?: number | string;
10
18
  /** Botões à direita do título */
11
19
  actions?: React.ReactNode;
12
20
  breadcrumbs?: PageHeaderBreadcrumb[];
@@ -15,8 +23,16 @@ export interface PageHeaderProps extends BoxProps, Omit<React.ComponentProps<'di
15
23
  /** `display` usa display-small (página principal); `headline` usa headline-large @default 'headline' */
16
24
  size?: 'display' | 'headline';
17
25
  }
18
- /** Cabeçalho de página: breadcrumbs, kicker, título, descrição e ações. */
19
- export declare function PageHeader({ kicker, title, description, actions, breadcrumbs, linkComponent, size, ...others }: PageHeaderProps): import("react").JSX.Element;
20
- export declare namespace PageHeader {
21
- var displayName: string;
22
- }
26
+ export type PageHeaderFactory = Factory<{
27
+ props: PageHeaderProps;
28
+ ref: HTMLDivElement;
29
+ stylesNames: PageHeaderStylesNames;
30
+ vars: PageHeaderCssVariables;
31
+ }>;
32
+ /** Cabeçalho de página: breadcrumbs, ícone, kicker, título, descrição e ações. */
33
+ export declare const PageHeader: import("@mantine/core").MantineComponent<{
34
+ props: PageHeaderProps;
35
+ ref: HTMLDivElement;
36
+ stylesNames: PageHeaderStylesNames;
37
+ vars: PageHeaderCssVariables;
38
+ }>;
@@ -1,9 +1,17 @@
1
1
  import { type BoxProps, type ElementProps, type Factory, type StylesApiProps } from '@mantine/core';
2
- export type TopNavStylesNames = 'root' | 'brand' | 'links' | 'link' | 'right' | 'burger' | 'mobileLinks';
2
+ export type TopNavStylesNames = 'root' | 'brand' | 'links' | 'link' | 'linkSection' | 'linkLabel' | 'right' | 'burger' | 'mobileLinks';
3
3
  export interface TopNavLink {
4
4
  label: React.ReactNode;
5
5
  href?: string;
6
6
  active?: boolean;
7
+ /** Ícone/conteúdo antes do texto (como `leftSection` do Mantine) */
8
+ leftSection?: React.ReactNode;
9
+ /** Conteúdo depois do texto (ex.: contador, chevron) */
10
+ rightSection?: React.ReactNode;
11
+ /** Desabilita o link (sem navegação nem `onClick`) */
12
+ disabled?: boolean;
13
+ /** Nome acessível, necessário quando o link mostra só um ícone */
14
+ 'aria-label'?: string;
7
15
  onClick?: (event: React.MouseEvent) => void;
8
16
  }
9
17
  export interface TopNavProps extends BoxProps, StylesApiProps<TopNavFactory>, ElementProps<'div'> {