@flytedan/flytebot-design-system 0.11.5 → 0.12.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 +76 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.js +93 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/styles/components.css +18 -0
package/dist/index.d.cts
CHANGED
|
@@ -457,14 +457,27 @@ interface TopbarProps {
|
|
|
457
457
|
href?: string;
|
|
458
458
|
onClick?: () => void;
|
|
459
459
|
}>;
|
|
460
|
+
/** Rendered last, after any `overflowActions` — the one control every screen needs to
|
|
461
|
+
* keep directly reachable (typically the account menu). Never collapses, at any width. */
|
|
460
462
|
actions?: React.ReactNode;
|
|
463
|
+
/** Secondary controls (an assistant launcher, a version badge, appearance/dev-tools
|
|
464
|
+
* toggles, undo — whatever a screen needs beside `actions`). Rendered inline, in the
|
|
465
|
+
* same spot `actions` used to hold everything, on a viewport wider than
|
|
466
|
+
* `overflowBreakpoint`. At or under it, they collapse into a single "More actions"
|
|
467
|
+
* menu instead of pushing the rest of the bar past the screen edge. Decided once
|
|
468
|
+
* here, so every Topbar consumer gets it without re-solving it per screen. */
|
|
469
|
+
overflowActions?: React.ReactNode;
|
|
470
|
+
/** Viewport width (px) at or under which `overflowActions` collapses into the "More
|
|
471
|
+
* actions" menu. Default 640 — comfortably covers 320/375/430 phone widths without
|
|
472
|
+
* touching any tablet or desktop layout above it. */
|
|
473
|
+
overflowBreakpoint?: number;
|
|
461
474
|
/** Shows the indeterminate route loader on the top edge. */
|
|
462
475
|
loading?: boolean;
|
|
463
476
|
/** Mobile menu handler — renders the hamburger. */
|
|
464
477
|
onMenu?: () => void;
|
|
465
478
|
className?: string;
|
|
466
479
|
}
|
|
467
|
-
declare function Topbar({ crumbs, actions, loading, onMenu, className, ...rest }: TopbarProps): React.JSX.Element;
|
|
480
|
+
declare function Topbar({ crumbs, actions, overflowActions, overflowBreakpoint, loading, onMenu, className, ...rest }: TopbarProps): React.JSX.Element;
|
|
468
481
|
|
|
469
482
|
/** A person or organisation mark. Initials on a soft brand tint when no image exists. */
|
|
470
483
|
interface AvatarProps {
|
|
@@ -2559,9 +2572,9 @@ declare function SaturationDistribution({ campuses, floorBand, floorScore, onSel
|
|
|
2559
2572
|
|
|
2560
2573
|
/**
|
|
2561
2574
|
* Realized media mix against the strategic target, one row per channel.
|
|
2562
|
-
* Realized is a solid
|
|
2563
|
-
*
|
|
2564
|
-
* in a legend.
|
|
2575
|
+
* Realized is a solid tone bar, target a striped marker on the same row, and the
|
|
2576
|
+
* realized share is labelled directly beside them — the gap is the story, so it
|
|
2577
|
+
* never hides in a legend.
|
|
2565
2578
|
*/
|
|
2566
2579
|
interface MixGapRow {
|
|
2567
2580
|
channel: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -457,14 +457,27 @@ interface TopbarProps {
|
|
|
457
457
|
href?: string;
|
|
458
458
|
onClick?: () => void;
|
|
459
459
|
}>;
|
|
460
|
+
/** Rendered last, after any `overflowActions` — the one control every screen needs to
|
|
461
|
+
* keep directly reachable (typically the account menu). Never collapses, at any width. */
|
|
460
462
|
actions?: React.ReactNode;
|
|
463
|
+
/** Secondary controls (an assistant launcher, a version badge, appearance/dev-tools
|
|
464
|
+
* toggles, undo — whatever a screen needs beside `actions`). Rendered inline, in the
|
|
465
|
+
* same spot `actions` used to hold everything, on a viewport wider than
|
|
466
|
+
* `overflowBreakpoint`. At or under it, they collapse into a single "More actions"
|
|
467
|
+
* menu instead of pushing the rest of the bar past the screen edge. Decided once
|
|
468
|
+
* here, so every Topbar consumer gets it without re-solving it per screen. */
|
|
469
|
+
overflowActions?: React.ReactNode;
|
|
470
|
+
/** Viewport width (px) at or under which `overflowActions` collapses into the "More
|
|
471
|
+
* actions" menu. Default 640 — comfortably covers 320/375/430 phone widths without
|
|
472
|
+
* touching any tablet or desktop layout above it. */
|
|
473
|
+
overflowBreakpoint?: number;
|
|
461
474
|
/** Shows the indeterminate route loader on the top edge. */
|
|
462
475
|
loading?: boolean;
|
|
463
476
|
/** Mobile menu handler — renders the hamburger. */
|
|
464
477
|
onMenu?: () => void;
|
|
465
478
|
className?: string;
|
|
466
479
|
}
|
|
467
|
-
declare function Topbar({ crumbs, actions, loading, onMenu, className, ...rest }: TopbarProps): React.JSX.Element;
|
|
480
|
+
declare function Topbar({ crumbs, actions, overflowActions, overflowBreakpoint, loading, onMenu, className, ...rest }: TopbarProps): React.JSX.Element;
|
|
468
481
|
|
|
469
482
|
/** A person or organisation mark. Initials on a soft brand tint when no image exists. */
|
|
470
483
|
interface AvatarProps {
|
|
@@ -2559,9 +2572,9 @@ declare function SaturationDistribution({ campuses, floorBand, floorScore, onSel
|
|
|
2559
2572
|
|
|
2560
2573
|
/**
|
|
2561
2574
|
* Realized media mix against the strategic target, one row per channel.
|
|
2562
|
-
* Realized is a solid
|
|
2563
|
-
*
|
|
2564
|
-
* in a legend.
|
|
2575
|
+
* Realized is a solid tone bar, target a striped marker on the same row, and the
|
|
2576
|
+
* realized share is labelled directly beside them — the gap is the story, so it
|
|
2577
|
+
* never hides in a legend.
|
|
2565
2578
|
*/
|
|
2566
2579
|
interface MixGapRow {
|
|
2567
2580
|
channel: string;
|
package/dist/index.js
CHANGED
|
@@ -888,9 +888,35 @@ function Tabs({ tabs = [], value, onChange, className = "", ...rest }) {
|
|
|
888
888
|
|
|
889
889
|
// src/components/navigation/Topbar.tsx
|
|
890
890
|
import * as React5 from "react";
|
|
891
|
-
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
892
|
-
function
|
|
893
|
-
|
|
891
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
892
|
+
function useIsNarrow(breakpoint) {
|
|
893
|
+
const [narrow, setNarrow] = React5.useState(
|
|
894
|
+
() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
|
|
895
|
+
);
|
|
896
|
+
React5.useEffect(() => {
|
|
897
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
898
|
+
const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
|
|
899
|
+
const apply = () => setNarrow(mq.matches);
|
|
900
|
+
apply();
|
|
901
|
+
mq.addEventListener("change", apply);
|
|
902
|
+
return () => mq.removeEventListener("change", apply);
|
|
903
|
+
}, [breakpoint]);
|
|
904
|
+
return narrow;
|
|
905
|
+
}
|
|
906
|
+
function Topbar({
|
|
907
|
+
crumbs = [],
|
|
908
|
+
actions,
|
|
909
|
+
overflowActions,
|
|
910
|
+
overflowBreakpoint = 640,
|
|
911
|
+
loading = false,
|
|
912
|
+
onMenu,
|
|
913
|
+
className = "",
|
|
914
|
+
...rest
|
|
915
|
+
}) {
|
|
916
|
+
const narrow = useIsNarrow(overflowBreakpoint);
|
|
917
|
+
const [moreOpen, setMoreOpen] = React5.useState(false);
|
|
918
|
+
const moreRef = React5.useRef(null);
|
|
919
|
+
return /* @__PURE__ */ jsxs12("header", { className: ["fd-topbar", narrow ? "is-narrow" : "", className].filter(Boolean).join(" "), ...rest, children: [
|
|
894
920
|
onMenu ? /* @__PURE__ */ jsx16("button", { type: "button", className: "fd-btn-icon", "aria-label": "Menu", onClick: onMenu, children: /* @__PURE__ */ jsx16("i", { className: "ph ph-list" }) }) : null,
|
|
895
921
|
/* @__PURE__ */ jsx16("nav", { className: "fd-crumb", "aria-label": "Breadcrumb", children: crumbs.map((c, i) => /* @__PURE__ */ jsxs12(React5.Fragment, { children: [
|
|
896
922
|
i > 0 ? /* @__PURE__ */ jsx16("i", { className: "ph ph-caret-right", style: { fontSize: 11, opacity: 0.6 }, "aria-hidden": "true" }) : null,
|
|
@@ -907,6 +933,34 @@ function Topbar({ crumbs = [], actions, loading = false, onMenu, className = "",
|
|
|
907
933
|
)
|
|
908
934
|
] }, i)) }),
|
|
909
935
|
/* @__PURE__ */ jsx16("span", { style: { flex: 1 } }),
|
|
936
|
+
overflowActions != null ? narrow ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
937
|
+
/* @__PURE__ */ jsx16(
|
|
938
|
+
"button",
|
|
939
|
+
{
|
|
940
|
+
type: "button",
|
|
941
|
+
ref: moreRef,
|
|
942
|
+
className: "fd-btn-icon",
|
|
943
|
+
"aria-label": "More actions",
|
|
944
|
+
"aria-haspopup": "dialog",
|
|
945
|
+
"aria-expanded": moreOpen,
|
|
946
|
+
onClick: () => setMoreOpen((v) => !v),
|
|
947
|
+
children: /* @__PURE__ */ jsx16("i", { className: "ph ph-dots-three-vertical", "aria-hidden": "true" })
|
|
948
|
+
}
|
|
949
|
+
),
|
|
950
|
+
/* @__PURE__ */ jsx16(
|
|
951
|
+
Popover,
|
|
952
|
+
{
|
|
953
|
+
open: moreOpen,
|
|
954
|
+
anchorRef: moreRef,
|
|
955
|
+
onClose: () => setMoreOpen(false),
|
|
956
|
+
placement: "bottom-end",
|
|
957
|
+
padded: true,
|
|
958
|
+
label: "More actions",
|
|
959
|
+
className: "fd-topbar-overflow",
|
|
960
|
+
children: /* @__PURE__ */ jsx16("div", { className: "fd-stack fd-topbar-overflow-list", children: overflowActions })
|
|
961
|
+
}
|
|
962
|
+
)
|
|
963
|
+
] }) : overflowActions : null,
|
|
910
964
|
actions,
|
|
911
965
|
loading ? /* @__PURE__ */ jsx16("span", { className: "fd-topload", "aria-hidden": "true" }) : null
|
|
912
966
|
] });
|
|
@@ -958,12 +1012,12 @@ function EmptyState({ icon = "tray", title, children, action, className = "", ..
|
|
|
958
1012
|
}
|
|
959
1013
|
|
|
960
1014
|
// src/components/feedback/Flag.tsx
|
|
961
|
-
import { Fragment as
|
|
1015
|
+
import { Fragment as Fragment5, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
962
1016
|
function Flag({ statement, cost, actions, tone = "warning", className = "", ...rest }) {
|
|
963
1017
|
return /* @__PURE__ */ jsxs16("div", { className: ["fd-flag", "fd-flag-" + tone, className].filter(Boolean).join(" "), role: "status", ...rest, children: [
|
|
964
1018
|
/* @__PURE__ */ jsxs16("span", { className: "fd-flag-text", children: [
|
|
965
1019
|
statement,
|
|
966
|
-
cost ? /* @__PURE__ */ jsxs16(
|
|
1020
|
+
cost ? /* @__PURE__ */ jsxs16(Fragment5, { children: [
|
|
967
1021
|
" ",
|
|
968
1022
|
/* @__PURE__ */ jsx20("span", { className: "fd-flag-cost", children: cost })
|
|
969
1023
|
] }) : null
|
|
@@ -1546,7 +1600,7 @@ function StatTile({ label, value, sub, delta, loading = false, compact: compact3
|
|
|
1546
1600
|
|
|
1547
1601
|
// src/components/data/CodeBlock.tsx
|
|
1548
1602
|
import * as React9 from "react";
|
|
1549
|
-
import { Fragment as
|
|
1603
|
+
import { Fragment as Fragment6, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
1550
1604
|
var JS_KW = /^(?:const|let|var|function|return|if|else|for|while|do|new|class|extends|import|from|export|default|await|async|try|catch|finally|throw|typeof|instanceof|of|in|switch|case|break|continue|delete|void|yield|static|get|set|this|super|interface|type|enum|implements|public|private|protected|readonly|abstract|as|satisfies|declare|namespace)$/;
|
|
1551
1605
|
var RULES = {
|
|
1552
1606
|
js: [
|
|
@@ -1702,7 +1756,7 @@ function tokenize(src, lang) {
|
|
|
1702
1756
|
}
|
|
1703
1757
|
function Highlighted({ code, lang }) {
|
|
1704
1758
|
const toks = React9.useMemo(() => tokenize(code, lang), [code, lang]);
|
|
1705
|
-
return /* @__PURE__ */ jsx30(
|
|
1759
|
+
return /* @__PURE__ */ jsx30(Fragment6, { children: toks.map((tk, i) => tk.c ? /* @__PURE__ */ jsx30("span", { className: "tok-" + tk.c, children: tk.t }, i) : tk.t) });
|
|
1706
1760
|
}
|
|
1707
1761
|
function useCopy() {
|
|
1708
1762
|
const [done, setDone] = React9.useState(false);
|
|
@@ -2066,7 +2120,7 @@ function Meter({ segments = [], unallocated = 0, height = 8, label, legend = fal
|
|
|
2066
2120
|
}
|
|
2067
2121
|
|
|
2068
2122
|
// src/components/data/Pagination.tsx
|
|
2069
|
-
import { Fragment as
|
|
2123
|
+
import { Fragment as Fragment8, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2070
2124
|
function Pagination({
|
|
2071
2125
|
page = 1,
|
|
2072
2126
|
pageSize = 25,
|
|
@@ -2092,10 +2146,10 @@ function Pagination({
|
|
|
2092
2146
|
else if (nums[nums.length - 1] !== "\u2026") nums.push("\u2026");
|
|
2093
2147
|
}
|
|
2094
2148
|
return /* @__PURE__ */ jsxs28("div", { className: ["fd-pag", className].filter(Boolean).join(" "), ...rest, children: [
|
|
2095
|
-
/* @__PURE__ */ jsx33("span", { className: "fd-pag-count", children: loading ? /* @__PURE__ */ jsx33("span", { className: "fd-skel fd-skel-text", style: { display: "inline-block", height: 12, width: 108 } }) : total === 0 ? /* @__PURE__ */ jsxs28(
|
|
2149
|
+
/* @__PURE__ */ jsx33("span", { className: "fd-pag-count", children: loading ? /* @__PURE__ */ jsx33("span", { className: "fd-skel fd-skel-text", style: { display: "inline-block", height: 12, width: 108 } }) : total === 0 ? /* @__PURE__ */ jsxs28(Fragment8, { children: [
|
|
2096
2150
|
"No ",
|
|
2097
2151
|
unit
|
|
2098
|
-
] }) : /* @__PURE__ */ jsxs28(
|
|
2152
|
+
] }) : /* @__PURE__ */ jsxs28(Fragment8, { children: [
|
|
2099
2153
|
/* @__PURE__ */ jsxs28("strong", { children: [
|
|
2100
2154
|
from.toLocaleString(),
|
|
2101
2155
|
"\u2013",
|
|
@@ -6579,7 +6633,7 @@ var SessionKit = {
|
|
|
6579
6633
|
};
|
|
6580
6634
|
|
|
6581
6635
|
// src/components/platform/AccountMenu.tsx
|
|
6582
|
-
import { Fragment as
|
|
6636
|
+
import { Fragment as Fragment15, jsx as jsx57, jsxs as jsxs52 } from "react/jsx-runtime";
|
|
6583
6637
|
var DEFAULT_LINKS = [
|
|
6584
6638
|
{ id: "profile", label: "Your profile", icon: "user-circle", href: "../admin/index.html#profile" },
|
|
6585
6639
|
{ id: "preferences", label: "Preferences", icon: "sliders-horizontal", href: "../admin/index.html#preferences" }
|
|
@@ -6632,7 +6686,7 @@ function AccountMenu({
|
|
|
6632
6686
|
if (onSignOut) return onSignOut();
|
|
6633
6687
|
window.alert("Signed out. (Simulated \u2014 no auth provider is wired up.)");
|
|
6634
6688
|
};
|
|
6635
|
-
return /* @__PURE__ */ jsxs52(
|
|
6689
|
+
return /* @__PURE__ */ jsxs52(Fragment15, { children: [
|
|
6636
6690
|
/* @__PURE__ */ jsxs52(
|
|
6637
6691
|
"button",
|
|
6638
6692
|
{
|
|
@@ -7469,7 +7523,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
|
|
|
7469
7523
|
// src/components/platform/ComingSoon.tsx
|
|
7470
7524
|
import * as React35 from "react";
|
|
7471
7525
|
import { createPortal as createPortal5 } from "react-dom";
|
|
7472
|
-
import { Fragment as
|
|
7526
|
+
import { Fragment as Fragment17, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
|
|
7473
7527
|
var BYPASS_STORE = "fd.soon.bypass.v1";
|
|
7474
7528
|
function readBypassed() {
|
|
7475
7529
|
try {
|
|
@@ -7499,7 +7553,7 @@ function useBypass(key) {
|
|
|
7499
7553
|
return [on, set];
|
|
7500
7554
|
}
|
|
7501
7555
|
function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass }) {
|
|
7502
|
-
return /* @__PURE__ */ jsxs56(
|
|
7556
|
+
return /* @__PURE__ */ jsxs56(Fragment17, { children: [
|
|
7503
7557
|
/* @__PURE__ */ jsxs56("span", { className: "fd-soon-badge", children: [
|
|
7504
7558
|
/* @__PURE__ */ jsx61("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" }),
|
|
7505
7559
|
label
|
|
@@ -7850,7 +7904,7 @@ function PermissionHint({ perm, children }) {
|
|
|
7850
7904
|
|
|
7851
7905
|
// src/components/platform/ModeSwitch.tsx
|
|
7852
7906
|
import * as React36 from "react";
|
|
7853
|
-
import { Fragment as
|
|
7907
|
+
import { Fragment as Fragment18, jsx as jsx63, jsxs as jsxs58 } from "react/jsx-runtime";
|
|
7854
7908
|
function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
|
|
7855
7909
|
const mode2 = useRuntimeMode();
|
|
7856
7910
|
const [open, setOpen] = React36.useState(false);
|
|
@@ -7920,7 +7974,7 @@ function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog
|
|
|
7920
7974
|
s.total,
|
|
7921
7975
|
" features complete"
|
|
7922
7976
|
] }),
|
|
7923
|
-
onOpenSpec ? /* @__PURE__ */ jsxs58(
|
|
7977
|
+
onOpenSpec ? /* @__PURE__ */ jsxs58(Fragment18, { children: [
|
|
7924
7978
|
/* @__PURE__ */ jsx63("span", { style: { flex: 1 } }),
|
|
7925
7979
|
/* @__PURE__ */ jsxs58("button", { type: "button", className: "fd-soon-link", onClick: () => {
|
|
7926
7980
|
setOpen(false);
|
|
@@ -8105,12 +8159,25 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
|
|
|
8105
8159
|
|
|
8106
8160
|
// src/components/planner/MixGap.tsx
|
|
8107
8161
|
import * as React37 from "react";
|
|
8108
|
-
import { Fragment as
|
|
8162
|
+
import { Fragment as Fragment19, jsx as jsx66, jsxs as jsxs61 } from "react/jsx-runtime";
|
|
8163
|
+
var MIX_GAP_LAYOUT = {
|
|
8164
|
+
trackHeight: 30,
|
|
8165
|
+
barHeight: 20,
|
|
8166
|
+
targetWidth: 6,
|
|
8167
|
+
labelGap: 6,
|
|
8168
|
+
labelGutter: 50
|
|
8169
|
+
};
|
|
8170
|
+
var MIX_GAP_TARGET_STRIPE = "repeating-linear-gradient(135deg, var(--n-600) 0 2px, var(--n-300) 2px 4px)";
|
|
8171
|
+
function mixGapOffset(value, max) {
|
|
8172
|
+
return `calc((100% - ${MIX_GAP_LAYOUT.labelGutter}px) * ${value / max})`;
|
|
8173
|
+
}
|
|
8109
8174
|
function MixGap({ rows = [], loading = false, className = "" }) {
|
|
8110
8175
|
const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
|
|
8111
8176
|
const [hover, setHover] = React37.useState(null);
|
|
8112
8177
|
const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
|
|
8113
8178
|
const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
|
|
8179
|
+
const { trackHeight, barHeight, targetWidth, labelGap } = MIX_GAP_LAYOUT;
|
|
8180
|
+
const barTop = (trackHeight - barHeight) / 2;
|
|
8114
8181
|
return /* @__PURE__ */ jsxs61("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 4 }, children: [
|
|
8115
8182
|
/* @__PURE__ */ jsxs61("div", { className: "fd-row", style: { gap: 16, justifyContent: "flex-end", paddingBottom: 6, flexWrap: "wrap" }, children: [
|
|
8116
8183
|
[["On target", TONE.ok], ["Close", TONE.warn], ["Short", TONE.danger]].map(([l, c]) => /* @__PURE__ */ jsxs61("span", { className: "fd-row fd-body-sm fd-muted", style: { gap: 6 }, children: [
|
|
@@ -8118,7 +8185,7 @@ function MixGap({ rows = [], loading = false, className = "" }) {
|
|
|
8118
8185
|
l
|
|
8119
8186
|
] }, l)),
|
|
8120
8187
|
/* @__PURE__ */ jsxs61("span", { className: "fd-row fd-body-sm fd-muted", style: { gap: 6 }, children: [
|
|
8121
|
-
/* @__PURE__ */ jsx66("span", { style: { width:
|
|
8188
|
+
/* @__PURE__ */ jsx66("span", { "data-mixgap-legend-target": "", style: { width: targetWidth, height: 16, borderRadius: 1, background: MIX_GAP_TARGET_STRIPE } }),
|
|
8122
8189
|
"Target"
|
|
8123
8190
|
] })
|
|
8124
8191
|
] }),
|
|
@@ -8126,6 +8193,7 @@ function MixGap({ rows = [], loading = false, className = "" }) {
|
|
|
8126
8193
|
const gap = r.realized - r.target;
|
|
8127
8194
|
const tone = toneOf(gap);
|
|
8128
8195
|
const tip = "Realized " + r.realized + "% vs " + r.target + "% target" + (gap < -1 ? " \xB7 " + Math.abs(gap).toFixed(0) + " pts short" : gap > 1 ? " \xB7 " + gap.toFixed(0) + " pts over" : " \xB7 on target") + (r.note ? " \u2014 " + r.note : "");
|
|
8196
|
+
const labelLeft = r.realized > r.target ? `calc(${mixGapOffset(r.realized, max)} + ${labelGap}px)` : `calc(${mixGapOffset(r.target, max)} + ${targetWidth / 2 + labelGap}px)`;
|
|
8129
8197
|
return /* @__PURE__ */ jsxs61(
|
|
8130
8198
|
"div",
|
|
8131
8199
|
{
|
|
@@ -8135,14 +8203,14 @@ function MixGap({ rows = [], loading = false, className = "" }) {
|
|
|
8135
8203
|
onMouseLeave: () => setHover(null),
|
|
8136
8204
|
children: [
|
|
8137
8205
|
/* @__PURE__ */ jsx66("span", { style: { width: 128, flex: "none" }, children: /* @__PURE__ */ jsx66(ChannelTag, { channel: r.channel, size: "sm" }) }),
|
|
8138
|
-
/* @__PURE__ */ jsx66("span", { style: { position: "relative", flex: 1, height:
|
|
8139
|
-
/* @__PURE__ */ jsx66("span", { style: { position: "absolute", left: 0, top:
|
|
8140
|
-
/* @__PURE__ */ jsx66("span", { style: { position: "absolute", left: r.target
|
|
8141
|
-
/* @__PURE__ */ jsxs61("span", { className: "fd-num", style: { position: "absolute",
|
|
8206
|
+
/* @__PURE__ */ jsx66("span", { style: { position: "relative", flex: 1, height: trackHeight, minWidth: 120 }, children: loading ? /* @__PURE__ */ jsx66("span", { className: "fd-skel", style: { position: "absolute", left: 0, right: 0, top: barTop, height: barHeight, borderRadius: 3 } }) : /* @__PURE__ */ jsxs61(Fragment19, { children: [
|
|
8207
|
+
/* @__PURE__ */ jsx66("span", { "data-mixgap-bar": "", style: { position: "absolute", left: 0, top: barTop, height: barHeight, width: mixGapOffset(r.realized, max), background: TONE[tone], borderRadius: "2px 3px 3px 2px", transition: "width var(--dur-slow) var(--ease), background var(--dur-base) var(--ease)" } }),
|
|
8208
|
+
/* @__PURE__ */ jsx66("span", { "data-mixgap-target": "", style: { position: "absolute", left: `calc(${mixGapOffset(r.target, max)} - ${targetWidth / 2}px)`, top: 0, width: targetWidth, height: trackHeight, background: MIX_GAP_TARGET_STRIPE, borderRadius: 1 } }),
|
|
8209
|
+
/* @__PURE__ */ jsxs61("span", { "data-mixgap-label": "", className: "fd-num", style: { position: "absolute", left: labelLeft, top: "50%", transform: "translateY(-50%)", fontSize: 12, color: "var(--text-muted)", whiteSpace: "nowrap" }, children: [
|
|
8142
8210
|
r.realized,
|
|
8143
8211
|
"%"
|
|
8144
8212
|
] }),
|
|
8145
|
-
hover === r.channel ? /* @__PURE__ */ jsx66("span", { className: "fd-tooltip", role: "tooltip", style: { position: "absolute", left: 0, right: "auto", bottom:
|
|
8213
|
+
hover === r.channel ? /* @__PURE__ */ jsx66("span", { className: "fd-tooltip", role: "tooltip", style: { position: "absolute", left: 0, right: "auto", bottom: trackHeight + 4, maxWidth: "min(340px, 100%)", whiteSpace: "normal", textAlign: "left", width: "max-content" }, children: tip }) : null
|
|
8146
8214
|
] }) })
|
|
8147
8215
|
]
|
|
8148
8216
|
},
|
|
@@ -8249,7 +8317,7 @@ function ChannelContribution({
|
|
|
8249
8317
|
|
|
8250
8318
|
// src/components/planner/BudgetReallocator.tsx
|
|
8251
8319
|
import * as React38 from "react";
|
|
8252
|
-
import { Fragment as
|
|
8320
|
+
import { Fragment as Fragment20, jsx as jsx68, jsxs as jsxs63 } from "react/jsx-runtime";
|
|
8253
8321
|
var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
|
|
8254
8322
|
function BudgetReallocator({
|
|
8255
8323
|
campus,
|
|
@@ -8289,7 +8357,7 @@ function BudgetReallocator({
|
|
|
8289
8357
|
/* @__PURE__ */ jsxs63("div", { className: "fd-row", style: { gap: 12, flexWrap: "wrap" }, children: [
|
|
8290
8358
|
/* @__PURE__ */ jsx68("span", { className: "fd-h4", style: { flex: 1, minWidth: 140 }, children: campus }),
|
|
8291
8359
|
/* @__PURE__ */ jsx68(CsiBadge, { band: nowBand, crp, size: "medium" }),
|
|
8292
|
-
dirty ? /* @__PURE__ */ jsxs63(
|
|
8360
|
+
dirty ? /* @__PURE__ */ jsxs63(Fragment20, { children: [
|
|
8293
8361
|
/* @__PURE__ */ jsx68("i", { className: "ph ph-arrow-right fd-muted", "aria-hidden": "true" }),
|
|
8294
8362
|
/* @__PURE__ */ jsx68(CsiBadge, { band: nextBand, crp: nextCrp, size: "medium", preview: true })
|
|
8295
8363
|
] }) : null
|