ai-design-system 0.1.35 → 0.1.37

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 (86) hide show
  1. package/components/ai-elements/edge.tsx +42 -93
  2. package/components/ai-elements/node.tsx +1 -1
  3. package/components/blocks/DataTable/DataTable.tsx +4 -1
  4. package/components/blocks/FormReportsSection/FormReportsSection.tsx +4 -0
  5. package/components/blocks/InboxPanel/InboxPanel.stories.tsx +48 -0
  6. package/components/blocks/InboxPanel/InboxPanel.tsx +55 -0
  7. package/components/blocks/InboxPanel/index.ts +2 -0
  8. package/components/blocks/SectionLayout/SectionLayout.stories.tsx +106 -0
  9. package/components/blocks/SectionLayout/SectionLayout.tsx +4 -2
  10. package/components/blocks/SectionLayout/interfaces.ts +2 -0
  11. package/components/blocks/WorkflowCanvas/WorkflowCanvas.tsx +1 -0
  12. package/components/blocks/index.ts +3 -0
  13. package/components/composites/AdjustableLayout/AdjustableLayout.tsx +17 -3
  14. package/components/composites/AppHeader/AppHeader.tsx +15 -9
  15. package/components/composites/DataTable/DragHandleCell.tsx +1 -1
  16. package/components/composites/DataTable/DraggableRow.tsx +4 -2
  17. package/components/composites/DataTable/EnhancedDataTable.behaviors.stories.tsx +20 -3
  18. package/components/composites/DataTable/EnhancedDataTable.stories.tsx +18 -0
  19. package/components/composites/DataTable/EnhancedDataTable.tsx +336 -131
  20. package/components/composites/DataTable/index.ts +1 -0
  21. package/components/composites/DataTable/table-types.ts +17 -12
  22. package/components/composites/DataTable/useEnhancedDataTable.ts +30 -9
  23. package/components/composites/FilePreviewDialog/FilePreviewDialog.tsx +150 -0
  24. package/components/composites/FilePreviewDialog/index.ts +2 -0
  25. package/components/composites/FormReports/FormReportsDrawerForm.tsx +10 -16
  26. package/components/composites/FormReports/FormReportsTable.tsx +96 -56
  27. package/components/composites/FormReports/index.ts +1 -0
  28. package/components/composites/InboxList/InboxList.stories.tsx +45 -0
  29. package/components/composites/InboxList/InboxList.tsx +99 -0
  30. package/components/composites/InboxList/index.ts +2 -0
  31. package/components/composites/LoadingShimmer/LoadingShimmer.tsx +24 -0
  32. package/components/composites/LoadingShimmer/index.ts +2 -0
  33. package/components/composites/LoadingShimmer/interfaces.ts +4 -0
  34. package/components/composites/ModeSwitcher/ModeSwitcher.tsx +3 -3
  35. package/components/composites/ModeSwitcher/index.ts +1 -1
  36. package/components/composites/StateNode/StateNode.tsx +8 -5
  37. package/components/composites/TransitionNode/TransitionNode.tsx +7 -5
  38. package/components/composites/WorkflowRunObservabilityPanel/WorkflowRunObservabilityPanel.tsx +549 -0
  39. package/components/composites/WorkflowRunObservabilityPanel/index.ts +17 -0
  40. package/components/composites/index.ts +28 -4
  41. package/components/features/DashboardFeature/DashboardFeature.mocks.ts +158 -0
  42. package/components/features/DashboardFeature/DashboardFeature.stories.tsx +6 -0
  43. package/components/features/DashboardFeature/DashboardFeature.tsx +6 -49
  44. package/components/features/DashboardFeature/useDashboardFeature.d.ts +3 -1
  45. package/components/features/DashboardFeature/useDashboardFeature.mock.ts +34 -1
  46. package/components/features/FormReportsFeature/FormReportsFeature.mocks.ts +155 -54
  47. package/components/features/FormReportsFeature/FormReportsFeature.tsx +29 -16
  48. package/components/features/FormReportsFeature/useFormReportsFeature.d.ts +2 -0
  49. package/components/features/FormReportsFeature/useFormReportsFeature.mock.ts +5 -0
  50. package/components/features/PageLayout/PageLayout.mocks.ts +27 -0
  51. package/components/features/PageLayout/PageLayout.stories.tsx +31 -51
  52. package/components/features/PageLayout/PageLayout.tsx +54 -19
  53. package/components/features/PageLayout/usePageLayout.d.ts +4 -0
  54. package/components/features/PageLayout/usePageLayout.mock.ts +64 -1
  55. package/components/features/RefinementPanel/RefinementPanel.stories.tsx +2 -2
  56. package/components/features/RefinementPanel/useRefinementPanel.mock.ts +8 -0
  57. package/components/features/SpecNavigator/README.md +23 -3
  58. package/components/features/SpecNavigator/SpecNavigator.behaviors.stories.tsx +29 -11
  59. package/components/features/SpecNavigator/SpecNavigator.mocks.ts +35 -8
  60. package/components/features/SpecNavigator/SpecNavigator.stories.tsx +38 -36
  61. package/components/features/SpecNavigator/SpecNavigator.tsx +56 -3
  62. package/components/features/SpecNavigator/index.ts +5 -1
  63. package/components/features/SpecNavigator/useSpecNavigator.d.ts +2 -2
  64. package/components/features/SpecNavigator/useSpecNavigator.mock.ts +4 -4
  65. package/components/features/WorkflowBuilder/WorkflowBuilder.stories.tsx +45 -39
  66. package/components/features/WorkflowBuilder/WorkflowBuilder.tsx +6 -2
  67. package/components/features/WorkflowObservabilityFeature/README.md +114 -0
  68. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.behaviors.stories.tsx +206 -0
  69. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.mocks.ts +165 -0
  70. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.stories.tsx +151 -0
  71. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.tsx +227 -0
  72. package/components/features/WorkflowObservabilityFeature/index.ts +7 -0
  73. package/components/features/WorkflowObservabilityFeature/useWorkflowObservabilityFeature.d.ts +37 -0
  74. package/components/features/WorkflowObservabilityFeature/useWorkflowObservabilityFeature.mock.ts +286 -0
  75. package/components/features/index.ts +5 -0
  76. package/components/index.ts +12 -12
  77. package/components/ui/chart.tsx +3 -3
  78. package/components/ui/input-group.tsx +1 -1
  79. package/components/ui/input.tsx +1 -1
  80. package/dist/index.cjs +9267 -8661
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.css +393 -20
  83. package/dist/index.d.ts +494 -245
  84. package/dist/index.js +9101 -8294
  85. package/dist/index.js.map +1 -1
  86. package/package.json +5 -2
