@you-agent-factory/components 0.0.0

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 (92) hide show
  1. package/README.md +223 -0
  2. package/docs/README.md +83 -0
  3. package/docs/button.md +119 -0
  4. package/docs/charts.md +325 -0
  5. package/docs/data-display-code-panel.md +120 -0
  6. package/docs/feedback-alert-panel.md +117 -0
  7. package/docs/feedback-skeleton.md +54 -0
  8. package/docs/forms-form-field.md +490 -0
  9. package/docs/forms-input-primitives.md +311 -0
  10. package/docs/forms-select-primitives.md +362 -0
  11. package/docs/graphs.md +250 -0
  12. package/docs/layout-display-primitives.md +211 -0
  13. package/docs/overlays.md +414 -0
  14. package/docs/table-data-table.md +331 -0
  15. package/docs/typography-roles.md +182 -0
  16. package/docs/widget-frame-recipes.md +273 -0
  17. package/package.json +161 -0
  18. package/src/category-paths.ts +20 -0
  19. package/src/charts/chart-state-panel.tsx +103 -0
  20. package/src/charts/chart.tsx +287 -0
  21. package/src/charts/index.ts +21 -0
  22. package/src/data-display/code-panel.tsx +67 -0
  23. package/src/data-display/data-table.tsx +180 -0
  24. package/src/data-display/description-list.tsx +24 -0
  25. package/src/data-display/index.ts +34 -0
  26. package/src/data-display/table-layout.ts +12 -0
  27. package/src/data-display/table.tsx +131 -0
  28. package/src/feedback/alert-panel-semantics.ts +75 -0
  29. package/src/feedback/alert-panel.tsx +187 -0
  30. package/src/feedback/index.ts +21 -0
  31. package/src/feedback/skeleton.tsx +16 -0
  32. package/src/forms/index.ts +73 -0
  33. package/src/forms/package-checkbox.tsx +50 -0
  34. package/src/forms/package-enum-select.tsx +185 -0
  35. package/src/forms/package-file-input.tsx +25 -0
  36. package/src/forms/package-form-field.tsx +202 -0
  37. package/src/forms/package-input.tsx +25 -0
  38. package/src/forms/package-native-select.tsx +25 -0
  39. package/src/forms/package-select.tsx +209 -0
  40. package/src/forms/package-textarea.tsx +35 -0
  41. package/src/forms/select-icons.tsx +35 -0
  42. package/src/graphs/graph-edge-path.ts +241 -0
  43. package/src/graphs/graph-edge.tsx +130 -0
  44. package/src/graphs/graph-interactive-example.tsx +187 -0
  45. package/src/graphs/graph-node-button.tsx +51 -0
  46. package/src/graphs/graph-node-handle-badge.tsx +108 -0
  47. package/src/graphs/graph-node-handle.ts +28 -0
  48. package/src/graphs/graph-node-shell.tsx +111 -0
  49. package/src/graphs/graph-node-state-indicator.tsx +47 -0
  50. package/src/graphs/graph-node-state.ts +111 -0
  51. package/src/graphs/graph-viewport-surface.tsx +27 -0
  52. package/src/graphs/index.ts +41 -0
  53. package/src/icons/index.ts +4 -0
  54. package/src/index.ts +209 -0
  55. package/src/layout/action-row.tsx +53 -0
  56. package/src/layout/index.ts +9 -0
  57. package/src/layout/surface-panel.tsx +91 -0
  58. package/src/navigation/index.ts +4 -0
  59. package/src/overlays/collapsible.tsx +22 -0
  60. package/src/overlays/dialog.tsx +148 -0
  61. package/src/overlays/index.ts +31 -0
  62. package/src/overlays/overlay-layout.ts +9 -0
  63. package/src/overlays/popover.tsx +29 -0
  64. package/src/overlays/scroll-area.tsx +78 -0
  65. package/src/primitives/button-link.tsx +22 -0
  66. package/src/primitives/button.tsx +258 -0
  67. package/src/primitives/icon-button-shell.tsx +49 -0
  68. package/src/primitives/index.ts +31 -0
  69. package/src/primitives/package-text.tsx +29 -0
  70. package/src/primitives/typography-roles.ts +12 -0
  71. package/src/primitives/typography.tsx +165 -0
  72. package/src/recipes/index.ts +61 -0
  73. package/src/recipes/widget-frame-content.tsx +114 -0
  74. package/src/recipes/widget-frame-disclosure.tsx +161 -0
  75. package/src/recipes/widget-frame-layout.ts +40 -0
  76. package/src/recipes/widget-frame-skeleton.tsx +16 -0
  77. package/src/recipes/widget-frame-states.tsx +99 -0
  78. package/src/recipes/widget-frame-typography.ts +19 -0
  79. package/src/recipes/widget-frame.tsx +84 -0
  80. package/src/styles/color-palette-presets.css +167 -0
  81. package/src/styles/color-role-tokens.css +163 -0
  82. package/src/styles/layout-role-tokens.css +27 -0
  83. package/src/styles/text-color-role-tokens.css +29 -0
  84. package/src/styles/typography-role-tokens.css +72 -0
  85. package/src/styles/typography-role-utilities.css +73 -0
  86. package/src/styles/typography-roles.css +53 -0
  87. package/src/styles.css +32 -0
  88. package/src/testing/index.ts +13 -0
  89. package/src/testing/render.tsx +16 -0
  90. package/src/tokens/index.ts +4 -0
  91. package/src/utilities/cn.ts +5 -0
  92. package/src/utilities/index.ts +6 -0
