@actioneer/ads 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/DESIGN.md +80 -86
  2. package/README.md +6 -0
  3. package/components/ai-elements/chart.js +20 -20
  4. package/components/ai-elements/inline-citation.js +50 -50
  5. package/components/ai-elements/message.js +1 -1
  6. package/components/ai-elements/tool.js +5 -5
  7. package/components/alert.d.ts +5 -4
  8. package/components/alert.js +91 -79
  9. package/components/answer.js +1 -1
  10. package/components/assistant-ui/action-bar.js +1 -1
  11. package/components/assistant-ui/attachment.js +68 -49
  12. package/components/assistant-ui/composer-add-context.js +1 -1
  13. package/components/assistant-ui/composer-trigger-popover.js +1 -1
  14. package/components/assistant-ui/composer.js +13 -13
  15. package/components/assistant-ui/message-timing.js +6 -6
  16. package/components/assistant-ui/selection-toolbar.js +5 -5
  17. package/components/assistant-ui/sources.js +15 -15
  18. package/components/assistant-ui/tooltip-icon-button.js +18 -14
  19. package/components/audio-waveform.d.ts +21 -8
  20. package/components/audio-waveform.js +371 -94
  21. package/components/autocomplete.js +9 -9
  22. package/components/breadcrumbs.js +32 -38
  23. package/components/button.js +83 -73
  24. package/components/card.js +1 -1
  25. package/components/chart.js +732 -673
  26. package/components/chip.js +37 -35
  27. package/components/command-palette.js +12 -12
  28. package/components/connector-card.d.ts +1 -2
  29. package/components/connector-card.js +17 -17
  30. package/components/date-range-picker.js +1 -2
  31. package/components/drawer.js +9 -10
  32. package/components/dropdown.js +1 -1
  33. package/components/empty-state.d.ts +2 -0
  34. package/components/empty-state.js +17 -15
  35. package/components/field.js +9 -10
  36. package/components/file-dropzone.d.ts +6 -3
  37. package/components/file-dropzone.js +72 -60
  38. package/components/infinite-canvas.js +9 -9
  39. package/components/modal.js +1 -1
  40. package/components/page-body.js +8 -8
  41. package/components/popover.js +11 -11
  42. package/components/progress-circle.d.ts +1 -1
  43. package/components/progress-circle.js +57 -48
  44. package/components/radio.d.ts +1 -1
  45. package/components/radio.js +53 -53
  46. package/components/segmented.d.ts +1 -0
  47. package/components/segmented.js +34 -36
  48. package/components/select.js +2 -2
  49. package/components/selection-action-bar.js +32 -31
  50. package/components/setting-row.js +10 -10
  51. package/components/sidebar.js +74 -73
  52. package/components/slider.js +5 -5
  53. package/components/stepper.d.ts +1 -1
  54. package/components/stepper.js +1 -1
  55. package/components/switch.js +10 -10
  56. package/components/table.d.ts +17 -5
  57. package/components/table.js +559 -405
  58. package/components/tabs.js +41 -35
  59. package/components/timeline.js +3 -3
  60. package/components/toast.js +53 -56
  61. package/components/ui/button.js +4 -4
  62. package/components/ui/dialog.js +1 -1
  63. package/components/viz-charts.js +263 -221
  64. package/examples/README.md +30 -0
  65. package/examples/agents-screen.tsx +477 -0
  66. package/examples/settings-screen.tsx +90 -0
  67. package/examples/workspace-header.tsx +71 -0
  68. package/lib/icons.d.ts +8 -0
  69. package/lib/icons.js +171 -118
  70. package/llms.txt +16 -16
  71. package/manifest.json +178 -54
  72. package/package.json +1 -1
  73. package/styles.css +60 -72
  74. package/theme.css +55 -63
