ai-design-system 0.1.25 → 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 (26) 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.tsx +2 -0
  5. package/components/blocks/SectionLayout/interfaces.ts +2 -0
  6. package/components/blocks/index.ts +3 -0
  7. package/components/composites/AdjustableLayout/AdjustableLayout.tsx +15 -2
  8. package/components/composites/DataTable/EnhancedDataTable.behaviors.stories.tsx +20 -3
  9. package/components/composites/DataTable/EnhancedDataTable.stories.tsx +18 -0
  10. package/components/composites/DataTable/EnhancedDataTable.tsx +2 -2
  11. package/components/composites/InboxList/InboxList.stories.tsx +45 -0
  12. package/components/composites/InboxList/InboxList.tsx +99 -0
  13. package/components/composites/InboxList/index.ts +2 -0
  14. package/components/composites/index.ts +4 -0
  15. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.behaviors.stories.tsx +34 -5
  16. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.mocks.ts +20 -0
  17. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.stories.tsx +36 -11
  18. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.tsx +68 -8
  19. package/components/features/index.ts +1 -0
  20. package/dist/index.cjs +143 -11
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.css +46 -6
  23. package/dist/index.d.ts +15 -0
  24. package/dist/index.js +142 -12
  25. package/dist/index.js.map +1 -1
  26. 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"
@@ -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,6 +26,7 @@ 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 && (
@@ -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
  }
