ai-design-system 0.1.30 → 0.1.32

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.
@@ -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
@@ -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
  };
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+ import { Shimmer } from "@/components/ai-elements/shimmer"
3
+ import { cn } from "@/lib/utils"
4
+ import type { LoadingShimmerProps } from "./interfaces"
5
+
6
+ export const LoadingShimmer = React.memo<LoadingShimmerProps>(({ message = "Loading...", className }) => {
7
+ return (
8
+ <div className={cn("flex h-full min-h-0 flex-1 items-center justify-center px-6 py-10", className)}>
9
+ <div className="w-full max-w-3xl space-y-5">
10
+ <Shimmer className="text-sm text-muted-foreground">{message}</Shimmer>
11
+ <div className="space-y-3">
12
+ <div className="h-10 w-1/3 animate-pulse rounded-md bg-muted/70" />
13
+ <div className="h-24 w-full animate-pulse rounded-xl bg-muted/60" />
14
+ <div className="grid gap-3 md:grid-cols-2">
15
+ <div className="h-36 animate-pulse rounded-xl bg-muted/55" />
16
+ <div className="h-36 animate-pulse rounded-xl bg-muted/55" />
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ )
22
+ })
23
+
24
+ LoadingShimmer.displayName = "LoadingShimmer"
@@ -0,0 +1,2 @@
1
+ export { LoadingShimmer } from './LoadingShimmer'
2
+ export type { LoadingShimmerProps } from './interfaces'
@@ -0,0 +1,4 @@
1
+ export interface LoadingShimmerProps {
2
+ message?: string
3
+ className?: string
4
+ }
@@ -68,7 +68,7 @@ export const StateNode = memo(({ data, selected, id }: StateNodeProps) => {
68
68
  return (
69
69
  <Node
70
70
  className={cn(
71
- "relative flex h-auto w-auto min-w-[120px] max-w-[180px] flex-col items-center justify-center border border-border bg-card shadow-none transition-all duration-150 ease-out",
71
+ "relative flex flex-col items-center justify-center border border-border bg-card shadow-none transition-all duration-150 ease-out",
72
72
  selected && "border-primary border-2",
73
73
  isTerminal && "border-2 border-primary",
74
74
  isDisabled && "opacity-50"
@@ -87,11 +87,14 @@ export const StateNode = memo(({ data, selected, id }: StateNodeProps) => {
87
87
  {/* Status indicator badge in top right */}
88
88
  <StatusBadge status={status} />
89
89
 
90
- <div className="flex items-center gap-1.5 px-3 py-2">
91
- <Zap className="size-3 shrink-0 text-primary" strokeWidth={1.5} /> <div className="flex flex-col">
92
- <NodeTitle className="text-xs font-medium leading-tight">{displayTitle}</NodeTitle>
90
+ <div className="flex h-full w-full items-center justify-center gap-1.5 px-3 py-2">
91
+ <Zap className="size-3 shrink-0 text-primary" strokeWidth={1.5} />
92
+ <div className="min-w-0 flex-1 text-center">
93
+ <NodeTitle className="line-clamp-2 text-center text-xs font-medium leading-tight" title={displayTitle}>
94
+ {displayTitle}
95
+ </NodeTitle>
93
96
  {displayDescription && (
94
- <NodeDescription className="text-[10px] leading-tight mt-0.5">
97
+ <NodeDescription className="mt-0.5 line-clamp-2 text-center text-[10px] leading-tight" title={displayDescription}>
95
98
  {displayDescription}
96
99
  </NodeDescription>
97
100
  )}
@@ -53,7 +53,7 @@ export const TransitionNode = memo(
53
53
  return (
54
54
  <Node
55
55
  className={cn(
56
- "flex h-auto w-auto min-w-[120px] max-w-[180px] flex-col items-center justify-center border border-border bg-muted/40 shadow-none transition-all duration-150 ease-out",
56
+ "flex flex-col items-center justify-center border border-border bg-secondary text-secondary-foreground shadow-none transition-all duration-150 ease-out",
57
57
  selected && "border-primary border-2"
58
58
  )}
59
59
  data-testid={`transition-node-${id}`}
@@ -78,12 +78,14 @@ export const TransitionNode = memo(
78
78
  </div>
79
79
  )}
80
80
 
81
- <div className="flex items-center gap-1.5 px-3 py-2">
81
+ <div className="flex h-full w-full items-center justify-center gap-1.5 px-3 py-2">
82
82
  <GitBranch className="size-3 shrink-0 text-muted-foreground" strokeWidth={1.5} />
83
- <div className="flex flex-col">
84
- <NodeTitle className="text-xs font-medium leading-tight">{displayTitle}</NodeTitle>
83
+ <div className="min-w-0 flex-1 text-center">
84
+ <NodeTitle className="line-clamp-2 text-center text-xs font-medium leading-tight" title={displayTitle}>
85
+ {displayTitle}
86
+ </NodeTitle>
85
87
  {displayDescription && (
86
- <NodeDescription className="text-[10px] leading-tight mt-0.5">
88
+ <NodeDescription className="mt-0.5 line-clamp-2 text-center text-[10px] leading-tight" title={displayDescription}>
87
89
  {displayDescription}
88
90
  </NodeDescription>
89
91
  )}
@@ -484,7 +484,7 @@ export const WorkflowRunObservabilityDetailsPanel = React.memo<WorkflowRunObserv
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
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
- {events.slice(0, 3).map((event) => (
487
+ {events.map((event) => (
488
488
  <div className="px-2 py-1 text-xs" key={event.id}>
489
489
  <div>{event.title}</div>
490
490
  {event.timestamp ? (
@@ -118,6 +118,10 @@ export * from './AdjustableLayout'
118
118
  export * from './PageContainer'
119
119
  export type { PageContainerProps } from './PageContainer'
120
120
 
121
+ // LoadingShimmer Composite
122
+ export { LoadingShimmer } from './LoadingShimmer'
123
+ export type { LoadingShimmerProps } from './LoadingShimmer'
124
+
121
125
  // StateNode Composite
122
126
  export { StateNode } from './StateNode'
123
127
  export type { StateNodeData } from './StateNode'
@@ -4,26 +4,11 @@ import { LayoutProvider } from "@/components/blocks/LayoutProvider"
4
4
  import { SectionLayout } from "@/components/blocks/SectionLayout/SectionLayout"
5
5
  import type { SectionLayoutSection } from "@/components/blocks/SectionLayout/interfaces"
6
6
  import { AppHeader, type AppHeaderProps } from "@/components/composites/AppHeader"
7
+ import { LoadingShimmer } from "@/components/composites/LoadingShimmer"
7
8
  import { PageContainer } from "@/components/composites/PageContainer"
8
9
 
9
10
  function PageLayoutLoadingState({ message }: { message: string }) {
10
- return (
11
- <div className="flex h-full min-h-0 flex-1 items-center justify-center px-6 py-10">
12
- <div className="w-full max-w-3xl space-y-5">
13
- <div className="inline-flex w-fit animate-pulse rounded-md bg-muted px-3 py-1 text-sm text-muted-foreground">
14
- {message}
15
- </div>
16
- <div className="space-y-3">
17
- <div className="h-10 w-1/3 animate-pulse rounded-md bg-muted/70" />
18
- <div className="h-24 w-full animate-pulse rounded-xl bg-muted/60" />
19
- <div className="grid gap-3 md:grid-cols-2">
20
- <div className="h-36 animate-pulse rounded-xl bg-muted/55" />
21
- <div className="h-36 animate-pulse rounded-xl bg-muted/55" />
22
- </div>
23
- </div>
24
- </div>
25
- </div>
26
- )
11
+ return <LoadingShimmer message={message} />
27
12
  }
28
13
 
29
14
  /**
@@ -181,17 +166,13 @@ export const PageLayout = React.memo<PageLayoutProps>(
181
166
  </PageContainer>
182
167
  )
183
168
 
184
- if (!sidebar) {
185
- return pageContainer
186
- }
187
-
188
169
  return (
189
170
  <LayoutProvider
190
171
  defaultOpen={defaultSidebarOpen}
191
172
  sidebarWidth={sidebarWidth}
192
173
  sidebarWidthIcon={sidebarWidthIcon}
193
174
  >
194
- <AppSidebar {...sidebar} />
175
+ {sidebar ? <AppSidebar {...sidebar} /> : null}
195
176
  {pageContainer}
196
177
  </LayoutProvider>
197
178
  )
@@ -52,10 +52,75 @@ const baseArgs: Story["args"] = {
52
52
  className: "h-full",
53
53
  }
54
54
 
55
+ const hookFocusedRunActions = [
56
+ { id: 'wake-up-hook', label: 'Resume Hook', resourceTypes: ['hook'], tone: 'neutral' as const, surface: 'details' as const },
57
+ { id: 'cancel-hook', label: 'Cancel Hook', resourceTypes: ['hook'], tone: 'danger' as const, surface: 'menu' as const },
58
+ ]
59
+
60
+ const sleepFocusedRunActions = [
61
+ { id: 'wake-up-sleep', label: 'Wake Up Sleep', resourceTypes: ['sleep'], tone: 'amber' as const, surface: 'details' as const },
62
+ { id: 'cancel-active-sleeps', label: 'Cancel Active Sleeps', resourceTypes: ['sleep'], tone: 'danger' as const, surface: 'menu' as const },
63
+ ]
64
+
55
65
  export const Default: Story = {
56
66
  args: baseArgs,
57
67
  }
58
68
 
69
+ export const NoSelection: Story = {
70
+ args: {
71
+ ...baseArgs,
72
+ selectedRun: null,
73
+ selectedSpanId: null,
74
+ },
75
+ }
76
+
77
+ export const SelectedTraceRunDetails: Story = {
78
+ args: {
79
+ ...baseArgs,
80
+ selectedSpanId: 'span_generateBirthdayCard',
81
+ },
82
+ }
83
+
84
+ export const HookSuspensionState: Story = {
85
+ args: {
86
+ ...baseArgs,
87
+ selectedSpanId: 'hook_01KP45XGJK16SW3BS6GGC5A04B',
88
+ runActions: hookFocusedRunActions,
89
+ },
90
+ }
91
+
92
+ export const SleepSuspensionState: Story = {
93
+ args: {
94
+ ...baseArgs,
95
+ selectedSpanId: 'sleep_wait_01KP45XGJK16SW3BS6GGC5A04H',
96
+ runActions: sleepFocusedRunActions,
97
+ },
98
+ }
99
+
100
+ export const LiveUpdateSnapshot: Story = {
101
+ args: {
102
+ ...baseArgs,
103
+ events: [
104
+ ...workflowEventRecordsMock,
105
+ {
106
+ id: 'evt_4',
107
+ title: 'run_resumed',
108
+ timestamp: '4/13/2026, 12:46:12 PM',
109
+ description: 'Workflow resumed after manual approval.',
110
+ },
111
+ ],
112
+ streams: [
113
+ ...workflowStreamRecordsMock,
114
+ {
115
+ id: 'stream_3',
116
+ channel: 'event',
117
+ payload: JSON.stringify({ event_type: 'run_resumed', actor: 'manager' }),
118
+ timestamp: '12:46:12 PM',
119
+ },
120
+ ],
121
+ },
122
+ }
123
+
59
124
  export const WithStateManagement: Story = {
60
125
  args: baseArgs,
61
126
  render: () => {
@@ -175,6 +175,9 @@ export const WorkflowObservabilityFeature = React.memo<WorkflowObservabilityFeat
175
175
  return null
176
176
  }
177
177
 
178
+ const inboxDefaultSize = inbox.defaultSize ?? 25
179
+ const inboxMinSize = inbox.minSize ?? 25
180
+
178
181
  return [
179
182
  {
180
183
  id: "inbox",
@@ -190,13 +193,15 @@ export const WorkflowObservabilityFeature = React.memo<WorkflowObservabilityFeat
190
193
  className="h-full"
191
194
  />
192
195
  ),
193
- fixedSize: "16rem",
196
+ defaultSize: inboxDefaultSize,
197
+ minSize: inboxMinSize,
198
+ maxSize: 45,
194
199
  },
195
200
  {
196
201
  id: "observability",
197
202
  content: observabilityContent,
198
- defaultSize: 70,
199
- minSize: 40,
203
+ defaultSize: 100 - inboxDefaultSize,
204
+ minSize: 50,
200
205
  },
201
206
  ]
202
207
  }, [inbox, observabilityContent])