@@ -0,0 +1,30 @@
1
+ # ADS sample screens
2
+
3
+ Full screens built only from `@actioneer/ads` exports. Open them to see how the
4
+ components are meant to be composed, or copy one into your app as a starting
5
+ point and swap the sample data for your own.
6
+
7
+ Before rendering any of them, finish the package setup in the main README:
8
+ import `@actioneer/ads/styles.css` right after `tailwindcss`, and wrap your app
9
+ root in `MotionConfig`, `ToastProvider` and `TooltipProvider`.
10
+
11
+ | File | Export | What it shows |
12
+ |---|---|---|
13
+ | `agents-screen.tsx` | `AgentsScreen` | A full list page: `PageHeader` with a primary action, a summary strip, `Tabs` with counts leading a `SearchDisclosure`, a filter `Dropdown`, a sortable `Table` with clickable rows and a pinned action column, per-row actions with `Tooltip`s, and filtered `EmptyState`s. |
14
+ | `settings-screen.tsx` | `SettingsScreen` | A settings card: `Card` header with a status `Badge`, an `InputField`, a `SettingList` of `SettingRow`s with `Switch` and `Button` controls, and one primary action. |
15
+ | `workspace-header.tsx` | `WorkspaceHeaderScreen` | A page header with a status `Badge` and trailing actions, then `Tabs` with counts whose panels hold `Card`s. |
16
+
17
+ ```tsx
18
+ // app/agents/page.tsx
19
+ "use client";
20
+
21
+ import { AgentsScreen } from "./agents-screen"; // copied from node_modules/@actioneer/ads/examples
22
+
23
+ export default function Page() {
24
+ return <AgentsScreen />;
25
+ }
26
+ ```
27
+
28
+ These are the same compositions the ADS Storybook renders under
29
+ **Recipes**, so what you see there is what these files produce. Icons come from
30
+ `@actioneer/ads/lib/icons`; never install a second icon package.
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Agents screen — a full list page built only from ADS exports.
3
+ *
4
+ * What it shows: a PageHeader with a primary action, a summary strip, Tabs
5
+ * with counts leading a trailing SearchDisclosure, a filter Dropdown, a
6
+ * sortable Table whose rows open a record and keep a pinned action column,
7
+ * per-row Dropdown actions with Tooltips, and filtered EmptyStates.
8
+ *
9
+ * Copy it into your app and replace the sample AGENTS data with your own.
10
+ */
11
+ import * as React from "react";
12
+ import {
13
+ Avatar,
14
+ Button,
15
+ Dropdown,
16
+ DropdownContent,
17
+ DropdownItem,
18
+ DropdownTrigger,
19
+ EmptyState,
20
+ PageHeader,
21
+ SearchDisclosure,
22
+ Tab,
23
+ TabList,
24
+ Table,
25
+ TableBody,
26
+ TableCell,
27
+ TableColumn,
28
+ TableHeader,
29
+ TableRow,
30
+ Tabs,
31
+ Tooltip,
32
+ type SortDescriptor,
33
+ } from "@actioneer/ads";
34
+ import {
35
+ Close,
36
+ Copy,
37
+ Eye,
38
+ Filter,
39
+ MoreHorizontal,
40
+ PhoneIncoming,
41
+ Search,
42
+ Trash,
43
+ } from "@actioneer/ads/lib/icons";
44
+
45
+ export type AgentStatus = "deployed" | "not-deployed";
46
+ export type AgentUsage = "all" | "deployed" | "not-deployed" | "hotlines" | "campaigns";
47
+ export type ManualFilter = "original" | "duplicated" | "has-calls" | "no-calls";
48
+
49
+ export interface AgentRecord {
50
+ id: string;
51
+ name: string;
52
+ type: string;
53
+ updated: string;
54
+ updatedSort: number;
55
+ status: AgentStatus;
56
+ lineage?: string;
57
+ hotlines: number;
58
+ campaigns: number;
59
+ steps: number;
60
+ calls: number;
61
+ }
62
+
63
+ export const AGENTS: AgentRecord[] = [
64
+ { id: "simran", name: "Simran", type: "Voice Agent", updated: "Aug 25, 2026", updatedSort: 20260825, status: "deployed", hotlines: 0, campaigns: 1, steps: 0, calls: 0 },
65
+ { id: "vivaan", name: "Vivaan", type: "Voice Agent", updated: "Aug 26, 2026", updatedSort: 20260826, status: "not-deployed", lineage: "Simran", hotlines: 0, campaigns: 0, steps: 0, calls: 0 },
66
+ { id: "manish", name: "Manish", type: "Voice Agent", updated: "Aug 26, 2026", updatedSort: 20260826, status: "not-deployed", lineage: "Esha", hotlines: 0, campaigns: 0, steps: 0, calls: 0 },
67
+ { id: "esha", name: "Esha", type: "Voice Agent", updated: "Aug 26, 2026", updatedSort: 20260826, status: "not-deployed", lineage: "Simran", hotlines: 0, campaigns: 0, steps: 0, calls: 0 },
68
+ { id: "ananya", name: "Ananya", type: "KYC completion support", updated: "Aug 6, 2026", updatedSort: 20260806, status: "deployed", hotlines: 0, campaigns: 5, steps: 28, calls: 96 },
69
+ { id: "aarohi", name: "Aarohi", type: "EMI due-date reminder", updated: "Aug 6, 2026", updatedSort: 20260806, status: "deployed", hotlines: 0, campaigns: 4, steps: 28, calls: 96 },
70
+ ];
71
+
72
+ export const USAGE_TABS = [
73
+ { value: "all", label: "All", count: 6 },
74
+ { value: "deployed", label: "Deployed", count: 3 },
75
+ { value: "not-deployed", label: "Not yet deployed", count: 3 },
76
+ { value: "hotlines", label: "On hotlines", count: 0 },
77
+ { value: "campaigns", label: "In campaigns", count: 3 },
78
+ ] as const;
79
+
80
+ export const MANUAL_FILTERS = [
81
+ { value: "original", label: "Original agents" },
82
+ { value: "duplicated", label: "Duplicated agents" },
83
+ { value: "has-calls", label: "Has calls" },
84
+ { value: "no-calls", label: "No calls" },
85
+ ] as const;
86
+
87
+ export function ScreenFrame({ children }: { children: React.ReactNode }) {
88
+ return <div className="min-h-screen bg-card p-5 text-foreground">{children}</div>;
89
+ }
90
+
91
+ export function AgentStatusAvatar({ name, status }: { name: string; status: AgentStatus }) {
92
+ const label = status === "deployed" ? "Deployed" : "Not yet deployed";
93
+ return (
94
+ <span className="relative inline-flex shrink-0">
95
+ <Avatar name={name} size="sm" />
96
+ <span
97
+ aria-hidden="true"
98
+ className={status === "deployed"
99
+ ? "absolute bottom-0 right-0 size-2 rounded-full bg-success ring-1 ring-card"
100
+ : "absolute bottom-0 right-0 size-2 rounded-full bg-wg6 ring-1 ring-card"}
101
+ />
102
+ <span className="sr-only">{label}</span>
103
+ </span>
104
+ );
105
+ }
106
+
107
+ export function AgentIdentity({ agent }: { agent: AgentRecord }) {
108
+ return (
109
+ <div className="flex min-w-0 items-center gap-3">
110
+ <AgentStatusAvatar name={agent.name} status={agent.status} />
111
+ <div className="min-w-0">
112
+ <div className="truncate text-m font-medium text-foreground">{agent.name}</div>
113
+ {agent.lineage && (
114
+ <div className="truncate text-s text-secondary">
115
+ Copy of {agent.lineage}
116
+ </div>
117
+ )}
118
+ </div>
119
+ </div>
120
+ );
121
+ }
122
+
123
+ export function AgentSummaryStripContent() {
124
+ const items = [
125
+ { label: "Deployed", value: "3", detail: "of 6 agents" },
126
+ { label: "In campaigns", value: "3", detail: "ready to engage" },
127
+ { label: "Call activity", value: "192", detail: "all-time calls" },
128
+ ];
129
+ return (
130
+ <section
131
+ aria-label="Agent summary"
132
+ className="grid min-h-20 grid-cols-1 gap-4 rounded-m border border-table-stroke px-4 py-5 sm:grid-cols-3 sm:gap-0"
133
+ >
134
+ {items.map((item, index) => (
135
+ <div
136
+ key={item.label}
137
+ className={index === 0 ? "min-w-0 sm:pr-6" : "min-w-0 border-table-stroke sm:border-l sm:px-6"}
138
+ >
139
+ <p className="text-s font-medium uppercase tracking-wide text-table-header-foreground">
140
+ {item.label}
141
+ </p>
142
+ <p className="mt-2 flex items-baseline gap-2">
143
+ <span className="text-xl font-medium leading-none tabular-nums text-foreground">{item.value}</span>
144
+ <span className="text-m text-muted-foreground">{item.detail}</span>
145
+ </p>
146
+ </div>
147
+ ))}
148
+ </section>
149
+ );
150
+ }
151
+
152
+ export function AgentRowActions({
153
+ name,
154
+ defaultOpen = false,
155
+ focusTrigger = false,
156
+ }: {
157
+ name: string;
158
+ defaultOpen?: boolean;
159
+ focusTrigger?: boolean;
160
+ }) {
161
+ return (
162
+ <Dropdown defaultOpen={defaultOpen}>
163
+ <Tooltip content={`Actions for ${name}`}>
164
+ <span className="inline-flex">
165
+ <DropdownTrigger>
166
+ <Button
167
+ autoFocus={focusTrigger}
168
+ variant="ghost"
169
+ size="icon-sm"
170
+ aria-label={`Actions for ${name}`}
171
+ className="enabled:hover:bg-card enabled:hover:text-foreground enabled:active:bg-card enabled:active:text-foreground"
172
+ >
173
+ <MoreHorizontal aria-hidden="true" />
174
+ </Button>
175
+ </DropdownTrigger>
176
+ </span>
177
+ </Tooltip>
178
+ <DropdownContent align="end" autoFocusFirstItem={defaultOpen}>
179
+ <DropdownItem startContent={<Eye aria-hidden="true" />}>See details</DropdownItem>
180
+ <DropdownItem startContent={<Copy aria-hidden="true" />}>Duplicate</DropdownItem>
181
+ <DropdownItem danger startContent={<Trash aria-hidden="true" />}>Delete</DropdownItem>
182
+ </DropdownContent>
183
+ </Dropdown>
184
+ );
185
+ }
186
+
187
+ export function matchesUsage(agent: AgentRecord, usage: AgentUsage) {
188
+ if (usage === "deployed") return agent.status === "deployed";
189
+ if (usage === "not-deployed") return agent.status === "not-deployed";
190
+ if (usage === "hotlines") return agent.hotlines > 0;
191
+ if (usage === "campaigns") return agent.campaigns > 0;
192
+ return true;
193
+ }
194
+
195
+ export function matchesManualFilter(agent: AgentRecord, filter: ManualFilter) {
196
+ if (filter === "original") return !agent.lineage;
197
+ if (filter === "duplicated") return Boolean(agent.lineage);
198
+ if (filter === "has-calls") return agent.calls > 0;
199
+ return agent.calls === 0;
200
+ }
201
+
202
+ export function sortAgents(agents: AgentRecord[], descriptor?: SortDescriptor) {
203
+ if (!descriptor) return agents;
204
+ const direction = descriptor.direction === "ascending" ? 1 : -1;
205
+ const value = (agent: AgentRecord): string | number => {
206
+ if (descriptor.column === "type") return agent.type;
207
+ if (descriptor.column === "updated") return agent.updatedSort;
208
+ if (descriptor.column === "hotlines") return agent.hotlines;
209
+ if (descriptor.column === "campaigns") return agent.campaigns;
210
+ if (descriptor.column === "steps") return agent.steps;
211
+ if (descriptor.column === "calls") return agent.calls;
212
+ return agent.name;
213
+ };
214
+ return [...agents].sort((left, right) => {
215
+ const a = value(left);
216
+ const b = value(right);
217
+ const result = typeof a === "number" && typeof b === "number"
218
+ ? a - b
219
+ : String(a).localeCompare(String(b));
220
+ return result * direction;
221
+ });
222
+ }
223
+
224
+ export function AgentsFilterMenu({
225
+ filters,
226
+ onAdd,
227
+ }: {
228
+ filters: ManualFilter[];
229
+ onAdd: (filter: ManualFilter) => void;
230
+ }) {
231
+ return (
232
+ <Dropdown>
233
+ <Tooltip content="Add filter">
234
+ <span className="inline-flex">
235
+ <DropdownTrigger>
236
+ <Button
237
+ variant="outline"
238
+ size="icon-sm"
239
+ aria-label="Add filter"
240
+ data-active={filters.length > 0 || undefined}
241
+ className="aria-expanded:text-action-primary-text data-[active=true]:text-action-primary-text"
242
+ >
243
+ <Filter aria-hidden="true" />
244
+ {filters.length > 0 && (
245
+ <span aria-hidden="true" className="absolute -right-1 -top-1 size-2 rounded-full bg-danger" />
246
+ )}
247
+ </Button>
248
+ </DropdownTrigger>
249
+ </span>
250
+ </Tooltip>
251
+ <DropdownContent align="end" autoFocusFirstItem>
252
+ {MANUAL_FILTERS.map((filter) => (
253
+ <DropdownItem
254
+ key={filter.value}
255
+ disabled={filters.includes(filter.value)}
256
+ onSelect={() => onAdd(filter.value)}
257
+ >
258
+ {filter.label}
259
+ </DropdownItem>
260
+ ))}
261
+ </DropdownContent>
262
+ </Dropdown>
263
+ );
264
+ }
265
+
266
+ export function AgentsEmptyState({
267
+ title,
268
+ description,
269
+ hotline = false,
270
+ action,
271
+ }: {
272
+ title: string;
273
+ description?: string;
274
+ hotline?: boolean;
275
+ action?: "view-all" | "clear";
276
+ }) {
277
+ return (
278
+ <div className="rounded-m border border-table-stroke bg-card">
279
+ <EmptyState
280
+ icon={hotline ? <PhoneIncoming aria-hidden="true" /> : <Search aria-hidden="true" />}
281
+ title={title}
282
+ description={description}
283
+ action={action === "view-all"
284
+ ? <Button size="sm" variant="outline">View all agents</Button>
285
+ : action === "clear"
286
+ ? <Button size="sm" variant="outline">Clear filters</Button>
287
+ : undefined}
288
+ />
289
+ </div>
290
+ );
291
+ }
292
+
293
+ export function AgentsComposition() {
294
+ const toolbarRef = React.useRef<HTMLDivElement>(null);
295
+ const [usage, setUsage] = React.useState<AgentUsage>("all");
296
+ const [search, setSearch] = React.useState("");
297
+ const [searchOpen, setSearchOpen] = React.useState(false);
298
+ const [manualFilters, setManualFilters] = React.useState<ManualFilter[]>([]);
299
+ const [sortDescriptor, setSortDescriptor] = React.useState<SortDescriptor>();
300
+ const [toolbarIsConstrained, setToolbarIsConstrained] = React.useState(false);
301
+
302
+ React.useEffect(() => {
303
+ const toolbar = toolbarRef.current;
304
+ if (!toolbar) return;
305
+
306
+ const update = () => setToolbarIsConstrained(toolbar.clientWidth < 820);
307
+ update();
308
+
309
+ const observer = new ResizeObserver(update);
310
+ observer.observe(toolbar);
311
+ return () => observer.disconnect();
312
+ }, []);
313
+
314
+ const filtered = AGENTS.filter((agent) => {
315
+ const query = search.trim().toLowerCase();
316
+ const matchesQuery = !query || `${agent.name} ${agent.type} ${agent.lineage ?? ""}`.toLowerCase().includes(query);
317
+ return matchesQuery && matchesUsage(agent, usage) && manualFilters.every((filter) => matchesManualFilter(agent, filter));
318
+ });
319
+ const rows = sortAgents(filtered, sortDescriptor);
320
+ const hasManualQuery = Boolean(search.trim() || manualFilters.length > 0);
321
+ const compactTabs = searchOpen || toolbarIsConstrained;
322
+
323
+ const clearQuery = () => {
324
+ setSearch("");
325
+ setManualFilters([]);
326
+ };
327
+
328
+ let emptyTitle = "No agents match";
329
+ let emptyDescription: string | undefined = hasManualQuery ? "No agents match the applied search or manual filters." : undefined;
330
+ let emptyHotline = false;
331
+ if (!hasManualQuery && usage === "hotlines") {
332
+ emptyTitle = "No agents on hotlines";
333
+ emptyDescription = undefined;
334
+ emptyHotline = true;
335
+ } else if (!hasManualQuery && usage === "deployed") {
336
+ emptyTitle = "No deployed agents";
337
+ } else if (!hasManualQuery && usage === "not-deployed") {
338
+ emptyTitle = "All agents are deployed";
339
+ } else if (!hasManualQuery && usage === "campaigns") {
340
+ emptyTitle = "No agents in campaigns";
341
+ }
342
+
343
+ return (
344
+ <ScreenFrame>
345
+ <PageHeader
346
+ title="Agents"
347
+ actions={<Button size="sm">New agent</Button>}
348
+ />
349
+
350
+ <div className="mt-10">
351
+ <AgentSummaryStripContent />
352
+ </div>
353
+
354
+ <div className="mt-10">
355
+ <div ref={toolbarRef} className="flex w-full items-center gap-2">
356
+ <Tabs
357
+ className="min-w-0 flex-1 gap-0"
358
+ value={usage}
359
+ onValueChange={(value) => setUsage(value as AgentUsage)}
360
+ >
361
+ <TabList aria-label="Filter agents by usage" className="min-w-max">
362
+ {USAGE_TABS.map((tab, index) =>
363
+ compactTabs && index > 2 ? null : (
364
+ <Tab key={tab.value} value={tab.value} count={tab.count}>
365
+ {tab.label}
366
+ </Tab>
367
+ ),
368
+ )}
369
+ {compactTabs && (
370
+ <button
371
+ type="button"
372
+ aria-label="Show 2 more usage filters"
373
+ onClick={() => setSearchOpen(false)}
374
+ className="relative -mb-px inline-flex h-8 items-center justify-center whitespace-nowrap text-l font-normal text-secondary outline-none transition-colors hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring"
375
+ >
376
+ +2 more
377
+ </button>
378
+ )}
379
+ </TabList>
380
+ </Tabs>
381
+ <div className="flex shrink-0 items-center gap-2">
382
+ <SearchDisclosure
383
+ value={search}
384
+ onValueChange={setSearch}
385
+ open={searchOpen}
386
+ onOpenChange={setSearchOpen}
387
+ placeholder="Search agents"
388
+ inputLabel="Search agents by name or type"
389
+ triggerLabel="Search agents"
390
+ />
391
+ <AgentsFilterMenu
392
+ filters={manualFilters}
393
+ onAdd={(filter) => setManualFilters((current) => current.includes(filter) ? current : [...current, filter])}
394
+ />
395
+ </div>
396
+ </div>
397
+
398
+ {manualFilters.length > 0 && (
399
+ <div className="mt-2 flex flex-wrap items-center gap-2">
400
+ {manualFilters.map((filter) => {
401
+ const label = MANUAL_FILTERS.find((item) => item.value === filter)?.label ?? filter;
402
+ return (
403
+ <Button
404
+ key={filter}
405
+ variant="ghost"
406
+ size="xs"
407
+ aria-label={`Remove filter ${label}`}
408
+ onClick={() => setManualFilters((current) => current.filter((item) => item !== filter))}
409
+ endContent={<Close aria-hidden="true" />}
410
+ className="bg-action-primary-soft text-s text-action-primary-text [&>span]:gap-2"
411
+ >
412
+ {label}
413
+ </Button>
414
+ );
415
+ })}
416
+ <Button
417
+ variant="ghost"
418
+ size="xs"
419
+ onClick={() => setManualFilters([])}
420
+ className="text-s enabled:hover:bg-muted enabled:hover:text-foreground enabled:active:bg-muted enabled:active:text-foreground"
421
+ >
422
+ Clear
423
+ </Button>
424
+ </div>
425
+ )}
426
+
427
+ <section aria-label="Agents" className="mt-6">
428
+ {rows.length === 0 ? (
429
+ <EmptyState
430
+ icon={emptyHotline ? <PhoneIncoming aria-hidden="true" /> : <Search aria-hidden="true" />}
431
+ title={emptyTitle}
432
+ description={emptyDescription}
433
+ action={hasManualQuery
434
+ ? <Button size="sm" variant="outline" onClick={clearQuery}>Clear filters</Button>
435
+ : <Button size="sm" variant="outline" onClick={() => setUsage("all")}>View all agents</Button>}
436
+ />
437
+ ) : (
438
+ <Table
439
+ aria-label="Your agents"
440
+ sortDescriptor={sortDescriptor}
441
+ onSortChange={setSortDescriptor}
442
+ >
443
+ <TableHeader>
444
+ <TableRow>
445
+ <TableColumn sortKey="agent">Agent</TableColumn>
446
+ <TableColumn sortKey="type">Type</TableColumn>
447
+ <TableColumn sortKey="updated" align="right">Updated</TableColumn>
448
+ <TableColumn sortKey="hotlines" align="right">Hotlines</TableColumn>
449
+ <TableColumn sortKey="campaigns" align="right">Campaigns</TableColumn>
450
+ <TableColumn sortKey="steps" align="right">Steps</TableColumn>
451
+ <TableColumn sortKey="calls" align="right">Calls</TableColumn>
452
+ <TableColumn allowsSorting={false} align="right" aria-label="Actions" />
453
+ </TableRow>
454
+ </TableHeader>
455
+ <TableBody>
456
+ {rows.map((agent) => (
457
+ <TableRow key={agent.id}>
458
+ <TableCell><AgentIdentity agent={agent} /></TableCell>
459
+ <TableCell className="text-secondary">{agent.type}</TableCell>
460
+ <TableCell align="right" className="tabular-nums text-secondary" sortValue={agent.updatedSort}>{agent.updated}</TableCell>
461
+ <TableCell align="right" className="tabular-nums">{agent.hotlines}</TableCell>
462
+ <TableCell align="right" className="tabular-nums">{agent.campaigns}</TableCell>
463
+ <TableCell align="right" className="tabular-nums">{agent.steps}</TableCell>
464
+ <TableCell align="right" className="tabular-nums">{agent.calls}</TableCell>
465
+ <TableCell align="right"><AgentRowActions name={agent.name} /></TableCell>
466
+ </TableRow>
467
+ ))}
468
+ </TableBody>
469
+ </Table>
470
+ )}
471
+ </section>
472
+ </div>
473
+ </ScreenFrame>
474
+ );
475
+ }
476
+
477
+ export { AgentsComposition as AgentsScreen };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Settings screen — a settings card built only from ADS exports.
3
+ *
4
+ * What it shows: Card header with a status Badge, an InputField, a SettingList
5
+ * of SettingRows whose trailing control is a Switch or Button, and a footer
6
+ * with one primary action.
7
+ */
8
+ import * as React from "react";
9
+ import {
10
+ Badge,
11
+ Button,
12
+ Card,
13
+ CardBody,
14
+ CardDescription,
15
+ CardFooter,
16
+ CardHeader,
17
+ CardTitle,
18
+ InputField,
19
+ SettingList,
20
+ SettingRow,
21
+ Switch,
22
+ } from "@actioneer/ads";
23
+ import {
24
+ Database,
25
+ Settings,
26
+ User,
27
+ } from "@actioneer/ads/lib/icons";
28
+
29
+ export function SettingsScreen() {
30
+ const [digest, setDigest] = React.useState(false);
31
+ const [sharing, setSharing] = React.useState(true);
32
+
33
+ return (
34
+ <Card className="mx-auto w-full max-w-2xl">
35
+ <CardHeader>
36
+ <div className="flex items-start justify-between gap-4">
37
+ <div className="flex min-w-0 flex-col gap-1">
38
+ <CardTitle>Workspace preferences</CardTitle>
39
+ <CardDescription>
40
+ A canonical settings composition: context first, grouped controls second, one clear save action.
41
+ </CardDescription>
42
+ </div>
43
+ <Badge variant="dot" color="success">Saved</Badge>
44
+ </div>
45
+ </CardHeader>
46
+
47
+ <CardBody className="flex flex-col gap-5">
48
+ <InputField
49
+ label="Workspace name"
50
+ defaultValue="Actioneer research"
51
+ description="Shown to everyone in this workspace."
52
+ />
53
+
54
+ <SettingList aria-label="Workspace settings">
55
+ <SettingRow
56
+ icon={<User />}
57
+ label="Weekly digest"
58
+ description="Receive a concise summary every Monday."
59
+ action={
60
+ <Switch checked={digest} onCheckedChange={setDigest}>
61
+ Weekly digest
62
+ </Switch>
63
+ }
64
+ />
65
+ <SettingRow
66
+ icon={<Database />}
67
+ label="Source sharing"
68
+ description="Allow teammates to reuse connected sources."
69
+ action={
70
+ <Switch checked={sharing} onCheckedChange={setSharing}>
71
+ Source sharing
72
+ </Switch>
73
+ }
74
+ />
75
+ <SettingRow
76
+ icon={<Settings />}
77
+ label="Advanced controls"
78
+ description="Manage retention and export permissions."
79
+ action={<Button variant="ghost" size="sm">Manage</Button>}
80
+ />
81
+ </SettingList>
82
+ </CardBody>
83
+
84
+ <CardFooter className="justify-end gap-2">
85
+ <Button variant="ghost">Cancel</Button>
86
+ <Button>Save changes</Button>
87
+ </CardFooter>
88
+ </Card>
89
+ );
90
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Workspace header — a page header and tabbed body built only from ADS exports.
3
+ *
4
+ * What it shows: title with a status Badge, trailing page actions, and Tabs
5
+ * with counts whose panels hold Cards.
6
+ */
7
+ import {
8
+ Badge,
9
+ Button,
10
+ Card,
11
+ CardBody,
12
+ Tab,
13
+ TabList,
14
+ TabPanel,
15
+ Tabs,
16
+ } from "@actioneer/ads";
17
+ import {
18
+ ArrowUpRight,
19
+ Plus,
20
+ } from "@actioneer/ads/lib/icons";
21
+
22
+ export function WorkspaceHeaderScreen() {
23
+ return (
24
+ <section className="mx-auto flex w-full max-w-4xl flex-col gap-6">
25
+ <header className="flex flex-col items-start gap-5 sm:flex-row sm:justify-between">
26
+ <div className="flex w-full min-w-0 flex-1 flex-col gap-2">
27
+ <div className="flex flex-wrap items-center gap-2">
28
+ <h1 className="text-2xl font-medium text-foreground">Research workspace</h1>
29
+ <Badge variant="dot" color="success">Connected</Badge>
30
+ </div>
31
+ <p className="max-w-2xl text-base text-muted-foreground">
32
+ Keep status beside the title, supporting context below, and primary actions at the far edge.
33
+ </p>
34
+ </div>
35
+ <div className="flex shrink-0 items-center gap-2">
36
+ <Button variant="outline" endContent={<ArrowUpRight aria-hidden />}>Share</Button>
37
+ <Button startContent={<Plus aria-hidden />}>New board</Button>
38
+ </div>
39
+ </header>
40
+
41
+ <Tabs defaultValue="boards">
42
+ <TabList aria-label="Workspace sections">
43
+ <Tab value="boards" count={12}>Boards</Tab>
44
+ <Tab value="sources" count={6}>Sources</Tab>
45
+ <Tab value="members" count={8}>Members</Tab>
46
+ </TabList>
47
+ <TabPanel value="boards">
48
+ <Card>
49
+ <CardBody>
50
+ <p className="text-base text-foreground">Boards keep ongoing analysis visible and reusable.</p>
51
+ </CardBody>
52
+ </Card>
53
+ </TabPanel>
54
+ <TabPanel value="sources">
55
+ <Card>
56
+ <CardBody>
57
+ <p className="text-base text-foreground">Six connected sources are available to this workspace.</p>
58
+ </CardBody>
59
+ </Card>
60
+ </TabPanel>
61
+ <TabPanel value="members">
62
+ <Card>
63
+ <CardBody>
64
+ <p className="text-base text-foreground">Eight members can collaborate here.</p>
65
+ </CardBody>
66
+ </Card>
67
+ </TabPanel>
68
+ </Tabs>
69
+ </section>
70
+ );
71
+ }
package/lib/icons.d.ts CHANGED
@@ -21,6 +21,14 @@ export type IconProps = Omit<React.ComponentProps<PhosphorIcon>, "ref"> & {
21
21
  /** Legacy Hugeicons prop — `>= 2` maps to weight="bold", else ignored. */
22
22
  strokeWidth?: number;
23
23
  };
24
+ /**
25
+ * Icon-only controls (Button `size="icon*"`, TooltipIconButton) provide this
26
+ * context. The control turns its icon teal on hover and draws no background.
27
+ * Only the info icon also swaps to its `fill` weight (a hidden twin that
28
+ * `[data-icon-hover-fill]:hover` in theme.css reveals); every other icon keeps
29
+ * its regular outline on hover.
30
+ */
31
+ export declare const IconHoverFillContext: React.Context<boolean>;
24
32
  export declare const ArrowDown: React.ForwardRefExoticComponent<Omit<import('@phosphor-icons/react').IconProps, "ref"> & {
25
33
  /** Legacy Hugeicons prop — `>= 2` maps to weight="bold", else ignored. */
26
34
  strokeWidth?: number;