@@ -504,7 +504,7 @@ export function EnhancedDataTable({
504
504
  </div>
505
505
 
506
506
  <div className="relative flex min-h-0 flex-1 flex-col gap-4 overflow-hidden px-4 lg:px-6">
507
- <div className="overflow-x-auto overflow-y-hidden rounded-lg border [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
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"
@@ -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'
@@ -6,12 +6,14 @@ import { WorkflowObservabilityFeature } from "./WorkflowObservabilityFeature"
6
6
  import {
7
7
  selectedWorkflowRunMock,
8
8
  workflowEventRecordsMock,
9
+ workflowInboxItemsMock,
9
10
  workflowSpanRecordsMock,
10
11
  workflowStreamRecordsMock,
11
12
  } from "./WorkflowObservabilityFeature.mocks"
12
13
 
13
14
  const onSearchQueryChange = fn()
14
15
  const onSelectSpan = fn()
16
+ const onSelectInboxItem = fn()
15
17
  const onReplayRun = fn()
16
18
  const onReenqueue = fn()
17
19
  const onCancelActiveSleeps = fn()
@@ -22,6 +24,11 @@ const meta = {
22
24
  title: "Features/WorkflowObservabilityFeature/Behaviors",
23
25
  component: WorkflowObservabilityFeature,
24
26
  tags: ["test"],
27
+ render: (args) => (
28
+ <div className="h-dvh min-h-0 p-2">
29
+ <WorkflowObservabilityFeature {...args} className="h-full" />
30
+ </div>
31
+ ),
25
32
  globals: {
26
33
  theme: "dark-neutral",
27
34
  },
@@ -49,17 +56,28 @@ const args: Story["args"] = {
49
56
  { id: "wake-up-sleep", label: "Wake Up Sleep", onClick: onWakeUpSleep, resourceTypes: ["sleep"], tone: "amber", surface: "details" },
50
57
  { id: "cancel-run", label: "Cancel", onClick: onCancel, resourceTypes: ["run"], tone: "danger", surface: "details" },
51
58
  ],
59
+ inbox: {
60
+ items: workflowInboxItemsMock,
61
+ selectedItemId: selectedWorkflowRunMock.runId,
62
+ onSelectItem: onSelectInboxItem,
63
+ searchQuery: "",
64
+ onSearchQueryChange: fn(),
65
+ },
66
+ className: "h-full",
52
67
  }
53
68
 
54
69
  function InteractiveStoryHarness() {
55
70
  const [selectedSpanId, setSelectedSpanId] = React.useState<string | null>(null)
56
71
 
57
72
  return (
58
- <WorkflowObservabilityFeature
59
- {...args}
60
- onSelectSpan={setSelectedSpanId}
61
- selectedSpanId={selectedSpanId}
62
- />
73
+ <div className="h-dvh min-h-0 p-2">
74
+ <WorkflowObservabilityFeature
75
+ {...args}
76
+ className="h-full"
77
+ onSelectSpan={setSelectedSpanId}
78
+ selectedSpanId={selectedSpanId}
79
+ />
80
+ </div>
63
81
  )
64
82
  }
65
83
 
@@ -175,3 +193,14 @@ export const SleepBehavior: Story = {
175
193
  await expect(await canvas.findByRole("menuitem", { name: /Cancel Active Sleeps/i })).toBeInTheDocument()
176
194
  },
177
195
  }
196
+
197
+ export const SelectRunFromInbox: Story = {
198
+ args,
199
+ play: async ({ canvasElement }) => {
200
+ const canvas = within(canvasElement)
201
+
202
+ const targetRun = workflowInboxItemsMock[1]
203
+ await userEvent.click(canvas.getByRole("button", { name: new RegExp(targetRun.id, "i") }))
204
+ await expect(onSelectInboxItem).toHaveBeenCalledWith(targetRun.id)
205
+ },
206
+ }
@@ -4,6 +4,26 @@ import type {
4
4
  WorkflowSpanRecord,
5
5
  WorkflowStreamRecord,
6
6
  } from "@/components/composites/WorkflowRunObservabilityPanel"
7
+ import type { InboxListItem } from "@/components/composites/InboxList"
8
+
9
+ export const workflowInboxItemsMock: InboxListItem[] = [
10
+ {
11
+ id: "wrun_01KP45XGBHRMT7HQJXXHKBEQS4",
12
+ title: "wrun_01KP45XGBHRMT7HQJXXHKBEQS4",
13
+ subtitle: "running • 1m 43s • retries 0",
14
+ badge: "running",
15
+ timestamp: "1m ago",
16
+ preview: "hook_waiting",
17
+ },
18
+ {
19
+ id: "wrun_01KP45XGBHRMT7HQJXXHKBEQS5",
20
+ title: "wrun_01KP45XGBHRMT7HQJXXHKBEQS5",
21
+ subtitle: "completed • 43s • retries 1",
22
+ badge: "completed",
23
+ timestamp: "7m ago",
24
+ preview: "run_completed",
25
+ },
26
+ ]
7
27
 
8
28
  export const selectedWorkflowRunMock: WorkflowRunSummary = {
9
29
  runId: "wrun_01KP45XGBHRMT7HQJXXHKBEQS4",
@@ -1,9 +1,11 @@
1
1
  import type { Meta, StoryObj } from "@storybook/nextjs-vite"
2
+ import { useState } from "react"
2
3
 
3
4
  import { WorkflowObservabilityFeature } from "./WorkflowObservabilityFeature"
4
5
  import {
5
6
  selectedWorkflowRunMock,
6
7
  workflowEventRecordsMock,
8
+ workflowInboxItemsMock,
7
9
  workflowSpanRecordsMock,
8
10
  workflowStreamRecordsMock,
9
11
  } from "./WorkflowObservabilityFeature.mocks"
@@ -13,6 +15,11 @@ const meta = {
13
15
  title: "Features/WorkflowObservabilityFeature",
14
16
  component: WorkflowObservabilityFeature,
15
17
  tags: ["autodocs"],
18
+ render: (args) => (
19
+ <div className="h-dvh min-h-0 p-2">
20
+ <WorkflowObservabilityFeature {...args} className="h-full" />
21
+ </div>
22
+ ),
16
23
  globals: {
17
24
  theme: "dark-neutral",
18
25
  },
@@ -38,6 +45,12 @@ const baseArgs: Story["args"] = {
38
45
  { id: "wake-up-sleep", label: "Wake Up Sleep", resourceTypes: ["sleep"], tone: "amber", surface: "details" },
39
46
  { id: "cancel-run", label: "Cancel", resourceTypes: ["run"], tone: "danger", surface: "details" },
40
47
  ],
48
+ inbox: {
49
+ items: workflowInboxItemsMock,
50
+ selectedItemId: selectedWorkflowRunMock.runId,
51
+ searchQuery: "",
52
+ },
53
+ className: "h-full",
41
54
  }
42
55
 
43
56
  export const Default: Story = {
@@ -48,19 +61,31 @@ export const WithStateManagement: Story = {
48
61
  args: baseArgs,
49
62
  render: () => {
50
63
  const state = useWorkflowObservabilityFeatureMock()
64
+ const [searchQuery, setSearchQuery] = useState("")
65
+ const [selectedItemId, setSelectedItemId] = useState<string | null>(state.selectedRun.runId)
51
66
 
52
67
  return (
53
- <WorkflowObservabilityFeature
54
- selectedRun={state.selectedRun}
55
- spans={state.spans}
56
- events={state.events}
57
- streams={state.streams}
58
- selectedSpanId={state.selectedSpanId}
59
- searchQuery={state.searchQuery}
60
- runActions={state.runActions}
61
- onSearchQueryChange={state.actionHandlers?.onSearchQueryChange}
62
- onSelectSpan={state.actionHandlers?.onSelectSpan}
63
- />
68
+ <div className="h-dvh min-h-0 p-2">
69
+ <WorkflowObservabilityFeature
70
+ selectedRun={state.selectedRun}
71
+ spans={state.spans}
72
+ events={state.events}
73
+ streams={state.streams}
74
+ selectedSpanId={state.selectedSpanId}
75
+ searchQuery={state.searchQuery}
76
+ runActions={state.runActions}
77
+ onSearchQueryChange={state.actionHandlers?.onSearchQueryChange}
78
+ onSelectSpan={state.actionHandlers?.onSelectSpan}
79
+ inbox={{
80
+ items: workflowInboxItemsMock,
81
+ selectedItemId,
82
+ onSelectItem: setSelectedItemId,
83
+ searchQuery,
84
+ onSearchQueryChange: setSearchQuery,
85
+ }}
86
+ className="h-full"
87
+ />
88
+ </div>
64
89
  )
65
90
  },
66
91
  }