@snksergio/design-system 0.62.0 → 0.63.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/dist-lib/ai/blocos/chart/budget-breakdown.tsx +174 -0
- package/dist-lib/ai/blocos/indice.md +43 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-com-tabela.tsx +450 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-de-tarefa-com-abas.tsx +570 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-do-registro.tsx +555 -0
- package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
- package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
- package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
- package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
- package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
- package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
- package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
- package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
- package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
- package/dist-lib/ai/exemplos/chat/index.ts +1 -0
- package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
- package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
- package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
- package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
- package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
- package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
- package/dist-lib/ai/exemplos/finance/index.ts +1 -0
- package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
- package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
- package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
- package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
- package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
- package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
- package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
- package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
- package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
- package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
- package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
- package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
- package/dist-lib/ai/indice.json +30 -15
- package/dist-lib/ai/manifest.json +31 -5
- package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
- package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
- package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
- package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
- package/dist-lib/ai/roteiros/crud-builder/entrevista.md +139 -0
- package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
- package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
- package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
- package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
- package/dist-lib/ai/roteiros/list-builder/entrevista.md +188 -0
- package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
- package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
- package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
- package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
- package/package.json +1 -1
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/blueprint.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/entrevista.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/geracao.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/roteiro.md +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { LucideIcon } from "@/lib/lucide-types";
|
|
2
|
+
|
|
3
|
+
/* ── Channels (canais de comunicação) ───────────────────────────── */
|
|
4
|
+
|
|
5
|
+
export type ChannelId =
|
|
6
|
+
| "whatsapp"
|
|
7
|
+
| "telegram"
|
|
8
|
+
| "instagram"
|
|
9
|
+
| "webchat"
|
|
10
|
+
| "email";
|
|
11
|
+
|
|
12
|
+
/* ── Conversation (item da fila) ─────────────────────────────────── */
|
|
13
|
+
|
|
14
|
+
export type ConvTagKind = "warning" | "info" | "success";
|
|
15
|
+
|
|
16
|
+
export type ConversationStatus = "attending" | "waiting" | "ai";
|
|
17
|
+
|
|
18
|
+
export type Conversation = {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
initials: string;
|
|
22
|
+
avatarHex: string;
|
|
23
|
+
channel: ChannelId;
|
|
24
|
+
tag: string;
|
|
25
|
+
tagKind: ConvTagKind;
|
|
26
|
+
status: ConversationStatus;
|
|
27
|
+
statusLabel: string;
|
|
28
|
+
last: string;
|
|
29
|
+
time: string;
|
|
30
|
+
unread: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/* ── Details (painel direito da tela) ────────────────────────────── */
|
|
34
|
+
|
|
35
|
+
export type ConversationHistoryItem = {
|
|
36
|
+
id: string;
|
|
37
|
+
subject: string;
|
|
38
|
+
date: string;
|
|
39
|
+
status: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ConversationDetail = {
|
|
43
|
+
phone: string;
|
|
44
|
+
email: string;
|
|
45
|
+
firstContact: string;
|
|
46
|
+
assignedTo: string;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
extraTags: string[];
|
|
49
|
+
history: ConversationHistoryItem[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/* ── Filters (sidebar esquerda) ──────────────────────────────────── */
|
|
53
|
+
|
|
54
|
+
export type FilterGroupKey = "status" | "channels" | "categories" | "innerStatus";
|
|
55
|
+
|
|
56
|
+
export type FilterItem = {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
count: number;
|
|
60
|
+
hasUnread?: boolean;
|
|
61
|
+
/** Cor do dot/ícone leading — aceita CSS var ou hex. */
|
|
62
|
+
color?: string;
|
|
63
|
+
/** Componente lucide opcional pra leading. */
|
|
64
|
+
icon?: LucideIcon;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/* ── Messages (thread) ───────────────────────────────────────────── */
|
|
68
|
+
|
|
69
|
+
export type MessageStatus = "sent" | "read";
|
|
70
|
+
|
|
71
|
+
export type Message = {
|
|
72
|
+
id: number;
|
|
73
|
+
from: "me" | "them";
|
|
74
|
+
text: string;
|
|
75
|
+
time: string;
|
|
76
|
+
status?: MessageStatus;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/* ── Queue filter chips (Todas / Não lidas / Lidas) ─────────────── */
|
|
80
|
+
|
|
81
|
+
export type QueueFilter = "all" | "unread" | "read";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ChannelId } from "../../chat-v2.types";
|
|
2
|
+
import { CHANNEL_HEX, CHANNEL_LABEL } from "../../chat-v2-mocks";
|
|
3
|
+
|
|
4
|
+
export type ChannelDotProps = {
|
|
5
|
+
channel: ChannelId;
|
|
6
|
+
/** Quando true, mostra apenas o dot (sem label). Default: false. */
|
|
7
|
+
dotOnly?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Dot 8px + label do canal. Cor vem de `CHANNEL_HEX`. Usado em
|
|
12
|
+
* `ConversationListItem`, header de `ConversationColumn` e tooltips.
|
|
13
|
+
*/
|
|
14
|
+
export function ChannelDot({ channel, dotOnly = false }: ChannelDotProps) {
|
|
15
|
+
return (
|
|
16
|
+
<span
|
|
17
|
+
className="inline-flex items-center gap-[4px] text-caption-sm text-fg-muted"
|
|
18
|
+
title={CHANNEL_LABEL[channel]}
|
|
19
|
+
>
|
|
20
|
+
<span
|
|
21
|
+
className="size-[8px] rounded-radius-full shrink-0"
|
|
22
|
+
style={{ background: CHANNEL_HEX[channel] }}
|
|
23
|
+
aria-hidden
|
|
24
|
+
/>
|
|
25
|
+
{!dotOnly && CHANNEL_LABEL[channel]}
|
|
26
|
+
</span>
|
|
27
|
+
);
|
|
28
|
+
}
|
package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Archive,
|
|
3
|
+
CheckCircle2,
|
|
4
|
+
MoreVertical,
|
|
5
|
+
Phone,
|
|
6
|
+
Tag,
|
|
7
|
+
UserPlus,
|
|
8
|
+
Video,
|
|
9
|
+
} from "lucide-react";
|
|
10
|
+
import { Button } from "@/components/ui/Button/button";
|
|
11
|
+
import {
|
|
12
|
+
DropdownMenu,
|
|
13
|
+
DropdownMenuContent,
|
|
14
|
+
DropdownMenuItem,
|
|
15
|
+
DropdownMenuSeparator,
|
|
16
|
+
DropdownMenuTrigger,
|
|
17
|
+
} from "@/components/shadcn/dropdown-menu";
|
|
18
|
+
|
|
19
|
+
export type ConversationActionsMenuProps = {
|
|
20
|
+
/** Quando true, adiciona "Resolver" no topo do dropdown (mobile compact). */
|
|
21
|
+
showResolveInMenu?: boolean;
|
|
22
|
+
onResolve?: () => void;
|
|
23
|
+
onCall?: () => void;
|
|
24
|
+
onVideo?: () => void;
|
|
25
|
+
onAddTag?: () => void;
|
|
26
|
+
onAssign?: () => void;
|
|
27
|
+
onArchive?: () => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Dropdown "..." do header da conversa com 5–6 ações (Ligar, Vídeo, Tag,
|
|
32
|
+
* Atribuir, Arquivar; opcionalmente Resolver no topo pra mobile).
|
|
33
|
+
*/
|
|
34
|
+
export function ConversationActionsMenu({
|
|
35
|
+
showResolveInMenu = false,
|
|
36
|
+
onResolve,
|
|
37
|
+
onCall,
|
|
38
|
+
onVideo,
|
|
39
|
+
onAddTag,
|
|
40
|
+
onAssign,
|
|
41
|
+
onArchive,
|
|
42
|
+
}: ConversationActionsMenuProps) {
|
|
43
|
+
return (
|
|
44
|
+
<DropdownMenu>
|
|
45
|
+
<DropdownMenuTrigger asChild>
|
|
46
|
+
<Button
|
|
47
|
+
color="secondary"
|
|
48
|
+
variant="outline"
|
|
49
|
+
size="icon-sm"
|
|
50
|
+
aria-label="Mais opções"
|
|
51
|
+
>
|
|
52
|
+
<MoreVertical />
|
|
53
|
+
</Button>
|
|
54
|
+
</DropdownMenuTrigger>
|
|
55
|
+
<DropdownMenuContent align="end" sideOffset={6} className="min-w-[200px]">
|
|
56
|
+
{showResolveInMenu && (
|
|
57
|
+
<>
|
|
58
|
+
<DropdownMenuItem onSelect={onResolve}>
|
|
59
|
+
<CheckCircle2 /> Resolver
|
|
60
|
+
</DropdownMenuItem>
|
|
61
|
+
<DropdownMenuSeparator />
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
<DropdownMenuItem onSelect={onCall}>
|
|
65
|
+
<Phone /> Ligar
|
|
66
|
+
</DropdownMenuItem>
|
|
67
|
+
<DropdownMenuItem onSelect={onVideo}>
|
|
68
|
+
<Video /> Vídeo chamada
|
|
69
|
+
</DropdownMenuItem>
|
|
70
|
+
<DropdownMenuItem onSelect={onAddTag}>
|
|
71
|
+
<Tag /> Adicionar tag
|
|
72
|
+
</DropdownMenuItem>
|
|
73
|
+
<DropdownMenuItem onSelect={onAssign}>
|
|
74
|
+
<UserPlus /> Atribuir
|
|
75
|
+
</DropdownMenuItem>
|
|
76
|
+
<DropdownMenuSeparator />
|
|
77
|
+
<DropdownMenuItem variant="destructive" onSelect={onArchive}>
|
|
78
|
+
<Archive /> Arquivar
|
|
79
|
+
</DropdownMenuItem>
|
|
80
|
+
</DropdownMenuContent>
|
|
81
|
+
</DropdownMenu>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tv } from "@/utils/tv";
|
|
2
|
+
|
|
3
|
+
export const conversationColumnStyles = tv({
|
|
4
|
+
slots: {
|
|
5
|
+
root: "flex flex-col flex-1 min-w-0 bg-bg-surface border border-border-subtle rounded-radius-xl overflow-hidden",
|
|
6
|
+
header:
|
|
7
|
+
"flex items-center gap-gp-lg px-pad-2xl py-pad-md border-b border-border-subtle shrink-0",
|
|
8
|
+
headerInfo: "flex-1 min-w-0 flex flex-col gap-[2px]",
|
|
9
|
+
headerTitleRow: "flex items-center gap-gp-sm",
|
|
10
|
+
headerName:
|
|
11
|
+
"text-title-md text-fg-default whitespace-nowrap overflow-hidden text-ellipsis",
|
|
12
|
+
headerMetaRow:
|
|
13
|
+
"flex flex-wrap items-center gap-x-gp-sm gap-y-[2px] text-caption-sm text-fg-muted min-w-0",
|
|
14
|
+
headerActions: "flex items-center gap-gp-sm shrink-0",
|
|
15
|
+
statusInner: "inline-flex items-center gap-[4px]",
|
|
16
|
+
statusDot: "size-[6px] rounded-radius-full shrink-0",
|
|
17
|
+
thread:
|
|
18
|
+
"flex-1 min-h-0 overflow-y-auto bg-bg-muted dark:bg-bg-canvas px-pad-3xl py-pad-2xl flex flex-col gap-gp-md",
|
|
19
|
+
composer:
|
|
20
|
+
"flex items-center gap-gp-sm px-pad-2xl py-pad-md border-t border-border-subtle shrink-0 bg-bg-surface",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ArrowLeft,
|
|
4
|
+
CheckCircle2,
|
|
5
|
+
Mic,
|
|
6
|
+
PanelRightClose,
|
|
7
|
+
PanelRightOpen,
|
|
8
|
+
Paperclip,
|
|
9
|
+
Send,
|
|
10
|
+
Smile,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
import { Button } from "@/components/ui/Button/button";
|
|
14
|
+
import { Chip } from "@/components/ui/Chip";
|
|
15
|
+
import { Input } from "@/components/shadcn/input";
|
|
16
|
+
import { SAMPLE_MESSAGES, STATUS_DOT } from "../../chat-v2-mocks";
|
|
17
|
+
import type { Message } from "../../chat-v2.types";
|
|
18
|
+
import { ChannelDot } from "../ChannelDot/channel-dot";
|
|
19
|
+
import { ConversationActionsMenu } from "../ConversationActionsMenu";
|
|
20
|
+
import { DateSeparator } from "../DateSeparator/date-separator";
|
|
21
|
+
import { MessageBubble } from "../MessageBubble";
|
|
22
|
+
import { PersonAvatar } from "../PersonAvatar";
|
|
23
|
+
import { conversationColumnStyles } from "./conversation-column.styles";
|
|
24
|
+
import type { ConversationColumnProps } from "./conversation-column.types";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Coluna central. Header com avatar + nome + meta (id/canal/status) + ações,
|
|
28
|
+
* thread scrollável com `DateSeparator` + `MessageBubble`, composer com Input
|
|
29
|
+
* + botões. Estado local: texto digitado + lista de mensagens.
|
|
30
|
+
*/
|
|
31
|
+
export function ConversationColumn({
|
|
32
|
+
conversation,
|
|
33
|
+
detailsOpen,
|
|
34
|
+
onToggleDetails,
|
|
35
|
+
onBackToList,
|
|
36
|
+
className,
|
|
37
|
+
}: ConversationColumnProps) {
|
|
38
|
+
const s = conversationColumnStyles();
|
|
39
|
+
const [text, setText] = useState("");
|
|
40
|
+
const [msgs, setMsgs] = useState<Message[]>(SAMPLE_MESSAGES);
|
|
41
|
+
|
|
42
|
+
const send = (e?: React.FormEvent) => {
|
|
43
|
+
e?.preventDefault();
|
|
44
|
+
if (!text.trim()) return;
|
|
45
|
+
setMsgs((m) => [
|
|
46
|
+
...m,
|
|
47
|
+
{
|
|
48
|
+
id: Date.now(),
|
|
49
|
+
from: "me",
|
|
50
|
+
text: text.trim(),
|
|
51
|
+
time: new Date().toLocaleTimeString("pt-BR", {
|
|
52
|
+
hour: "2-digit",
|
|
53
|
+
minute: "2-digit",
|
|
54
|
+
}),
|
|
55
|
+
status: "sent",
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
setText("");
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<section aria-label="Conversa" className={cn(s.root(), className)}>
|
|
63
|
+
{/* Header da conversa */}
|
|
64
|
+
<header className={s.header()}>
|
|
65
|
+
{onBackToList && (
|
|
66
|
+
<Button
|
|
67
|
+
color="secondary"
|
|
68
|
+
variant="ghost"
|
|
69
|
+
size="icon-sm"
|
|
70
|
+
aria-label="Voltar pra lista"
|
|
71
|
+
onClick={onBackToList}
|
|
72
|
+
className="md:hidden"
|
|
73
|
+
>
|
|
74
|
+
<ArrowLeft />
|
|
75
|
+
</Button>
|
|
76
|
+
)}
|
|
77
|
+
<div className="max-md:hidden">
|
|
78
|
+
<PersonAvatar
|
|
79
|
+
initials={conversation.initials}
|
|
80
|
+
hex={conversation.avatarHex}
|
|
81
|
+
size="md"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className={s.headerInfo()}>
|
|
86
|
+
<div className={s.headerTitleRow()}>
|
|
87
|
+
<span className={s.headerName()}>{conversation.name}</span>
|
|
88
|
+
<Chip color={conversation.tagKind} variant="soft" size="sm" shape="pill">
|
|
89
|
+
{conversation.tag}
|
|
90
|
+
</Chip>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className={s.headerMetaRow()}>
|
|
94
|
+
<span className="[font-variant-numeric:tabular-nums]">
|
|
95
|
+
{conversation.id}
|
|
96
|
+
</span>
|
|
97
|
+
<span aria-hidden>·</span>
|
|
98
|
+
<ChannelDot channel={conversation.channel} />
|
|
99
|
+
<span aria-hidden>·</span>
|
|
100
|
+
<span className={s.statusInner()}>
|
|
101
|
+
<span
|
|
102
|
+
className={s.statusDot()}
|
|
103
|
+
style={{ background: STATUS_DOT[conversation.status] }}
|
|
104
|
+
aria-hidden
|
|
105
|
+
/>
|
|
106
|
+
{conversation.statusLabel}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div className={s.headerActions()}>
|
|
112
|
+
<Button
|
|
113
|
+
color="primary"
|
|
114
|
+
variant="filled"
|
|
115
|
+
size="sm"
|
|
116
|
+
iconLeft={<CheckCircle2 />}
|
|
117
|
+
className="max-md:hidden"
|
|
118
|
+
>
|
|
119
|
+
Resolver
|
|
120
|
+
</Button>
|
|
121
|
+
<ConversationActionsMenu
|
|
122
|
+
showResolveInMenu
|
|
123
|
+
onResolve={() => console.log("resolver")}
|
|
124
|
+
onCall={() => console.log("ligar")}
|
|
125
|
+
onVideo={() => console.log("video")}
|
|
126
|
+
onAddTag={() => console.log("tag")}
|
|
127
|
+
onAssign={() => console.log("atribuir")}
|
|
128
|
+
onArchive={() => console.log("arquivar")}
|
|
129
|
+
/>
|
|
130
|
+
<Button
|
|
131
|
+
color="secondary"
|
|
132
|
+
variant="outline"
|
|
133
|
+
size="icon-sm"
|
|
134
|
+
aria-label={detailsOpen ? "Ocultar detalhes" : "Mostrar detalhes"}
|
|
135
|
+
aria-pressed={detailsOpen}
|
|
136
|
+
onClick={onToggleDetails}
|
|
137
|
+
>
|
|
138
|
+
{detailsOpen ? <PanelRightClose /> : <PanelRightOpen />}
|
|
139
|
+
</Button>
|
|
140
|
+
</div>
|
|
141
|
+
</header>
|
|
142
|
+
|
|
143
|
+
{/* Thread */}
|
|
144
|
+
<div className={s.thread()} aria-live="polite">
|
|
145
|
+
<DateSeparator label="Hoje" />
|
|
146
|
+
{msgs.map((m) => (
|
|
147
|
+
<MessageBubble key={m.id} msg={m} />
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Composer */}
|
|
152
|
+
<form onSubmit={send} className={s.composer()}>
|
|
153
|
+
<Button
|
|
154
|
+
color="secondary"
|
|
155
|
+
variant="ghost"
|
|
156
|
+
size="icon-sm"
|
|
157
|
+
aria-label="Anexar"
|
|
158
|
+
type="button"
|
|
159
|
+
>
|
|
160
|
+
<Paperclip />
|
|
161
|
+
</Button>
|
|
162
|
+
<Input
|
|
163
|
+
type="text"
|
|
164
|
+
value={text}
|
|
165
|
+
onChange={(e) => setText(e.target.value)}
|
|
166
|
+
placeholder="Digite uma mensagem..."
|
|
167
|
+
aria-label="Mensagem"
|
|
168
|
+
className="flex-1"
|
|
169
|
+
/>
|
|
170
|
+
<Button
|
|
171
|
+
color="secondary"
|
|
172
|
+
variant="ghost"
|
|
173
|
+
size="icon-sm"
|
|
174
|
+
aria-label="Emoji"
|
|
175
|
+
type="button"
|
|
176
|
+
>
|
|
177
|
+
<Smile />
|
|
178
|
+
</Button>
|
|
179
|
+
{text.trim() ? (
|
|
180
|
+
<Button
|
|
181
|
+
color="primary"
|
|
182
|
+
variant="filled"
|
|
183
|
+
size="icon-sm"
|
|
184
|
+
aria-label="Enviar"
|
|
185
|
+
type="submit"
|
|
186
|
+
>
|
|
187
|
+
<Send strokeWidth={2.2} />
|
|
188
|
+
</Button>
|
|
189
|
+
) : (
|
|
190
|
+
<Button
|
|
191
|
+
color="primary"
|
|
192
|
+
variant="filled"
|
|
193
|
+
size="icon-sm"
|
|
194
|
+
aria-label="Gravar áudio"
|
|
195
|
+
type="button"
|
|
196
|
+
>
|
|
197
|
+
<Mic strokeWidth={2.2} />
|
|
198
|
+
</Button>
|
|
199
|
+
)}
|
|
200
|
+
</form>
|
|
201
|
+
</section>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Conversation } from "../../chat-v2.types";
|
|
2
|
+
|
|
3
|
+
export type ConversationColumnProps = {
|
|
4
|
+
conversation: Conversation;
|
|
5
|
+
detailsOpen: boolean;
|
|
6
|
+
onToggleDetails: () => void;
|
|
7
|
+
/** Visível só em mobile (max-md:flex). Desktop esconde. */
|
|
8
|
+
onBackToList?: () => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { tv, type VariantProps } from "@/utils/tv";
|
|
2
|
+
|
|
3
|
+
export const conversationListItemStyles = tv({
|
|
4
|
+
slots: {
|
|
5
|
+
/** Card raiz (button) — flex horizontal com avatar + conteúdo. */
|
|
6
|
+
root: [
|
|
7
|
+
"group/conv text-left relative",
|
|
8
|
+
"flex gap-gp-md p-pad-md rounded-radius-md",
|
|
9
|
+
"cursor-pointer transition-colors duration-150",
|
|
10
|
+
].join(" "),
|
|
11
|
+
/** Coluna direita (3 rows). */
|
|
12
|
+
content: "flex-1 min-w-0 flex flex-col gap-[3px]",
|
|
13
|
+
/** Row 1 — nome + horário. */
|
|
14
|
+
rowTop: "flex items-center gap-gp-sm",
|
|
15
|
+
/** Nome (ellipsis). */
|
|
16
|
+
name: "text-body-sm font-semibold text-fg-default whitespace-nowrap overflow-hidden text-ellipsis flex-1 min-w-0",
|
|
17
|
+
/** Horário. */
|
|
18
|
+
time: "text-caption-sm text-fg-muted shrink-0 [font-variant-numeric:tabular-nums]",
|
|
19
|
+
/** Row 2 — preview + badge unread. */
|
|
20
|
+
rowMid: "flex items-center gap-gp-sm",
|
|
21
|
+
/** Preview da última mensagem (ellipsis). */
|
|
22
|
+
preview:
|
|
23
|
+
"text-body-xs font-normal text-fg-muted whitespace-nowrap overflow-hidden text-ellipsis flex-1 min-w-0",
|
|
24
|
+
/** Badge contador de não lidas — `caption-xs` (10/400) + override
|
|
25
|
+
`font-bold leading-none` pra centralizar o número dentro do círculo.
|
|
26
|
+
Tabular-nums alinha dígitos verticalmente em badges com counters dinâmicos. */
|
|
27
|
+
unread:
|
|
28
|
+
"inline-flex items-center justify-center min-w-[18px] h-[18px] px-[5px] rounded-radius-full bg-bg-brand text-fg-on-brand text-caption-xs font-bold leading-none shrink-0 [font-variant-numeric:tabular-nums]",
|
|
29
|
+
/** Row 3 — canal + tag + ID. */
|
|
30
|
+
rowBot: "flex items-center gap-gp-sm",
|
|
31
|
+
/** ID da conversa (canto inferior direito). */
|
|
32
|
+
id: "ml-auto text-caption-xs text-fg-subtle shrink-0 [font-variant-numeric:tabular-nums]",
|
|
33
|
+
},
|
|
34
|
+
variants: {
|
|
35
|
+
isActive: {
|
|
36
|
+
true: {
|
|
37
|
+
root: [
|
|
38
|
+
"bg-bg-muted dark:bg-bg-emphasis",
|
|
39
|
+
"before:content-[''] before:absolute before:left-0 before:top-[10px] before:bottom-[10px]",
|
|
40
|
+
"before:w-[3px] before:bg-bg-brand before:rounded-r-[2px] before:pointer-events-none",
|
|
41
|
+
].join(" "),
|
|
42
|
+
},
|
|
43
|
+
false: {
|
|
44
|
+
root: "hover:bg-bg-muted",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
defaultVariants: {
|
|
49
|
+
isActive: false,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type ConversationListItemVariants = VariantProps<
|
|
54
|
+
typeof conversationListItemStyles
|
|
55
|
+
>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Chip } from "@/components/ui/Chip";
|
|
2
|
+
import { ChannelDot } from "../ChannelDot/channel-dot";
|
|
3
|
+
import { PersonAvatar } from "../PersonAvatar";
|
|
4
|
+
import { conversationListItemStyles } from "./conversation-list-item.styles";
|
|
5
|
+
import type { ConversationListItemProps } from "./conversation-list-item.types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Item da fila de conversas. Avatar (36px) à esquerda + 3 rows de info à direita.
|
|
9
|
+
* Variant `isActive` adiciona strip lateral brand de 3px + bg muted.
|
|
10
|
+
*/
|
|
11
|
+
export function ConversationListItem({
|
|
12
|
+
conv,
|
|
13
|
+
isActive,
|
|
14
|
+
onSelect,
|
|
15
|
+
}: ConversationListItemProps) {
|
|
16
|
+
const s = conversationListItemStyles({ isActive });
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
onClick={() => onSelect(conv.id)}
|
|
22
|
+
className={s.root()}
|
|
23
|
+
aria-pressed={isActive}
|
|
24
|
+
>
|
|
25
|
+
<PersonAvatar initials={conv.initials} hex={conv.avatarHex} size="md" />
|
|
26
|
+
|
|
27
|
+
<div className={s.content()}>
|
|
28
|
+
<div className={s.rowTop()}>
|
|
29
|
+
<span className={s.name()}>{conv.name}</span>
|
|
30
|
+
<span className={s.time()}>{conv.time}</span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className={s.rowMid()}>
|
|
34
|
+
<span className={s.preview()}>{conv.last}</span>
|
|
35
|
+
{conv.unread > 0 && (
|
|
36
|
+
<span className={s.unread()} aria-label={`${conv.unread} não lidas`}>
|
|
37
|
+
{conv.unread}
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className={s.rowBot()}>
|
|
43
|
+
<ChannelDot channel={conv.channel} />
|
|
44
|
+
<Chip color={conv.tagKind} variant="soft" size="sm" shape="pill">
|
|
45
|
+
{conv.tag}
|
|
46
|
+
</Chip>
|
|
47
|
+
<span className={s.id()}>{conv.id}</span>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type DateSeparatorProps = {
|
|
2
|
+
/** Texto exibido — ex: "Hoje", "Ontem", "12 mar 2024". */
|
|
3
|
+
label: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pílula centralizada com label de data entre grupos de mensagens.
|
|
8
|
+
* Visual: chip arredondado com `bg-bg-muted`, tipografia tabular.
|
|
9
|
+
*/
|
|
10
|
+
export function DateSeparator({ label }: DateSeparatorProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex items-center justify-center my-pad-md">
|
|
13
|
+
<span className="px-pad-lg py-pad-2xs rounded-radius-full bg-bg-muted text-caption-sm text-fg-muted [font-variant-numeric:tabular-nums]">
|
|
14
|
+
{label}
|
|
15
|
+
</span>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type DetailFieldProps = {
|
|
4
|
+
/** Label à esquerda (text-fg-muted). */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Valor à direita — string, número ou ReactNode (Chip, link, etc). */
|
|
7
|
+
value: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Row label : value usada nas seções de detalhes (Contato, Atendimento).
|
|
12
|
+
* Layout flex baseline justify-between, quebra de palavra no valor.
|
|
13
|
+
*/
|
|
14
|
+
export function DetailField({ label, value }: DetailFieldProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex items-baseline justify-between gap-gp-md py-pad-2xs">
|
|
17
|
+
<span className="text-body-xs font-normal text-fg-muted shrink-0">{label}</span>
|
|
18
|
+
<span className="text-body-xs font-normal text-fg-default text-right break-words min-w-0">
|
|
19
|
+
{value || "—"}
|
|
20
|
+
</span>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export type DetailSectionProps = {
|
|
5
|
+
title: string;
|
|
6
|
+
open: boolean;
|
|
7
|
+
onToggle: () => void;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Seção colapsável da `DetailsColumn` (Contato / Atendimento / Histórico).
|
|
13
|
+
* Header clicável com chevron rotativo + corpo só monta quando `open`.
|
|
14
|
+
*/
|
|
15
|
+
export function DetailSection({
|
|
16
|
+
title,
|
|
17
|
+
open,
|
|
18
|
+
onToggle,
|
|
19
|
+
children,
|
|
20
|
+
}: DetailSectionProps) {
|
|
21
|
+
return (
|
|
22
|
+
<section className="flex flex-col border-b border-border-subtle pb-pad-xl last:border-b-0 last:pb-0">
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
onClick={onToggle}
|
|
26
|
+
aria-expanded={open}
|
|
27
|
+
className="flex items-center justify-between w-full px-pad-sm py-pad-sm -mx-pad-sm rounded-radius-md cursor-pointer text-left transition-colors duration-150 hover:bg-bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring-brand"
|
|
28
|
+
>
|
|
29
|
+
<span className="text-body-sm font-semibold text-fg-default">{title}</span>
|
|
30
|
+
<ChevronDown
|
|
31
|
+
size={14}
|
|
32
|
+
className={[
|
|
33
|
+
"text-fg-muted transition-transform duration-150",
|
|
34
|
+
open ? "" : "-rotate-90",
|
|
35
|
+
].join(" ")}
|
|
36
|
+
/>
|
|
37
|
+
</button>
|
|
38
|
+
{open && <div className="flex flex-col gap-gp-sm pt-pad-xs">{children}</div>}
|
|
39
|
+
</section>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DetailSection, type DetailSectionProps } from "./detail-section";
|