@flytedan/flytebot-design-system 0.12.8 → 0.12.10

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.d.cts CHANGED
@@ -31,12 +31,16 @@ interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>
31
31
  size?: "sm" | "md" | "lg";
32
32
  /** "ghost" and "outline" are quiet: no fill, the glyph carries the tone. "solid" is a
33
33
  * filled button in the tone's colour — for the one action on a surface that has to be
34
- * found at a glance, like the add/remove button on every row of a transfer list. */
35
- variant?: "ghost" | "outline" | "solid";
36
- /** What the action does. On "solid" it picks the fill; on "ghost"/"outline" it tints the
37
- * glyph. "neutral" is the default look of each variant, which for "solid" is the brand
38
- * fill. Every tone has hover, pressed and focus states and is legible in both themes —
39
- * the fills are the --btn-solid-* tokens, measured against light and dark grounds. */
34
+ * found at a glance, like the add/remove button on every row of a transfer list. "soft"
35
+ * is the same idea with a lighter, less saturated fill and a visibly darker border
36
+ * drawn around it, for a caller that wants the button to read as a button without the
37
+ * full-saturation "solid" look only "ok" and "danger" tones are defined for it. */
38
+ variant?: "ghost" | "outline" | "solid" | "soft";
39
+ /** What the action does. On "solid"/"soft" it picks the fill; on "ghost"/"outline" it
40
+ * tints the glyph. "neutral" is the default look of each variant, which for "solid" is
41
+ * the brand fill ("soft" has no neutral fill — pass "ok" or "danger"). Every tone has
42
+ * hover, pressed and focus states and is legible in both themes — the fills are the
43
+ * --btn-solid-* / --btn-soft-* tokens, measured against light and dark grounds. */
40
44
  tone?: "neutral" | "ok" | "danger";
41
45
  round?: boolean;
42
46
  disabled?: boolean;
@@ -1346,10 +1350,11 @@ interface EntityRowAction {
1346
1350
  label: string;
1347
1351
  onClick: () => void;
1348
1352
  /** Picks the fill — "add" is the constructive (ok) fill, "remove" the destructive
1349
- * (danger) fill, and no tone the brand fill. The button is always a filled
1350
- * IconButton (variant "solid"): it is the one action on the row, repeated down a long
1351
- * list, and has to be findable at a glance on a default card and on a danger-toned one
1352
- * alike. No effect on layout, so a caller can still pass any icon. */
1353
+ * (danger) fill, and no tone the brand fill. The button is always a filled IconButton
1354
+ * (variant "soft": a lighter fill with a darker border, MP-52): it is the one action on
1355
+ * the row, repeated down a long list, and has to be findable at a glance on a default
1356
+ * card and on a danger-toned one alike. No effect on layout, so a caller can still pass
1357
+ * any icon. */
1353
1358
  tone?: "add" | "remove";
1354
1359
  }
1355
1360
  /** Width in px a metric cell takes when it doesn't ask for its own. Wide enough for a
@@ -1390,8 +1395,23 @@ interface EntityRowMetric {
1390
1395
  * as the drag ghost) hands one of these around whole, so a prop added here can't be
1391
1396
  * silently dropped by the second render site the way individually-spread props were.
1392
1397
  */
