@agentforge-io/chat-react 3.0.0 → 4.0.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.
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.HelpTab = HelpTab;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ function HelpTab({ theme, adapter, title = 'Help' }) {
8
+ const [query, setQuery] = (0, react_1.useState)('');
9
+ const [debounced, setDebounced] = (0, react_1.useState)('');
10
+ const [results, setResults] = (0, react_1.useState)(null);
11
+ const [collections, setCollections] = (0, react_1.useState)(null);
12
+ const [loading, setLoading] = (0, react_1.useState)(false);
13
+ const [error, setError] = (0, react_1.useState)(null);
14
+ // Debounce query
15
+ (0, react_1.useEffect)(() => {
16
+ const t = setTimeout(() => setDebounced(query.trim()), 250);
17
+ return () => clearTimeout(t);
18
+ }, [query]);
19
+ // Load collections once
20
+ (0, react_1.useEffect)(() => {
21
+ let cancelled = false;
22
+ (async () => {
23
+ try {
24
+ const cols = await adapter.listCollections();
25
+ if (!cancelled)
26
+ setCollections(cols);
27
+ }
28
+ catch (e) {
29
+ if (!cancelled)
30
+ setError(e.message);
31
+ }
32
+ })();
33
+ return () => {
34
+ cancelled = true;
35
+ };
36
+ }, [adapter]);
37
+ // Search on debounced query change
38
+ (0, react_1.useEffect)(() => {
39
+ if (!debounced) {
40
+ setResults(null);
41
+ return;
42
+ }
43
+ let cancelled = false;
44
+ setLoading(true);
45
+ setError(null);
46
+ (async () => {
47
+ try {
48
+ const r = await adapter.search(debounced);
49
+ if (!cancelled)
50
+ setResults(r);
51
+ }
52
+ catch (e) {
53
+ if (!cancelled)
54
+ setError(e.message);
55
+ }
56
+ finally {
57
+ if (!cancelled)
58
+ setLoading(false);
59
+ }
60
+ })();
61
+ return () => {
62
+ cancelled = true;
63
+ };
64
+ }, [adapter, debounced]);
65
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
66
+ display: 'flex',
67
+ flexDirection: 'column',
68
+ height: '100%',
69
+ background: theme.surface,
70
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
71
+ padding: '14px 16px 10px',
72
+ borderBottom: '1px solid #e2e8f0',
73
+ flexShrink: 0,
74
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
75
+ fontSize: 16,
76
+ fontWeight: 600,
77
+ color: theme.onSurface,
78
+ textAlign: 'center',
79
+ marginBottom: 10,
80
+ }, children: title }), (0, jsx_runtime_1.jsx)(SearchInput, { value: query, onChange: setQuery, accent: theme.accent })] }), (0, jsx_runtime_1.jsxs)("div", { style: { flex: 1, minHeight: 0, overflowY: 'auto' }, children: [error && ((0, jsx_runtime_1.jsx)("div", { style: {
81
+ padding: 16,
82
+ color: '#dc2626',
83
+ fontSize: 13,
84
+ }, children: error })), debounced ? ((0, jsx_runtime_1.jsx)(ResultsList, { results: results, loading: loading, onWalkthroughOpen: adapter.onWalkthroughOpen, accent: theme.accent })) : ((0, jsx_runtime_1.jsx)(CollectionsList, { collections: collections, accent: theme.accent }))] })] }));
85
+ }
86
+ function SearchInput({ value, onChange, accent, }) {
87
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
88
+ display: 'flex',
89
+ alignItems: 'center',
90
+ gap: 8,
91
+ background: '#f1f5f9',
92
+ borderRadius: 10,
93
+ padding: '8px 12px',
94
+ }, children: [(0, jsx_runtime_1.jsx)(SearchIcon, { color: "#64748b" }), (0, jsx_runtime_1.jsx)("input", { value: value, onChange: (e) => onChange(e.target.value), placeholder: "Search for help", style: {
95
+ flex: 1,
96
+ border: 'none',
97
+ outline: 'none',
98
+ background: 'transparent',
99
+ fontSize: 14,
100
+ color: '#0f172a',
101
+ fontFamily: 'inherit',
102
+ }, "aria-label": "Search help" }), value && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => onChange(''), "aria-label": "Clear search", style: {
103
+ background: 'transparent',
104
+ border: 'none',
105
+ color: accent,
106
+ cursor: 'pointer',
107
+ fontSize: 18,
108
+ lineHeight: 1,
109
+ padding: 0,
110
+ }, children: "\u00D7" }))] }));
111
+ }
112
+ function CollectionsList({ collections, accent, }) {
113
+ if (collections === null) {
114
+ return (0, jsx_runtime_1.jsx)(Skeleton, { rows: 3 });
115
+ }
116
+ if (collections.length === 0) {
117
+ return ((0, jsx_runtime_1.jsx)("div", { style: { padding: 20, fontSize: 13, color: '#64748b', textAlign: 'center' }, children: "No help content yet." }));
118
+ }
119
+ return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { style: {
120
+ padding: '14px 16px 4px',
121
+ fontSize: 13,
122
+ fontWeight: 600,
123
+ color: '#0f172a',
124
+ }, children: [collections.length, " collection", collections.length === 1 ? '' : 's'] }), collections.map((c) => ((0, jsx_runtime_1.jsxs)("div", { style: {
125
+ padding: '14px 16px',
126
+ borderTop: '1px solid #e2e8f0',
127
+ display: 'flex',
128
+ alignItems: 'center',
129
+ gap: 10,
130
+ cursor: 'pointer',
131
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
132
+ fontSize: 12,
133
+ fontWeight: 700,
134
+ letterSpacing: '0.04em',
135
+ textTransform: 'uppercase',
136
+ color: '#0f172a',
137
+ marginBottom: 4,
138
+ }, children: c.name }), c.description && ((0, jsx_runtime_1.jsx)("div", { style: { fontSize: 13, color: '#334155', marginBottom: 4 }, children: c.description })), (0, jsx_runtime_1.jsxs)("div", { style: { fontSize: 12, color: '#94a3b8' }, children: [c.itemCount, " article", c.itemCount === 1 ? '' : 's'] })] }), (0, jsx_runtime_1.jsx)(ChevronRight, { color: accent })] }, c.id)))] }));
139
+ }
140
+ function ResultsList({ results, loading, onWalkthroughOpen, accent, }) {
141
+ if (loading && results === null) {
142
+ return (0, jsx_runtime_1.jsx)(Skeleton, { rows: 4 });
143
+ }
144
+ if (!results || results.length === 0) {
145
+ return ((0, jsx_runtime_1.jsx)("div", { style: { padding: 20, fontSize: 13, color: '#64748b', textAlign: 'center' }, children: "No matches." }));
146
+ }
147
+ return ((0, jsx_runtime_1.jsx)("ul", { style: { listStyle: 'none', margin: 0, padding: 0 }, children: results.map((r) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)(ResultRow, { result: r, onWalkthroughOpen: onWalkthroughOpen, accent: accent }) }, r.id))) }));
148
+ }
149
+ function ResultRow({ result, onWalkthroughOpen, accent, }) {
150
+ const body = ((0, jsx_runtime_1.jsxs)("div", { style: {
151
+ padding: '12px 16px',
152
+ borderTop: '1px solid #e2e8f0',
153
+ display: 'flex',
154
+ alignItems: 'center',
155
+ gap: 10,
156
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
157
+ fontSize: 14,
158
+ fontWeight: 600,
159
+ color: '#0f172a',
160
+ marginBottom: 2,
161
+ }, children: result.title }), result.excerpt && ((0, jsx_runtime_1.jsx)("div", { style: {
162
+ fontSize: 12,
163
+ color: '#64748b',
164
+ overflow: 'hidden',
165
+ textOverflow: 'ellipsis',
166
+ whiteSpace: 'nowrap',
167
+ }, children: result.excerpt }))] }), result.kind === 'walkthrough' ? ((0, jsx_runtime_1.jsx)(PlayIcon, { color: accent })) : ((0, jsx_runtime_1.jsx)(ChevronRight, { color: accent }))] }));
168
+ if (result.kind === 'walkthrough') {
169
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
170
+ if (onWalkthroughOpen) {
171
+ onWalkthroughOpen(result.id);
172
+ }
173
+ else if (typeof console !== 'undefined') {
174
+ // Loud warning so the integrator notices the missing wire.
175
+ console.warn(`[MessengerShell] HelpAdapter.onWalkthroughOpen not provided; clicking walkthrough result "${result.id}" is a no-op.`);
176
+ }
177
+ }, style: {
178
+ all: 'unset',
179
+ display: 'block',
180
+ width: '100%',
181
+ cursor: 'pointer',
182
+ fontFamily: 'inherit',
183
+ }, children: body }));
184
+ }
185
+ return ((0, jsx_runtime_1.jsx)("a", { href: result.href, target: "_blank", rel: "noopener noreferrer", style: { display: 'block', textDecoration: 'none' }, children: body }));
186
+ }
187
+ function Skeleton({ rows }) {
188
+ return ((0, jsx_runtime_1.jsx)("div", { children: Array.from({ length: rows }).map((_, i) => ((0, jsx_runtime_1.jsxs)("div", { style: {
189
+ padding: '14px 16px',
190
+ borderTop: i === 0 ? 'none' : '1px solid #e2e8f0',
191
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
192
+ height: 12,
193
+ width: '40%',
194
+ background: '#e2e8f0',
195
+ borderRadius: 4,
196
+ marginBottom: 8,
197
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
198
+ height: 10,
199
+ width: '70%',
200
+ background: '#e2e8f0',
201
+ borderRadius: 4,
202
+ } })] }, i))) }));
203
+ }
204
+ function SearchIcon({ color }) {
205
+ return ((0, jsx_runtime_1.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("circle", { cx: "11", cy: "11", r: "7", stroke: color, strokeWidth: "1.8" }), (0, jsx_runtime_1.jsx)("path", { d: "m20 20-3.5-3.5", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" })] }));
206
+ }
207
+ function ChevronRight({ color }) {
208
+ return ((0, jsx_runtime_1.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: (0, jsx_runtime_1.jsx)("path", { d: "M9 6l6 6-6 6", stroke: color, strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }));
209
+ }
210
+ function PlayIcon({ color }) {
211
+ return ((0, jsx_runtime_1.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "9", stroke: color, strokeWidth: "1.8" }), (0, jsx_runtime_1.jsx)("path", { d: "M10 8.5v7l6-3.5-6-3.5Z", fill: color })] }));
212
+ }
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ResolvedTheme } from '../theme';
3
+ import type { HomeAction } from '../adapters';
4
+ export interface HomeTabProps {
5
+ theme: ResolvedTheme;
6
+ brand?: ReactNode;
7
+ onClose?: () => void;
8
+ /** First-name and emoji shown in the greeting headline. When `name`
9
+ * is absent we render "Hi 👋 / How can I help?" so the page never
10
+ * shows a half-empty greeting. */
11
+ greeting?: {
12
+ name?: string;
13
+ emoji?: string;
14
+ };
15
+ /** Question shown under the greeting. Defaults to "How can I help?". */
16
+ question?: string;
17
+ /** Support team avatars rendered in the header. Up to 3. */
18
+ avatars?: Array<{
19
+ src: string;
20
+ alt: string;
21
+ }>;
22
+ /** The "Send us a message" CTA. When omitted we render a default one
23
+ * that switches to the Messages tab on click. */
24
+ onSendMessage?: () => void;
25
+ sendMessageLabel?: string;
26
+ /** Vertical list of action cards under the CTA. */
27
+ actions?: HomeAction[];
28
+ /** Optional extra content rendered above the tabs (e.g. a footer
29
+ * with featured content). Renders inside the scroll area so it
30
+ * scrolls with the page. */
31
+ footer?: ReactNode;
32
+ }
33
+ export declare function HomeTab({ theme, brand, onClose, greeting, question, avatars, onSendMessage, sendMessageLabel, actions, footer, }: HomeTabProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.HomeTab = HomeTab;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const MessengerHeader_1 = require("../MessengerHeader");
7
+ function HomeTab({ theme, brand, onClose, greeting, question = 'How can I help?', avatars, onSendMessage, sendMessageLabel = 'Send us a message', actions = [], footer, }) {
8
+ const name = greeting?.name?.trim();
9
+ const emoji = greeting?.emoji ?? '👋';
10
+ return ((0, jsx_runtime_1.jsx)("div", { style: {
11
+ display: 'flex',
12
+ flexDirection: 'column',
13
+ height: '100%',
14
+ background: theme.surface,
15
+ overflow: 'hidden',
16
+ }, children: (0, jsx_runtime_1.jsxs)("div", { style: {
17
+ flex: 1,
18
+ minHeight: 0,
19
+ overflowY: 'auto',
20
+ // Negative margin pulls the scrollable content under the
21
+ // header's bottom radius so the rounded corner looks like
22
+ // a real shape (the content scrolls under it).
23
+ }, children: [(0, jsx_runtime_1.jsx)(MessengerHeader_1.MessengerHeader, { theme: theme, brand: brand, avatars: avatars, onClose: onClose, bottomRadius: 24, greeting: (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: 12 }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
24
+ fontSize: 22,
25
+ fontWeight: 500,
26
+ opacity: 0.7,
27
+ marginBottom: 4,
28
+ }, children: [name ? `Hi ${name}` : 'Hi', " ", emoji] }), (0, jsx_runtime_1.jsx)("div", { style: { fontSize: 26, fontWeight: 700 }, children: question })] }) }), (0, jsx_runtime_1.jsxs)("div", { style: {
29
+ display: 'flex',
30
+ flexDirection: 'column',
31
+ gap: 10,
32
+ padding: '12px 12px 16px',
33
+ // Pull up under the header's rounded bottom so the first
34
+ // card sits visually inside the curve.
35
+ marginTop: -16,
36
+ position: 'relative',
37
+ zIndex: 1,
38
+ }, children: [onSendMessage !== undefined && ((0, jsx_runtime_1.jsx)(ActionCard, { accent: theme.accent, variant: "primary", label: sendMessageLabel, onClick: onSendMessage, trailing: (0, jsx_runtime_1.jsx)(SendIcon, { color: theme.accent }) })), actions.map((a) => ((0, jsx_runtime_1.jsx)(ActionCard, { accent: theme.accent, variant: "secondary", icon: a.icon, label: a.label, href: a.href, onClick: a.onClick, trailing: (0, jsx_runtime_1.jsx)(TrailingIcon, { kind: a.trailingIcon ?? (a.href ? 'external' : 'arrow'), color: theme.accent }) }, a.id))), footer && (0, jsx_runtime_1.jsx)("div", { style: { paddingTop: 8 }, children: footer })] })] }) }));
39
+ }
40
+ function ActionCard({ accent, variant, icon, label, href, onClick, trailing, }) {
41
+ const baseStyle = {
42
+ display: 'flex',
43
+ alignItems: 'center',
44
+ gap: 10,
45
+ padding: '14px 16px',
46
+ background: '#fff',
47
+ border: '1px solid #e2e8f0',
48
+ borderRadius: 12,
49
+ boxShadow: '0 1px 2px rgba(15, 23, 42, 0.06)',
50
+ color: '#0f172a',
51
+ textDecoration: 'none',
52
+ fontSize: 15,
53
+ fontWeight: variant === 'primary' ? 600 : 500,
54
+ cursor: onClick || href ? 'pointer' : 'default',
55
+ fontFamily: 'inherit',
56
+ };
57
+ const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [icon && ((0, jsx_runtime_1.jsx)("span", { style: { fontSize: 18, display: 'flex', alignItems: 'center' }, children: icon })), (0, jsx_runtime_1.jsx)("span", { style: { flex: 1, minWidth: 0 }, children: label }), trailing] }));
58
+ if (href) {
59
+ return ((0, jsx_runtime_1.jsx)("a", { href: href, target: "_blank", rel: "noopener noreferrer", style: baseStyle, children: content }));
60
+ }
61
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClick, style: baseStyle, children: content }));
62
+ }
63
+ function SendIcon({ color }) {
64
+ return ((0, jsx_runtime_1.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: (0, jsx_runtime_1.jsx)("path", { d: "M4 12l16-8-6 16-2.5-6L4 12Z", fill: color, stroke: color, strokeWidth: "1.2", strokeLinejoin: "round" }) }));
65
+ }
66
+ function TrailingIcon({ kind, color, }) {
67
+ if (kind === 'none')
68
+ return null;
69
+ if (kind === 'external') {
70
+ return ((0, jsx_runtime_1.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: (0, jsx_runtime_1.jsx)("path", { d: "M14 5h5v5M19 5l-9 9M5 7v12h12", stroke: color, strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }));
71
+ }
72
+ return ((0, jsx_runtime_1.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: (0, jsx_runtime_1.jsx)("path", { d: "M9 6l6 6-6 6", stroke: color, strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }));
73
+ }
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type ChatPanelProps } from '../../ChatPanel';
3
+ import type { ResolvedTheme } from '../theme';
4
+ /**
5
+ * Messages tab. Thin wrapper around `ChatPanel` so the shell can
6
+ * decorate the chat with a tab-specific header (title only, no
7
+ * greeting block — the chat needs the height for its message list).
8
+ *
9
+ * We don't recompose ChatPanel — that would duplicate every interaction
10
+ * (input, scroll, bubble rendering). The shell just sits above it.
11
+ */
12
+ export interface MessagesTabProps {
13
+ theme: ResolvedTheme;
14
+ /** Title shown in the simple top bar. Defaults to "Messages". */
15
+ title?: string;
16
+ /** Rendered on the right side of the top bar (e.g. close button). */
17
+ topBarAction?: ReactNode;
18
+ /** Forwarded to ChatPanel. */
19
+ panelProps?: Omit<ChatPanelProps, 'style' | 'className'>;
20
+ }
21
+ export declare function MessagesTab({ theme, title, topBarAction, panelProps, }: MessagesTabProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MessagesTab = MessagesTab;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const ChatPanel_1 = require("../../ChatPanel");
7
+ function MessagesTab({ theme, title = 'Messages', topBarAction, panelProps, }) {
8
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
9
+ display: 'flex',
10
+ flexDirection: 'column',
11
+ height: '100%',
12
+ background: theme.surface,
13
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
14
+ display: 'flex',
15
+ alignItems: 'center',
16
+ justifyContent: 'space-between',
17
+ padding: '14px 16px',
18
+ borderBottom: '1px solid #e2e8f0',
19
+ flexShrink: 0,
20
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
21
+ fontSize: 16,
22
+ fontWeight: 600,
23
+ color: theme.onSurface,
24
+ }, children: title }), topBarAction] }), (0, jsx_runtime_1.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: (0, jsx_runtime_1.jsx)(ChatPanel_1.ChatPanel, { ...panelProps,
25
+ // The shell already provides the surface + border, so we
26
+ // strip the panel's own chrome to avoid double-bordering.
27
+ style: {
28
+ height: '100%',
29
+ border: 'none',
30
+ borderRadius: 0,
31
+ } }) })] }));
32
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ResolvedTheme } from '../theme';
3
+ import type { NewsAdapter } from '../adapters';
4
+ /**
5
+ * News tab. Shows a feed of announcements with read/unread state and
6
+ * an empty-state when there's nothing new. The shell delegates all
7
+ * storage / persistence to the adapter.
8
+ *
9
+ * Ordering: pinned items first, then by publishedAt desc.
10
+ */
11
+ export interface NewsTabProps {
12
+ theme: ResolvedTheme;
13
+ adapter: NewsAdapter;
14
+ title?: string;
15
+ /** Header section content. The shell renders a small "Latest" label
16
+ * with avatars by default, but the consumer can override. */
17
+ header?: ReactNode;
18
+ /** Avatars rendered in the default header. */
19
+ teamAvatars?: Array<{
20
+ src: string;
21
+ alt: string;
22
+ }>;
23
+ /** Team name rendered in the default header. */
24
+ teamLabel?: string;
25
+ }
26
+ export declare function NewsTab({ theme, adapter, title, header, teamAvatars, teamLabel, }: NewsTabProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.NewsTab = NewsTab;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ function NewsTab({ theme, adapter, title = 'News', header, teamAvatars, teamLabel, }) {
8
+ const [items, setItems] = (0, react_1.useState)(null);
9
+ const [error, setError] = (0, react_1.useState)(null);
10
+ (0, react_1.useEffect)(() => {
11
+ let cancelled = false;
12
+ (async () => {
13
+ try {
14
+ const list = await adapter.listAnnouncements();
15
+ if (cancelled)
16
+ return;
17
+ // Sort once on receive so the render path stays simple.
18
+ const sorted = [...list].sort((a, b) => {
19
+ if (a.pinned !== b.pinned)
20
+ return a.pinned ? -1 : 1;
21
+ return (new Date(b.publishedAt).getTime() -
22
+ new Date(a.publishedAt).getTime());
23
+ });
24
+ setItems(sorted);
25
+ }
26
+ catch (e) {
27
+ if (!cancelled)
28
+ setError(e.message);
29
+ }
30
+ })();
31
+ return () => {
32
+ cancelled = true;
33
+ };
34
+ }, [adapter]);
35
+ async function handleClick(item) {
36
+ if (!item.read && adapter.markRead) {
37
+ // Optimistic update; we silently ignore failures because the
38
+ // user already saw the content — flipping back to unread would
39
+ // be more confusing than a slightly stale badge.
40
+ setItems((prev) => prev
41
+ ? prev.map((i) => (i.id === item.id ? { ...i, read: true } : i))
42
+ : prev);
43
+ try {
44
+ await adapter.markRead(item.id);
45
+ }
46
+ catch {
47
+ /* swallow */
48
+ }
49
+ }
50
+ if (item.href) {
51
+ window.open(item.href, '_blank', 'noopener,noreferrer');
52
+ }
53
+ }
54
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
55
+ display: 'flex',
56
+ flexDirection: 'column',
57
+ height: '100%',
58
+ background: theme.surface,
59
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
60
+ padding: '14px 16px',
61
+ borderBottom: '1px solid #e2e8f0',
62
+ fontSize: 16,
63
+ fontWeight: 600,
64
+ color: theme.onSurface,
65
+ textAlign: 'center',
66
+ flexShrink: 0,
67
+ }, children: title }), (0, jsx_runtime_1.jsxs)("div", { style: { flex: 1, minHeight: 0, overflowY: 'auto' }, children: [header ?? ((0, jsx_runtime_1.jsx)(DefaultHeader, { teamAvatars: teamAvatars, teamLabel: teamLabel })), error && ((0, jsx_runtime_1.jsx)("div", { style: { padding: 16, color: '#dc2626', fontSize: 13 }, children: error })), items === null ? ((0, jsx_runtime_1.jsx)(Skeleton, { rows: 2 })) : items.length === 0 ? ((0, jsx_runtime_1.jsx)(EmptyState, {})) : ((0, jsx_runtime_1.jsxs)("div", { style: { padding: '0 16px 24px' }, children: [items.map((item) => ((0, jsx_runtime_1.jsx)(AnnouncementCard, { item: item, onClick: () => void handleClick(item), accent: theme.accent }, item.id))), (0, jsx_runtime_1.jsx)(CaughtUpFooter, {})] }))] })] }));
68
+ }
69
+ function DefaultHeader({ teamAvatars, teamLabel, }) {
70
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
71
+ display: 'flex',
72
+ alignItems: 'center',
73
+ justifyContent: 'space-between',
74
+ padding: '16px',
75
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 18, fontWeight: 700, color: '#0f172a' }, children: "Latest" }), teamLabel && ((0, jsx_runtime_1.jsxs)("div", { style: { fontSize: 13, color: '#64748b' }, children: ["From ", teamLabel] }))] }), teamAvatars && teamAvatars.length > 0 && ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex' }, children: teamAvatars.slice(0, 3).map((a, i) => ((0, jsx_runtime_1.jsx)("img", { src: a.src, alt: a.alt, style: {
76
+ width: 30,
77
+ height: 30,
78
+ borderRadius: '50%',
79
+ border: '2px solid #fff',
80
+ marginLeft: i === 0 ? 0 : -8,
81
+ objectFit: 'cover',
82
+ } }, a.src))) }))] }));
83
+ }
84
+ function AnnouncementCard({ item, onClick, accent, }) {
85
+ return ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: onClick, style: {
86
+ all: 'unset',
87
+ display: 'block',
88
+ width: '100%',
89
+ marginBottom: 12,
90
+ padding: 14,
91
+ background: '#fff',
92
+ border: '1px solid #e2e8f0',
93
+ borderRadius: 12,
94
+ boxShadow: '0 1px 2px rgba(15, 23, 42, 0.05)',
95
+ cursor: 'pointer',
96
+ fontFamily: 'inherit',
97
+ }, children: [item.imageUrl && ((0, jsx_runtime_1.jsx)("img", { src: item.imageUrl, alt: "", style: {
98
+ width: '100%',
99
+ aspectRatio: '16 / 9',
100
+ objectFit: 'cover',
101
+ borderRadius: 8,
102
+ marginBottom: 10,
103
+ } })), (0, jsx_runtime_1.jsx)("div", { style: {
104
+ display: 'flex',
105
+ alignItems: 'center',
106
+ gap: 8,
107
+ marginBottom: 6,
108
+ }, children: !item.read && ((0, jsx_runtime_1.jsx)("span", { style: {
109
+ display: 'inline-block',
110
+ padding: '2px 8px',
111
+ fontSize: 11,
112
+ fontWeight: 600,
113
+ background: `${accent}22`,
114
+ color: accent,
115
+ borderRadius: 10,
116
+ }, children: "New" })) }), (0, jsx_runtime_1.jsx)("div", { style: {
117
+ fontSize: 15,
118
+ fontWeight: 700,
119
+ color: '#0f172a',
120
+ marginBottom: 6,
121
+ lineHeight: 1.3,
122
+ }, children: item.title }), (0, jsx_runtime_1.jsx)("div", { style: {
123
+ fontSize: 13,
124
+ color: '#475569',
125
+ lineHeight: 1.45,
126
+ display: '-webkit-box',
127
+ WebkitLineClamp: 2,
128
+ WebkitBoxOrient: 'vertical',
129
+ overflow: 'hidden',
130
+ }, children: item.body })] }));
131
+ }
132
+ function CaughtUpFooter() {
133
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
134
+ padding: '24px 16px',
135
+ textAlign: 'center',
136
+ color: '#94a3b8',
137
+ fontSize: 13,
138
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
139
+ display: 'inline-flex',
140
+ alignItems: 'center',
141
+ justifyContent: 'center',
142
+ width: 36,
143
+ height: 36,
144
+ borderRadius: '50%',
145
+ background: '#f1f5f9',
146
+ marginBottom: 8,
147
+ }, children: "\u2713" }), (0, jsx_runtime_1.jsx)("div", { children: "You're all caught up!" })] }));
148
+ }
149
+ function EmptyState() {
150
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
151
+ padding: '60px 24px',
152
+ textAlign: 'center',
153
+ color: '#64748b',
154
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 32, marginBottom: 8 }, children: "\uD83D\uDCE3" }), (0, jsx_runtime_1.jsx)("div", { style: { fontSize: 14 }, children: "No announcements yet." })] }));
155
+ }
156
+ function Skeleton({ rows }) {
157
+ return ((0, jsx_runtime_1.jsx)("div", { style: { padding: '0 16px' }, children: Array.from({ length: rows }).map((_, i) => ((0, jsx_runtime_1.jsxs)("div", { style: {
158
+ background: '#fff',
159
+ border: '1px solid #e2e8f0',
160
+ borderRadius: 12,
161
+ padding: 14,
162
+ marginBottom: 12,
163
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
164
+ height: 14,
165
+ width: '70%',
166
+ background: '#e2e8f0',
167
+ borderRadius: 4,
168
+ marginBottom: 8,
169
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
170
+ height: 10,
171
+ width: '90%',
172
+ background: '#e2e8f0',
173
+ borderRadius: 4,
174
+ } })] }, i))) }));
175
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Resolves the extended ChatTheme into a flat set of CSS values the
3
+ * Messenger components use. Keeping the resolution in one place means
4
+ * the same source-of-truth defaults flow through Header / Footer / tabs
5
+ * — no component invents its own "what if primaryColor is missing".
6
+ */
7
+ import type { ChatTheme } from '@agentforge-io/chat-sdk';
8
+ export interface ResolvedTheme {
9
+ primary: string;
10
+ primarySoft: string;
11
+ headerBg: string;
12
+ surface: string;
13
+ accent: string;
14
+ onHeader: string;
15
+ onSurface: string;
16
+ }
17
+ export declare function resolveTheme(theme: ChatTheme | undefined): ResolvedTheme;
18
+ /** Same helper ChatPanel uses — duplicated locally to avoid coupling
19
+ * the messenger module to ChatPanel's internals. Tiny enough to copy. */
20
+ export declare function hexToRgba(hex: string, alpha: number): string;
21
+ /**
22
+ * Lighten/darken a hex color by a percentage. Negative = darker.
23
+ * Used only for the auto-derived header gradient — when the consumer
24
+ * supplies `headerGradient` we never hit this code path.
25
+ */
26
+ export declare function shade(hex: string, percent: number): string;