ai-design-system 0.1.24 → 0.1.26

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 (29) hide show
  1. package/components/blocks/InboxPanel/InboxPanel.stories.tsx +48 -0
  2. package/components/blocks/InboxPanel/InboxPanel.tsx +55 -0
  3. package/components/blocks/InboxPanel/index.ts +2 -0
  4. package/components/blocks/SectionLayout/SectionLayout.stories.tsx +106 -0
  5. package/components/blocks/SectionLayout/SectionLayout.tsx +3 -1
  6. package/components/blocks/SectionLayout/interfaces.ts +2 -0
  7. package/components/blocks/index.ts +3 -0
  8. package/components/composites/AdjustableLayout/AdjustableLayout.tsx +15 -2
  9. package/components/composites/DataTable/EnhancedDataTable.behaviors.stories.tsx +20 -3
  10. package/components/composites/DataTable/EnhancedDataTable.stories.tsx +18 -0
  11. package/components/composites/DataTable/EnhancedDataTable.tsx +4 -4
  12. package/components/composites/InboxList/InboxList.stories.tsx +45 -0
  13. package/components/composites/InboxList/InboxList.tsx +99 -0
  14. package/components/composites/InboxList/index.ts +2 -0
  15. package/components/composites/WorkflowRunObservabilityPanel/WorkflowRunObservabilityPanel.tsx +9 -9
  16. package/components/composites/index.ts +4 -0
  17. package/components/features/FormReportsFeature/FormReportsFeature.tsx +1 -1
  18. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.behaviors.stories.tsx +34 -5
  19. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.mocks.ts +20 -0
  20. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.stories.tsx +36 -11
  21. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.tsx +68 -8
  22. package/components/features/index.ts +1 -0
  23. package/dist/index.cjs +156 -24
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.css +57 -3
  26. package/dist/index.d.ts +15 -0
  27. package/dist/index.js +155 -25
  28. package/dist/index.js.map +1 -1
  29. package/package.json +1 -1
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite"
2
+
3
+ import { InboxPanel } from "./InboxPanel"
4
+
5
+ const items = Array.from({ length: 30 }).map((_, index) => ({
6
+ id: `item-${index + 1}`,
7
+ title: `Item ${index + 1}`,
8
+ subtitle: "completed • 12s • retries 0",
9
+ preview: "Inbox-style preview text for validating panel-level vertical scrolling behavior.",
10
+ timestamp: `${index + 1}m ago`,
11
+ badge: index % 3 === 0 ? "running" : "completed",
12
+ }))
13
+
14
+ const meta = {
15
+ title: "Blocks/InboxPanel",
16
+ component: InboxPanel,
17
+ tags: ["autodocs"],
18
+ parameters: {
19
+ layout: "fullscreen",
20
+ },
21
+ } satisfies Meta<typeof InboxPanel>
22
+
23
+ export default meta
24
+ type Story = StoryObj<typeof meta>
25
+
26
+ export const Default: Story = {
27
+ args: {
28
+ items,
29
+ selectedItemId: "item-2",
30
+ },
31
+ render: (args) => (
32
+ <div className="h-[720px]">
33
+ <InboxPanel {...args} />
34
+ </div>
35
+ ),
36
+ }
37
+
38
+ export const Empty: Story = {
39
+ args: {
40
+ items: [],
41
+ emptyMessage: "No matching items.",
42
+ },
43
+ render: (args) => (
44
+ <div className="h-[720px]">
45
+ <InboxPanel {...args} />
46
+ </div>
47
+ ),
48
+ }
@@ -0,0 +1,55 @@
1
+ import * as React from "react"
2
+
3
+ import { InboxList, type InboxListItem } from "@/components/composites/InboxList"
4
+ import { Input } from "@/components/primitives/Input"
5
+
6
+ export interface InboxPanelProps {
7
+ items: InboxListItem[]
8
+ selectedItemId?: string | null
9
+ onSelectItem?: (itemId: string) => void
10
+ searchQuery?: string
11
+ onSearchQueryChange?: (value: string) => void
12
+ searchPlaceholder?: string
13
+ isLoading?: boolean
14
+ emptyMessage?: string
15
+ className?: string
16
+ }
17
+
18
+ export const InboxPanel = React.memo<InboxPanelProps>(
19
+ ({
20
+ items,
21
+ selectedItemId,
22
+ onSelectItem,
23
+ searchQuery = "",
24
+ onSearchQueryChange,
25
+ searchPlaceholder = "Type to search...",
26
+ isLoading = false,
27
+ emptyMessage,
28
+ className,
29
+ }) => {
30
+ return (
31
+ <section className={`flex min-h-0 flex-1 flex-col overflow-hidden ${className ?? ""}`}>
32
+ <div className="p-4">
33
+ <Input
34
+ aria-label="Search inbox items"
35
+ className="h-8"
36
+ onChange={(event) => onSearchQueryChange?.(event.target.value)}
37
+ placeholder={searchPlaceholder}
38
+ value={searchQuery}
39
+ />
40
+ </div>
41
+
42
+ <InboxList
43
+ className="min-h-0 flex-1"
44
+ emptyMessage={emptyMessage}
45
+ isLoading={isLoading}
46
+ items={items}
47
+ onSelectItem={onSelectItem}
48
+ selectedItemId={selectedItemId}
49
+ />
50
+ </section>
51
+ )
52
+ }
53
+ )
54
+
55
+ InboxPanel.displayName = "InboxPanel"
@@ -0,0 +1,2 @@
1
+ export { InboxPanel } from "./InboxPanel"
2
+ export type { InboxPanelProps } from "./InboxPanel"
@@ -1,6 +1,32 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { SectionLayout } from './SectionLayout'
3
3
 
