@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.
- package/README.md +223 -0
- package/docs/README.md +83 -0
- package/docs/button.md +119 -0
- package/docs/charts.md +325 -0
- package/docs/data-display-code-panel.md +120 -0
- package/docs/feedback-alert-panel.md +117 -0
- package/docs/feedback-skeleton.md +54 -0
- package/docs/forms-form-field.md +490 -0
- package/docs/forms-input-primitives.md +311 -0
- package/docs/forms-select-primitives.md +362 -0
- package/docs/graphs.md +250 -0
- package/docs/layout-display-primitives.md +211 -0
- package/docs/overlays.md +414 -0
- package/docs/table-data-table.md +331 -0
- package/docs/typography-roles.md +182 -0
- package/docs/widget-frame-recipes.md +273 -0
- package/package.json +161 -0
- package/src/category-paths.ts +20 -0
- package/src/charts/chart-state-panel.tsx +103 -0
- package/src/charts/chart.tsx +287 -0
- package/src/charts/index.ts +21 -0
- package/src/data-display/code-panel.tsx +67 -0
- package/src/data-display/data-table.tsx +180 -0
- package/src/data-display/description-list.tsx +24 -0
- package/src/data-display/index.ts +34 -0
- package/src/data-display/table-layout.ts +12 -0
- package/src/data-display/table.tsx +131 -0
- package/src/feedback/alert-panel-semantics.ts +75 -0
- package/src/feedback/alert-panel.tsx +187 -0
- package/src/feedback/index.ts +21 -0
- package/src/feedback/skeleton.tsx +16 -0
- package/src/forms/index.ts +73 -0
- package/src/forms/package-checkbox.tsx +50 -0
- package/src/forms/package-enum-select.tsx +185 -0
- package/src/forms/package-file-input.tsx +25 -0
- package/src/forms/package-form-field.tsx +202 -0
- package/src/forms/package-input.tsx +25 -0
- package/src/forms/package-native-select.tsx +25 -0
- package/src/forms/package-select.tsx +209 -0
- package/src/forms/package-textarea.tsx +35 -0
- package/src/forms/select-icons.tsx +35 -0
- package/src/graphs/graph-edge-path.ts +241 -0
- package/src/graphs/graph-edge.tsx +130 -0
- package/src/graphs/graph-interactive-example.tsx +187 -0
- package/src/graphs/graph-node-button.tsx +51 -0
- package/src/graphs/graph-node-handle-badge.tsx +108 -0
- package/src/graphs/graph-node-handle.ts +28 -0
- package/src/graphs/graph-node-shell.tsx +111 -0
- package/src/graphs/graph-node-state-indicator.tsx +47 -0
- package/src/graphs/graph-node-state.ts +111 -0
- package/src/graphs/graph-viewport-surface.tsx +27 -0
- package/src/graphs/index.ts +41 -0
- package/src/icons/index.ts +4 -0
- package/src/index.ts +209 -0
- package/src/layout/action-row.tsx +53 -0
- package/src/layout/index.ts +9 -0
- package/src/layout/surface-panel.tsx +91 -0
- package/src/navigation/index.ts +4 -0
- package/src/overlays/collapsible.tsx +22 -0
- package/src/overlays/dialog.tsx +148 -0
- package/src/overlays/index.ts +31 -0
- package/src/overlays/overlay-layout.ts +9 -0
- package/src/overlays/popover.tsx +29 -0
- package/src/overlays/scroll-area.tsx +78 -0
- package/src/primitives/button-link.tsx +22 -0
- package/src/primitives/button.tsx +258 -0
- package/src/primitives/icon-button-shell.tsx +49 -0
- package/src/primitives/index.ts +31 -0
- package/src/primitives/package-text.tsx +29 -0
- package/src/primitives/typography-roles.ts +12 -0
- package/src/primitives/typography.tsx +165 -0
- package/src/recipes/index.ts +61 -0
- package/src/recipes/widget-frame-content.tsx +114 -0
- package/src/recipes/widget-frame-disclosure.tsx +161 -0
- package/src/recipes/widget-frame-layout.ts +40 -0
- package/src/recipes/widget-frame-skeleton.tsx +16 -0
- package/src/recipes/widget-frame-states.tsx +99 -0
- package/src/recipes/widget-frame-typography.ts +19 -0
- package/src/recipes/widget-frame.tsx +84 -0
- package/src/styles/color-palette-presets.css +167 -0
- package/src/styles/color-role-tokens.css +163 -0
- package/src/styles/layout-role-tokens.css +27 -0
- package/src/styles/text-color-role-tokens.css +29 -0
- package/src/styles/typography-role-tokens.css +72 -0
- package/src/styles/typography-role-utilities.css +73 -0
- package/src/styles/typography-roles.css +53 -0
- package/src/styles.css +32 -0
- package/src/testing/index.ts +13 -0
- package/src/testing/render.tsx +16 -0
- package/src/tokens/index.ts +4 -0
- package/src/utilities/cn.ts +5 -0
- 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
|
+
});
|