@homebound/beam 3.70.0 → 3.72.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
@@ -9944,7 +9944,26 @@ type StepperTabProps = {
9944
9944
  };
9945
9945
  declare function StepperTab(props: StepperTabProps): JSX.Element;
9946
9946
 
9947
- type StepperTabsStep = Pick<StepperTabProps, "label" | "value" | "completed" | "disabled">;
9947
+ type WorkflowActionsProps = {
9948
+ isFirstStep: boolean;
9949
+ isLastStep: boolean;
9950
+ isMobile: boolean;
9951
+ onBack: () => void;
9952
+ /** Leaves the workflow without saving. Always shown. */
9953
+ onCancel: (e: PressEvent) => void;
9954
+ /** Saves partial progress and exits. Used whenever canExitEarly is true. */
9955
+ onSaveAndExit?: (e: PressEvent) => void | Promise<void>;
9956
+ /** Label for the completion button shown on the last step. */
9957
+ completeLabel: "Create" | "Save";
9958
+ /** Called when the completion button is clicked. Only shown on the last step. */
9959
+ onComplete: (e: PressEvent) => void | Promise<void>;
9960
+ /** Runs before continuing to the next step. Return `false` to stay put, like when the save failed. */
9961
+ onContinue: () => boolean | void | Promise<boolean | void>;
9962
+ /** Disables whichever of Continue/Complete is currently shown, e.g. while the active step is invalid. */
9963
+ primaryDisabled?: boolean;
9964
+ };
9965
+
9966
+ type StepperTabsStep = Pick<StepperTabProps, "label" | "value" | "completed" | "disabled"> & Partial<Pick<WorkflowActionsProps, "onContinue">>;
9948
9967
  type StepperTabsProps = {
9949
9968
  steps: StepperTabsStep[];
9950
9969
  currentStep: StepperTabsStep["value"];
@@ -10237,24 +10256,6 @@ type PageHeaderLayoutProps<V extends string, X> = {
10237
10256
  /** Page header + body shell with auto-hide chrome. Contract: `docs/layouts.md`. */
10238
10257
  declare function PageHeaderLayout<V extends string, X extends Only<TabsContentXss, X>>(props: PageHeaderLayoutProps<V, X>): JSX.Element;
10239
10258
 
10240
- type WorkflowActionsProps = {
10241
- isFirstStep: boolean;
10242
- isLastStep: boolean;
10243
- isMobile: boolean;
10244
- onBack: () => void;
10245
- /** Leaves the workflow without saving. Always shown. */
10246
- onCancel: (e: PressEvent) => void;
10247
- /** Saves partial progress and exits. Used whenever canExitEarly is true. */
10248
- onSaveAndExit?: (e: PressEvent) => void | Promise<void>;
10249
- /** Label for the completion button shown on the last step. */
10250
- completeLabel: "Create" | "Save";
10251
- /** Called when the completion button is clicked. Only shown on the last step. */
10252
- onComplete: (e: PressEvent) => void | Promise<void>;
10253
- onContinue: () => void;
10254
- /** Disables whichever of Continue/Complete is currently shown, e.g. while the active step is invalid. */
10255
- primaryDisabled?: boolean;
10256
- };
10257
-
10258
10259
  /** A `WorkflowLayout` step: a `StepperTabsStep` (minus `value`, which is derived from `label`) plus the page content rendered while it's active — `completed` also gates the Continue/Complete CTA when this is the active step. */
10259
10260
  type WorkflowLayoutStep = Omit<StepperTabsStep, "value"> & {
10260
10261
  /** Rendered as the page body while this is the active step. */
@@ -10273,8 +10274,9 @@ type WorkflowLayoutProps = Pick<BaseHeaderProps, "title" | "documentTitleSuffix"
10273
10274
  *
10274
10275
  * A standalone, full-page layout for step-based workflow pages — nest it directly under
10275
10276
  * `EnvironmentBannerLayout`, never under `NavbarLayout`/`SideNavLayout`/`PageHeaderLayout`. Unlike
10276
- * `PageHeaderLayout`, the header here never auto-hides; the stepper tabs collapse to a condensed
10277
- * indicator bar on mobile instead.
10277
+ * `PageHeaderLayout`, the header here never auto-hides. The stepper tabs always collapse to a condensed
10278
+ * indicator bar on mobile, and on larger viewports also collapse once scrolled past a threshold,
10279
+ * re-expanding on scroll-up (even before reaching the top).
10278
10280
  *
10279
10281
  * Owns the workflow's fixed CTA set (Back/Cancel/Save & Exit/Continue-or-Complete) via `WorkflowActions`
10280
10282
  * so it can move them into a mobile footer at the `sm` breakpoint — `WorkflowHeader` itself is not part
package/dist/index.d.ts CHANGED
@@ -9944,7 +9944,26 @@ type StepperTabProps = {
9944
9944
  };
9945
9945
  declare function StepperTab(props: StepperTabProps): JSX.Element;
9946
9946
 
9947
- type StepperTabsStep = Pick<StepperTabProps, "label" | "value" | "completed" | "disabled">;
9947
+ type WorkflowActionsProps = {
9948
+ isFirstStep: boolean;
9949
+ isLastStep: boolean;
9950
+ isMobile: boolean;
9951
+ onBack: () => void;
9952
+ /** Leaves the workflow without saving. Always shown. */
9953
+ onCancel: (e: PressEvent) => void;
9954
+ /** Saves partial progress and exits. Used whenever canExitEarly is true. */
9955
+ onSaveAndExit?: (e: PressEvent) => void | Promise<void>;
9956
+ /** Label for the completion button shown on the last step. */
9957
+ completeLabel: "Create" | "Save";
9958
+ /** Called when the completion button is clicked. Only shown on the last step. */
9959
+ onComplete: (e: PressEvent) => void | Promise<void>;
9960
+ /** Runs before continuing to the next step. Return `false` to stay put, like when the save failed. */
9961
+ onContinue: () => boolean | void | Promise<boolean | void>;
9962
+ /** Disables whichever of Continue/Complete is currently shown, e.g. while the active step is invalid. */
9963
+ primaryDisabled?: boolean;
9964
+ };
9965
+
9966
+ type StepperTabsStep = Pick<StepperTabProps, "label" | "value" | "completed" | "disabled"> & Partial<Pick<WorkflowActionsProps, "onContinue">>;
9948
9967
  type StepperTabsProps = {
9949
9968
  steps: StepperTabsStep[];
9950
9969
  currentStep: StepperTabsStep["value"];
@@ -10237,24 +10256,6 @@ type PageHeaderLayoutProps<V extends string, X> = {
10237
10256
  /** Page header + body shell with auto-hide chrome. Contract: `docs/layouts.md`. */
10238
10257
  declare function PageHeaderLayout<V extends string, X extends Only<TabsContentXss, X>>(props: PageHeaderLayoutProps<V, X>): JSX.Element;
10239
10258
 
10240
- type WorkflowActionsProps = {
10241
- isFirstStep: boolean;
10242
- isLastStep: boolean;
10243
- isMobile: boolean;
10244
- onBack: () => void;
10245
- /** Leaves the workflow without saving. Always shown. */
10246
- onCancel: (e: PressEvent) => void;
10247
- /** Saves partial progress and exits. Used whenever canExitEarly is true. */
10248
- onSaveAndExit?: (e: PressEvent) => void | Promise<void>;
10249
- /** Label for the completion button shown on the last step. */
10250
- completeLabel: "Create" | "Save";
10251
- /** Called when the completion button is clicked. Only shown on the last step. */
10252
- onComplete: (e: PressEvent) => void | Promise<void>;
10253
- onContinue: () => void;
10254
- /** Disables whichever of Continue/Complete is currently shown, e.g. while the active step is invalid. */
10255
- primaryDisabled?: boolean;
10256
- };
10257
-
10258
10259
  /** A `WorkflowLayout` step: a `StepperTabsStep` (minus `value`, which is derived from `label`) plus the page content rendered while it's active — `completed` also gates the Continue/Complete CTA when this is the active step. */
10259
10260
  type WorkflowLayoutStep = Omit<StepperTabsStep, "value"> & {
10260
10261
  /** Rendered as the page body while this is the active step. */
@@ -10273,8 +10274,9 @@ type WorkflowLayoutProps = Pick<BaseHeaderProps, "title" | "documentTitleSuffix"
10273
10274
  *
10274
10275
  * A standalone, full-page layout for step-based workflow pages — nest it directly under
10275
10276
  * `EnvironmentBannerLayout`, never under `NavbarLayout`/`SideNavLayout`/`PageHeaderLayout`. Unlike
10276
- * `PageHeaderLayout`, the header here never auto-hides; the stepper tabs collapse to a condensed
10277
- * indicator bar on mobile instead.
10277
+ * `PageHeaderLayout`, the header here never auto-hides. The stepper tabs always collapse to a condensed
10278
+ * indicator bar on mobile, and on larger viewports also collapse once scrolled past a threshold,
10279
+ * re-expanding on scroll-up (even before reaching the top).
10278
10280
  *
10279
10281
  * Owns the workflow's fixed CTA set (Back/Cancel/Save & Exit/Continue-or-Complete) via `WorkflowActions`
10280
10282
  * so it can move them into a mobile footer at the `sm` breakpoint — `WorkflowHeader` itself is not part
package/dist/index.js CHANGED
@@ -26469,7 +26469,7 @@ function PageHeaderLayout(props) {
26469
26469
  }
26470
26470
 
26471
26471
  // src/layouts/WorkflowLayout/WorkflowLayout.tsx
26472
- import { useLayoutEffect as useLayoutEffect10, useRef as useRef68, useState as useState59 } from "react";
26472
+ import { useLayoutEffect as useLayoutEffect11, useRef as useRef69, useState as useState60 } from "react";
26473
26473
 
26474
26474
  // src/components/Headers/WorkflowHeader.tsx
26475
26475
  import { jsx as jsx215 } from "react/jsx-runtime";
@@ -26479,14 +26479,65 @@ function WorkflowHeader(props) {
26479
26479
  return /* @__PURE__ */ jsx215(BaseHeader, { ...otherProps, bottomSlot: /* @__PURE__ */ jsx215(StepperTabs, { ...stepperTabs, ...tid.stepperTabs }) });
26480
26480
  }
26481
26481
 
26482
+ // src/layouts/useScrollCollapse.ts
26483
+ import { useLayoutEffect as useLayoutEffect10, useRef as useRef67, useState as useState59 } from "react";
26484
+ function useScrollCollapse(enabled, restingOffset) {
26485
+ const [collapsed, setCollapsed] = useState59(() => typeof window !== "undefined" && window.scrollY > 0);
26486
+ const collapsedRef = useRef67(collapsed);
26487
+ collapsedRef.current = collapsed;
26488
+ const restingOffsetRef = useRef67(restingOffset);
26489
+ restingOffsetRef.current = restingOffset;
26490
+ const lastScrollYRef = useRef67(Number.POSITIVE_INFINITY);
26491
+ const lastScrollHeightRef = useRef67(0);
26492
+ useLayoutEffect10(() => {
26493
+ lastScrollHeightRef.current = document.documentElement.scrollHeight;
26494
+ }, [restingOffset]);
26495
+ useLayoutEffect10(() => {
26496
+ if (!enabled) return;
26497
+ const commit = (next) => {
26498
+ if (next !== collapsedRef.current) {
26499
+ collapsedRef.current = next;
26500
+ setCollapsed(next);
26501
+ }
26502
+ };
26503
+ const updateCollapsed = () => {
26504
+ const doc = document.documentElement;
26505
+ const currentY = window.scrollY;
26506
+ if (currentY <= 0) {
26507
+ lastScrollYRef.current = 0;
26508
+ lastScrollHeightRef.current = doc.scrollHeight;
26509
+ commit(false);
26510
+ return;
26511
+ }
26512
+ const currentScrollHeight = doc.scrollHeight;
26513
+ const scrollHeightChanged = lastScrollHeightRef.current !== 0 && currentScrollHeight !== lastScrollHeightRef.current;
26514
+ const dy = currentY - lastScrollYRef.current;
26515
+ lastScrollYRef.current = currentY;
26516
+ lastScrollHeightRef.current = currentScrollHeight;
26517
+ if (currentY <= restingOffsetRef.current) return;
26518
+ if (scrollHeightChanged) {
26519
+ commit(true);
26520
+ return;
26521
+ }
26522
+ const atBottom = currentY >= doc.scrollHeight - doc.clientHeight;
26523
+ if (dy > 0) commit(true);
26524
+ else if (dy < 0 && !atBottom) commit(false);
26525
+ };
26526
+ updateCollapsed();
26527
+ window.addEventListener("scroll", updateCollapsed, { passive: true });
26528
+ return () => window.removeEventListener("scroll", updateCollapsed);
26529
+ }, [enabled]);
26530
+ return collapsed;
26531
+ }
26532
+
26482
26533
  // src/layouts/WorkflowLayout/useUnsavedChangesGuard.tsx
26483
- import { useEffect as useEffect38, useRef as useRef67 } from "react";
26534
+ import { useEffect as useEffect38, useRef as useRef68 } from "react";
26484
26535
  import { useBlocker } from "react-router-dom";
26485
26536
  import { jsx as jsx216 } from "react/jsx-runtime";
26486
26537
  function useUnsavedChangesGuard(options) {
26487
26538
  const { isDirty, onCancel } = options;
26488
26539
  const { openModal } = useModal();
26489
- const isDirtyRef = useRef67(isDirty);
26540
+ const isDirtyRef = useRef68(isDirty);
26490
26541
  isDirtyRef.current = isDirty;
26491
26542
  useEffect38(() => {
26492
26543
  const onBeforeUnload = (e) => {
@@ -26570,7 +26621,7 @@ function WorkflowLayout(props) {
26570
26621
  ...step,
26571
26622
  value: defaultTestId(step.label)
26572
26623
  }));
26573
- const [currentStep, setCurrentStep] = useState59(() => getInitialStep(tabSteps, defaultStep));
26624
+ const [currentStep, setCurrentStep] = useState60(() => getInitialStep(tabSteps, defaultStep));
26574
26625
  const tid = useTestIds(props, "workflowLayout");
26575
26626
  const {
26576
26627
  sm: isMobile
@@ -26582,9 +26633,11 @@ function WorkflowLayout(props) {
26582
26633
  isDirty,
26583
26634
  onCancel
26584
26635
  });
26585
- const headerMetricsRef = useRef68(null);
26636
+ const bannerAndNavbarHeight = useBannerAndNavbarHeight();
26637
+ const headerMetricsRef = useRef69(null);
26586
26638
  const headerHeight = useMeasuredHeight(headerMetricsRef, true);
26587
- const collapsed = isMobile;
26639
+ const scrollCollapsed = useScrollCollapse(!isMobile, bannerAndNavbarHeight + headerHeight);
26640
+ const collapsed = isMobile || scrollCollapsed;
26588
26641
  const headerWidth = documentScrollChromeWidth();
26589
26642
  const outerTop = bannerAndNavbarChromeTop();
26590
26643
  const cssVars = headerHeight > 0 ? {
@@ -26594,9 +26647,16 @@ function WorkflowLayout(props) {
26594
26647
  const isFirstStep = currentIndex <= 0;
26595
26648
  const isLastStep = currentIndex === tabSteps.length - 1;
26596
26649
  const activeStep = tabSteps[currentIndex];
26597
- const buttons = /* @__PURE__ */ jsx218(WorkflowActions, { isFirstStep, isLastStep, isMobile, onBack: () => setCurrentStep(tabSteps[currentIndex - 1].value), onCancel: onCancelClick, onSaveAndExit, completeLabel, onComplete, primaryDisabled: !activeStep?.completed, onContinue: () => setCurrentStep(tabSteps[currentIndex + 1].value) });
26650
+ const buttons = /* @__PURE__ */ jsx218(WorkflowActions, { isFirstStep, isLastStep, isMobile, onBack: () => setCurrentStep(tabSteps[currentIndex - 1].value), onCancel: onCancelClick, onSaveAndExit, completeLabel, onComplete, primaryDisabled: !activeStep?.completed, onContinue: async () => {
26651
+ const onContinue = activeStep?.onContinue;
26652
+ if (onContinue) {
26653
+ const allowed = await onContinue();
26654
+ if (allowed === false) return;
26655
+ }
26656
+ setCurrentStep(tabSteps[currentIndex + 1].value);
26657
+ } });
26598
26658
  const showFooter = isMobile;
26599
- useLayoutEffect10(() => {
26659
+ useLayoutEffect11(() => {
26600
26660
  const root = document.documentElement;
26601
26661
  const previous = root.style.getPropertyValue(beamWorkflowLayoutFooterHeightVar);
26602
26662
  root.style.setProperty(beamWorkflowLayoutFooterHeightVar, showFooter ? `${mobileFooterHeightPx}px` : "0px");