4
+ function ScrollablePanelContent({
5
+ description,
6
+ items,
7
+ title,
8
+ }: {
9
+ description: string
10
+ items: string[]
11
+ title: string
12
+ }) {
13
+ return (
14
+ <div className="flex min-h-0 flex-col gap-3 rounded-lg border bg-muted p-4">
15
+ <div>
16
+ <h3 className="mb-1 font-medium">{title}</h3>
17
+ <p className="text-sm text-muted-foreground">{description}</p>
18
+ </div>
19
+ <div className="grid gap-2">
20
+ {items.map((item, index) => (
21
+ <div key={`${title}-${index}`} className="rounded-md border bg-background px-3 py-2 text-sm">
22
+ {item}
23
+ </div>
24
+ ))}
25
+ </div>
26
+ </div>
27
+ )
28
+ }
29
+
4
30
  /**
5
31
  * SectionLayout Block Stories
6
32
  *
@@ -274,3 +300,83 @@ export const AccentDragHandles: Story = {
274
300
  )
275
301
  },
276
302
  }
303
+
304
+ /**
305
+ * Independent Panel Scrolling
306
+ *
307
+ * Demonstrates that each panel owns its own vertical scroll region.
308
+ */
309
+ export const IndependentPanelScrolling: Story = {
310
+ render: () => {
311
+ const leftItems = Array.from({ length: 24 }, (_, index) => `Run record ${index + 1}`)
312
+ const centerItems = Array.from({ length: 18 }, (_, index) => `Trace span ${index + 1}`)
313
+ const rightItems = Array.from({ length: 20 }, (_, index) => `Details block ${index + 1}`)
314
+
315
+ const sections = [
316
+ {
317
+ id: 'left-scroll-panel',
318
+ content: (
319
+ <ScrollablePanelContent
320
+ title="Left Panel"
321
+ description="Scroll this panel independently. The center and right panels should keep their own scroll positions."
322
+ items={leftItems}
323
+ />
324
+ ),
325
+ defaultSize: 25,
326
+ minSize: 20,
327
+ header: {
328
+ tabs: [{ value: 'runs', label: 'runs' }],
329
+ defaultTab: 'runs',
330
+ showSidebarToggle: false,
331
+ showTitle: false,
332
+ },
333
+ },
334
+ {
335
+ id: 'center-scroll-panel',
336
+ content: (
337
+ <ScrollablePanelContent
338
+ title="Center Panel"
339
+ description="This panel should keep a separate scrollbar from the left list and the right details column."
340
+ items={centerItems}
341
+ />
342
+ ),
343
+ defaultSize: 50,
344
+ minSize: 35,
345
+ header: {
346
+ tabs: [{ value: 'trace', label: 'trace' }],
347
+ defaultTab: 'trace',
348
+ showSidebarToggle: false,
349
+ showTitle: false,
350
+ },
351
+ },
352
+ {
353
+ id: 'right-scroll-panel',
354
+ content: (
355
+ <ScrollablePanelContent
356
+ title="Right Panel"
357
+ description="Use this to confirm the details panel can overflow and scroll without affecting the other panels."
358
+ items={rightItems}
359
+ />
360
+ ),
361
+ defaultSize: 25,
362
+ minSize: 20,
363
+ header: {
364
+ tabs: [{ value: 'details', label: 'details' }],
365
+ defaultTab: 'details',
366
+ showSidebarToggle: false,
367
+ showTitle: false,
368
+ },
369
+ },
370
+ ]
371
+
372
+ return (
373
+ <div className="h-screen p-4">
374
+ <SectionLayout
375
+ sections={sections}
376
+ storageKey="section-layout-independent-scroll"
377
+ dragHandleColor="primary"
378
+ />
379
+ </div>
380
+ )
381
+ },
382
+ }
@@ -18,6 +18,7 @@ export const SectionLayout = React.memo<SectionLayoutProps>(
18
18
  orientation = "horizontal",
19
19
  storageKey,
20
20
  onSectionResize,
21
+ resizable = true,
21
22
  dragHandleColor = "border",
22
23
  className,
23
24
  ...props
@@ -25,12 +26,13 @@ export const SectionLayout = React.memo<SectionLayoutProps>(
25
26
  // Transform sections to include headers
26
27
  const transformedSections = sections.map(section => ({
27
28
  ...section,
29
+ resizable,
28
30
  content: (
29
31
  <div className="h-full min-h-0 flex flex-col overflow-hidden">
30
32
  {section.header && (
31
33
  <AppHeader {...section.header} />
32
34
  )}
33
- <div className="min-h-0 flex-1 overflow-auto">
35
+ <div className="min-h-0 flex-1 overflow-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
34
36
  {section.content}
35
37
  </div>
36
38
  </div>
@@ -4,6 +4,7 @@ import type { AppHeaderProps } from "@/components/composites/AppHeader/interface
4
4
  export interface SectionLayoutSection {
5
5
  id: string;
6
6
  content: React.ReactNode;
7
+ fixedSize?: string;
7
8
  defaultSize?: number;
8
9
  minSize?: number;
9
10
  maxSize?: number;
@@ -17,5 +18,6 @@ export interface SectionLayoutProps extends React.ComponentPropsWithoutRef<"div"
17
18
  orientation?: "horizontal" | "vertical";
18
19
  storageKey?: string;
19
20
  onSectionResize?: (sectionId: string, newSize: number) => void;
21
+ resizable?: boolean;
20
22
  dragHandleColor?: "primary" | "secondary" | "accent" | "border" | "muted";
21
23
  }
@@ -32,3 +32,6 @@ export type { DataTableProps } from './DataTable'
32
32
 
33
33
  export { FormReportsSection } from './FormReportsSection'
34
34
  export type { FormReportsSectionProps } from './FormReportsSection'
35
+
36
+ export { InboxPanel } from './InboxPanel'
37
+ export type { InboxPanelProps } from './InboxPanel'
@@ -4,6 +4,7 @@ import { cn } from "@/lib/utils"
4
4
  export interface AdjustableLayoutSection {
5
5
  id: string
6
6
  content: React.ReactNode
7
+ fixedSize?: string // CSS size value, e.g. "16rem"
7
8
  defaultSize?: number // percentage (0-100)
8
9
  minSize?: number // minimum percentage
9
10
  maxSize?: number // maximum percentage
@@ -168,8 +169,20 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
168
169
  }, [draggingIndex, handleMouseMove, handleMouseUp])
169
170
 
170
171
  const renderPanel = (section: AdjustableLayoutSection, size: number, index: number) => {
172
+ const nextSection = sections[index + 1]
171
173
  // Drag handles should be between panels, so only show for panels that aren't the last one
172
- const isResizable = section.resizable !== false && sections.length > 1 && index < sections.length - 1
174
+ const isResizable =
175
+ section.resizable !== false &&
176
+ sections.length > 1 &&
177
+ index < sections.length - 1 &&
178
+ !section.fixedSize &&
179
+ !nextSection?.fixedSize
180
+
181
+ const fixedStyle = section.fixedSize
182
+ ? orientation === "horizontal"
183
+ ? { flex: `0 0 ${section.fixedSize}`, width: section.fixedSize, minWidth: section.fixedSize }
184
+ : { flex: `0 0 ${section.fixedSize}`, height: section.fixedSize, minHeight: section.fixedSize }
185
+ : null
173
186
 
174
187
  return (
175
188
  <React.Fragment key={section.id}>
@@ -179,7 +192,7 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
179
192
  section.className
180
193
  )}
181
194
  style={{
182
- flex: `${size} 1 0%`,
195
+ ...(fixedStyle ?? { flex: `${size} 1 0%` }),
183
196
  minHeight: 0,
184
197
  minWidth: 0,
185
198
  }}
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/nextjs-vite"
2
2
  import { expect, userEvent, within } from "@storybook/test"
3
3
  import { Toaster } from "sonner"
4
+ import { DYNAMIC_TABLE_SCHEMA_VERSION, dynamicTableSchema } from "ui-schema-contracts"
4
5
 
5
6
  import { EnhancedDataTable } from "./EnhancedDataTable"
6
7
  import type { DashboardRow } from "./table-types"
@@ -11,6 +12,22 @@ const rows: DashboardRow[] = [
11
12
  { id: 3, header: "Executive summary", type: "Narrative", status: "Done", target: "10", limit: "13", reviewer: "Assign reviewer" },
12
13
  ]
13
14
 
15
+ const tableSchema = dynamicTableSchema.parse({
16
+ schemaVersion: DYNAMIC_TABLE_SCHEMA_VERSION,
17
+ rowKey: "id",
18
+ enableFiltering: true,
19
+ enablePagination: true,
20
+ enableRowSelection: true,
21
+ columns: [
22
+ { key: "header", label: "Header", sortable: true, hideable: false },
23
+ { key: "type", label: "Type", sortable: true },
24
+ { key: "status", label: "Status", renderType: "badge", sortable: true },
25
+ { key: "target", label: "Target", align: "right", sortable: true },
26
+ { key: "limit", label: "Limit", align: "right", sortable: true },
27
+ { key: "reviewer", label: "Reviewer", sortable: true },
28
+ ],
29
+ })
30
+
14
31
  const meta = {
15
32
  title: "Composites/EnhancedDataTable/Behaviors",
16
33
  component: EnhancedDataTable,
@@ -30,7 +47,7 @@ export default meta
30
47
  type Story = StoryObj<typeof meta>
31
48
 
32
49
  export const SelectAllWorks: Story = {
33
- args: { data: rows },
50
+ args: { data: rows, tableSchema },
34
51
  play: async ({ canvasElement }) => {
35
52
  const canvas = within(canvasElement)
36
53
  const selectAll = canvas.getByLabelText("Select all")
@@ -40,7 +57,7 @@ export const SelectAllWorks: Story = {
40
57
  }
41
58
 
42
59
  export const DrawerOpensFromHeader: Story = {
43
- args: { data: rows },
60
+ args: { data: rows, tableSchema },
44
61
  play: async ({ canvasElement }) => {
45
62
  const canvas = within(canvasElement)
46
63
  const trigger = canvas.getByRole("button", { name: "Cover page" })
@@ -50,7 +67,7 @@ export const DrawerOpensFromHeader: Story = {
50
67
  }
51
68
 
52
69
  export const SwitchesViewTab: Story = {
53
- args: { data: rows },
70
+ args: { data: rows, tableSchema },
54
71
  play: async ({ canvasElement }) => {
55
72
  const canvas = within(canvasElement)
56
73
  const tab = canvas.getByRole("tab", { name: /Past Performance/i })
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from "@storybook/nextjs-vite"
2
2
  import { Toaster } from "sonner"
3
+ import { DYNAMIC_TABLE_SCHEMA_VERSION, dynamicTableSchema } from "ui-schema-contracts"
3
4
 
4
5
  import { EnhancedDataTable } from "./EnhancedDataTable"
5
6
  import type { DashboardRow } from "./table-types"
@@ -19,6 +20,22 @@ const data: DashboardRow[] = [
19
20
  { id: 12, header: "Advantages Over Current Technologies", type: "Narrative", status: "Not Started", target: "12", limit: "0", reviewer: "Assign reviewer" },
20
21
  ]
21
22
 
23
+ const tableSchema = dynamicTableSchema.parse({
24
+ schemaVersion: DYNAMIC_TABLE_SCHEMA_VERSION,
25
+ rowKey: "id",
26
+ enableFiltering: true,
27
+ enablePagination: true,
28
+ enableRowSelection: true,
29
+ columns: [
30
+ { key: "header", label: "Header", sortable: true, hideable: false },
31
+ { key: "type", label: "Type", sortable: true },
32
+ { key: "status", label: "Status", renderType: "badge", sortable: true },
33
+ { key: "target", label: "Target", align: "right", sortable: true },
34
+ { key: "limit", label: "Limit", align: "right", sortable: true },
35
+ { key: "reviewer", label: "Reviewer", sortable: true },
36
+ ],
37
+ })
38
+
22
39
  const meta = {
23
40
  title: "Composites/EnhancedDataTable",
24
41
  component: EnhancedDataTable,
@@ -38,5 +55,6 @@ type Story = StoryObj<typeof meta>
38
55
  export const Default: Story = {
39
56
  args: {
40
57
  data,
58
+ tableSchema,
41
59
  },
42
60
  }
@@ -454,7 +454,7 @@ export function EnhancedDataTable({
454
454
  }, [handlers, table])
455
455
 
456
456
  return (
457
- <div className={`flex w-full flex-col justify-start gap-6 ${className ?? ""}`}>
457
+ <div className={`flex min-h-0 w-full flex-1 flex-col justify-start gap-6 overflow-hidden ${className ?? ""}`}>
458
458
  <div className="flex items-center justify-between gap-2 px-4 lg:px-6">
459
459
  <div className="flex items-center gap-2 [&>button]:h-8">
460
460
  <Input
@@ -503,8 +503,8 @@ export function EnhancedDataTable({
503
503
  </div>
504
504
  </div>
505
505
 
506
- <div className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6">
507
- <div className="overflow-hidden rounded-lg border">
506
+ <div className="relative flex min-h-0 flex-1 flex-col gap-4 overflow-hidden px-4 lg:px-6">
507
+ <div className="min-h-0 flex-1 overflow-x-auto overflow-y-auto rounded-lg border [scrollbar-width:thin] [scrollbar-color:hsl(var(--border))_transparent] [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/70">
508
508
  <DndContext
509
509
  collisionDetection={closestCenter}
510
510
  modifiers={[restrictToVerticalAxis]}
@@ -565,7 +565,7 @@ export function EnhancedDataTable({
565
565
  <SelectValue placeholder={currentPageSize} />
566
566
  </SelectTrigger>
567
567
  <SelectContent side="top">
568
- {[10, 20, 30, 40, 50].map((pageSize) => (
568
+ {[10, 20, 25, 30, 40, 50].map((pageSize) => (
569
569
  <SelectItem key={pageSize} value={`${pageSize}`}>
570
570
  {pageSize}
571
571
  </SelectItem>
@@ -0,0 +1,45 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite"
2
+
3
+ import { InboxList } from "./InboxList"
4
+
5
+ const items = Array.from({ length: 18 }).map((_, index) => ({
6
+ id: `item-${index + 1}`,
7
+ title: `Run ${index + 1}`,
8
+ subtitle: index % 2 === 0 ? "completed • 5s • retries 0" : "running • 23s • retries 1",
9
+ preview: "This is an email-style preview row for validating list readability and vertical scrolling.",
10
+ timestamp: `${index + 1}m ago`,
11
+ badge: index % 2 === 0 ? "completed" : "running",
12
+ }))
13
+
14
+ const meta = {
15
+ title: "Composites/InboxList",
16
+ component: InboxList,
17
+ tags: ["autodocs"],
18
+ parameters: {
19
+ layout: "padded",
20
+ },
21
+ } satisfies Meta<typeof InboxList>
22
+
23
+ export default meta
24
+ type Story = StoryObj<typeof meta>
25
+
26
+ export const Default: Story = {
27
+ args: {
28
+ items,
29
+ selectedItemId: "item-2",
30
+ },
31
+ }
32
+
33
+ export const Empty: Story = {
34
+ args: {
35
+ items: [],
36
+ emptyMessage: "No messages yet.",
37
+ },
38
+ }
39
+
40
+ export const Loading: Story = {
41
+ args: {
42
+ items: [],
43
+ isLoading: true,
44
+ },
45
+ }
@@ -0,0 +1,99 @@
1
+ import * as React from "react"
2
+
3
+ import { Badge } from "@/components/primitives/Badge"
4
+
5
+ export interface InboxListItem {
6
+ id: string
7
+ title: string
8
+ subtitle?: string
9
+ preview?: string
10
+ timestamp?: string
11
+ badge?: string
12
+ }
13
+
14
+ export interface InboxListProps {
15
+ items: InboxListItem[]
16
+ selectedItemId?: string | null
17
+ onSelectItem?: (itemId: string) => void
18
+ isLoading?: boolean
19
+ emptyMessage?: string
20
+ className?: string
21
+ }
22
+
23
+ function LoadingRows() {
24
+ return (
25
+ <div className="space-y-2 p-2">
26
+ {Array.from({ length: 8 }).map((_, index) => (
27
+ <div key={index} className="space-y-2 rounded-md border p-3">
28
+ <div className="h-4 w-2/3 animate-pulse rounded bg-muted" />
29
+ <div className="h-3 w-full animate-pulse rounded bg-muted" />
30
+ <div className="h-3 w-1/2 animate-pulse rounded bg-muted" />
31
+ </div>
32
+ ))}
33
+ </div>
34
+ )
35
+ }
36
+
37
+ export const InboxList = React.memo<InboxListProps>(
38
+ ({
39
+ items,
40
+ selectedItemId,
41
+ onSelectItem,
42
+ isLoading = false,
43
+ emptyMessage = "No items found.",
44
+ className,
45
+ }) => {
46
+ if (isLoading) {
47
+ return (
48
+ <div className={`min-h-0 flex-1 overflow-auto ${className ?? ""}`}>
49
+ <LoadingRows />
50
+ </div>
51
+ )
52
+ }
53
+
54
+ if (!items.length) {
55
+ return (
56
+ <div className={`flex min-h-0 flex-1 items-center justify-center p-4 text-sm text-muted-foreground ${className ?? ""}`}>
57
+ {emptyMessage}
58
+ </div>
59
+ )
60
+ }
61
+
62
+ return (
63
+ <div className={`min-h-0 flex-1 overflow-auto ${className ?? ""}`}>
64
+ <div className="space-y-1 p-2">
65
+ {items.map((item) => {
66
+ const isSelected = selectedItemId === item.id
67
+
68
+ return (
69
+ <button
70
+ key={item.id}
71
+ type="button"
72
+ className={`w-full rounded-md p-3 text-left transition-colors ${
73
+ isSelected
74
+ ? "bg-accent ring-1 ring-inset ring-primary"
75
+ : "bg-background hover:bg-accent/60"
76
+ }`}
77
+ onClick={() => onSelectItem?.(item.id)}
78
+ >
79
+ <div className="flex items-center gap-2">
80
+ <div className="truncate font-medium text-sm">{item.title}</div>
81
+ {item.badge ? <Badge variant="outline">{item.badge}</Badge> : null}
82
+ {item.timestamp ? (
83
+ <div className="ml-auto shrink-0 text-muted-foreground text-xs">{item.timestamp}</div>
84
+ ) : null}
85
+ </div>
86
+ {item.subtitle ? (
87
+ <div className="mt-1 truncate text-muted-foreground text-xs">{item.subtitle}</div>
88
+ ) : null}
89
+ {item.preview ? <div className="mt-2 line-clamp-2 text-sm">{item.preview}</div> : null}
90
+ </button>
91
+ )
92
+ })}
93
+ </div>
94
+ </div>
95
+ )
96
+ }
97
+ )
98
+
99
+ InboxList.displayName = "InboxList"
@@ -0,0 +1,2 @@
1
+ export { InboxList } from "./InboxList"
2
+ export type { InboxListItem, InboxListProps } from "./InboxList"
@@ -282,7 +282,7 @@ export const WorkflowRunObservabilityTracePanel = React.memo<WorkflowRunObservab
282
282
  </div>
283
283
  </div>
284
284
 
285
- <div className="min-h-0 flex-1 overflow-auto p-2">
285
+ <div className="min-h-0 flex-1 overflow-auto p-2 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
286
286
  {spans.length === 0 ? (
287
287
  <div className="text-[#8a8c96] text-sm">No spans yet.</div>
288
288
  ) : (
@@ -325,7 +325,7 @@ export const WorkflowRunObservabilityTracePanel = React.memo<WorkflowRunObservab
325
325
  </TabsContent>
326
326
 
327
327
  <TabsContent className="min-h-0 flex-1 overflow-hidden" value="events">
328
- <div className="h-full min-h-0 space-y-2 overflow-auto rounded-lg border border-[#2a2a2f] bg-[#07080b] p-3">
328
+ <div className="h-full min-h-0 space-y-2 overflow-auto rounded-lg border border-[#2a2a2f] bg-[#07080b] p-3 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
329
329
  {events.length === 0 ? (
330
330
  <div className="text-[#8a8c96] text-sm">No events yet.</div>
331
331
  ) : (
@@ -345,7 +345,7 @@ export const WorkflowRunObservabilityTracePanel = React.memo<WorkflowRunObservab
345
345
  </TabsContent>
346
346
 
347
347
  <TabsContent className="min-h-0 flex-1 overflow-hidden" value="streams">
348
- <div className="h-full min-h-0 space-y-2 overflow-auto rounded-lg border border-[#2a2a2f] bg-[#07080b] p-3">
348
+ <div className="h-full min-h-0 space-y-2 overflow-auto rounded-lg border border-[#2a2a2f] bg-[#07080b] p-3 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
349
349
  {streams.length === 0 ? (
350
350
  <div className="text-[#8a8c96] text-sm">No streams yet.</div>
351
351
  ) : (
@@ -355,7 +355,7 @@ export const WorkflowRunObservabilityTracePanel = React.memo<WorkflowRunObservab
355
355
  <div className="font-medium">{stream.channel}</div>
356
356
  <div className="text-[#8a8c96]">{stream.timestamp ?? ""}</div>
357
357
  </div>
358
- <pre className="mt-1 overflow-auto font-mono text-xs text-[#cfd1d9]">{stream.payload}</pre>
358
+ <pre className="mt-1 overflow-auto font-mono text-xs text-[#cfd1d9] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">{stream.payload}</pre>
359
359
  </div>
360
360
  ))
361
361
  )}
@@ -413,7 +413,7 @@ export const WorkflowRunObservabilityDetailsPanel = React.memo<WorkflowRunObserv
413
413
  </div>
414
414
  </div>
415
415
 
416
- <div className="mt-3 min-h-0 flex-1 space-y-3 overflow-y-auto pr-1">
416
+ <div className="mt-3 min-h-0 flex-1 space-y-3 overflow-y-auto pr-1 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
417
417
 
418
418
  <div className="space-y-3 text-xs">
419
419
  <div>
@@ -467,23 +467,23 @@ export const WorkflowRunObservabilityDetailsPanel = React.memo<WorkflowRunObserv
467
467
 
468
468
  <div className="space-y-2 pt-2">
469
469
  <div className="text-[#8a8c96] text-xs uppercase tracking-wide">Arguments</div>
470
- <pre className="max-h-24 overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f] p-2 font-mono text-[11px] text-[#cfd1d9]">{formatPayload(selectedSpan.argumentsPayload ?? run.argumentsPayload)}</pre>
470
+ <pre className="max-h-24 overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f] p-2 font-mono text-[11px] text-[#cfd1d9] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">{formatPayload(selectedSpan.argumentsPayload ?? run.argumentsPayload)}</pre>
471
471
  </div>
472
472
 
473
473
  <div className="space-y-2">
474
474
  <div className="text-[#8a8c96] text-xs uppercase tracking-wide">Input</div>
475
- <pre className="max-h-24 overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f] p-2 font-mono text-[11px] text-[#cfd1d9]">{formatPayload(selectedSpan.inputPayload ?? run.inputPayload)}</pre>
475
+ <pre className="max-h-24 overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f] p-2 font-mono text-[11px] text-[#cfd1d9] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">{formatPayload(selectedSpan.inputPayload ?? run.inputPayload)}</pre>
476
476
  </div>
477
477
 
478
478
  <div className="space-y-2">
479
479
  <div className="text-[#8a8c96] text-xs uppercase tracking-wide">Output</div>
480
- <pre className="max-h-24 overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f] p-2 font-mono text-[11px] text-[#cfd1d9]">{formatPayload(selectedSpan.outputPayload ?? run.outputPayload)}</pre>
480
+ <pre className="max-h-24 overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f] p-2 font-mono text-[11px] text-[#cfd1d9] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">{formatPayload(selectedSpan.outputPayload ?? run.outputPayload)}</pre>
481
481
  </div>
482
482
  </div>
483
483
 
484
484
  <div className="space-y-2 border-t border-[#23242a] pt-3">
485
485
  <div className="font-medium text-sm">Events ({events.length})</div>
486
- <div className="max-h-32 divide-y divide-[#23242a] overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f]">
486
+ <div className="max-h-32 divide-y divide-[#23242a] overflow-auto rounded border border-[#2a2a2f] bg-[#090a0f] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
487
487
  {events.slice(0, 3).map((event) => (
488
488
  <div className="px-2 py-1 text-xs" key={event.id}>
489
489
  <div>{event.title}</div>
@@ -141,3 +141,7 @@ export type {
141
141
  WorkflowSpanRecord,
142
142
  WorkflowStreamRecord,
143
143
  } from './WorkflowRunObservabilityPanel'
144
+
145
+ // InboxList Composite
146
+ export { InboxList } from './InboxList'
147
+ export type { InboxListItem, InboxListProps } from './InboxList'