@flytedan/flytebot-design-system 0.41.0 → 0.42.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.d.cts CHANGED
@@ -316,7 +316,11 @@ interface CollapsibleProps {
316
316
  }
317
317
  declare function Collapsible({ title, subtitle, badge, icon, defaultOpen, open, onToggle, children, className, ...rest }: CollapsibleProps): React.JSX.Element;
318
318
 
319
- /** A right-side panel for detail and editing without losing the list behind it. */
319
+ /** A right-side panel for detail and editing without losing the list behind it.
320
+ * MP-248: always modal (declares `aria-modal="true"`), so it traps Tab/Shift+Tab inside
321
+ * itself and returns focus to its trigger on close, via the design system's shared
322
+ * `useFocusTrap` (the same hook Modal uses) — one focus-trap implementation, reused
323
+ * rather than hand-rolled a second time. */
320
324
  interface DrawerProps {
321
325
  open?: boolean;
322
326
  title?: React.ReactNode;
@@ -3177,7 +3181,12 @@ interface FeatureGateProps {
3177
3181
  }
3178
3182
  /** Feature gate. ALWAYS silent when off — an unreleased feature should leave no trace. */
3179
3183
  declare function FeatureGate({ flag, fallback, children, onRoadmap, blur, minHeight, label, variant, preview, bypassPerm }: FeatureGateProps): React.ReactNode;
3180
- /** Inline "why is this disabled" affordance for buttons that must stay visible. */
3184
+ /** Inline "why is this disabled" affordance for buttons that must stay visible.
3185
+ * MP-253: while the session is pending, permission is unknown, not denied — the same rule
3186
+ * Gate applies. Rendering the control live would risk a stale-role grant; rendering the
3187
+ * "Requires X" denial would assert a permission gap that might not be real. So it dims the
3188
+ * control (never `can()`), with a title that says the state is unverified rather than
3189
+ * claiming a specific permission is missing. */
3181
3190
  declare function PermissionHint({ perm, children }: {
3182
3191
  perm: string | string[];
3183
3192
  children?: React.ReactNode;
package/dist/index.d.ts CHANGED
@@ -316,7 +316,11 @@ interface CollapsibleProps {
316
316
  }
317
317
  declare function Collapsible({ title, subtitle, badge, icon, defaultOpen, open, onToggle, children, className, ...rest }: CollapsibleProps): React.JSX.Element;
318
318
 
319
- /** A right-side panel for detail and editing without losing the list behind it. */
319
+ /** A right-side panel for detail and editing without losing the list behind it.
320
+ * MP-248: always modal (declares `aria-modal="true"`), so it traps Tab/Shift+Tab inside
321
+ * itself and returns focus to its trigger on close, via the design system's shared
322
+ * `useFocusTrap` (the same hook Modal uses) — one focus-trap implementation, reused
323
+ * rather than hand-rolled a second time. */
320
324
  interface DrawerProps {
321
325
  open?: boolean;
322
326
  title?: React.ReactNode;
@@ -3177,7 +3181,12 @@ interface FeatureGateProps {
3177
3181
  }
3178
3182
  /** Feature gate. ALWAYS silent when off — an unreleased feature should leave no trace. */
3179
3183
  declare function FeatureGate({ flag, fallback, children, onRoadmap, blur, minHeight, label, variant, preview, bypassPerm }: FeatureGateProps): React.ReactNode;
3180
- /** Inline "why is this disabled" affordance for buttons that must stay visible. */
3184
+ /** Inline "why is this disabled" affordance for buttons that must stay visible.
3185
+ * MP-253: while the session is pending, permission is unknown, not denied — the same rule
3186
+ * Gate applies. Rendering the control live would risk a stale-role grant; rendering the
3187
+ * "Requires X" denial would assert a permission gap that might not be real. So it dims the
3188
+ * control (never `can()`), with a title that says the state is unverified rather than
3189
+ * claiming a specific permission is missing. */
3181
3190
  declare function PermissionHint({ perm, children }: {
3182
3191
  perm: string | string[];
3183
3192
  children?: React.ReactNode;
package/dist/index.js CHANGED
@@ -772,54 +772,17 @@ function Collapsible({
772
772
  }
773
773
 
774
774
  // src/components/surfaces/Drawer.tsx
775
- import * as React5 from "react";
775
+ import * as React6 from "react";
776
776
  import { createPortal as createPortal2 } from "react-dom";
777
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
778
- function Drawer({ open = true, title, onClose, footer, children, className = "", ...rest }) {
779
- const zIndex = useOverlayLayer(open);
780
- const [closing, setClosing] = React5.useState(false);
781
- const close = React5.useCallback(() => {
782
- if (!onClose) return;
783
- setClosing(true);
784
- setTimeout(onClose, 210);
785
- }, [onClose]);
786
- React5.useEffect(() => {
787
- if (!open || !onClose) return;
788
- const h = (e) => {
789
- if (e.key === "Escape") close();
790
- };
791
- document.addEventListener("keydown", h);
792
- return () => document.removeEventListener("keydown", h);
793
- }, [open, onClose, close]);
794
- if (!open) return null;
795
- return createPortal2(
796
- /* @__PURE__ */ jsxs6(LayerProvider, { zIndex, children: [
797
- /* @__PURE__ */ jsx8("div", { className: "fd-scrim" + (closing ? " is-closing" : ""), style: { display: "block", zIndex: zIndex - LAYER_STEP }, onClick: close }),
798
- /* @__PURE__ */ jsxs6("aside", { className: ["fd-drawer", closing ? "is-closing" : "", className].filter(Boolean).join(" "), role: "dialog", "aria-modal": "true", ...rest, style: { zIndex }, children: [
799
- /* @__PURE__ */ jsxs6("div", { className: "fd-card-head", style: { flex: "none" }, children: [
800
- /* @__PURE__ */ jsx8("span", { className: "fd-card-head-title", title: truncationTitle(title), children: title }),
801
- onClose ? /* @__PURE__ */ jsx8("button", { type: "button", className: "fd-btn-icon", "aria-label": "Close", onClick: close, children: /* @__PURE__ */ jsx8("i", { className: "ph ph-x" }) }) : null
802
- ] }),
803
- /* @__PURE__ */ jsx8("div", { style: { flex: "1 1 auto", minHeight: 0, overflow: "auto", padding: 20 }, children }),
804
- footer ? /* @__PURE__ */ jsx8("div", { className: "fd-card-foot", style: { flex: "none" }, children: typeof footer === "function" ? footer(close) : footer }) : null
805
- ] })
806
- ] }),
807
- document.body
808
- );
809
- }
810
-
811
- // src/components/surfaces/Modal.tsx
812
- import * as React7 from "react";
813
- import { createPortal as createPortal3 } from "react-dom";
814
777
 
815
778
  // src/kits/useFocusTrap.ts
816
- import * as React6 from "react";
779
+ import * as React5 from "react";
817
780
  var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
818
781
  function getFocusable(container) {
819
782
  return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
820
783
  }
821
784
  function useFocusTrap(containerRef, active) {
822
- React6.useEffect(() => {
785
+ React5.useEffect(() => {
823
786
  if (!active) return;
824
787
  const container = containerRef.current;
825
788
  if (!container) return;
@@ -855,7 +818,46 @@ function useFocusTrap(containerRef, active) {
855
818
  }, [active, containerRef]);
856
819
  }
857
820
 
821
+ // src/components/surfaces/Drawer.tsx
822
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
823
+ function Drawer({ open = true, title, onClose, footer, children, className = "", ...rest }) {
824
+ const zIndex = useOverlayLayer(open);
825
+ const asideRef = React6.useRef(null);
826
+ useFocusTrap(asideRef, !!open);
827
+ const [closing, setClosing] = React6.useState(false);
828
+ const close = React6.useCallback(() => {
829
+ if (!onClose) return;
830
+ setClosing(true);
831
+ setTimeout(onClose, 210);
832
+ }, [onClose]);
833
+ React6.useEffect(() => {
834
+ if (!open || !onClose) return;
835
+ const h = (e) => {
836
+ if (e.key === "Escape") close();
837
+ };
838
+ document.addEventListener("keydown", h);
839
+ return () => document.removeEventListener("keydown", h);
840
+ }, [open, onClose, close]);
841
+ if (!open) return null;
842
+ return createPortal2(
843
+ /* @__PURE__ */ jsxs6(LayerProvider, { zIndex, children: [
844
+ /* @__PURE__ */ jsx8("div", { className: "fd-scrim" + (closing ? " is-closing" : ""), style: { display: "block", zIndex: zIndex - LAYER_STEP }, onClick: close }),
845
+ /* @__PURE__ */ jsxs6("aside", { ref: asideRef, className: ["fd-drawer", closing ? "is-closing" : "", className].filter(Boolean).join(" "), role: "dialog", "aria-modal": "true", ...rest, style: { zIndex }, children: [
846
+ /* @__PURE__ */ jsxs6("div", { className: "fd-card-head", style: { flex: "none" }, children: [
847
+ /* @__PURE__ */ jsx8("span", { className: "fd-card-head-title", title: truncationTitle(title), children: title }),
848
+ onClose ? /* @__PURE__ */ jsx8("button", { type: "button", className: "fd-btn-icon", "aria-label": "Close", onClick: close, children: /* @__PURE__ */ jsx8("i", { className: "ph ph-x" }) }) : null
849
+ ] }),
850
+ /* @__PURE__ */ jsx8("div", { style: { flex: "1 1 auto", minHeight: 0, overflow: "auto", padding: 20 }, children }),
851
+ footer ? /* @__PURE__ */ jsx8("div", { className: "fd-card-foot", style: { flex: "none" }, children: typeof footer === "function" ? footer(close) : footer }) : null
852
+ ] })
853
+ ] }),
854
+ document.body
855
+ );
856
+ }
857
+
858
858
  // src/components/surfaces/Modal.tsx
859
+ import * as React7 from "react";
860
+ import { createPortal as createPortal3 } from "react-dom";
859
861
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
860
862
  function Modal({ open = true, title, description, onClose, footer, wide = false, children, className = "", ...rest }) {
861
863
  const [closing, setClosing] = React7.useState(false);
@@ -7060,14 +7062,14 @@ function AccountMenu({
7060
7062
  className = "",
7061
7063
  ...rest
7062
7064
  }) {
7063
- const { user, roles, can: can2, signOut: ctxSignOut, demo } = useSessionContext();
7065
+ const { user, roles, can: can2, signOut: ctxSignOut, demo, pending } = useSessionContext();
7064
7066
  const [open, setOpen] = React43.useState(false);
7065
7067
  const [switching, setSwitching] = React43.useState(false);
7066
7068
  const [signingOut, setSigningOut] = React43.useState(false);
7067
7069
  const [signOutError, setSignOutError] = React43.useState(null);
7068
7070
  const ref = React43.useRef(null);
7069
- const visibleLinks = links.filter((l) => !l.perm || can2(l.perm));
7070
- const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || can2(l.perm)) })).filter((g) => g.links.length);
7071
+ const visibleLinks = links.filter((l) => !l.perm || !pending && can2(l.perm));
7072
+ const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || !pending && can2(l.perm)) })).filter((g) => g.links.length);
7071
7073
  const closeMenu = () => {
7072
7074
  setOpen(false);
7073
7075
  setSignOutError(null);
@@ -10379,6 +10381,7 @@ function GatePending({ compact: compact3 = false, className = "" }) {
10379
10381
  "div",
10380
10382
  {
10381
10383
  className: ["fd-stack", className].filter(Boolean).join(" "),
10384
+ role: "status",
10382
10385
  style: {
10383
10386
  gap: 10,
10384
10387
  alignItems: "flex-start",
@@ -10428,14 +10431,14 @@ function FeatureGate({
10428
10431
  bypassPerm = "flags.manage"
10429
10432
  }) {
10430
10433
  SessionKit.useSession();
10431
- const { can: can2 } = useSessionContext();
10434
+ const { can: can2, pending } = useSessionContext();
10432
10435
  const rt = useFeatureStatus(flag);
10433
10436
  if (!SessionKit.isEnabled(flag)) return fallback;
10434
10437
  if (rt.complete || rt.mode === "test") return children;
10435
10438
  if (!preview) return fallback;
10436
10439
  const f = SessionKit.findFlag(flag) || {};
10437
10440
  const missing = rt.missing || [];
10438
- const canBypass = bypassPerm === null ? false : !bypassPerm || can2(bypassPerm);
10441
+ const canBypass = pending ? false : bypassPerm === null ? false : !bypassPerm || can2(bypassPerm);
10439
10442
  return /* @__PURE__ */ jsx77(
10440
10443
  ComingSoon,
10441
10444
  {
@@ -10455,7 +10458,10 @@ function FeatureGate({
10455
10458
  );
10456
10459
  }
10457
10460
  function PermissionHint({ perm, children }) {
10458
- const { can: can2 } = useSessionContext();
10461
+ const { can: can2, pending } = useSessionContext();
10462
+ if (pending) {
10463
+ return /* @__PURE__ */ jsx77("span", { title: "Access can't be verified right now", style: { display: "inline-flex", opacity: 0.45, cursor: "not-allowed" }, "aria-disabled": "true", children: /* @__PURE__ */ jsx77("span", { style: { pointerEvents: "none" }, children }) });
10464
+ }
10459
10465
  if (can2(perm)) return children;
10460
10466
  return /* @__PURE__ */ jsx77(
10461
10467
  "span",