@@ -0,0 +1,35 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ export function SelectChevronDownIcon(props: SVGProps<SVGSVGElement>) {
4
+ return (
5
+ <svg
6
+ aria-hidden="true"
7
+ fill="none"
8
+ stroke="currentColor"
9
+ strokeLinecap="round"
10
+ strokeLinejoin="round"
11
+ strokeWidth={1.8}
12
+ viewBox="0 0 24 24"
13
+ {...props}
14
+ >
15
+ <path d="m6 9 6 6 6-6" />
16
+ </svg>
17
+ );
18
+ }
19
+
20
+ export function SelectCheckIcon(props: SVGProps<SVGSVGElement>) {
21
+ return (
22
+ <svg
23
+ aria-hidden="true"
24
+ fill="none"
25
+ stroke="currentColor"
26
+ strokeLinecap="round"
27
+ strokeLinejoin="round"
28
+ strokeWidth={2}
29
+ viewBox="0 0 24 24"
30
+ {...props}
31
+ >
32
+ <path d="M20 6 9 17l-5-5" />
33
+ </svg>
34
+ );
35
+ }
@@ -0,0 +1,241 @@
1
+ import { getBezierPath, Position } from "@xyflow/react";
2
+
3
+ export type GraphEdgeWaypoint = {
4
+ x: number;
5
+ y: number;
6
+ };
7
+
8
+ const CATMULL_ROM_ALPHA = 0.5;
9
+ const MIN_PARAMETER_DISTANCE = 0.0001;
10
+ const VIRTUAL_ENDPOINT_DISTANCE = 96;
11
+
12
+ function positionVector(position: Position): GraphEdgeWaypoint {
13
+ switch (position) {
14
+ case Position.Left:
15
+ return { x: -1, y: 0 };
16
+ case Position.Right:
17
+ return { x: 1, y: 0 };
18
+ case Position.Top:
19
+ return { x: 0, y: -1 };
20
+ case Position.Bottom:
21
+ return { x: 0, y: 1 };
22
+ }
23
+ }
24
+
25
+ function pointDistance(
26
+ first: GraphEdgeWaypoint,
27
+ second: GraphEdgeWaypoint,
28
+ ): number {
29
+ return Math.hypot(second.x - first.x, second.y - first.y);
30
+ }
31
+
32
+ function catmullRomParameterDistance(
33
+ first: GraphEdgeWaypoint,
34
+ second: GraphEdgeWaypoint,
35
+ ): number {
36
+ return Math.max(
37
+ MIN_PARAMETER_DISTANCE,
38
+ pointDistance(first, second) ** CATMULL_ROM_ALPHA,
39
+ );
40
+ }
41
+
42
+ function formatPathNumber(value: number): string {
43
+ if (Object.is(value, -0)) {
44
+ return "0";
45
+ }
46
+
47
+ return Number.isInteger(value) ? `${value}` : value.toFixed(3);
48
+ }
49
+
50
+ function compactConsecutiveRoutePoints(
51
+ routePoints: readonly GraphEdgeWaypoint[],
52
+ ): GraphEdgeWaypoint[] {
53
+ const compacted: GraphEdgeWaypoint[] = [];
54
+
55
+ for (const point of routePoints) {
56
+ const previous = compacted.at(-1);
57
+ if (previous && previous.x === point.x && previous.y === point.y) {
58
+ continue;
59
+ }
60
+
61
+ compacted.push(point);
62
+ }
63
+
64
+ return compacted;
65
+ }
66
+
67
+ function virtualEndpoint(input: {
68
+ direction: GraphEdgeWaypoint;
69
+ from: GraphEdgeWaypoint;
70
+ mode: "after" | "before";
71
+ neighbor: GraphEdgeWaypoint;
72
+ }): GraphEdgeWaypoint {
73
+ const distance = Math.max(
74
+ VIRTUAL_ENDPOINT_DISTANCE,
75
+ pointDistance(input.from, input.neighbor),
76
+ );
77
+ const scalar = input.mode === "before" ? -distance : distance;
78
+
79
+ return {
80
+ x: input.from.x + input.direction.x * scalar,
81
+ y: input.from.y + input.direction.y * scalar,
82
+ };
83
+ }
84
+
85
+ function catmullRomTangent(input: {
86
+ current: GraphEdgeWaypoint;
87
+ next: GraphEdgeWaypoint;
88
+ previous: GraphEdgeWaypoint;
89
+ tCurrent: number;
90
+ tNext: number;
91
+ tPrevious: number;
92
+ }): GraphEdgeWaypoint {
93
+ const previousSpan = input.tCurrent - input.tPrevious;
94
+ const nextSpan = input.tNext - input.tCurrent;
95
+ const totalSpan = input.tNext - input.tPrevious;
96
+
97
+ return {
98
+ x:
99
+ nextSpan *
100
+ ((input.current.x - input.previous.x) / previousSpan -
101
+ (input.next.x - input.previous.x) / totalSpan +
102
+ (input.next.x - input.current.x) / nextSpan),
103
+ y:
104
+ nextSpan *
105
+ ((input.current.y - input.previous.y) / previousSpan -
106
+ (input.next.y - input.previous.y) / totalSpan +
107
+ (input.next.y - input.current.y) / nextSpan),
108
+ };
109
+ }
110
+
111
+ function buildWaypointCatmullRomPath(input: {
112
+ routePoints: readonly GraphEdgeWaypoint[];
113
+ sourcePosition: Position;
114
+ targetPosition: Position;
115
+ }): string {
116
+ const routePoints = compactConsecutiveRoutePoints(input.routePoints);
117
+ if (routePoints.length <= 1) {
118
+ const [point] = routePoints;
119
+ return point
120
+ ? `M ${formatPathNumber(point.x)} ${formatPathNumber(point.y)}`
121
+ : "";
122
+ }
123
+
124
+ const lastPointIndex = routePoints.length - 1;
125
+ const [source] = routePoints;
126
+ const points = [
127
+ virtualEndpoint({
128
+ direction: positionVector(input.sourcePosition),
129
+ from: source,
130
+ mode: "before",
131
+ neighbor: routePoints[1],
132
+ }),
133
+ ...routePoints,
134
+ virtualEndpoint({
135
+ direction: {
136
+ x: -positionVector(input.targetPosition).x,
137
+ y: -positionVector(input.targetPosition).y,
138
+ },
139
+ from: routePoints[lastPointIndex],
140
+ mode: "after",
141
+ neighbor: routePoints[lastPointIndex - 1],
142
+ }),
143
+ ];
144
+ const segments = [
145
+ `M ${formatPathNumber(source.x)} ${formatPathNumber(source.y)}`,
146
+ ];
147
+
148
+ for (let index = 1; index <= lastPointIndex; index += 1) {
149
+ const previous = points[index - 1];
150
+ const start = points[index];
151
+ const end = points[index + 1];
152
+ const next = points[index + 2];
153
+ const tPrevious = 0;
154
+ const tStart = tPrevious + catmullRomParameterDistance(previous, start);
155
+ const tEnd = tStart + catmullRomParameterDistance(start, end);
156
+ const tNext = tEnd + catmullRomParameterDistance(end, next);
157
+ const startTangent = catmullRomTangent({
158
+ current: start,
159
+ next: end,
160
+ previous,
161
+ tCurrent: tStart,
162
+ tNext: tEnd,
163
+ tPrevious,
164
+ });
165
+ const endTangent = catmullRomTangent({
166
+ current: end,
167
+ next,
168
+ previous: start,
169
+ tCurrent: tEnd,
170
+ tNext,
171
+ tPrevious: tStart,
172
+ });
173
+ const firstControl = {
174
+ x: start.x + startTangent.x / 3,
175
+ y: start.y + startTangent.y / 3,
176
+ };
177
+ const secondControl = {
178
+ x: end.x - endTangent.x / 3,
179
+ y: end.y - endTangent.y / 3,
180
+ };
181
+
182
+ segments.push(
183
+ [
184
+ "C",
185
+ formatPathNumber(firstControl.x),
186
+ formatPathNumber(firstControl.y),
187
+ `${formatPathNumber(secondControl.x)},`,
188
+ formatPathNumber(secondControl.y),
189
+ `${formatPathNumber(end.x)},`,
190
+ formatPathNumber(end.y),
191
+ ].join(" "),
192
+ );
193
+ }
194
+
195
+ return segments.join(" ");
196
+ }
197
+
198
+ export function buildGraphEdgePathThroughWaypoints(input: {
199
+ sourceX: number;
200
+ sourceY: number;
201
+ sourcePosition: Position;
202
+ targetX: number;
203
+ targetY: number;
204
+ targetPosition: Position;
205
+ waypoints?: readonly GraphEdgeWaypoint[];
206
+ }): {
207
+ labelX: number;
208
+ labelY: number;
209
+ path: string;
210
+ } {
211
+ const routePoints = [
212
+ { x: input.sourceX, y: input.sourceY },
213
+ ...(input.waypoints ?? []),
214
+ { x: input.targetX, y: input.targetY },
215
+ ];
216
+
217
+ if (routePoints.length <= 2) {
218
+ const [path, labelX, labelY] = getBezierPath({
219
+ sourcePosition: input.sourcePosition,
220
+ sourceX: input.sourceX,
221
+ sourceY: input.sourceY,
222
+ targetPosition: input.targetPosition,
223
+ targetX: input.targetX,
224
+ targetY: input.targetY,
225
+ });
226
+ return { labelX, labelY, path };
227
+ }
228
+
229
+ const path = buildWaypointCatmullRomPath({
230
+ routePoints,
231
+ sourcePosition: input.sourcePosition,
232
+ targetPosition: input.targetPosition,
233
+ });
234
+ const midpointIndex = Math.floor((routePoints.length - 1) / 2);
235
+ const labelPoint = routePoints[midpointIndex];
236
+ return {
237
+ labelX: labelPoint.x,
238
+ labelY: labelPoint.y,
239
+ path,
240
+ };
241
+ }
@@ -0,0 +1,130 @@
1
+ import { BaseEdge, type EdgeProps, getBezierPath } from "@xyflow/react";
2
+ import { useEffect, useRef, useState } from "react";
3
+
4
+ import {
5
+ buildGraphEdgePathThroughWaypoints,
6
+ type GraphEdgeWaypoint,
7
+ } from "./graph-edge-path";
8
+
9
+ export type GraphEdgeData = {
10
+ alwaysShowLabel?: boolean;
11
+ label?: string;
12
+ waypoints?: GraphEdgeWaypoint[];
13
+ };
14
+
15
+ export const GRAPH_EDGE_TYPES = {
16
+ graphEdge: GraphEdge,
17
+ };
18
+
19
+ export type GraphEdgeProps = EdgeProps & {
20
+ edgeClassName?: string;
21
+ labelClassName?: string;
22
+ };
23
+
24
+ export function GraphEdge({
25
+ data,
26
+ edgeClassName = "graph-edge",
27
+ id,
28
+ interactionWidth,
29
+ labelClassName = "graph-edge-label pointer-events-none fill-on-surface-subtle text-[11px] font-semibold",
30
+ markerEnd,
31
+ selected,
32
+ sourcePosition,
33
+ sourceX,
34
+ sourceY,
35
+ style,
36
+ targetPosition,
37
+ targetX,
38
+ targetY,
39
+ }: GraphEdgeProps) {
40
+ const edgeRef = useRef<SVGGElement | null>(null);
41
+ const [inspected, setInspected] = useState(false);
42
+ const edgeData = (data ?? {}) as GraphEdgeData;
43
+ const routedPath = buildGraphEdgePathThroughWaypoints({
44
+ sourcePosition,
45
+ sourceX,
46
+ sourceY,
47
+ targetPosition,
48
+ targetX,
49
+ targetY,
50
+ waypoints: edgeData.waypoints,
51
+ });
52
+ const [fallbackPath, fallbackLabelX, fallbackLabelY] = getBezierPath({
53
+ sourcePosition,
54
+ sourceX,
55
+ sourceY,
56
+ targetPosition,
57
+ targetX,
58
+ targetY,
59
+ });
60
+ const edgePath =
61
+ edgeData.waypoints && edgeData.waypoints.length > 0
62
+ ? routedPath.path
63
+ : fallbackPath;
64
+ const labelX =
65
+ edgeData.waypoints && edgeData.waypoints.length > 0
66
+ ? routedPath.labelX
67
+ : fallbackLabelX;
68
+ const labelY =
69
+ edgeData.waypoints && edgeData.waypoints.length > 0
70
+ ? routedPath.labelY
71
+ : fallbackLabelY;
72
+
73
+ useEffect(() => {
74
+ const edgeElement = edgeRef.current?.parentElement;
75
+ if (!edgeElement) {
76
+ return;
77
+ }
78
+
79
+ const show = () => setInspected(true);
80
+ const hide = () => setInspected(false);
81
+
82
+ edgeElement.addEventListener("mouseenter", show);
83
+ edgeElement.addEventListener("mouseleave", hide);
84
+ edgeElement.addEventListener("focusin", show);
85
+ edgeElement.addEventListener("focusout", hide);
86
+
87
+ return () => {
88
+ edgeElement.removeEventListener("mouseenter", show);
89
+ edgeElement.removeEventListener("mouseleave", hide);
90
+ edgeElement.removeEventListener("focusin", show);
91
+ edgeElement.removeEventListener("focusout", hide);
92
+ };
93
+ }, []);
94
+
95
+ const revealLabel = Boolean(
96
+ edgeData.label && (edgeData.alwaysShowLabel || inspected || selected),
97
+ );
98
+
99
+ return (
100
+ <g
101
+ className={edgeClassName}
102
+ data-edge-id={id}
103
+ data-label-visible={revealLabel ? "true" : "false"}
104
+ ref={edgeRef}
105
+ >
106
+ <BaseEdge
107
+ interactionWidth={interactionWidth}
108
+ markerEnd={markerEnd}
109
+ path={edgePath}
110
+ style={style}
111
+ />
112
+ {edgeData.label ? (
113
+ <text
114
+ className={labelClassName}
115
+ style={{
116
+ paintOrder: "stroke",
117
+ stroke: "var(--color-surface)",
118
+ strokeLinejoin: "round",
119
+ strokeWidth: 8,
120
+ }}
121
+ textAnchor="middle"
122
+ x={labelX}
123
+ y={labelY}
124
+ >
125
+ {edgeData.label}
126
+ </text>
127
+ ) : null}
128
+ </g>
129
+ );
130
+ }
@@ -0,0 +1,187 @@
1
+ import "@xyflow/react/dist/style.css";
2
+
3
+ import {
4
+ Background,
5
+ Controls,
6
+ ReactFlow,
7
+ ReactFlowProvider,
8
+ type Edge,
9
+ type Node,
10
+ type NodeProps,
11
+ } from "@xyflow/react";
12
+ import { useCallback, useMemo, useState } from "react";
13
+
14
+ import { GRAPH_EDGE_TYPES } from "./graph-edge";
15
+ import {
16
+ desktopInteractiveGraphNodes,
17
+ type GraphInteractiveFixtureNode,
18
+ } from "./graph-story-fixtures";
19
+ import { GraphNodeButton } from "./graph-node-button";
20
+ import { GraphNodeShell } from "./graph-node-shell";
21
+ import type { GraphNodeState } from "./graph-node-state";
22
+ import { GraphViewportSurface } from "./graph-viewport-surface";
23
+
24
+ type GenericGraphFlowNodeData = GraphInteractiveFixtureNode & {
25
+ graphState: GraphNodeState;
26
+ onActivate?: () => void;
27
+ shellState: GraphNodeState;
28
+ };
29
+
30
+ function GenericGraphFlowNode({ data }: NodeProps<Node<GenericGraphFlowNodeData>>) {
31
+ return (
32
+ <div className="w-44 max-w-full sm:w-52">
33
+ <GraphNodeShell
34
+ handles={data.handles}
35
+ nodeKind={data.nodeKind}
36
+ state={data.shellState}
37
+ stateLabel={data.stateLabel}
38
+ >
39
+ <GraphNodeButton
40
+ graphState={data.graphState}
41
+ onClick={data.onActivate}
42
+ stateLabel={data.stateLabel}
43
+ >
44
+ {data.label}
45
+ </GraphNodeButton>
46
+ </GraphNodeShell>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ const GENERIC_GRAPH_NODE_TYPES = {
52
+ genericGraph: GenericGraphFlowNode,
53
+ };
54
+
55
+ function buildFlowNodes(
56
+ fixtureNodes: GraphInteractiveFixtureNode[],
57
+ selectedNodeId: string | null,
58
+ onSelectNode: (nodeId: string) => void,
59
+ ): Node<GenericGraphFlowNodeData>[] {
60
+ return fixtureNodes.map((fixtureNode) => {
61
+ const isSelectable = fixtureNode.selectable === true;
62
+ const isSelected = isSelectable && selectedNodeId === fixtureNode.id;
63
+ const resolvedState =
64
+ fixtureNode.fixedState ?? (isSelected ? "selected" : "default");
65
+
66
+ return {
67
+ data: {
68
+ ...fixtureNode,
69
+ graphState: resolvedState,
70
+ onActivate: isSelectable
71
+ ? () => {
72
+ onSelectNode(fixtureNode.id);
73
+ }
74
+ : undefined,
75
+ shellState: resolvedState,
76
+ },
77
+ draggable: false,
78
+ id: fixtureNode.id,
79
+ position: fixtureNode.position,
80
+ selectable: false,
81
+ type: "genericGraph",
82
+ };
83
+ });
84
+ }
85
+
86
+ function buildFlowEdges(
87
+ fixtureNodes: GraphInteractiveFixtureNode[],
88
+ selectedNodeId: string | null,
89
+ ): Edge[] {
90
+ const readyNode = fixtureNodes.find((node) => node.id === "ready-node");
91
+ const targetNode = fixtureNodes.find((node) => node.id === "target-node");
92
+
93
+ if (!readyNode || !targetNode) {
94
+ return [];
95
+ }
96
+
97
+ return [
98
+ {
99
+ data: {
100
+ alwaysShowLabel: true,
101
+ label: "Example edge",
102
+ waypoints:
103
+ readyNode.position.y === targetNode.position.y
104
+ ? undefined
105
+ : [
106
+ {
107
+ x: (readyNode.position.x + targetNode.position.x) / 2 + 72,
108
+ y: readyNode.position.y + 48,
109
+ },
110
+ {
111
+ x: (readyNode.position.x + targetNode.position.x) / 2 + 72,
112
+ y: targetNode.position.y + 48,
113
+ },
114
+ ],
115
+ },
116
+ id: "ready-to-target",
117
+ selectable: false,
118
+ source: readyNode.id,
119
+ target: targetNode.id,
120
+ type: "graphEdge",
121
+ },
122
+ ].map((edge) => ({
123
+ ...edge,
124
+ selected: selectedNodeId === edge.source || selectedNodeId === edge.target,
125
+ }));
126
+ }
127
+
128
+ export type GraphInteractiveExampleProps = {
129
+ "aria-label"?: string;
130
+ className?: string;
131
+ fixtureNodes?: GraphInteractiveFixtureNode[];
132
+ initialSelectedNodeId?: string | null;
133
+ viewportWidthClass?: string;
134
+ };
135
+
136
+ export function GraphInteractiveExample({
137
+ "aria-label": ariaLabel = "Interactive graph example",
138
+ className = "h-[28rem]",
139
+ fixtureNodes = desktopInteractiveGraphNodes,
140
+ initialSelectedNodeId = null,
141
+ viewportWidthClass = "w-[48rem] max-w-full",
142
+ }: GraphInteractiveExampleProps) {
143
+ const [selectedNodeId, setSelectedNodeId] = useState<string | null>(
144
+ initialSelectedNodeId,
145
+ );
146
+
147
+ const handleSelectNode = useCallback((nodeId: string) => {
148
+ setSelectedNodeId(nodeId);
149
+ }, []);
150
+
151
+ const nodes = useMemo(
152
+ () => buildFlowNodes(fixtureNodes, selectedNodeId, handleSelectNode),
153
+ [fixtureNodes, handleSelectNode, selectedNodeId],
154
+ );
155
+ const edges = useMemo(
156
+ () => buildFlowEdges(fixtureNodes, selectedNodeId),
157
+ [fixtureNodes, selectedNodeId],
158
+ );
159
+
160
+ return (
161
+ <ReactFlowProvider>
162
+ <div className={viewportWidthClass}>
163
+ <GraphViewportSurface aria-label={ariaLabel} className={className}>
164
+ <ReactFlow
165
+ defaultEdgeOptions={{ selectable: false }}
166
+ edgeTypes={GRAPH_EDGE_TYPES}
167
+ edges={edges}
168
+ fitView
169
+ fitViewOptions={{ padding: 0.2 }}
170
+ maxZoom={1.25}
171
+ minZoom={0.5}
172
+ nodeTypes={GENERIC_GRAPH_NODE_TYPES}
173
+ nodes={nodes}
174
+ nodesConnectable={false}
175
+ nodesDraggable={false}
176
+ panOnDrag={true}
177
+ proOptions={{ hideAttribution: true }}
178
+ zoomOnScroll={true}
179
+ >
180
+ <Background gap={16} size={1} />
181
+ <Controls showInteractive={false} />
182
+ </ReactFlow>
183
+ </GraphViewportSurface>
184
+ </div>
185
+ </ReactFlowProvider>
186
+ );
187
+ }
@@ -0,0 +1,51 @@
1
+ import { type ButtonHTMLAttributes, forwardRef } from "react";
2
+
3
+ import { cn } from "../utilities/cn";
4
+ import {
5
+ type GraphNodeState,
6
+ graphNodeButtonIsDisabled,
7
+ graphNodeButtonStateAttributes,
8
+ graphNodeButtonStateClassName,
9
+ } from "./graph-node-state";
10
+
11
+ export type GraphNodeButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
12
+ graphState?: GraphNodeState;
13
+ stateLabel?: string;
14
+ };
15
+
16
+ export const GRAPH_NODE_BUTTON_BASE_CLASS =
17
+ "nodrag nopan cursor-pointer border-0 bg-transparent p-0 text-left text-inherit focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-af-focus-ring disabled:cursor-not-allowed disabled:opacity-100";
18
+
19
+ export const GraphNodeButton = forwardRef<
20
+ HTMLButtonElement,
21
+ GraphNodeButtonProps
22
+ >(function GraphNodeButton(
23
+ {
24
+ className,
25
+ disabled,
26
+ graphState = "default",
27
+ onClick,
28
+ stateLabel,
29
+ type = "button",
30
+ ...props
31
+ },
32
+ ref,
33
+ ) {
34
+ const isDisabled = graphNodeButtonIsDisabled(graphState, disabled);
35
+
36
+ return (
37
+ <button
38
+ className={cn(
39
+ GRAPH_NODE_BUTTON_BASE_CLASS,
40
+ graphNodeButtonStateClassName(graphState),
41
+ className,
42
+ )}
43
+ disabled={isDisabled}
44
+ onClick={isDisabled ? undefined : onClick}
45
+ ref={ref}
46
+ type={type}
47
+ {...graphNodeButtonStateAttributes(graphState, stateLabel)}
48
+ {...props}
49
+ />
50
+ );
51
+ });