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.
- package/components/blocks/InboxPanel/InboxPanel.stories.tsx +48 -0
- package/components/blocks/InboxPanel/InboxPanel.tsx +55 -0
- package/components/blocks/InboxPanel/index.ts +2 -0
- package/components/blocks/SectionLayout/SectionLayout.stories.tsx +106 -0
- package/components/blocks/SectionLayout/SectionLayout.tsx +3 -1
- package/components/blocks/SectionLayout/interfaces.ts +2 -0
- package/components/blocks/index.ts +3 -0
- package/components/composites/AdjustableLayout/AdjustableLayout.tsx +15 -2
- package/components/composites/DataTable/EnhancedDataTable.behaviors.stories.tsx +20 -3
- package/components/composites/DataTable/EnhancedDataTable.stories.tsx +18 -0
- package/components/composites/DataTable/EnhancedDataTable.tsx +4 -4
- package/components/composites/InboxList/InboxList.stories.tsx +45 -0
- package/components/composites/InboxList/InboxList.tsx +99 -0
- package/components/composites/InboxList/index.ts +2 -0
- package/components/composites/WorkflowRunObservabilityPanel/WorkflowRunObservabilityPanel.tsx +9 -9
- package/components/composites/index.ts +4 -0
- package/components/features/FormReportsFeature/FormReportsFeature.tsx +1 -1
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.behaviors.stories.tsx +34 -5
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.mocks.ts +20 -0
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.stories.tsx +36 -11
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.tsx +68 -8
- package/components/features/index.ts +1 -0
- package/dist/index.cjs +156 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +57 -3
- package/dist/index.d.ts +15 -0
- package/dist/index.js +155 -25
- package/dist/index.js.map +1 -1
- 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"
|
|
@@ -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 =
|
|
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-
|
|
507
|
-
<div className="overflow-
|
|
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"
|
package/components/composites/WorkflowRunObservabilityPanel/WorkflowRunObservabilityPanel.tsx
CHANGED
|
@@ -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>
|