@devfellowship/components 3.3.0 → 3.5.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/dist/index.cjs +282 -11
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +274 -4
- package/dist/styles/theme-mappings.css +27 -0
- package/dist/styles/tokens.css +42 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -244,6 +244,7 @@ __export(src_exports, {
|
|
|
244
244
|
ResizableHandle: () => ResizableHandle,
|
|
245
245
|
ResizablePanel: () => ResizablePanel,
|
|
246
246
|
ResizablePanelGroup: () => ResizablePanelGroup,
|
|
247
|
+
Roadmap: () => Roadmap,
|
|
247
248
|
ScrollArea: () => ScrollArea,
|
|
248
249
|
ScrollBar: () => ScrollBar,
|
|
249
250
|
Select: () => Select,
|
|
@@ -9103,18 +9104,287 @@ function firstFreeRow(taken, columns, from) {
|
|
|
9103
9104
|
}
|
|
9104
9105
|
return row;
|
|
9105
9106
|
}
|
|
9107
|
+
var pointText = (p) => `${p.x} ${p.y}`;
|
|
9108
|
+
var center = (r) => ({ x: (r.left + r.right) / 2, y: (r.top + r.bottom) / 2 });
|
|
9109
|
+
function segmentHits(a, b, r) {
|
|
9110
|
+
return a.x === b.x ? a.x > r.left && a.x < r.right && Math.max(a.y, b.y) > r.top && Math.min(a.y, b.y) < r.bottom : a.y > r.top && a.y < r.bottom && Math.max(a.x, b.x) > r.left && Math.min(a.x, b.x) < r.right;
|
|
9111
|
+
}
|
|
9112
|
+
function routeEdge(source, target, edge, obstacles = []) {
|
|
9113
|
+
const a = center(source), b = center(target);
|
|
9114
|
+
const sameColumn = Math.abs(a.x - b.x) < 1;
|
|
9115
|
+
const vertical = sameColumn || source.left < target.right && target.left < source.right;
|
|
9116
|
+
const down = b.y >= a.y, right = b.x >= a.x;
|
|
9117
|
+
const start = vertical ? { x: a.x, y: down ? source.bottom : source.top } : { x: right ? source.right : source.left, y: a.y };
|
|
9118
|
+
const end = vertical ? { x: b.x, y: down ? target.top : target.bottom } : { x: right ? target.left : target.right, y: b.y };
|
|
9119
|
+
const route = edge.route ?? "auto";
|
|
9120
|
+
if (route === "straight" || route === "auto" && sameColumn) return { start, end, d: `M ${pointText(start)} L ${pointText(end)}` };
|
|
9121
|
+
if (route !== "elbow") {
|
|
9122
|
+
const mid = vertical ? (start.y + end.y) / 2 : (start.x + end.x) / 2;
|
|
9123
|
+
const c1 = vertical ? { x: start.x, y: mid } : { x: mid, y: start.y };
|
|
9124
|
+
const c2 = vertical ? { x: end.x, y: mid } : { x: mid, y: end.y };
|
|
9125
|
+
return { start, end, d: `M ${pointText(start)} C ${pointText(c1)} ${pointText(c2)} ${pointText(end)}` };
|
|
9126
|
+
}
|
|
9127
|
+
const boxes = [.../* @__PURE__ */ new Set([source, target, ...obstacles])];
|
|
9128
|
+
const clearance = 4;
|
|
9129
|
+
const ys = [...new Set(boxes.flatMap((r) => [r.top - clearance, r.bottom + clearance]))];
|
|
9130
|
+
let best, bestLength = Infinity;
|
|
9131
|
+
for (const sx of [source.left, source.right]) for (const tx of [target.left, target.right]) {
|
|
9132
|
+
const s = { x: sx, y: a.y }, t = { x: tx, y: b.y };
|
|
9133
|
+
const gx = sx + (sx === source.left ? -clearance : clearance);
|
|
9134
|
+
const hx = tx + (tx === target.left ? -clearance : clearance);
|
|
9135
|
+
for (const y of [a.y, b.y, ...ys]) {
|
|
9136
|
+
const points2 = [s, { x: gx, y: a.y }, { x: gx, y }, { x: hx, y }, { x: hx, y: b.y }, t];
|
|
9137
|
+
const length = points2.slice(1).reduce((sum, p, i) => sum + Math.abs(p.x - points2[i].x) + Math.abs(p.y - points2[i].y), 0);
|
|
9138
|
+
if (length >= bestLength || points2.slice(1).some((p, i) => boxes.some((r) => segmentHits(points2[i], p, r)))) continue;
|
|
9139
|
+
best = points2;
|
|
9140
|
+
bestLength = length;
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
9143
|
+
if (!best) return { start, end, d: "" };
|
|
9144
|
+
const points = best.filter((p, i) => i === 0 || p.x !== best[i - 1].x || p.y !== best[i - 1].y);
|
|
9145
|
+
return { start: points[0], end: points[points.length - 1], points, d: points.map((p, i) => `${i ? "L" : "M"} ${pointText(p)}`).join(" ") };
|
|
9146
|
+
}
|
|
9106
9147
|
|
|
9107
|
-
// src/
|
|
9148
|
+
// src/components/organisms/roadmap/Roadmap.tsx
|
|
9149
|
+
var import_react8 = require("react");
|
|
9150
|
+
|
|
9151
|
+
// src/components/organisms/roadmap/RoadmapNode.tsx
|
|
9152
|
+
var import_class_variance_authority14 = require("class-variance-authority");
|
|
9153
|
+
|
|
9154
|
+
// src/components/organisms/roadmap/RoadmapLegend.tsx
|
|
9155
|
+
var import_lucide_react22 = require("lucide-react");
|
|
9156
|
+
|
|
9157
|
+
// src/components/organisms/roadmap/appearance.ts
|
|
9158
|
+
function toneStyle(tone) {
|
|
9159
|
+
return {
|
|
9160
|
+
"--c-roadmap-node-bg": `var(--c-roadmap-${tone}-bg)`,
|
|
9161
|
+
"--c-roadmap-node-fg": `var(--c-roadmap-${tone}-fg)`
|
|
9162
|
+
};
|
|
9163
|
+
}
|
|
9164
|
+
var focusClass = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--c-roadmap-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--c-roadmap-surface)]";
|
|
9165
|
+
|
|
9166
|
+
// src/components/organisms/roadmap/RoadmapLegend.tsx
|
|
9167
|
+
var import_jsx_runtime65 = require("react/jsx-runtime");
|
|
9168
|
+
var glyphs = { check: import_lucide_react22.Check, globe: import_lucide_react22.Globe, lock: import_lucide_react22.Lock, star: import_lucide_react22.Star, "book-open": import_lucide_react22.BookOpen, play: import_lucide_react22.Play, circle: import_lucide_react22.Circle, clock: import_lucide_react22.Clock, link: import_lucide_react22.Link, "external-link": import_lucide_react22.ExternalLink };
|
|
9169
|
+
function RoadmapBadge({ name = "check", tone = "neutral", label, className = "" }) {
|
|
9170
|
+
const Glyph = glyphs[name] ?? import_lucide_react22.CircleHelp;
|
|
9171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { "aria-label": label, "aria-hidden": label ? void 0 : true, style: toneStyle(tone), className: `inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2 border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)] ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(Glyph, { size: 12, "aria-hidden": "true" }) });
|
|
9172
|
+
}
|
|
9173
|
+
function RoadmapLegendView({ legend, testIdPrefix = "roadmap" }) {
|
|
9174
|
+
return /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("aside", { "data-testid": `${testIdPrefix}-legend`, "aria-label": "Legend", className: "min-w-0 rounded-[var(--radius)] border-2 border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-neutral-bg)] p-3 text-[var(--c-roadmap-neutral-fg)]", children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("ul", { className: "m-0 flex list-none flex-wrap gap-3 p-0", children: legend.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("li", { className: "flex min-w-0 items-center gap-2 text-xs leading-normal", children: [
|
|
9175
|
+
/* @__PURE__ */ (0, import_jsx_runtime65.jsx)(RoadmapBadge, { name: entry.icon, tone: entry.tone }),
|
|
9176
|
+
/* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "[overflow-wrap:anywhere]", children: entry.label })
|
|
9177
|
+
] }, entry.id)) }) });
|
|
9178
|
+
}
|
|
9179
|
+
|
|
9180
|
+
// src/components/organisms/roadmap/RoadmapNode.tsx
|
|
9181
|
+
var import_jsx_runtime66 = require("react/jsx-runtime");
|
|
9182
|
+
var nodeVariants = (0, import_class_variance_authority14.cva)("relative flex min-w-0 flex-col justify-center rounded-[var(--radius)] border-2 px-2 py-3 text-center text-xs leading-normal md:px-4 md:text-base text-[var(--c-roadmap-node-fg)]", {
|
|
9183
|
+
variants: {
|
|
9184
|
+
kind: { topic: "min-h-[49px] border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", subtopic: "min-h-[49px] border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", button: "min-h-[49px] border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", label: "border-transparent bg-transparent", title: "border-transparent bg-transparent font-semibold md:text-[28px]", paragraph: "text-left border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", legend: "border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]" },
|
|
9185
|
+
state: { todo: "", done: "[--c-roadmap-node-bg:var(--c-roadmap-done-bg)] [--c-roadmap-node-fg:var(--c-roadmap-done-fg)]", learning: "[--c-roadmap-node-bg:var(--c-roadmap-learning-bg)] [--c-roadmap-node-fg:var(--c-roadmap-learning-fg)]", skipped: "[--c-roadmap-node-bg:var(--c-roadmap-skipped-bg)] [--c-roadmap-node-fg:var(--c-roadmap-skipped-fg)]", locked: "opacity-60" }
|
|
9186
|
+
}
|
|
9187
|
+
});
|
|
9188
|
+
function RoadmapNodeView({ node, state, badge, testIdPrefix, onNodeClick, onAction }) {
|
|
9189
|
+
const locked = state === "locked";
|
|
9190
|
+
const nodeTone = resolveNodeTone(node);
|
|
9191
|
+
const label = /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { "data-testid": `${testIdPrefix}-node-label`, className: `[overflow-wrap:anywhere] ${state === "done" ? "line-through" : state === "learning" ? "underline" : ""}`, children: node.label });
|
|
9192
|
+
const interactiveClass = `block w-full min-w-0 text-inherit ${focusClass}`;
|
|
9193
|
+
const wholeBox = !locked && !node.action && !node.links?.length;
|
|
9194
|
+
const Box = wholeBox && node.href ? "a" : wholeBox && onNodeClick ? "button" : "div";
|
|
9195
|
+
const primary = Box !== "div" ? label : node.href && !locked ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("a", { href: node.href, className: interactiveClass, onClick: () => onNodeClick?.(node), children: label }) : onNodeClick && !locked ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: interactiveClass, onClick: () => onNodeClick(node), children: label }) : label;
|
|
9196
|
+
const icon = node.icon ?? (badge ? { name: badge.icon ?? "check", side: "right", tone: badge.tone } : void 0);
|
|
9197
|
+
return /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { style: toneStyle(nodeTone), className: "min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Box, { style: { borderColor: node.kind === "title" || node.kind === "label" ? "transparent" : "var(--c-roadmap-border)" }, "data-roadmap-node-box": node.id, href: Box === "a" ? node.href : void 0, type: Box === "button" ? "button" : void 0, onClick: Box !== "div" ? () => onNodeClick?.(node) : void 0, className: `${nodeVariants({ kind: node.kind, state })} w-full ${Box !== "div" ? focusClass : ""}`, "aria-disabled": locked || void 0, title: node.kind === "topic" || node.kind === "subtopic" ? node.description : void 0, children: [
|
|
9198
|
+
icon && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(RoadmapBadge, { name: icon.name, tone: icon.tone ?? nodeTone, label: badge?.label ?? icon.name, className: `absolute top-1/2 -translate-y-1/2 ${icon.side === "left" ? "left-0 -translate-x-1/2" : "right-0 translate-x-1/2"}` }),
|
|
9199
|
+
primary,
|
|
9200
|
+
node.description && node.kind === "paragraph" && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "mt-2 [overflow-wrap:anywhere]", children: node.description }),
|
|
9201
|
+
node.links && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("ul", { className: "mt-2 list-none space-y-2 p-0", children: node.links.map((link, index) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("li", { children: locked ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { children: link.label }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("a", { href: link.href, className: `underline [overflow-wrap:anywhere] ${focusClass}`, onClick: (e) => e.stopPropagation(), children: link.label }) }, index)) }),
|
|
9202
|
+
node.action && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { style: toneStyle(node.action.tone ?? "primary"), className: "mt-3 min-w-0", children: node.action.href && !locked ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("a", { href: node.action.href, className: `inline-block rounded px-2 py-2 bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)] [overflow-wrap:anywhere] ${focusClass}`, children: node.action.label }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", disabled: locked, className: `max-w-full rounded px-2 py-2 bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)] [overflow-wrap:anywhere] ${focusClass}`, onClick: (e) => {
|
|
9203
|
+
e.stopPropagation();
|
|
9204
|
+
if (node.action?.actionId) onAction?.(node.action.actionId, node);
|
|
9205
|
+
}, children: node.action.label }) })
|
|
9206
|
+
] }) });
|
|
9207
|
+
}
|
|
9208
|
+
|
|
9209
|
+
// src/components/organisms/roadmap/RoadmapGroup.tsx
|
|
9210
|
+
var import_jsx_runtime67 = require("react/jsx-runtime");
|
|
9211
|
+
function RoadmapGroupView({ group, testIdPrefix }) {
|
|
9212
|
+
const tone = resolveGroupTone(group);
|
|
9213
|
+
const appearance = tone === "accent" ? { "--c-roadmap-node-bg": "var(--c-roadmap-group-accent-bg)", "--c-roadmap-node-fg": "var(--c-roadmap-group-accent-fg)" } : toneStyle(tone);
|
|
9214
|
+
return /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(import_jsx_runtime67.Fragment, { children: groupColumnRuns(group).map((run, index) => /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)(
|
|
9215
|
+
"div",
|
|
9216
|
+
{
|
|
9217
|
+
"data-testid": `${testIdPrefix}-group`,
|
|
9218
|
+
"data-group-id": group.id,
|
|
9219
|
+
style: { borderColor: "var(--c-roadmap-border)", ...appearance, gridColumn: `${run.start} / span ${run.span}`, gridRow: `${group.from + 1} / span ${group.to - group.from + 1}` },
|
|
9220
|
+
className: "pointer-events-none relative z-0 min-w-0 self-stretch rounded-[var(--radius)] border-2 border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)]",
|
|
9221
|
+
children: [
|
|
9222
|
+
index === 0 && group.title && /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { title: group.title, className: "absolute left-2 right-2 top-0 -translate-y-1/2 text-xs leading-normal", children: /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "inline-block max-w-full overflow-hidden text-ellipsis whitespace-nowrap bg-[var(--c-roadmap-surface)] px-1 align-top text-[var(--c-roadmap-neutral-fg)]", children: group.title }) }),
|
|
9223
|
+
index === 0 && group.description && /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "sr-only", children: group.description })
|
|
9224
|
+
]
|
|
9225
|
+
},
|
|
9226
|
+
run.start
|
|
9227
|
+
)) });
|
|
9228
|
+
}
|
|
9229
|
+
|
|
9230
|
+
// src/components/organisms/roadmap/RoadmapEdgeLayer.tsx
|
|
9108
9231
|
var import_react7 = require("react");
|
|
9232
|
+
var import_jsx_runtime68 = require("react/jsx-runtime");
|
|
9233
|
+
function Edge({ edge, path, instance, prefix }) {
|
|
9234
|
+
const pathRef = (0, import_react7.useRef)(null);
|
|
9235
|
+
const [midpoint, setMidpoint] = (0, import_react7.useState)();
|
|
9236
|
+
(0, import_react7.useLayoutEffect)(() => {
|
|
9237
|
+
const element = pathRef.current;
|
|
9238
|
+
if (edge.label && element?.getTotalLength && path.d) {
|
|
9239
|
+
const p = element.getPointAtLength(element.getTotalLength() / 2);
|
|
9240
|
+
setMidpoint({ x: p.x, y: p.y });
|
|
9241
|
+
} else setMidpoint(void 0);
|
|
9242
|
+
}, [path.d, edge.label]);
|
|
9243
|
+
const arrow = resolveEdgeArrow(edge), style = resolveEdgeStyle(edge);
|
|
9244
|
+
const marker = `${instance}-${edge.id}`;
|
|
9245
|
+
const color = edge.tone ? `var(--c-roadmap-${edge.tone}-edge)` : "var(--c-roadmap-edge)";
|
|
9246
|
+
return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("g", { style: { color }, "data-roadmap-edge": edge.id, children: [
|
|
9247
|
+
arrow !== "none" && /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("marker", { id: marker, viewBox: "0 0 8 8", refX: "8", refY: "4", markerWidth: "8", markerHeight: "8", markerUnits: "userSpaceOnUse", orient: "auto-start-reverse", children: /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("path", { d: "M 0 0 L 8 4 L 0 8 Z", fill: "currentColor" }) }) }),
|
|
9248
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)(
|
|
9249
|
+
"path",
|
|
9250
|
+
{
|
|
9251
|
+
ref: pathRef,
|
|
9252
|
+
"data-testid": `${prefix}-edge`,
|
|
9253
|
+
"data-edge-id": edge.id,
|
|
9254
|
+
"data-source": edge.source,
|
|
9255
|
+
"data-target": edge.target,
|
|
9256
|
+
d: path.d,
|
|
9257
|
+
fill: "none",
|
|
9258
|
+
stroke: "currentColor",
|
|
9259
|
+
strokeLinecap: "round",
|
|
9260
|
+
strokeLinejoin: "round",
|
|
9261
|
+
strokeDasharray: style === "solid" ? void 0 : style === "dotted" ? "0.8 4" : "0.8 8",
|
|
9262
|
+
className: "[stroke-width:2px] md:[stroke-width:3px]",
|
|
9263
|
+
markerStart: arrow === "both" ? `url(#${marker})` : void 0,
|
|
9264
|
+
markerEnd: arrow !== "none" ? `url(#${marker})` : void 0
|
|
9265
|
+
}
|
|
9266
|
+
),
|
|
9267
|
+
edge.label && midpoint && /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("foreignObject", { "data-testid": `${prefix}-edge-label`, "data-edge-id": edge.id, x: midpoint.x, y: midpoint.y, width: "1", height: "1", overflow: "visible", children: /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(
|
|
9268
|
+
"div",
|
|
9269
|
+
{
|
|
9270
|
+
className: "w-max max-w-[160px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[var(--c-roadmap-edge-label-bg)] px-2 py-0.5 text-center text-xs leading-normal text-[var(--c-roadmap-edge-label-fg)] [overflow-wrap:anywhere]",
|
|
9271
|
+
style: { boxShadow: "0 0 0 2px var(--c-roadmap-surface)" },
|
|
9272
|
+
children: edge.label
|
|
9273
|
+
}
|
|
9274
|
+
) })
|
|
9275
|
+
] });
|
|
9276
|
+
}
|
|
9277
|
+
function RoadmapEdgeLayer({ containerRef, document: documentModel, testIdPrefix, debugPerf }) {
|
|
9278
|
+
const instance = `roadmap-${(0, import_react7.useId)().replace(/:/g, "")}`;
|
|
9279
|
+
const [measured, setMeasured] = (0, import_react7.useState)([]);
|
|
9280
|
+
(0, import_react7.useEffect)(() => {
|
|
9281
|
+
const container = containerRef.current;
|
|
9282
|
+
if (!container || !documentModel.edges.length) {
|
|
9283
|
+
setMeasured([]);
|
|
9284
|
+
return;
|
|
9285
|
+
}
|
|
9286
|
+
let disposed = false, frame = 0;
|
|
9287
|
+
const observed = /* @__PURE__ */ new Set();
|
|
9288
|
+
const measure = () => {
|
|
9289
|
+
frame = 0;
|
|
9290
|
+
if (disposed) return;
|
|
9291
|
+
const started = performance.now();
|
|
9292
|
+
const root = container.getBoundingClientRect();
|
|
9293
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
9294
|
+
for (const wrapper of container.querySelectorAll("[data-roadmap-node]")) {
|
|
9295
|
+
if (wrapper.closest("[data-roadmap-grid]") !== container) continue;
|
|
9296
|
+
const id = wrapper.dataset.roadmapNode;
|
|
9297
|
+
const border = Array.from(wrapper.querySelectorAll("[data-roadmap-node-box]")).find((el) => el.dataset.roadmapNodeBox === id);
|
|
9298
|
+
const element = border ?? wrapper.firstElementChild ?? wrapper;
|
|
9299
|
+
for (const target of [wrapper, element]) if (!observed.has(target)) {
|
|
9300
|
+
observer?.observe(target);
|
|
9301
|
+
observed.add(target);
|
|
9302
|
+
}
|
|
9303
|
+
const rect = element.getBoundingClientRect();
|
|
9304
|
+
boxes.set(id, { left: rect.left - root.left, right: rect.right - root.left, top: rect.top - root.top, bottom: rect.bottom - root.top });
|
|
9305
|
+
}
|
|
9306
|
+
for (const element of observed) if (!container.contains(element)) {
|
|
9307
|
+
observer?.unobserve(element);
|
|
9308
|
+
observed.delete(element);
|
|
9309
|
+
}
|
|
9310
|
+
const obstacles = [...boxes.values()];
|
|
9311
|
+
const next = documentModel.edges.flatMap((edge) => {
|
|
9312
|
+
const source = boxes.get(edge.source), target = boxes.get(edge.target);
|
|
9313
|
+
return source && target ? [{ edge, path: routeEdge(source, target, edge, obstacles) }] : [];
|
|
9314
|
+
});
|
|
9315
|
+
setMeasured(next);
|
|
9316
|
+
if (debugPerf) performance.measure("roadmap:edges", { start: started, end: performance.now() });
|
|
9317
|
+
};
|
|
9318
|
+
const schedule = () => {
|
|
9319
|
+
if (!disposed && !frame) frame = requestAnimationFrame(measure);
|
|
9320
|
+
};
|
|
9321
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule);
|
|
9322
|
+
observer?.observe(container);
|
|
9323
|
+
const mutations = new MutationObserver((records) => {
|
|
9324
|
+
if (records.some((record) => (record.target instanceof Element ? record.target : record.target.parentElement)?.closest("[data-roadmap-node]"))) schedule();
|
|
9325
|
+
});
|
|
9326
|
+
mutations.observe(container, { childList: true, subtree: true, characterData: true, attributes: true });
|
|
9327
|
+
const fonts = container.ownerDocument.fonts;
|
|
9328
|
+
fonts?.ready.then(schedule);
|
|
9329
|
+
fonts?.addEventListener("loadingdone", schedule);
|
|
9330
|
+
schedule();
|
|
9331
|
+
return () => {
|
|
9332
|
+
disposed = true;
|
|
9333
|
+
cancelAnimationFrame(frame);
|
|
9334
|
+
observer?.disconnect();
|
|
9335
|
+
mutations.disconnect();
|
|
9336
|
+
fonts?.removeEventListener("loadingdone", schedule);
|
|
9337
|
+
};
|
|
9338
|
+
}, [containerRef, documentModel, debugPerf]);
|
|
9339
|
+
return /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("svg", { "data-testid": `${testIdPrefix}-edges`, "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-[1] h-full w-full overflow-visible", children: measured.map((item) => /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(Edge, { ...item, instance, prefix: testIdPrefix }, item.edge.id)) });
|
|
9340
|
+
}
|
|
9341
|
+
|
|
9342
|
+
// src/components/organisms/roadmap/Roadmap.tsx
|
|
9343
|
+
var import_jsx_runtime69 = require("react/jsx-runtime");
|
|
9344
|
+
function Roadmap({ document: document2, state, renderNode, onNodeClick, onAction, testIdPrefix = "roadmap", className = "", ariaLabel, debugPerf }) {
|
|
9345
|
+
const gridRef = (0, import_react8.useRef)(null);
|
|
9346
|
+
const { placements } = (0, import_react8.useMemo)(() => placeNodes(document2), [document2]);
|
|
9347
|
+
const badges = (0, import_react8.useMemo)(() => new Map(document2.legend?.entries.map((entry) => [entry.id, entry])), [document2.legend]);
|
|
9348
|
+
const legendPlacement = document2.legend ? resolveLegendPlacement(document2.legend) : void 0;
|
|
9349
|
+
const legend = document2.legend && /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(RoadmapLegendView, { legend: document2.legend, testIdPrefix });
|
|
9350
|
+
return /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("section", { "data-testid": testIdPrefix, "aria-label": ariaLabel ?? document2.title ?? "Roadmap", className: `min-w-0 bg-[var(--c-roadmap-surface)] p-3 text-[var(--c-roadmap-neutral-fg)] ${className}`, children: [
|
|
9351
|
+
legendPlacement === "top" && /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "mb-6", children: legend }),
|
|
9352
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { ref: gridRef, "data-roadmap-grid": "", "data-testid": `${testIdPrefix}-grid`, role: "list", "aria-label": "Roadmap nodes", className: "relative isolate grid min-w-0 grid-cols-3 items-center gap-x-3 gap-y-2 md:gap-x-8", children: [
|
|
9353
|
+
/* @__PURE__ */ (0, import_jsx_runtime69.jsx)(RoadmapEdgeLayer, { containerRef: gridRef, document: document2, testIdPrefix, debugPerf }),
|
|
9354
|
+
document2.groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(RoadmapGroupView, { group, testIdPrefix }, group.id)),
|
|
9355
|
+
placements.map(({ node, row, column }) => {
|
|
9356
|
+
const resolvedState = resolveNodeState(node, state);
|
|
9357
|
+
return /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
|
|
9358
|
+
"div",
|
|
9359
|
+
{
|
|
9360
|
+
role: "listitem",
|
|
9361
|
+
"data-testid": `${testIdPrefix}-node`,
|
|
9362
|
+
"data-node-id": node.id,
|
|
9363
|
+
"data-roadmap-node": node.id,
|
|
9364
|
+
"data-state": resolvedState,
|
|
9365
|
+
style: { gridColumn: `${column.start} / span ${column.span}`, gridRow: `${row.start} / span 1` },
|
|
9366
|
+
className: "relative z-10 min-w-0 px-2 py-2",
|
|
9367
|
+
children: node.kind === "legend" ? legendPlacement === "inline" ? legend : null : renderNode ? renderNode(node, resolvedState) : /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(RoadmapNodeView, { node, state: resolvedState, badge: node.badge ? badges.get(node.badge) : void 0, onNodeClick, onAction, testIdPrefix })
|
|
9368
|
+
},
|
|
9369
|
+
node.id
|
|
9370
|
+
);
|
|
9371
|
+
})
|
|
9372
|
+
] }),
|
|
9373
|
+
legendPlacement === "bottom" && /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "mt-6", children: legend })
|
|
9374
|
+
] });
|
|
9375
|
+
}
|
|
9376
|
+
|
|
9377
|
+
// src/hooks/use-iframe-auth.ts
|
|
9378
|
+
var import_react9 = require("react");
|
|
9109
9379
|
function useIframeAuth() {
|
|
9110
|
-
const ctx = (0,
|
|
9380
|
+
const ctx = (0, import_react9.useContext)(IframeContext);
|
|
9111
9381
|
return { token: ctx.token, userId: ctx.userId, ready: ctx.ready };
|
|
9112
9382
|
}
|
|
9113
9383
|
|
|
9114
9384
|
// src/hooks/use-iframe-navigate.ts
|
|
9115
|
-
var
|
|
9385
|
+
var import_react10 = require("react");
|
|
9116
9386
|
function useIframeNavigate() {
|
|
9117
|
-
return (0,
|
|
9387
|
+
return (0, import_react10.useCallback)((path) => {
|
|
9118
9388
|
if (!window.parent || window.parent === window) return;
|
|
9119
9389
|
window.parent.postMessage(
|
|
9120
9390
|
{ type: "DFL_NAVIGATE", path },
|
|
@@ -9124,9 +9394,9 @@ function useIframeNavigate() {
|
|
|
9124
9394
|
}
|
|
9125
9395
|
|
|
9126
9396
|
// src/providers/feature-flag-provider.tsx
|
|
9127
|
-
var
|
|
9128
|
-
var
|
|
9129
|
-
var FeatureFlagContext = (0,
|
|
9397
|
+
var import_react11 = require("react");
|
|
9398
|
+
var import_jsx_runtime70 = require("react/jsx-runtime");
|
|
9399
|
+
var FeatureFlagContext = (0, import_react11.createContext)({
|
|
9130
9400
|
flags: {},
|
|
9131
9401
|
isEnabled: () => false
|
|
9132
9402
|
});
|
|
@@ -9134,21 +9404,21 @@ var FeatureFlagProvider = ({
|
|
|
9134
9404
|
children,
|
|
9135
9405
|
flags
|
|
9136
9406
|
}) => {
|
|
9137
|
-
const value = (0,
|
|
9407
|
+
const value = (0, import_react11.useMemo)(
|
|
9138
9408
|
() => ({
|
|
9139
9409
|
flags,
|
|
9140
9410
|
isEnabled: (flag) => Boolean(flags[flag])
|
|
9141
9411
|
}),
|
|
9142
9412
|
[flags]
|
|
9143
9413
|
);
|
|
9144
|
-
return /* @__PURE__ */ (0,
|
|
9414
|
+
return /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(FeatureFlagContext.Provider, { value, children });
|
|
9145
9415
|
};
|
|
9146
9416
|
var useFeatureFlag = (flag) => {
|
|
9147
|
-
const { isEnabled } = (0,
|
|
9417
|
+
const { isEnabled } = (0, import_react11.useContext)(FeatureFlagContext);
|
|
9148
9418
|
return isEnabled(flag);
|
|
9149
9419
|
};
|
|
9150
9420
|
var useFeatureFlags = () => {
|
|
9151
|
-
const { flags } = (0,
|
|
9421
|
+
const { flags } = (0, import_react11.useContext)(FeatureFlagContext);
|
|
9152
9422
|
return flags;
|
|
9153
9423
|
};
|
|
9154
9424
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -9367,6 +9637,7 @@ var useFeatureFlags = () => {
|
|
|
9367
9637
|
ResizableHandle,
|
|
9368
9638
|
ResizablePanel,
|
|
9369
9639
|
ResizablePanelGroup,
|
|
9640
|
+
Roadmap,
|
|
9370
9641
|
ScrollArea,
|
|
9371
9642
|
ScrollBar,
|
|
9372
9643
|
Select,
|
package/dist/index.d.cts
CHANGED
|
@@ -2219,7 +2219,7 @@ declare function PublishDrawer({ open, onOpenChange, supabase, videoUrl, transcr
|
|
|
2219
2219
|
*
|
|
2220
2220
|
* Colour is a TOKEN, never a hex (plan ADR-6). `tone` is one of eight names and
|
|
2221
2221
|
* the schema REJECTS any other string, `"#fdff00"` included. That closed
|
|
2222
|
-
* vocabulary is what keeps the dark design system
|
|
2222
|
+
* vocabulary is what keeps the dark design system and a
|
|
2223
2223
|
* downstream `--s-*` rebrand all working from one document.
|
|
2224
2224
|
*
|
|
2225
2225
|
* The live progress state does NOT live in the document (plan ADR-8). A document
|
|
@@ -3262,4 +3262,18 @@ declare function placeNodes(doc: Pick<RoadmapDocument, "nodes">): {
|
|
|
3262
3262
|
*/
|
|
3263
3263
|
declare function firstFreeRow(taken: ReadonlySet<string>, columns: readonly RoadmapColumn[], from: number): number;
|
|
3264
3264
|
|
|
3265
|
-
|
|
3265
|
+
interface RoadmapProps {
|
|
3266
|
+
document: RoadmapDocument;
|
|
3267
|
+
state?: RoadmapStateOverlay;
|
|
3268
|
+
renderNode?: (node: RoadmapNode, state: RoadmapNodeState) => ReactNode;
|
|
3269
|
+
onNodeClick?: (node: RoadmapNode) => void;
|
|
3270
|
+
onAction?: (actionId: string, node: RoadmapNode) => void;
|
|
3271
|
+
debugPerf?: boolean;
|
|
3272
|
+
testIdPrefix?: string;
|
|
3273
|
+
className?: string;
|
|
3274
|
+
ariaLabel?: string;
|
|
3275
|
+
}
|
|
3276
|
+
/** Dark-only, ordinary document scroll. The JSON owns placement; CSS owns the pixels. */
|
|
3277
|
+
declare function Roadmap({ document, state, renderNode, onNodeClick, onAction, testIdPrefix, className, ariaLabel, debugPerf }: RoadmapProps): React__default.JSX.Element;
|
|
3278
|
+
|
|
3279
|
+
export { ALLOWED_ORIGINS, AVATAR_MEMBER_PALETTE_SIZE, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, type AlertDialogActionProps, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppNavbar, type AppNavbarProps, AppSidebar, type AppSidebarProps, AspectRatio, Avatar, AvatarFallback, AvatarGroup, AvatarImage, type AvatarSize, AvatarStatus, type AvatarStatusValue, type AvatarTone, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, type BreadcrumbEntry, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleHeader, CollapsibleItem, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_EDGE_ARROW, DEFAULT_EDGE_ROUTE, DEFAULT_EDGE_STYLE, DEFAULT_GROUP_TONE, DEFAULT_LEGEND_PLACEMENT, DEFAULT_NAME_COL_WIDTH, DEFAULT_NODE_SPAN, DEFAULT_NODE_STATE, type DflIframeMessage, type DflNavigateMessage, type DflReadyMessage, DflRemote, type DflRemoteProps, type DflResizeMessage, type DflSetTokenMessage, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Gantt, type GanttDependency, type GanttMilestone, type GanttProps, type GanttStage, HoverCard, HoverCardContent, HoverCardTrigger, IconButton, type IconButtonProps, IframeAware, type IframeAwareProps, IframeContext, type IframeContextValue, type IframeMessageType, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Kbd, type KbdProps, Label, LoginPage, type LoginPageProps, LoginScreen, type LoginScreenProps, MEMBER_PALETTE_SIZE, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NODE_KIND_DEFAULT_TONE, type NavGroup, type NavItem, type NavbarUserInfo, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type OTPStatus, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, type PasswordInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, type ProgressProps, ProtectedRoute, type ProtectedRouteProps, PublishDrawer, type PublishDrawerProps, type PublishDrawerSupabase, type PublishResult, type PublishStatus, type PublisherAccount, ROADMAP_ARROWS, ROADMAP_COLUMNS, ROADMAP_EDGE_LABEL_MAX, ROADMAP_EDGE_ROUTES, ROADMAP_EDGE_STYLES, ROADMAP_LABEL_MAX, ROADMAP_LEGEND_PLACEMENTS, ROADMAP_NODE_ID_PATTERN, ROADMAP_NODE_KINDS, ROADMAP_NODE_STATES, ROADMAP_TONES, RadioGroup, RadioGroupItem, RadioGroupRow, ResizableHandle, ResizablePanel, ResizablePanelGroup, Roadmap, type RoadmapAction, type RoadmapArrow, type RoadmapColumn, type RoadmapDocument, type RoadmapEdge, type RoadmapEdgeRoute, type RoadmapEdgeStyle, type RoadmapGroup, type RoadmapGroupRange, type RoadmapIcon, type RoadmapLegend, type RoadmapLegendEntry, type RoadmapLegendPlacement, type RoadmapLink, type RoadmapNode, type RoadmapNodeKind, type RoadmapNodeState, type RoadmapOverlap, type RoadmapPlacement, type RoadmapProps, type RoadmapStateOverlay, type RoadmapTone, ScrollArea, type ScrollAreaScrollbars, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeparatorWithLabel, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, SonnerToaster, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, Toast, ToastAction, type ToastActionElement, ToastClose, type ToastComponentProps, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserAvatar, type UserAvatarProps, type UserInfo, UserMenu, type UserMenuItem, type UserMenuProps, avatarMemberColor, badgeVariants, barGridColumn, buttonVariants, clampPct, columnIndex, columnsCovered, columnsUsed, filterPublishableAccounts, firstFreeRow, getInitials, gridColumnFor, groupColumnRuns, groupRanges, iconButtonVariants, isAllowedOrigin, kbdVariants, labelVariants, maxRowOf, memberHueIndex, memberHueVar, navigationMenuTriggerStyle, nodesByRow, parseRoadmapDocument, parseRoadmapStateOverlay, parseTags, placeNodes, resolveEdgeArrow, resolveEdgeRoute, resolveEdgeStyle, resolveGroupColumns, resolveGroupTone, resolveLegendPlacement, resolveNameColWidth, resolveNodeSpan, resolveNodeState, resolveNodeTone, resolveWeekCount, resolveWeekLabels, roadmapActionSchema, roadmapDocumentSchema, roadmapEdgeSchema, roadmapGroupSchema, roadmapIconSchema, roadmapLegendEntrySchema, roadmapLegendSchema, roadmapLinkSchema, roadmapNodeSchema, roadmapNodeStateSchema, roadmapStateOverlaySchema, roadmapStateSchema, rowsOf, safeParseRoadmapDocument, safeParseRoadmapStateOverlay, stageProgress, toggleVariants, useFormField, useSidebar, validateNoOverlap, validatePublishForm };
|
package/dist/index.d.ts
CHANGED
|
@@ -2219,7 +2219,7 @@ declare function PublishDrawer({ open, onOpenChange, supabase, videoUrl, transcr
|
|
|
2219
2219
|
*
|
|
2220
2220
|
* Colour is a TOKEN, never a hex (plan ADR-6). `tone` is one of eight names and
|
|
2221
2221
|
* the schema REJECTS any other string, `"#fdff00"` included. That closed
|
|
2222
|
-
* vocabulary is what keeps the dark design system
|
|
2222
|
+
* vocabulary is what keeps the dark design system and a
|
|
2223
2223
|
* downstream `--s-*` rebrand all working from one document.
|
|
2224
2224
|
*
|
|
2225
2225
|
* The live progress state does NOT live in the document (plan ADR-8). A document
|
|
@@ -3262,4 +3262,18 @@ declare function placeNodes(doc: Pick<RoadmapDocument, "nodes">): {
|
|
|
3262
3262
|
*/
|
|
3263
3263
|
declare function firstFreeRow(taken: ReadonlySet<string>, columns: readonly RoadmapColumn[], from: number): number;
|
|
3264
3264
|
|
|
3265
|
-
|
|
3265
|
+
interface RoadmapProps {
|
|
3266
|
+
document: RoadmapDocument;
|
|
3267
|
+
state?: RoadmapStateOverlay;
|
|
3268
|
+
renderNode?: (node: RoadmapNode, state: RoadmapNodeState) => ReactNode;
|
|
3269
|
+
onNodeClick?: (node: RoadmapNode) => void;
|
|
3270
|
+
onAction?: (actionId: string, node: RoadmapNode) => void;
|
|
3271
|
+
debugPerf?: boolean;
|
|
3272
|
+
testIdPrefix?: string;
|
|
3273
|
+
className?: string;
|
|
3274
|
+
ariaLabel?: string;
|
|
3275
|
+
}
|
|
3276
|
+
/** Dark-only, ordinary document scroll. The JSON owns placement; CSS owns the pixels. */
|
|
3277
|
+
declare function Roadmap({ document, state, renderNode, onNodeClick, onAction, testIdPrefix, className, ariaLabel, debugPerf }: RoadmapProps): React__default.JSX.Element;
|
|
3278
|
+
|
|
3279
|
+
export { ALLOWED_ORIGINS, AVATAR_MEMBER_PALETTE_SIZE, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, type AlertDialogActionProps, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppNavbar, type AppNavbarProps, AppSidebar, type AppSidebarProps, AspectRatio, Avatar, AvatarFallback, AvatarGroup, AvatarImage, type AvatarSize, AvatarStatus, type AvatarStatusValue, type AvatarTone, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, type BreadcrumbEntry, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleHeader, CollapsibleItem, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_EDGE_ARROW, DEFAULT_EDGE_ROUTE, DEFAULT_EDGE_STYLE, DEFAULT_GROUP_TONE, DEFAULT_LEGEND_PLACEMENT, DEFAULT_NAME_COL_WIDTH, DEFAULT_NODE_SPAN, DEFAULT_NODE_STATE, type DflIframeMessage, type DflNavigateMessage, type DflReadyMessage, DflRemote, type DflRemoteProps, type DflResizeMessage, type DflSetTokenMessage, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Gantt, type GanttDependency, type GanttMilestone, type GanttProps, type GanttStage, HoverCard, HoverCardContent, HoverCardTrigger, IconButton, type IconButtonProps, IframeAware, type IframeAwareProps, IframeContext, type IframeContextValue, type IframeMessageType, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Kbd, type KbdProps, Label, LoginPage, type LoginPageProps, LoginScreen, type LoginScreenProps, MEMBER_PALETTE_SIZE, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NODE_KIND_DEFAULT_TONE, type NavGroup, type NavItem, type NavbarUserInfo, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type OTPStatus, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, type PasswordInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, type ProgressProps, ProtectedRoute, type ProtectedRouteProps, PublishDrawer, type PublishDrawerProps, type PublishDrawerSupabase, type PublishResult, type PublishStatus, type PublisherAccount, ROADMAP_ARROWS, ROADMAP_COLUMNS, ROADMAP_EDGE_LABEL_MAX, ROADMAP_EDGE_ROUTES, ROADMAP_EDGE_STYLES, ROADMAP_LABEL_MAX, ROADMAP_LEGEND_PLACEMENTS, ROADMAP_NODE_ID_PATTERN, ROADMAP_NODE_KINDS, ROADMAP_NODE_STATES, ROADMAP_TONES, RadioGroup, RadioGroupItem, RadioGroupRow, ResizableHandle, ResizablePanel, ResizablePanelGroup, Roadmap, type RoadmapAction, type RoadmapArrow, type RoadmapColumn, type RoadmapDocument, type RoadmapEdge, type RoadmapEdgeRoute, type RoadmapEdgeStyle, type RoadmapGroup, type RoadmapGroupRange, type RoadmapIcon, type RoadmapLegend, type RoadmapLegendEntry, type RoadmapLegendPlacement, type RoadmapLink, type RoadmapNode, type RoadmapNodeKind, type RoadmapNodeState, type RoadmapOverlap, type RoadmapPlacement, type RoadmapProps, type RoadmapStateOverlay, type RoadmapTone, ScrollArea, type ScrollAreaScrollbars, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeparatorWithLabel, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, SonnerToaster, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, Toast, ToastAction, type ToastActionElement, ToastClose, type ToastComponentProps, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserAvatar, type UserAvatarProps, type UserInfo, UserMenu, type UserMenuItem, type UserMenuProps, avatarMemberColor, badgeVariants, barGridColumn, buttonVariants, clampPct, columnIndex, columnsCovered, columnsUsed, filterPublishableAccounts, firstFreeRow, getInitials, gridColumnFor, groupColumnRuns, groupRanges, iconButtonVariants, isAllowedOrigin, kbdVariants, labelVariants, maxRowOf, memberHueIndex, memberHueVar, navigationMenuTriggerStyle, nodesByRow, parseRoadmapDocument, parseRoadmapStateOverlay, parseTags, placeNodes, resolveEdgeArrow, resolveEdgeRoute, resolveEdgeStyle, resolveGroupColumns, resolveGroupTone, resolveLegendPlacement, resolveNameColWidth, resolveNodeSpan, resolveNodeState, resolveNodeTone, resolveWeekCount, resolveWeekLabels, roadmapActionSchema, roadmapDocumentSchema, roadmapEdgeSchema, roadmapGroupSchema, roadmapIconSchema, roadmapLegendEntrySchema, roadmapLegendSchema, roadmapLinkSchema, roadmapNodeSchema, roadmapNodeStateSchema, roadmapStateOverlaySchema, roadmapStateSchema, rowsOf, safeParseRoadmapDocument, safeParseRoadmapStateOverlay, stageProgress, toggleVariants, useFormField, useSidebar, validateNoOverlap, validatePublishForm };
|
package/dist/index.js
CHANGED
|
@@ -8715,6 +8715,275 @@ function firstFreeRow(taken, columns, from) {
|
|
|
8715
8715
|
}
|
|
8716
8716
|
return row;
|
|
8717
8717
|
}
|
|
8718
|
+
var pointText = (p) => `${p.x} ${p.y}`;
|
|
8719
|
+
var center = (r) => ({ x: (r.left + r.right) / 2, y: (r.top + r.bottom) / 2 });
|
|
8720
|
+
function segmentHits(a, b, r) {
|
|
8721
|
+
return a.x === b.x ? a.x > r.left && a.x < r.right && Math.max(a.y, b.y) > r.top && Math.min(a.y, b.y) < r.bottom : a.y > r.top && a.y < r.bottom && Math.max(a.x, b.x) > r.left && Math.min(a.x, b.x) < r.right;
|
|
8722
|
+
}
|
|
8723
|
+
function routeEdge(source, target, edge, obstacles = []) {
|
|
8724
|
+
const a = center(source), b = center(target);
|
|
8725
|
+
const sameColumn = Math.abs(a.x - b.x) < 1;
|
|
8726
|
+
const vertical = sameColumn || source.left < target.right && target.left < source.right;
|
|
8727
|
+
const down = b.y >= a.y, right = b.x >= a.x;
|
|
8728
|
+
const start = vertical ? { x: a.x, y: down ? source.bottom : source.top } : { x: right ? source.right : source.left, y: a.y };
|
|
8729
|
+
const end = vertical ? { x: b.x, y: down ? target.top : target.bottom } : { x: right ? target.left : target.right, y: b.y };
|
|
8730
|
+
const route = edge.route ?? "auto";
|
|
8731
|
+
if (route === "straight" || route === "auto" && sameColumn) return { start, end, d: `M ${pointText(start)} L ${pointText(end)}` };
|
|
8732
|
+
if (route !== "elbow") {
|
|
8733
|
+
const mid = vertical ? (start.y + end.y) / 2 : (start.x + end.x) / 2;
|
|
8734
|
+
const c1 = vertical ? { x: start.x, y: mid } : { x: mid, y: start.y };
|
|
8735
|
+
const c2 = vertical ? { x: end.x, y: mid } : { x: mid, y: end.y };
|
|
8736
|
+
return { start, end, d: `M ${pointText(start)} C ${pointText(c1)} ${pointText(c2)} ${pointText(end)}` };
|
|
8737
|
+
}
|
|
8738
|
+
const boxes = [.../* @__PURE__ */ new Set([source, target, ...obstacles])];
|
|
8739
|
+
const clearance = 4;
|
|
8740
|
+
const ys = [...new Set(boxes.flatMap((r) => [r.top - clearance, r.bottom + clearance]))];
|
|
8741
|
+
let best, bestLength = Infinity;
|
|
8742
|
+
for (const sx of [source.left, source.right]) for (const tx of [target.left, target.right]) {
|
|
8743
|
+
const s = { x: sx, y: a.y }, t = { x: tx, y: b.y };
|
|
8744
|
+
const gx = sx + (sx === source.left ? -clearance : clearance);
|
|
8745
|
+
const hx = tx + (tx === target.left ? -clearance : clearance);
|
|
8746
|
+
for (const y of [a.y, b.y, ...ys]) {
|
|
8747
|
+
const points2 = [s, { x: gx, y: a.y }, { x: gx, y }, { x: hx, y }, { x: hx, y: b.y }, t];
|
|
8748
|
+
const length = points2.slice(1).reduce((sum, p, i) => sum + Math.abs(p.x - points2[i].x) + Math.abs(p.y - points2[i].y), 0);
|
|
8749
|
+
if (length >= bestLength || points2.slice(1).some((p, i) => boxes.some((r) => segmentHits(points2[i], p, r)))) continue;
|
|
8750
|
+
best = points2;
|
|
8751
|
+
bestLength = length;
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
if (!best) return { start, end, d: "" };
|
|
8755
|
+
const points = best.filter((p, i) => i === 0 || p.x !== best[i - 1].x || p.y !== best[i - 1].y);
|
|
8756
|
+
return { start: points[0], end: points[points.length - 1], points, d: points.map((p, i) => `${i ? "L" : "M"} ${pointText(p)}`).join(" ") };
|
|
8757
|
+
}
|
|
8758
|
+
|
|
8759
|
+
// src/components/organisms/roadmap/Roadmap.tsx
|
|
8760
|
+
import { useMemo as useMemo5, useRef as useRef6 } from "react";
|
|
8761
|
+
|
|
8762
|
+
// src/components/organisms/roadmap/RoadmapNode.tsx
|
|
8763
|
+
import { cva as cva14 } from "class-variance-authority";
|
|
8764
|
+
|
|
8765
|
+
// src/components/organisms/roadmap/RoadmapLegend.tsx
|
|
8766
|
+
import { Check as Check4, Globe, Lock, Star, BookOpen, Play, Circle as Circle2, CircleHelp, Clock, Link as Link2, ExternalLink } from "lucide-react";
|
|
8767
|
+
|
|
8768
|
+
// src/components/organisms/roadmap/appearance.ts
|
|
8769
|
+
function toneStyle(tone) {
|
|
8770
|
+
return {
|
|
8771
|
+
"--c-roadmap-node-bg": `var(--c-roadmap-${tone}-bg)`,
|
|
8772
|
+
"--c-roadmap-node-fg": `var(--c-roadmap-${tone}-fg)`
|
|
8773
|
+
};
|
|
8774
|
+
}
|
|
8775
|
+
var focusClass = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--c-roadmap-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--c-roadmap-surface)]";
|
|
8776
|
+
|
|
8777
|
+
// src/components/organisms/roadmap/RoadmapLegend.tsx
|
|
8778
|
+
import { jsx as jsx65, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
8779
|
+
var glyphs = { check: Check4, globe: Globe, lock: Lock, star: Star, "book-open": BookOpen, play: Play, circle: Circle2, clock: Clock, link: Link2, "external-link": ExternalLink };
|
|
8780
|
+
function RoadmapBadge({ name = "check", tone = "neutral", label, className = "" }) {
|
|
8781
|
+
const Glyph = glyphs[name] ?? CircleHelp;
|
|
8782
|
+
return /* @__PURE__ */ jsx65("span", { "aria-label": label, "aria-hidden": label ? void 0 : true, style: toneStyle(tone), className: `inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2 border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)] ${className}`, children: /* @__PURE__ */ jsx65(Glyph, { size: 12, "aria-hidden": "true" }) });
|
|
8783
|
+
}
|
|
8784
|
+
function RoadmapLegendView({ legend, testIdPrefix = "roadmap" }) {
|
|
8785
|
+
return /* @__PURE__ */ jsx65("aside", { "data-testid": `${testIdPrefix}-legend`, "aria-label": "Legend", className: "min-w-0 rounded-[var(--radius)] border-2 border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-neutral-bg)] p-3 text-[var(--c-roadmap-neutral-fg)]", children: /* @__PURE__ */ jsx65("ul", { className: "m-0 flex list-none flex-wrap gap-3 p-0", children: legend.entries.map((entry) => /* @__PURE__ */ jsxs40("li", { className: "flex min-w-0 items-center gap-2 text-xs leading-normal", children: [
|
|
8786
|
+
/* @__PURE__ */ jsx65(RoadmapBadge, { name: entry.icon, tone: entry.tone }),
|
|
8787
|
+
/* @__PURE__ */ jsx65("span", { className: "[overflow-wrap:anywhere]", children: entry.label })
|
|
8788
|
+
] }, entry.id)) }) });
|
|
8789
|
+
}
|
|
8790
|
+
|
|
8791
|
+
// src/components/organisms/roadmap/RoadmapNode.tsx
|
|
8792
|
+
import { jsx as jsx66, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
8793
|
+
var nodeVariants = cva14("relative flex min-w-0 flex-col justify-center rounded-[var(--radius)] border-2 px-2 py-3 text-center text-xs leading-normal md:px-4 md:text-base text-[var(--c-roadmap-node-fg)]", {
|
|
8794
|
+
variants: {
|
|
8795
|
+
kind: { topic: "min-h-[49px] border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", subtopic: "min-h-[49px] border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", button: "min-h-[49px] border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", label: "border-transparent bg-transparent", title: "border-transparent bg-transparent font-semibold md:text-[28px]", paragraph: "text-left border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]", legend: "border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)]" },
|
|
8796
|
+
state: { todo: "", done: "[--c-roadmap-node-bg:var(--c-roadmap-done-bg)] [--c-roadmap-node-fg:var(--c-roadmap-done-fg)]", learning: "[--c-roadmap-node-bg:var(--c-roadmap-learning-bg)] [--c-roadmap-node-fg:var(--c-roadmap-learning-fg)]", skipped: "[--c-roadmap-node-bg:var(--c-roadmap-skipped-bg)] [--c-roadmap-node-fg:var(--c-roadmap-skipped-fg)]", locked: "opacity-60" }
|
|
8797
|
+
}
|
|
8798
|
+
});
|
|
8799
|
+
function RoadmapNodeView({ node, state, badge, testIdPrefix, onNodeClick, onAction }) {
|
|
8800
|
+
const locked = state === "locked";
|
|
8801
|
+
const nodeTone = resolveNodeTone(node);
|
|
8802
|
+
const label = /* @__PURE__ */ jsx66("span", { "data-testid": `${testIdPrefix}-node-label`, className: `[overflow-wrap:anywhere] ${state === "done" ? "line-through" : state === "learning" ? "underline" : ""}`, children: node.label });
|
|
8803
|
+
const interactiveClass = `block w-full min-w-0 text-inherit ${focusClass}`;
|
|
8804
|
+
const wholeBox = !locked && !node.action && !node.links?.length;
|
|
8805
|
+
const Box = wholeBox && node.href ? "a" : wholeBox && onNodeClick ? "button" : "div";
|
|
8806
|
+
const primary = Box !== "div" ? label : node.href && !locked ? /* @__PURE__ */ jsx66("a", { href: node.href, className: interactiveClass, onClick: () => onNodeClick?.(node), children: label }) : onNodeClick && !locked ? /* @__PURE__ */ jsx66("button", { type: "button", className: interactiveClass, onClick: () => onNodeClick(node), children: label }) : label;
|
|
8807
|
+
const icon = node.icon ?? (badge ? { name: badge.icon ?? "check", side: "right", tone: badge.tone } : void 0);
|
|
8808
|
+
return /* @__PURE__ */ jsx66("div", { style: toneStyle(nodeTone), className: "min-w-0", children: /* @__PURE__ */ jsxs41(Box, { style: { borderColor: node.kind === "title" || node.kind === "label" ? "transparent" : "var(--c-roadmap-border)" }, "data-roadmap-node-box": node.id, href: Box === "a" ? node.href : void 0, type: Box === "button" ? "button" : void 0, onClick: Box !== "div" ? () => onNodeClick?.(node) : void 0, className: `${nodeVariants({ kind: node.kind, state })} w-full ${Box !== "div" ? focusClass : ""}`, "aria-disabled": locked || void 0, title: node.kind === "topic" || node.kind === "subtopic" ? node.description : void 0, children: [
|
|
8809
|
+
icon && /* @__PURE__ */ jsx66(RoadmapBadge, { name: icon.name, tone: icon.tone ?? nodeTone, label: badge?.label ?? icon.name, className: `absolute top-1/2 -translate-y-1/2 ${icon.side === "left" ? "left-0 -translate-x-1/2" : "right-0 translate-x-1/2"}` }),
|
|
8810
|
+
primary,
|
|
8811
|
+
node.description && node.kind === "paragraph" && /* @__PURE__ */ jsx66("p", { className: "mt-2 [overflow-wrap:anywhere]", children: node.description }),
|
|
8812
|
+
node.links && /* @__PURE__ */ jsx66("ul", { className: "mt-2 list-none space-y-2 p-0", children: node.links.map((link, index) => /* @__PURE__ */ jsx66("li", { children: locked ? /* @__PURE__ */ jsx66("span", { children: link.label }) : /* @__PURE__ */ jsx66("a", { href: link.href, className: `underline [overflow-wrap:anywhere] ${focusClass}`, onClick: (e) => e.stopPropagation(), children: link.label }) }, index)) }),
|
|
8813
|
+
node.action && /* @__PURE__ */ jsx66("div", { style: toneStyle(node.action.tone ?? "primary"), className: "mt-3 min-w-0", children: node.action.href && !locked ? /* @__PURE__ */ jsx66("a", { href: node.action.href, className: `inline-block rounded px-2 py-2 bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)] [overflow-wrap:anywhere] ${focusClass}`, children: node.action.label }) : /* @__PURE__ */ jsx66("button", { type: "button", disabled: locked, className: `max-w-full rounded px-2 py-2 bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)] [overflow-wrap:anywhere] ${focusClass}`, onClick: (e) => {
|
|
8814
|
+
e.stopPropagation();
|
|
8815
|
+
if (node.action?.actionId) onAction?.(node.action.actionId, node);
|
|
8816
|
+
}, children: node.action.label }) })
|
|
8817
|
+
] }) });
|
|
8818
|
+
}
|
|
8819
|
+
|
|
8820
|
+
// src/components/organisms/roadmap/RoadmapGroup.tsx
|
|
8821
|
+
import { Fragment as Fragment10, jsx as jsx67, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
8822
|
+
function RoadmapGroupView({ group, testIdPrefix }) {
|
|
8823
|
+
const tone = resolveGroupTone(group);
|
|
8824
|
+
const appearance = tone === "accent" ? { "--c-roadmap-node-bg": "var(--c-roadmap-group-accent-bg)", "--c-roadmap-node-fg": "var(--c-roadmap-group-accent-fg)" } : toneStyle(tone);
|
|
8825
|
+
return /* @__PURE__ */ jsx67(Fragment10, { children: groupColumnRuns(group).map((run, index) => /* @__PURE__ */ jsxs42(
|
|
8826
|
+
"div",
|
|
8827
|
+
{
|
|
8828
|
+
"data-testid": `${testIdPrefix}-group`,
|
|
8829
|
+
"data-group-id": group.id,
|
|
8830
|
+
style: { borderColor: "var(--c-roadmap-border)", ...appearance, gridColumn: `${run.start} / span ${run.span}`, gridRow: `${group.from + 1} / span ${group.to - group.from + 1}` },
|
|
8831
|
+
className: "pointer-events-none relative z-0 min-w-0 self-stretch rounded-[var(--radius)] border-2 border-[var(--c-roadmap-border)] bg-[var(--c-roadmap-node-bg)] text-[var(--c-roadmap-node-fg)]",
|
|
8832
|
+
children: [
|
|
8833
|
+
index === 0 && group.title && /* @__PURE__ */ jsx67("span", { title: group.title, className: "absolute left-2 right-2 top-0 -translate-y-1/2 text-xs leading-normal", children: /* @__PURE__ */ jsx67("span", { className: "inline-block max-w-full overflow-hidden text-ellipsis whitespace-nowrap bg-[var(--c-roadmap-surface)] px-1 align-top text-[var(--c-roadmap-neutral-fg)]", children: group.title }) }),
|
|
8834
|
+
index === 0 && group.description && /* @__PURE__ */ jsx67("span", { className: "sr-only", children: group.description })
|
|
8835
|
+
]
|
|
8836
|
+
},
|
|
8837
|
+
run.start
|
|
8838
|
+
)) });
|
|
8839
|
+
}
|
|
8840
|
+
|
|
8841
|
+
// src/components/organisms/roadmap/RoadmapEdgeLayer.tsx
|
|
8842
|
+
import { useId as useId4, useEffect as useEffect10, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState13 } from "react";
|
|
8843
|
+
import { jsx as jsx68, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
8844
|
+
function Edge({ edge, path, instance, prefix }) {
|
|
8845
|
+
const pathRef = useRef5(null);
|
|
8846
|
+
const [midpoint, setMidpoint] = useState13();
|
|
8847
|
+
useLayoutEffect2(() => {
|
|
8848
|
+
const element = pathRef.current;
|
|
8849
|
+
if (edge.label && element?.getTotalLength && path.d) {
|
|
8850
|
+
const p = element.getPointAtLength(element.getTotalLength() / 2);
|
|
8851
|
+
setMidpoint({ x: p.x, y: p.y });
|
|
8852
|
+
} else setMidpoint(void 0);
|
|
8853
|
+
}, [path.d, edge.label]);
|
|
8854
|
+
const arrow = resolveEdgeArrow(edge), style = resolveEdgeStyle(edge);
|
|
8855
|
+
const marker = `${instance}-${edge.id}`;
|
|
8856
|
+
const color = edge.tone ? `var(--c-roadmap-${edge.tone}-edge)` : "var(--c-roadmap-edge)";
|
|
8857
|
+
return /* @__PURE__ */ jsxs43("g", { style: { color }, "data-roadmap-edge": edge.id, children: [
|
|
8858
|
+
arrow !== "none" && /* @__PURE__ */ jsx68("defs", { children: /* @__PURE__ */ jsx68("marker", { id: marker, viewBox: "0 0 8 8", refX: "8", refY: "4", markerWidth: "8", markerHeight: "8", markerUnits: "userSpaceOnUse", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx68("path", { d: "M 0 0 L 8 4 L 0 8 Z", fill: "currentColor" }) }) }),
|
|
8859
|
+
/* @__PURE__ */ jsx68(
|
|
8860
|
+
"path",
|
|
8861
|
+
{
|
|
8862
|
+
ref: pathRef,
|
|
8863
|
+
"data-testid": `${prefix}-edge`,
|
|
8864
|
+
"data-edge-id": edge.id,
|
|
8865
|
+
"data-source": edge.source,
|
|
8866
|
+
"data-target": edge.target,
|
|
8867
|
+
d: path.d,
|
|
8868
|
+
fill: "none",
|
|
8869
|
+
stroke: "currentColor",
|
|
8870
|
+
strokeLinecap: "round",
|
|
8871
|
+
strokeLinejoin: "round",
|
|
8872
|
+
strokeDasharray: style === "solid" ? void 0 : style === "dotted" ? "0.8 4" : "0.8 8",
|
|
8873
|
+
className: "[stroke-width:2px] md:[stroke-width:3px]",
|
|
8874
|
+
markerStart: arrow === "both" ? `url(#${marker})` : void 0,
|
|
8875
|
+
markerEnd: arrow !== "none" ? `url(#${marker})` : void 0
|
|
8876
|
+
}
|
|
8877
|
+
),
|
|
8878
|
+
edge.label && midpoint && /* @__PURE__ */ jsx68("foreignObject", { "data-testid": `${prefix}-edge-label`, "data-edge-id": edge.id, x: midpoint.x, y: midpoint.y, width: "1", height: "1", overflow: "visible", children: /* @__PURE__ */ jsx68(
|
|
8879
|
+
"div",
|
|
8880
|
+
{
|
|
8881
|
+
className: "w-max max-w-[160px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[var(--c-roadmap-edge-label-bg)] px-2 py-0.5 text-center text-xs leading-normal text-[var(--c-roadmap-edge-label-fg)] [overflow-wrap:anywhere]",
|
|
8882
|
+
style: { boxShadow: "0 0 0 2px var(--c-roadmap-surface)" },
|
|
8883
|
+
children: edge.label
|
|
8884
|
+
}
|
|
8885
|
+
) })
|
|
8886
|
+
] });
|
|
8887
|
+
}
|
|
8888
|
+
function RoadmapEdgeLayer({ containerRef, document: documentModel, testIdPrefix, debugPerf }) {
|
|
8889
|
+
const instance = `roadmap-${useId4().replace(/:/g, "")}`;
|
|
8890
|
+
const [measured, setMeasured] = useState13([]);
|
|
8891
|
+
useEffect10(() => {
|
|
8892
|
+
const container = containerRef.current;
|
|
8893
|
+
if (!container || !documentModel.edges.length) {
|
|
8894
|
+
setMeasured([]);
|
|
8895
|
+
return;
|
|
8896
|
+
}
|
|
8897
|
+
let disposed = false, frame = 0;
|
|
8898
|
+
const observed = /* @__PURE__ */ new Set();
|
|
8899
|
+
const measure = () => {
|
|
8900
|
+
frame = 0;
|
|
8901
|
+
if (disposed) return;
|
|
8902
|
+
const started = performance.now();
|
|
8903
|
+
const root = container.getBoundingClientRect();
|
|
8904
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
8905
|
+
for (const wrapper of container.querySelectorAll("[data-roadmap-node]")) {
|
|
8906
|
+
if (wrapper.closest("[data-roadmap-grid]") !== container) continue;
|
|
8907
|
+
const id = wrapper.dataset.roadmapNode;
|
|
8908
|
+
const border = Array.from(wrapper.querySelectorAll("[data-roadmap-node-box]")).find((el) => el.dataset.roadmapNodeBox === id);
|
|
8909
|
+
const element = border ?? wrapper.firstElementChild ?? wrapper;
|
|
8910
|
+
for (const target of [wrapper, element]) if (!observed.has(target)) {
|
|
8911
|
+
observer?.observe(target);
|
|
8912
|
+
observed.add(target);
|
|
8913
|
+
}
|
|
8914
|
+
const rect = element.getBoundingClientRect();
|
|
8915
|
+
boxes.set(id, { left: rect.left - root.left, right: rect.right - root.left, top: rect.top - root.top, bottom: rect.bottom - root.top });
|
|
8916
|
+
}
|
|
8917
|
+
for (const element of observed) if (!container.contains(element)) {
|
|
8918
|
+
observer?.unobserve(element);
|
|
8919
|
+
observed.delete(element);
|
|
8920
|
+
}
|
|
8921
|
+
const obstacles = [...boxes.values()];
|
|
8922
|
+
const next = documentModel.edges.flatMap((edge) => {
|
|
8923
|
+
const source = boxes.get(edge.source), target = boxes.get(edge.target);
|
|
8924
|
+
return source && target ? [{ edge, path: routeEdge(source, target, edge, obstacles) }] : [];
|
|
8925
|
+
});
|
|
8926
|
+
setMeasured(next);
|
|
8927
|
+
if (debugPerf) performance.measure("roadmap:edges", { start: started, end: performance.now() });
|
|
8928
|
+
};
|
|
8929
|
+
const schedule = () => {
|
|
8930
|
+
if (!disposed && !frame) frame = requestAnimationFrame(measure);
|
|
8931
|
+
};
|
|
8932
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule);
|
|
8933
|
+
observer?.observe(container);
|
|
8934
|
+
const mutations = new MutationObserver((records) => {
|
|
8935
|
+
if (records.some((record) => (record.target instanceof Element ? record.target : record.target.parentElement)?.closest("[data-roadmap-node]"))) schedule();
|
|
8936
|
+
});
|
|
8937
|
+
mutations.observe(container, { childList: true, subtree: true, characterData: true, attributes: true });
|
|
8938
|
+
const fonts = container.ownerDocument.fonts;
|
|
8939
|
+
fonts?.ready.then(schedule);
|
|
8940
|
+
fonts?.addEventListener("loadingdone", schedule);
|
|
8941
|
+
schedule();
|
|
8942
|
+
return () => {
|
|
8943
|
+
disposed = true;
|
|
8944
|
+
cancelAnimationFrame(frame);
|
|
8945
|
+
observer?.disconnect();
|
|
8946
|
+
mutations.disconnect();
|
|
8947
|
+
fonts?.removeEventListener("loadingdone", schedule);
|
|
8948
|
+
};
|
|
8949
|
+
}, [containerRef, documentModel, debugPerf]);
|
|
8950
|
+
return /* @__PURE__ */ jsx68("svg", { "data-testid": `${testIdPrefix}-edges`, "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-[1] h-full w-full overflow-visible", children: measured.map((item) => /* @__PURE__ */ jsx68(Edge, { ...item, instance, prefix: testIdPrefix }, item.edge.id)) });
|
|
8951
|
+
}
|
|
8952
|
+
|
|
8953
|
+
// src/components/organisms/roadmap/Roadmap.tsx
|
|
8954
|
+
import { jsx as jsx69, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
8955
|
+
function Roadmap({ document: document2, state, renderNode, onNodeClick, onAction, testIdPrefix = "roadmap", className = "", ariaLabel, debugPerf }) {
|
|
8956
|
+
const gridRef = useRef6(null);
|
|
8957
|
+
const { placements } = useMemo5(() => placeNodes(document2), [document2]);
|
|
8958
|
+
const badges = useMemo5(() => new Map(document2.legend?.entries.map((entry) => [entry.id, entry])), [document2.legend]);
|
|
8959
|
+
const legendPlacement = document2.legend ? resolveLegendPlacement(document2.legend) : void 0;
|
|
8960
|
+
const legend = document2.legend && /* @__PURE__ */ jsx69(RoadmapLegendView, { legend: document2.legend, testIdPrefix });
|
|
8961
|
+
return /* @__PURE__ */ jsxs44("section", { "data-testid": testIdPrefix, "aria-label": ariaLabel ?? document2.title ?? "Roadmap", className: `min-w-0 bg-[var(--c-roadmap-surface)] p-3 text-[var(--c-roadmap-neutral-fg)] ${className}`, children: [
|
|
8962
|
+
legendPlacement === "top" && /* @__PURE__ */ jsx69("div", { className: "mb-6", children: legend }),
|
|
8963
|
+
/* @__PURE__ */ jsxs44("div", { ref: gridRef, "data-roadmap-grid": "", "data-testid": `${testIdPrefix}-grid`, role: "list", "aria-label": "Roadmap nodes", className: "relative isolate grid min-w-0 grid-cols-3 items-center gap-x-3 gap-y-2 md:gap-x-8", children: [
|
|
8964
|
+
/* @__PURE__ */ jsx69(RoadmapEdgeLayer, { containerRef: gridRef, document: document2, testIdPrefix, debugPerf }),
|
|
8965
|
+
document2.groups.map((group) => /* @__PURE__ */ jsx69(RoadmapGroupView, { group, testIdPrefix }, group.id)),
|
|
8966
|
+
placements.map(({ node, row, column }) => {
|
|
8967
|
+
const resolvedState = resolveNodeState(node, state);
|
|
8968
|
+
return /* @__PURE__ */ jsx69(
|
|
8969
|
+
"div",
|
|
8970
|
+
{
|
|
8971
|
+
role: "listitem",
|
|
8972
|
+
"data-testid": `${testIdPrefix}-node`,
|
|
8973
|
+
"data-node-id": node.id,
|
|
8974
|
+
"data-roadmap-node": node.id,
|
|
8975
|
+
"data-state": resolvedState,
|
|
8976
|
+
style: { gridColumn: `${column.start} / span ${column.span}`, gridRow: `${row.start} / span 1` },
|
|
8977
|
+
className: "relative z-10 min-w-0 px-2 py-2",
|
|
8978
|
+
children: node.kind === "legend" ? legendPlacement === "inline" ? legend : null : renderNode ? renderNode(node, resolvedState) : /* @__PURE__ */ jsx69(RoadmapNodeView, { node, state: resolvedState, badge: node.badge ? badges.get(node.badge) : void 0, onNodeClick, onAction, testIdPrefix })
|
|
8979
|
+
},
|
|
8980
|
+
node.id
|
|
8981
|
+
);
|
|
8982
|
+
})
|
|
8983
|
+
] }),
|
|
8984
|
+
legendPlacement === "bottom" && /* @__PURE__ */ jsx69("div", { className: "mt-6", children: legend })
|
|
8985
|
+
] });
|
|
8986
|
+
}
|
|
8718
8987
|
|
|
8719
8988
|
// src/hooks/use-iframe-auth.ts
|
|
8720
8989
|
import { useContext as useContext9 } from "react";
|
|
@@ -8739,9 +9008,9 @@ function useIframeNavigate() {
|
|
|
8739
9008
|
import {
|
|
8740
9009
|
createContext as createContext10,
|
|
8741
9010
|
useContext as useContext10,
|
|
8742
|
-
useMemo as
|
|
9011
|
+
useMemo as useMemo6
|
|
8743
9012
|
} from "react";
|
|
8744
|
-
import { jsx as
|
|
9013
|
+
import { jsx as jsx70 } from "react/jsx-runtime";
|
|
8745
9014
|
var FeatureFlagContext = createContext10({
|
|
8746
9015
|
flags: {},
|
|
8747
9016
|
isEnabled: () => false
|
|
@@ -8750,14 +9019,14 @@ var FeatureFlagProvider = ({
|
|
|
8750
9019
|
children,
|
|
8751
9020
|
flags
|
|
8752
9021
|
}) => {
|
|
8753
|
-
const value =
|
|
9022
|
+
const value = useMemo6(
|
|
8754
9023
|
() => ({
|
|
8755
9024
|
flags,
|
|
8756
9025
|
isEnabled: (flag) => Boolean(flags[flag])
|
|
8757
9026
|
}),
|
|
8758
9027
|
[flags]
|
|
8759
9028
|
);
|
|
8760
|
-
return /* @__PURE__ */
|
|
9029
|
+
return /* @__PURE__ */ jsx70(FeatureFlagContext.Provider, { value, children });
|
|
8761
9030
|
};
|
|
8762
9031
|
var useFeatureFlag = (flag) => {
|
|
8763
9032
|
const { isEnabled } = useContext10(FeatureFlagContext);
|
|
@@ -8982,6 +9251,7 @@ export {
|
|
|
8982
9251
|
ResizableHandle,
|
|
8983
9252
|
ResizablePanel,
|
|
8984
9253
|
ResizablePanelGroup,
|
|
9254
|
+
Roadmap,
|
|
8985
9255
|
ScrollArea,
|
|
8986
9256
|
ScrollBar,
|
|
8987
9257
|
Select,
|
|
@@ -19,6 +19,33 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
@theme inline {
|
|
22
|
+
/* Origin: agent — Roadmap token mappings. */
|
|
23
|
+
--color-c-roadmap-surface: var(--c-roadmap-surface);
|
|
24
|
+
--color-c-roadmap-border: var(--c-roadmap-border);
|
|
25
|
+
--color-c-roadmap-focus: var(--c-roadmap-focus);
|
|
26
|
+
--color-c-roadmap-done-bg: var(--c-roadmap-done-bg);
|
|
27
|
+
--color-c-roadmap-done-fg: var(--c-roadmap-done-fg);
|
|
28
|
+
--color-c-roadmap-learning-bg: var(--c-roadmap-learning-bg);
|
|
29
|
+
--color-c-roadmap-learning-fg: var(--c-roadmap-learning-fg);
|
|
30
|
+
--color-c-roadmap-skipped-bg: var(--c-roadmap-skipped-bg);
|
|
31
|
+
--color-c-roadmap-skipped-fg: var(--c-roadmap-skipped-fg);
|
|
32
|
+
--color-c-roadmap-primary-bg: var(--c-roadmap-primary-bg);
|
|
33
|
+
--color-c-roadmap-primary-fg: var(--c-roadmap-primary-fg);
|
|
34
|
+
--color-c-roadmap-secondary-bg: var(--c-roadmap-secondary-bg);
|
|
35
|
+
--color-c-roadmap-secondary-fg: var(--c-roadmap-secondary-fg);
|
|
36
|
+
--color-c-roadmap-accent-bg: var(--c-roadmap-accent-bg);
|
|
37
|
+
--color-c-roadmap-accent-fg: var(--c-roadmap-accent-fg);
|
|
38
|
+
--color-c-roadmap-muted-bg: var(--c-roadmap-muted-bg);
|
|
39
|
+
--color-c-roadmap-muted-fg: var(--c-roadmap-muted-fg);
|
|
40
|
+
--color-c-roadmap-neutral-bg: var(--c-roadmap-neutral-bg);
|
|
41
|
+
--color-c-roadmap-neutral-fg: var(--c-roadmap-neutral-fg);
|
|
42
|
+
--color-c-roadmap-success-bg: var(--c-roadmap-success-bg);
|
|
43
|
+
--color-c-roadmap-success-fg: var(--c-roadmap-success-fg);
|
|
44
|
+
--color-c-roadmap-info-bg: var(--c-roadmap-info-bg);
|
|
45
|
+
--color-c-roadmap-info-fg: var(--c-roadmap-info-fg);
|
|
46
|
+
--color-c-roadmap-danger-bg: var(--c-roadmap-danger-bg);
|
|
47
|
+
--color-c-roadmap-danger-fg: var(--c-roadmap-danger-fg);
|
|
48
|
+
|
|
22
49
|
/* ─── Colours ──────────────────────────────────────────────────────────── */
|
|
23
50
|
--color-background: var(--background);
|
|
24
51
|
--color-foreground: var(--foreground);
|
package/dist/styles/tokens.css
CHANGED
|
@@ -1706,3 +1706,45 @@
|
|
|
1706
1706
|
box-shadow: var(--c-appshell-focus-ring);
|
|
1707
1707
|
transition: none;
|
|
1708
1708
|
}
|
|
1709
|
+
|
|
1710
|
+
/* Origin: agent — Roadmap. Q1: dark default and dark only. */
|
|
1711
|
+
:root {
|
|
1712
|
+
--c-roadmap-group-accent-bg: var(--s-surface-elevated);
|
|
1713
|
+
--c-roadmap-group-accent-fg: var(--s-ink-primary);
|
|
1714
|
+
--c-roadmap-edge: var(--s-ink-muted);
|
|
1715
|
+
--c-roadmap-edge-label-bg: var(--s-surface-elevated);
|
|
1716
|
+
--c-roadmap-edge-label-fg: var(--s-ink-secondary);
|
|
1717
|
+
--c-roadmap-primary-edge: var(--s-brand-solid);
|
|
1718
|
+
--c-roadmap-secondary-edge: var(--s-ink-secondary);
|
|
1719
|
+
--c-roadmap-accent-edge: var(--s-ink-primary);
|
|
1720
|
+
--c-roadmap-muted-edge: var(--s-ink-muted);
|
|
1721
|
+
--c-roadmap-neutral-edge: var(--s-ink-secondary);
|
|
1722
|
+
--c-roadmap-success-edge: var(--s-success-fg);
|
|
1723
|
+
--c-roadmap-info-edge: var(--s-info-fg);
|
|
1724
|
+
--c-roadmap-danger-edge: var(--s-danger-fg);
|
|
1725
|
+
--c-roadmap-surface: var(--s-surface-page);
|
|
1726
|
+
--c-roadmap-border: var(--s-border-strong);
|
|
1727
|
+
--c-roadmap-focus: var(--s-border-focus);
|
|
1728
|
+
--c-roadmap-done-bg: var(--s-surface-raised);
|
|
1729
|
+
--c-roadmap-done-fg: var(--s-ink-secondary);
|
|
1730
|
+
--c-roadmap-learning-bg: var(--s-info-subtle);
|
|
1731
|
+
--c-roadmap-learning-fg: var(--s-info-fg);
|
|
1732
|
+
--c-roadmap-skipped-bg: var(--s-surface-elevated);
|
|
1733
|
+
--c-roadmap-skipped-fg: var(--s-ink-muted);
|
|
1734
|
+
--c-roadmap-primary-bg: var(--s-brand-solid);
|
|
1735
|
+
--c-roadmap-primary-fg: var(--s-ink-inverse);
|
|
1736
|
+
--c-roadmap-secondary-bg: var(--s-brand-subtle);
|
|
1737
|
+
--c-roadmap-secondary-fg: var(--s-ink-primary);
|
|
1738
|
+
--c-roadmap-accent-bg: var(--s-ink-primary);
|
|
1739
|
+
--c-roadmap-accent-fg: var(--s-ink-inverse);
|
|
1740
|
+
--c-roadmap-muted-bg: var(--s-surface-raised);
|
|
1741
|
+
--c-roadmap-muted-fg: var(--s-ink-muted);
|
|
1742
|
+
--c-roadmap-neutral-bg: var(--s-surface-panel);
|
|
1743
|
+
--c-roadmap-neutral-fg: var(--s-ink-primary);
|
|
1744
|
+
--c-roadmap-success-bg: var(--s-success-subtle);
|
|
1745
|
+
--c-roadmap-success-fg: var(--s-success-fg);
|
|
1746
|
+
--c-roadmap-info-bg: var(--s-info-subtle);
|
|
1747
|
+
--c-roadmap-info-fg: var(--s-info-fg);
|
|
1748
|
+
--c-roadmap-danger-bg: var(--s-danger-subtle);
|
|
1749
|
+
--c-roadmap-danger-fg: var(--s-danger-fg);
|
|
1750
|
+
}
|