@@ -1,12 +1,8 @@
1
1
  import {
2
2
  BaseEdge,
3
3
  type EdgeProps,
4
- getBezierPath,
5
4
  getSimpleBezierPath,
6
- type InternalNode,
7
- type Node,
8
- Position,
9
- useInternalNode,
5
+ getSmoothStepPath,
10
6
  } from "@xyflow/react";
11
7
 
12
8
  const Temporary = ({
@@ -39,97 +35,49 @@ const Temporary = ({
39
35
  );
40
36
  };
41
37
 
42
- const getHandleCoordsByPosition = (
43
- node: InternalNode<Node>,
44
- handlePosition: Position,
45
- handleType: "source" | "target"
46
- ) => {
47
- const handle = node.internals.handleBounds?.[handleType]?.find(
48
- (h) => h.position === handlePosition
49
- );
50
-
51
- if (!handle) {
52
- return [0, 0] as const;
53
- }
54
-
55
- let offsetX = handle.width / 2;
56
- let offsetY = handle.height / 2;
57
-
58
- switch (handlePosition) {
59
- case Position.Left:
60
- offsetX = 0;
61
- break;
62
- case Position.Right:
63
- offsetX = handle.width;
64
- break;
65
- case Position.Top:
66
- offsetY = 0;
67
- break;
68
- case Position.Bottom:
69
- offsetY = handle.height;
70
- break;
71
- default:
72
- throw new Error(`Invalid handle position: ${handlePosition}`);
73
- }
74
-
75
- const x = node.internals.positionAbsolute.x + handle.x + offsetX;
76
- const y = node.internals.positionAbsolute.y + handle.y + offsetY;
77
-
78
- return [x, y] as const;
79
- };
80
-
81
- const getEdgeParams = (
82
- source: InternalNode<Node>,
83
- target: InternalNode<Node>
84
- ) => {
85
- const sx = source.internals.positionAbsolute.x + (source.measured?.width ?? 0) / 2;
86
- const sy = source.internals.positionAbsolute.y + (source.measured?.height ?? 0) / 2;
87
- const tx = target.internals.positionAbsolute.x + (target.measured?.width ?? 0) / 2;
88
- const ty = target.internals.positionAbsolute.y + (target.measured?.height ?? 0) / 2;
89
-
90
- const dx = tx - sx;
91
- const dy = ty - sy;
92
-
93
- // Pick source/target positions based on dominant direction
94
- let sourcePos: Position;
95
- let targetPos: Position;
96
-
97
- if (Math.abs(dx) > Math.abs(dy)) {
98
- // Horizontal dominant
99
- sourcePos = dx > 0 ? Position.Right : Position.Left;
100
- targetPos = dx > 0 ? Position.Left : Position.Right;
101
- } else {
102
- // Vertical dominant
103
- sourcePos = dy > 0 ? Position.Bottom : Position.Top;
104
- targetPos = dy > 0 ? Position.Top : Position.Bottom;
105
- }
106
-
107
- const [srcX, srcY] = getHandleCoordsByPosition(source, sourcePos, "source");
108
- const [tgtX, tgtY] = getHandleCoordsByPosition(target, targetPos, "target");
38
+ const Strict = ({
39
+ id,
40
+ sourceX,
41
+ sourceY,
42
+ targetX,
43
+ targetY,
44
+ sourcePosition,
45
+ targetPosition,
46
+ markerEnd,
47
+ style,
48
+ }: EdgeProps) => {
49
+ const [edgePath] = getSmoothStepPath({
50
+ sourceX,
51
+ sourceY,
52
+ sourcePosition,
53
+ targetX,
54
+ targetY,
55
+ targetPosition,
56
+ borderRadius: 0,
57
+ });
109
58
 
110
- return { sx: srcX, sy: srcY, tx: tgtX, ty: tgtY, sourcePos, targetPos };
59
+ return <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />;
111
60
  };
112
61
 
113
- const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
114
- const sourceNode = useInternalNode(source);
115
- const targetNode = useInternalNode(target);
116
-
117
- if (!(sourceNode && targetNode)) {
118
- return null;
119
- }
120
-
121
- const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
122
- sourceNode,
123
- targetNode
124
- );
125
-
126
- const [edgePath] = getBezierPath({
127
- sourceX: sx,
128
- sourceY: sy,
129
- sourcePosition: sourcePos,
130
- targetX: tx,
131
- targetY: ty,
132
- targetPosition: targetPos,
62
+ const Animated = ({
63
+ id,
64
+ sourceX,
65
+ sourceY,
66
+ targetX,
67
+ targetY,
68
+ sourcePosition,
69
+ targetPosition,
70
+ markerEnd,
71
+ style,
72
+ }: EdgeProps) => {
73
+ const [edgePath] = getSmoothStepPath({
74
+ sourceX,
75
+ sourceY,
76
+ sourcePosition,
77
+ targetX,
78
+ targetY,
79
+ targetPosition,
80
+ borderRadius: 0,
133
81
  });
134
82
 
135
83
  return (
@@ -143,6 +91,7 @@ const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
143
91
  };
144
92
 
145
93
  export const Edge = {
94
+ Strict,
146
95
  Temporary,
147
96
  Animated,
148
97
  };
@@ -23,7 +23,7 @@ export type NodeProps = ComponentProps<typeof Card> & {
23
23
  export const Node = ({ handles, className, status, ...props }: NodeProps) => (
24
24
  <Card
25
25
  className={cn(
26
- "node-container relative size-full h-auto w-sm gap-0 rounded-md bg-card p-0 transition-all duration-200",
26
+ "node-container relative h-[52px] w-[180px] gap-0 overflow-hidden rounded-md bg-card p-0 transition-all duration-200",
27
27
  status === "success" && "border-green-500 border-2",
28
28
  status === "error" && "border-red-500 border-2",
29
29
  className
@@ -6,20 +6,23 @@ import {
6
6
  type DashboardRow,
7
7
  type DashboardTableActionHandlers,
8
8
  } from "@/components/composites/DataTable"
9
+ import type { DynamicTableSchema } from "ui-schema-contracts"
9
10
  import { Button } from "@/components/primitives/Button"
10
11
 
11
12
  export interface DataTableProps {
12
13
  rows: DashboardRow[]
14
+ tableSchema: DynamicTableSchema
13
15
  handlers?: DashboardTableActionHandlers
14
16
  onCreateClick?: () => void
15
17
  createButtonLabel?: string
16
18
  }
17
19
 
18
- export const DataTable = React.memo<DataTableProps>(({ rows, handlers, onCreateClick, createButtonLabel }) => {
20
+ export const DataTable = React.memo<DataTableProps>(({ rows, tableSchema, handlers, onCreateClick, createButtonLabel }) => {
19
21
  return (
20
22
  <section className="px-4 lg:px-6">
21
23
  <EnhancedDataTable
22
24
  data={rows}
25
+ tableSchema={tableSchema}
23
26
  handlers={handlers}
24
27
  onCreateClick={onCreateClick ?? handlers?.onCreateClick ?? handlers?.onAddSection}
25
28
  createButtonLabel={createButtonLabel}
@@ -5,6 +5,7 @@ import {
5
5
  type FormReportsEntity,
6
6
  type FormReportsRowAction,
7
7
  type FormReportsTableHandlers,
8
+ type DashboardPaginationState,
8
9
  } from "@/components/composites/FormReports"
9
10
 
10
11
  export interface FormReportsSectionProps {
@@ -13,6 +14,7 @@ export interface FormReportsSectionProps {
13
14
  items: FormReportsEntity[]
14
15
  columns: FormReportsColumn[]
15
16
  rowActions?: FormReportsRowAction[]
17
+ pagination?: DashboardPaginationState
16
18
  tableHandlers?: FormReportsTableHandlers
17
19
  tableLeftActions?: React.ReactNode
18
20
  }
@@ -24,6 +26,7 @@ export const FormReportsSection = React.memo<FormReportsSectionProps>(
24
26
  items,
25
27
  columns,
26
28
  rowActions,
29
+ pagination,
27
30
  tableHandlers,
28
31
  tableLeftActions,
29
32
  }) => {
@@ -33,6 +36,7 @@ export const FormReportsSection = React.memo<FormReportsSectionProps>(
33
36
  items={items}
34
37
  columns={columns}
35
38
  rowActions={rowActions}
39
+ pagination={pagination}
36
40
  handlers={tableHandlers}
37
41
  leftActions={tableLeftActions}
38
42
  onCreateClick={onCreateClick}
@@ -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
- <div className="h-full flex flex-col">
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="flex-1">
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
  }
@@ -24,6 +24,7 @@ import type { WorkflowCanvasProps, WorkflowEdge } from "./interfaces";
24
24
  import "@xyflow/react/dist/style.css";
25
25
 
26
26
  const edgeTypes = {
27
+ straight: Edge.Strict,
27
28
  animated: Edge.Animated,
28
29
  temporary: Edge.Temporary,
29
30
  };
@@ -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,18 +169,31 @@ 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}>
176
189
  <div
177
190
  className={cn(
178
- "h-full overflow-hidden bg-card border border-border rounded-md",
191
+ "min-h-0 overflow-hidden bg-card border border-border rounded-md",
179
192
  section.className
180
193
  )}
181
194
  style={{
182
- flex: `${size} 1 0%`,
195
+ ...(fixedStyle ?? { flex: `${size} 1 0%` }),
196
+ minHeight: 0,
183
197
  minWidth: 0,
184
198
  }}
185
199
  >
@@ -16,12 +16,17 @@ export const AppHeader = React.memo<AppHeaderProps>(({
16
16
  }) => {
17
17
  return (
18
18
  <header className={`flex h-14 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-14 ${className || ""}`}>
19
- <div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
20
- {showSidebarToggle && <SidebarTrigger className="-ml-1" />}
21
- {showSidebarToggle && title && <Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" />}
22
- {showTitle && title && <h1 className="text-base font-medium">{title}</h1>}
23
- {tabs && tabs.length > 0 && (
24
- <div className="flex-1 flex justify-center">
19
+ <div className="grid w-full grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center px-4 lg:px-6">
20
+ <div className="min-w-0 flex items-center gap-1 lg:gap-2">
21
+ {showSidebarToggle && <SidebarTrigger className="-ml-1" />}
22
+ {showSidebarToggle && showTitle && title && (
23
+ <Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" />
24
+ )}
25
+ {showTitle && title && <h1 className="max-w-[28rem] truncate text-base font-medium">{title}</h1>}
26
+ </div>
27
+
28
+ <div className="justify-self-center">
29
+ {tabs && tabs.length > 0 && (
25
30
  <Tabs defaultValue={defaultTab || tabs[0]?.value} onValueChange={onTabChange}>
26
31
  <TabsList>
27
32
  {tabs.map((tab) => (
@@ -31,9 +36,10 @@ export const AppHeader = React.memo<AppHeaderProps>(({
31
36
  ))}
32
37
  </TabsList>
33
38
  </Tabs>
34
- </div>
35
- )}
36
- <div className="ml-auto flex items-center gap-2">{actions}</div>
39
+ )}
40
+ </div>
41
+
42
+ <div className="flex min-w-0 items-center justify-end gap-2">{actions}</div>
37
43
  </div>
38
44
  </header>
39
45
  )
@@ -4,7 +4,7 @@ import { GripVertical } from "lucide-react"
4
4
  import { Button } from "@/components/primitives/Button"
5
5
 
6
6
  export interface DragHandleCellProps {
7
- id: number
7
+ id: number | string
8
8
  }
9
9
 
10
10
  export function DragHandleCell({ id }: DragHandleCellProps) {
@@ -7,16 +7,18 @@ import type { DashboardRow } from "./table-types"
7
7
 
8
8
  export interface DraggableRowProps {
9
9
  row: Row<DashboardRow>
10
+ rowId: number | string
10
11
  }
11
12
 
12
- export function DraggableRow({ row }: DraggableRowProps) {
13
+ export function DraggableRow({ row, rowId }: DraggableRowProps) {
13
14
  const { transform, transition, setNodeRef, isDragging } = useSortable({
14
- id: row.original.id,
15
+ id: rowId,
15
16
  })
16
17
 
17
18
  return (
18
19
  <TableRow
19
20
  ref={setNodeRef}
21
+ data-row-id={String(rowId)}
20
22
  data-state={row.getIsSelected() && "selected"}
21
23
  data-dragging={isDragging}
22
24
  className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
@@ -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 })