1398
+ /** An icon shown before the row's title, itself the Tooltip trigger together with the
1399
+ * title text (MP-52: "hovering EITHER the icon or the name" opens the same explanation —
1400
+ * one hoverable/focusable region rather than two separate targets that could disagree). */
1401
+ interface EntityRowTitleIcon {
1402
+ /** Phosphor icon name, e.g. "warning". */
1403
+ icon: string;
1404
+ tone?: "danger";
1405
+ /** The long-form explanation, shown in the kit's Tooltip on hover, keyboard focus and
1406
+ * tap — never a native `title`, so it is never truncated to a browser tooltip. */
1407
+ tooltip: React.ReactNode;
1408
+ }
1393
1409
  interface EntityRowContent {
1394
1410
  title: string;
1411
+ /** Renders before `title`, wrapped with it in the kit Tooltip. Drive this from the
1412
+ * row's own existing state (e.g. the same signal that sets `tone: "danger"`) — it is
1413
+ * not a second source of truth, just a second thing that one signal explains. */
1414
+ titleIcon?: EntityRowTitleIcon;
1395
1415
  /** A secondary line — a count, a subtitle, whatever the caller's data has that a raw
1396
1416
  * title doesn't. Optional because plenty of rows are fine with just a name. Sits
1397
1417
  * inline after the title; for numbers that must line up row-to-row use `metrics`. */
@@ -1470,7 +1490,7 @@ declare function EntityRowMetrics({ metrics, className, style }: EntityRowMetric
1470
1490
  * same move — pointer-only and keyboard users get an equally real affordance, not a
1471
1491
  * degraded fallback.
1472
1492
  */
1473
- declare function EntityRow({ title, meta, metrics, accessory, tone, action, draggable, onPointerDown, style, className, }: EntityRowProps): React.JSX.Element;
1493
+ declare function EntityRow({ title, titleIcon, meta, metrics, accessory, tone, action, draggable, onPointerDown, style, className, }: EntityRowProps): React.JSX.Element;
1474
1494
 
1475
1495
  type TransferListSide = "left" | "right";
1476
1496
  interface TransferListSideProps<T> {
package/dist/index.d.ts CHANGED
@@ -31,12 +31,16 @@ interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>
31
31
  size?: "sm" | "md" | "lg";
32
32
  /** "ghost" and "outline" are quiet: no fill, the glyph carries the tone. "solid" is a
33
33
  * filled button in the tone's colour — for the one action on a surface that has to be
34
- * found at a glance, like the add/remove button on every row of a transfer list. */
35
- variant?: "ghost" | "outline" | "solid";
36
- /** What the action does. On "solid" it picks the fill; on "ghost"/"outline" it tints the
37
- * glyph. "neutral" is the default look of each variant, which for "solid" is the brand
38
- * fill. Every tone has hover, pressed and focus states and is legible in both themes —
39
- * the fills are the --btn-solid-* tokens, measured against light and dark grounds. */
34
+ * found at a glance, like the add/remove button on every row of a transfer list. "soft"
35
+ * is the same idea with a lighter, less saturated fill and a visibly darker border
36
+ * drawn around it, for a caller that wants the button to read as a button without the
37
+ * full-saturation "solid" look only "ok" and "danger" tones are defined for it. */
38
+ variant?: "ghost" | "outline" | "solid" | "soft";
39
+ /** What the action does. On "solid"/"soft" it picks the fill; on "ghost"/"outline" it
40
+ * tints the glyph. "neutral" is the default look of each variant, which for "solid" is
41
+ * the brand fill ("soft" has no neutral fill — pass "ok" or "danger"). Every tone has
42
+ * hover, pressed and focus states and is legible in both themes — the fills are the
43
+ * --btn-solid-* / --btn-soft-* tokens, measured against light and dark grounds. */
40
44
  tone?: "neutral" | "ok" | "danger";
41
45
  round?: boolean;
42
46
  disabled?: boolean;
@@ -1346,10 +1350,11 @@ interface EntityRowAction {
1346
1350
  label: string;
1347
1351
  onClick: () => void;
1348
1352
  /** Picks the fill — "add" is the constructive (ok) fill, "remove" the destructive
1349
- * (danger) fill, and no tone the brand fill. The button is always a filled
1350
- * IconButton (variant "solid"): it is the one action on the row, repeated down a long
1351
- * list, and has to be findable at a glance on a default card and on a danger-toned one
1352
- * alike. No effect on layout, so a caller can still pass any icon. */
1353
+ * (danger) fill, and no tone the brand fill. The button is always a filled IconButton
1354
+ * (variant "soft": a lighter fill with a darker border, MP-52): it is the one action on
1355
+ * the row, repeated down a long list, and has to be findable at a glance on a default
1356
+ * card and on a danger-toned one alike. No effect on layout, so a caller can still pass
1357
+ * any icon. */
1353
1358
  tone?: "add" | "remove";
1354
1359
  }
1355
1360
  /** Width in px a metric cell takes when it doesn't ask for its own. Wide enough for a
@@ -1390,8 +1395,23 @@ interface EntityRowMetric {
1390
1395
  * as the drag ghost) hands one of these around whole, so a prop added here can't be
1391
1396
  * silently dropped by the second render site the way individually-spread props were.
1392
1397
  */
1398
+ /** An icon shown before the row's title, itself the Tooltip trigger together with the
1399
+ * title text (MP-52: "hovering EITHER the icon or the name" opens the same explanation —
1400
+ * one hoverable/focusable region rather than two separate targets that could disagree). */
1401
+ interface EntityRowTitleIcon {
1402
+ /** Phosphor icon name, e.g. "warning". */
1403
+ icon: string;
1404
+ tone?: "danger";
1405
+ /** The long-form explanation, shown in the kit's Tooltip on hover, keyboard focus and
1406
+ * tap — never a native `title`, so it is never truncated to a browser tooltip. */
1407
+ tooltip: React.ReactNode;
1408
+ }
1393
1409
  interface EntityRowContent {
1394
1410
  title: string;
1411
+ /** Renders before `title`, wrapped with it in the kit Tooltip. Drive this from the
1412
+ * row's own existing state (e.g. the same signal that sets `tone: "danger"`) — it is
1413
+ * not a second source of truth, just a second thing that one signal explains. */
1414
+ titleIcon?: EntityRowTitleIcon;
1395
1415
  /** A secondary line — a count, a subtitle, whatever the caller's data has that a raw
1396
1416
  * title doesn't. Optional because plenty of rows are fine with just a name. Sits
1397
1417
  * inline after the title; for numbers that must line up row-to-row use `metrics`. */
@@ -1470,7 +1490,7 @@ declare function EntityRowMetrics({ metrics, className, style }: EntityRowMetric
1470
1490
  * same move — pointer-only and keyboard users get an equally real affordance, not a
1471
1491
  * degraded fallback.
1472
1492
  */
1473
- declare function EntityRow({ title, meta, metrics, accessory, tone, action, draggable, onPointerDown, style, className, }: EntityRowProps): React.JSX.Element;
1493
+ declare function EntityRow({ title, titleIcon, meta, metrics, accessory, tone, action, draggable, onPointerDown, style, className, }: EntityRowProps): React.JSX.Element;
1474
1494
 
1475
1495
  type TransferListSide = "left" | "right";
1476
1496
  interface TransferListSideProps<T> {
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ function IconButton({
71
71
  const overflowRow = useOverflowRow();
72
72
  const cls = [
73
73
  "fd-btn-icon",
74
- variant === "outline" ? "fd-btn-icon-outline" : variant === "solid" ? "fd-btn-icon-solid" : "",
74
+ variant === "outline" ? "fd-btn-icon-outline" : variant === "solid" ? "fd-btn-icon-solid" : variant === "soft" ? "fd-btn-icon-soft" : "",
75
75
  tone !== "neutral" ? "is-" + tone : "",
76
76
  size === "sm" ? "fd-btn-icon-sm" : size === "lg" ? "fd-btn-icon-lg" : "",
77
77
  round ? "fd-btn-icon-round" : "",
@@ -2937,6 +2937,7 @@ function EntityRowMetrics({ metrics, className = "", style }) {
2937
2937
  }
2938
2938
  function EntityRow({
2939
2939
  title,
2940
+ titleIcon,
2940
2941
  meta,
2941
2942
  metrics,
2942
2943
  accessory,
@@ -2954,9 +2955,11 @@ function EntityRow({
2954
2955
  onPointerDown,
2955
2956
  className: ["fd-erow", tone === "danger" ? "fd-erow-danger" : "", className].filter(Boolean).join(" "),
2956
2957
  style: {
2957
- display: "flex",
2958
- alignItems: "center",
2959
- gap: 10,
2958
+ // display/alignItems/gap moved to the .fd-erow stylesheet rule (MP-114
2959
+ // follow-up): an inline style always wins over a stylesheet rule regardless of
2960
+ // specificity or media/container query, which would have made it impossible
2961
+ // for the narrow-container rule below to ever switch this row from a flex row
2962
+ // to a grid (see that rule's own comment for why it needs to).
2960
2963
  height: "100%",
2961
2964
  padding: "0 12px",
2962
2965
  cursor: draggable ? "grab" : void 0,
@@ -2966,10 +2969,20 @@ function EntityRow({
2966
2969
  ...style
2967
2970
  },
2968
2971
  children: [
2969
- draggable ? /* @__PURE__ */ jsx39("i", { className: "ph ph-dots-six-vertical", "aria-hidden": "true", style: { fontSize: 14, flex: "none", color: "var(--text-muted)" } }) : null,
2972
+ draggable ? /* @__PURE__ */ jsx39("i", { className: "ph ph-dots-six-vertical fd-erow-grip", "aria-hidden": "true", style: { fontSize: 14, flex: "none", color: "var(--text-muted)" } }) : null,
2970
2973
  /* @__PURE__ */ jsxs36("span", { className: "fd-erow-body", children: [
2971
2974
  /* @__PURE__ */ jsxs36("span", { className: "fd-erow-titleline", children: [
2972
- /* @__PURE__ */ jsx39("span", { className: "fd-erow-title fd-body-sm", children: title }),
2975
+ titleIcon ? /* @__PURE__ */ jsx39(Tooltip, { label: titleIcon.tooltip, children: /* @__PURE__ */ jsxs36(
2976
+ "button",
2977
+ {
2978
+ type: "button",
2979
+ className: ["fd-erow-title-icon", titleIcon.tone ? "is-" + titleIcon.tone : ""].filter(Boolean).join(" "),
2980
+ children: [
2981
+ /* @__PURE__ */ jsx39("i", { className: "ph ph-" + titleIcon.icon, "aria-hidden": "true" }),
2982
+ /* @__PURE__ */ jsx39("span", { className: "fd-erow-title fd-body-sm", children: title })
2983
+ ]
2984
+ }
2985
+ ) }) : /* @__PURE__ */ jsx39("span", { className: "fd-erow-title fd-body-sm", children: title }),
2973
2986
  meta ? /* @__PURE__ */ jsx39("span", { className: "fd-erow-meta", children: meta }) : null
2974
2987
  ] }),
2975
2988
  hasStrip ? /* @__PURE__ */ jsxs36("span", { className: "fd-erow-strip", children: [
@@ -2983,7 +2996,7 @@ function EntityRow({
2983
2996
  icon: action.icon,
2984
2997
  label: action.label,
2985
2998
  size: "sm",
2986
- variant: "solid",
2999
+ variant: action.tone ? "soft" : "solid",
2987
3000
  tone: action.tone === "add" ? "ok" : action.tone === "remove" ? "danger" : "neutral",
2988
3001
  className: "fd-erow-action",
2989
3002
  onClick: action.onClick
@@ -8152,7 +8165,7 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
8152
8165
  const tallest = Math.max(1, ...grouped.map((g) => g.items.length));
8153
8166
  const floor = BANDS2.find((b) => b.key === floorBand) || BANDS2[0];
8154
8167
  return /* @__PURE__ */ jsxs61("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 16 }, children: [
8155
- /* @__PURE__ */ jsx65("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 12, alignItems: "end" }, children: grouped.map((g) => {
8168
+ /* @__PURE__ */ jsx65("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,minmax(0,1fr))", gap: 12, alignItems: "end" }, children: grouped.map((g) => {
8156
8169
  const mark = "var(--csi-" + g.n + "-mark)";
8157
8170
  const active = selectedBand === g.key;
8158
8171
  return /* @__PURE__ */ jsxs61(
@@ -8160,16 +8173,23 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
8160
8173
  {
8161
8174
  type: "button",
8162
8175
  onClick: onSelectBand ? () => onSelectBand(active ? null : g.key) : void 0,
8163
- style: { display: "flex", flexDirection: "column", justifyContent: "flex-end", gap: 10, padding: 12, border: "1px solid " + (active ? "var(--border-strong)" : "var(--border)"), borderRadius: "var(--r-lg)", background: active ? "var(--surface-2)" : "var(--surface)", cursor: onSelectBand ? "pointer" : "default", textAlign: "left", minHeight: 190, transition: "background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)" },
8176
+ style: { display: "flex", flexDirection: "column", justifyContent: "flex-end", gap: 10, padding: 12, border: "1px solid " + (active ? "var(--border-strong)" : "var(--border)"), borderRadius: "var(--r-lg)", background: active ? "var(--surface-2)" : "var(--surface)", cursor: onSelectBand ? "pointer" : "default", textAlign: "left", minHeight: 190, minWidth: 0, transition: "background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)" },
8164
8177
  children: [
8165
- /* @__PURE__ */ jsx65("span", { style: { display: "flex", flexDirection: "column-reverse", gap: 4, minHeight: tallest * 24 }, children: loading ? Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsx65("span", { className: "fd-skel", style: { height: 16, borderRadius: 4 } }, i)) : g.items.map((c) => /* @__PURE__ */ jsx65(
8166
- "span",
8167
- {
8168
- title: c.name + " \xB7 " + c.crp,
8169
- style: { height: 18, borderRadius: 4, background: "var(--csi-" + g.n + "-fill)", border: "1px solid var(--csi-" + g.n + "-edge)", color: "var(--csi-" + g.n + "-text)", display: "flex", alignItems: "center", padding: "0 7px", fontSize: 10, fontWeight: 700, overflow: "hidden", whiteSpace: "nowrap" },
8170
- children: c.short || c.name
8171
- },
8172
- c.name
8178
+ /* @__PURE__ */ jsx65("span", { style: { display: "flex", flexDirection: "column-reverse", gap: 4, minHeight: tallest * 24, minWidth: 0 }, children: loading ? Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsx65("span", { className: "fd-skel", style: { height: 16, borderRadius: 4 } }, i)) : g.items.map((c) => (
8179
+ // `overflow: hidden` + `white-space: nowrap` only clips once the span
8180
+ // actually HAS a bounded width to clip against (see the grid/flex
8181
+ // `minWidth: 0` chain above) `textOverflow: ellipsis` then makes
8182
+ // that clip readable instead of a hard cut mid-letter; the full name
8183
+ // is still one hover away via `title`.
8184
+ /* @__PURE__ */ jsx65(
8185
+ "span",
8186
+ {
8187
+ title: c.name + " \xB7 " + c.crp,
8188
+ style: { height: 18, borderRadius: 4, background: "var(--csi-" + g.n + "-fill)", border: "1px solid var(--csi-" + g.n + "-edge)", color: "var(--csi-" + g.n + "-text)", display: "flex", alignItems: "center", padding: "0 7px", fontSize: 10, fontWeight: 700, overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" },
8189
+ children: c.short || c.name
8190
+ },
8191
+ c.name
8192
+ )
8173
8193
  )) }),
8174
8194
  /* @__PURE__ */ jsxs61("span", { className: "fd-row", style: { gap: 8, alignItems: "baseline" }, children: [
8175
8195
  /* @__PURE__ */ jsx65("span", { className: "fd-num-hero", style: { fontSize: 30, color: g.items.length ? "var(--text)" : "var(--text-disabled)" }, children: loading ? "\u2013" : g.items.length }),