ai-design-system 0.1.35 → 0.1.36
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/ai-elements/edge.tsx +42 -93
- package/components/ai-elements/node.tsx +1 -1
- package/components/blocks/DataTable/DataTable.tsx +4 -1
- package/components/blocks/FormReportsSection/FormReportsSection.tsx +4 -0
- 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 +4 -2
- package/components/blocks/SectionLayout/interfaces.ts +2 -0
- package/components/blocks/WorkflowCanvas/WorkflowCanvas.tsx +1 -0
- package/components/blocks/index.ts +3 -0
- package/components/composites/AdjustableLayout/AdjustableLayout.tsx +17 -3
- package/components/composites/AppHeader/AppHeader.tsx +15 -9
- package/components/composites/DataTable/DragHandleCell.tsx +1 -1
- package/components/composites/DataTable/DraggableRow.tsx +4 -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 +336 -131
- package/components/composites/DataTable/index.ts +1 -0
- package/components/composites/DataTable/table-types.ts +17 -12
- package/components/composites/DataTable/useEnhancedDataTable.ts +30 -9
- package/components/composites/FilePreviewDialog/FilePreviewDialog.tsx +150 -0
- package/components/composites/FilePreviewDialog/index.ts +2 -0
- package/components/composites/FormReports/FormReportsDrawerForm.tsx +10 -16
- package/components/composites/FormReports/FormReportsTable.tsx +96 -56
- package/components/composites/FormReports/index.ts +1 -0
- 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/LoadingShimmer/LoadingShimmer.tsx +24 -0
- package/components/composites/LoadingShimmer/index.ts +2 -0
- package/components/composites/LoadingShimmer/interfaces.ts +4 -0
- package/components/composites/ModeSwitcher/ModeSwitcher.tsx +3 -3
- package/components/composites/ModeSwitcher/index.ts +1 -1
- package/components/composites/StateNode/StateNode.tsx +8 -5
- package/components/composites/TransitionNode/TransitionNode.tsx +7 -5
- package/components/composites/WorkflowRunObservabilityPanel/WorkflowRunObservabilityPanel.tsx +549 -0
- package/components/composites/WorkflowRunObservabilityPanel/index.ts +17 -0
- package/components/composites/index.ts +28 -4
- package/components/features/DashboardFeature/DashboardFeature.mocks.ts +158 -0
- package/components/features/DashboardFeature/DashboardFeature.stories.tsx +6 -0
- package/components/features/DashboardFeature/DashboardFeature.tsx +6 -49
- package/components/features/DashboardFeature/useDashboardFeature.d.ts +3 -1
- package/components/features/DashboardFeature/useDashboardFeature.mock.ts +34 -1
- package/components/features/FormReportsFeature/FormReportsFeature.mocks.ts +155 -54
- package/components/features/FormReportsFeature/FormReportsFeature.tsx +29 -16
- package/components/features/FormReportsFeature/useFormReportsFeature.d.ts +2 -0
- package/components/features/FormReportsFeature/useFormReportsFeature.mock.ts +5 -0
- package/components/features/PageLayout/PageLayout.mocks.ts +27 -0
- package/components/features/PageLayout/PageLayout.stories.tsx +31 -51
- package/components/features/PageLayout/PageLayout.tsx +54 -19
- package/components/features/PageLayout/usePageLayout.d.ts +4 -0
- package/components/features/PageLayout/usePageLayout.mock.ts +64 -1
- package/components/features/RefinementPanel/RefinementPanel.stories.tsx +2 -2
- package/components/features/RefinementPanel/useRefinementPanel.mock.ts +8 -0
- package/components/features/SpecNavigator/README.md +23 -3
- package/components/features/SpecNavigator/SpecNavigator.behaviors.stories.tsx +29 -11
- package/components/features/SpecNavigator/SpecNavigator.mocks.ts +35 -8
- package/components/features/SpecNavigator/SpecNavigator.stories.tsx +38 -36
- package/components/features/SpecNavigator/SpecNavigator.tsx +56 -3
- package/components/features/SpecNavigator/index.ts +5 -1
- package/components/features/SpecNavigator/useSpecNavigator.d.ts +2 -2
- package/components/features/SpecNavigator/useSpecNavigator.mock.ts +4 -4
- package/components/features/WorkflowBuilder/WorkflowBuilder.stories.tsx +45 -39
- package/components/features/WorkflowBuilder/WorkflowBuilder.tsx +6 -2
- package/components/features/WorkflowObservabilityFeature/README.md +114 -0
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.behaviors.stories.tsx +206 -0
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.mocks.ts +165 -0
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.stories.tsx +151 -0
- package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.tsx +227 -0
- package/components/features/WorkflowObservabilityFeature/index.ts +7 -0
- package/components/features/WorkflowObservabilityFeature/useWorkflowObservabilityFeature.d.ts +37 -0
- package/components/features/WorkflowObservabilityFeature/useWorkflowObservabilityFeature.mock.ts +286 -0
- package/components/features/index.ts +5 -0
- package/components/index.ts +12 -12
- package/components/ui/chart.tsx +3 -3
- package/components/ui/input-group.tsx +1 -1
- package/components/ui/input.tsx +1 -1
- package/dist/index.cjs +9267 -8661
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +393 -20
- package/dist/index.d.ts +481 -232
- package/dist/index.js +9101 -8294
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BaseEdge,
|
|
3
3
|
type EdgeProps,
|
|
4
|
-
getBezierPath,
|
|
5
4
|
getSimpleBezierPath,
|
|
6
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 {
|
|
59
|
+
return <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />;
|
|
111
60
|
};
|
|
112
61
|
|
|
113
|
-
const Animated = ({
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
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"
|
|
@@ -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
|
}
|
|
@@ -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 =
|
|
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-
|
|
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="
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
)
|
|
@@ -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:
|
|
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 })
|