@homebound/beam 2.390.0 → 2.391.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 CHANGED
@@ -129,6 +129,7 @@ __export(index_exports, {
129
129
  Palette: () => Palette,
130
130
  PresentationProvider: () => PresentationProvider,
131
131
  PreventBrowserScroll: () => PreventBrowserScroll,
132
+ RIGHT_SIDEBAR_MIN_WIDTH: () => RIGHT_SIDEBAR_MIN_WIDTH,
132
133
  RadioGroupField: () => RadioGroupField,
133
134
  ResponsiveGrid: () => ResponsiveGrid,
134
135
  ResponsiveGridItem: () => ResponsiveGridItem,
@@ -15605,36 +15606,39 @@ function SubmitButton(props) {
15605
15606
  );
15606
15607
  }
15607
15608
 
15609
+ // src/components/Layout/FormPageLayout.tsx
15610
+ var import_use_debounce5 = require("use-debounce");
15611
+
15608
15612
  // src/components/RightSidebar.tsx
15609
15613
  var import_framer_motion2 = require("framer-motion");
15610
15614
  var import_react84 = require("react");
15611
15615
  var import_jsx_runtime119 = require("@emotion/react/jsx-runtime");
15616
+ var RIGHT_SIDEBAR_MIN_WIDTH = "250px";
15612
15617
  function RightSidebar({ content }) {
15613
15618
  const [selectedIcon, setSelectedIcon] = (0, import_react84.useState)(void 0);
15614
15619
  const tid = useTestIds({}, "rightSidebar");
15615
- const width2 = 380;
15616
15620
  return /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)(import_jsx_runtime119.Fragment, { children: [
15617
- /* @__PURE__ */ (0, import_jsx_runtime119.jsx)("div", { css: Css.df.jcfe.relative.pr3.$, children: /* @__PURE__ */ (0, import_jsx_runtime119.jsx)("div", { css: Css.df.gap2.z1.$, children: content.map(({ icon }) => /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
15618
- IconButton,
15621
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsx)("div", { css: Css.df.jcfe.absolute.right0.pr3.$, children: /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(import_framer_motion2.AnimatePresence, { children: !selectedIcon && /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
15622
+ import_framer_motion2.motion.div,
15619
15623
  {
15620
- circle: true,
15621
- active: icon === selectedIcon,
15622
- onClick: () => setSelectedIcon(icon),
15623
- icon,
15624
- inc: 3.5
15625
- },
15626
- `${icon}-${selectedIcon}`
15627
- )) }) }),
15624
+ css: Css.df.fdc.gap2.z1.$,
15625
+ initial: { x: "100%", opacity: 0 },
15626
+ animate: { x: 0, opacity: 1 },
15627
+ exit: { x: "100%", opacity: 0 },
15628
+ transition: { ease: [0.51, 0.92, 0.24, 1], duration: 0.3, delay: 0.2 },
15629
+ children: /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(IconButtonList, { content, selectedIcon, onIconClick: setSelectedIcon })
15630
+ }
15631
+ ) }) }),
15628
15632
  /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(import_framer_motion2.AnimatePresence, { children: selectedIcon && /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
15629
15633
  import_framer_motion2.motion.div,
15630
15634
  {
15631
- initial: { x: width2, opacity: 0 },
15635
+ initial: { x: "100%", opacity: 0 },
15632
15636
  animate: { x: 0, opacity: 1 },
15633
- transition: { delay: 0.2, ease: "linear", duration: 0.2 },
15634
- exit: { transition: { ease: "linear", duration: 0.2 }, x: width2 },
15635
- css: Css.wPx(width2).z0.$,
15636
- children: /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("div", { css: Css.relative.topPx(-48).z0.px3.$, children: [
15637
- /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("div", { css: Css.absolute.leftPx(-24).$, children: [
15637
+ transition: { delay: 0.2, ease: [0.51, 0.92, 0.24, 1], duration: 0.3 },
15638
+ exit: { transition: { ease: "linear", duration: 0.2 }, x: "100%" },
15639
+ css: Css.w100.mw(RIGHT_SIDEBAR_MIN_WIDTH).z0.$,
15640
+ children: /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("div", { css: Css.relative.z0.px3.$, children: [
15641
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsxs)("div", { css: Css.absolute.leftPx(-24).top0.$, children: [
15638
15642
  /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
15639
15643
  IconButton,
15640
15644
  {
@@ -15647,13 +15651,27 @@ function RightSidebar({ content }) {
15647
15651
  ),
15648
15652
  /* @__PURE__ */ (0, import_jsx_runtime119.jsx)("div", { css: Css.absolute.topPx(48).leftPx(23).h("calc(100vh - 168px)").wPx(1).bgGray300.$ })
15649
15653
  ] }),
15650
- selectedIcon && /* @__PURE__ */ (0, import_jsx_runtime119.jsx)("div", { ...tid.content, css: Css.ptPx(72).$, children: content.find((sidebar) => sidebar.icon === selectedIcon)?.render() })
15654
+ /* @__PURE__ */ (0, import_jsx_runtime119.jsx)("div", { css: Css.df.aic.jcfe.gap2.mb3.$, children: /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(IconButtonList, { content, selectedIcon, onIconClick: setSelectedIcon }) }),
15655
+ selectedIcon && /* @__PURE__ */ (0, import_jsx_runtime119.jsx)("div", { ...tid.content, children: content.find((sidebar) => sidebar.icon === selectedIcon)?.render() })
15651
15656
  ] })
15652
15657
  },
15653
15658
  "rightSidebar"
15654
15659
  ) })
15655
15660
  ] });
15656
15661
  }
15662
+ function IconButtonList({ content, selectedIcon, onIconClick }) {
15663
+ return /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(import_jsx_runtime119.Fragment, { children: content.map(({ icon }) => /* @__PURE__ */ (0, import_jsx_runtime119.jsx)(
15664
+ IconButton,
15665
+ {
15666
+ circle: true,
15667
+ active: icon === selectedIcon,
15668
+ onClick: () => onIconClick(icon),
15669
+ icon,
15670
+ inc: 3.5
15671
+ },
15672
+ `${icon}-${selectedIcon}`
15673
+ )) });
15674
+ }
15657
15675
 
15658
15676
  // src/components/Layout/PageHeaderBreadcrumbs.tsx
15659
15677
  var import_react85 = require("react");
@@ -15669,14 +15687,9 @@ function PageHeaderBreadcrumbs({ breadcrumb }) {
15669
15687
  }
15670
15688
 
15671
15689
  // src/components/Layout/FormPageLayout.tsx
15672
- var import_jsx_runtime121 = (
15673
- // This page is `fixed` to the full screen to allow it to act as a full screen modal while content is mounted below
15674
- // since this layout will be replacing most superdrawers/sidebars, we keep the listing mounted below to preserve the users's
15675
- // scroll position & filters
15676
- // Adding "align-items: start" allows "position: sticky" to work within a grid for the sidebars
15677
- require("@emotion/react/jsx-runtime")
15678
- );
15690
+ var import_jsx_runtime121 = require("@emotion/react/jsx-runtime");
15679
15691
  var headerHeightPx = 120;
15692
+ var maxContentWidthPx = 1600;
15680
15693
  function FormPageLayoutComponent(props) {
15681
15694
  const { formSections, formState, rightSideBar } = props;
15682
15695
  const tids = useTestIds(props, "formPageLayout");
@@ -15689,26 +15702,25 @@ function FormPageLayoutComponent(props) {
15689
15702
  })),
15690
15703
  [formSections]
15691
15704
  );
15692
- const gridColumns = "minMax(0, auto) minMax(100px, 250px) minMax(350px, 1000px) minMax(min-content, 300px) minMax(0, auto)";
15693
- return /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)(
15694
- "div",
15695
- {
15696
- css: Css.fixed.top0.bottom0.left0.right0.z(1e3).oya.bgWhite.dg.gtc(gridColumns).gtr("auto 1fr").cg3.ais.$,
15697
- ...tids,
15698
- children: [
15699
- /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(PageHeader, { ...props, ...tids.pageHeader }),
15700
- /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(LeftNav, { sectionsWithRefs, ...tids }),
15701
- /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(FormSections, { sectionsWithRefs, formState, ...tids }),
15702
- rightSideBar && /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("aside", { css: Css.gr(2).gc("4 / 5").sticky.topPx(headerHeightPx).$, children: /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(RightSidebar, { content: rightSideBar }) })
15703
- ]
15704
- }
15705
+ const gridColumns = `minMax(100px, 250px) minMax(350px, 1000px) minMax(${RIGHT_SIDEBAR_MIN_WIDTH}, 380px)`;
15706
+ return (
15707
+ // This page is `fixed` to the full screen to allow it to act as a full screen modal while content is mounted below
15708
+ // since this layout will be replacing most superdrawers/sidebars, we keep the listing mounted below to preserve the users's
15709
+ // scroll position & filters
15710
+ // Adding "align-items: start" allows "position: sticky" to work within a grid for the sidebars
15711
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("div", { css: Css.fixed.top0.bottom0.left0.right0.z(1e3).oya.bgWhite.df.jcc.aifs.$, ...tids, children: /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("div", { css: Css.w100.maxwPx(maxContentWidthPx).dg.gtc(gridColumns).gtr("auto 1fr").cg3.ais.$, children: [
15712
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(PageHeader, { ...props, ...tids.pageHeader }),
15713
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(LeftNav, { sectionsWithRefs, ...tids }),
15714
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(FormSections, { sectionsWithRefs, formState, ...tids }),
15715
+ rightSideBar && /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("aside", { css: Css.gr(2).gc("3 / 4").sticky.topPx(headerHeightPx).$, children: /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(RightSidebar, { content: rightSideBar }) })
15716
+ ] }) })
15705
15717
  );
15706
15718
  }
15707
15719
  var FormPageLayout = import_react86.default.memo(FormPageLayoutComponent);
15708
15720
  function PageHeader(props) {
15709
15721
  const { pageTitle, breadCrumb, submitAction, cancelAction, tertiaryAction, formState } = props;
15710
15722
  const tids = useTestIds(props);
15711
- return /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("header", { css: Css.gr(1).gc("2 / 5").sticky.top0.hPx(headerHeightPx).bgWhite.z5.$, ...tids, children: /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("div", { css: Css.py2.px3.df.jcsb.aic.$, children: [
15723
+ return /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("header", { css: Css.gr(1).gc("1 / 4").sticky.top0.hPx(headerHeightPx).bgWhite.z5.$, ...tids, children: /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("div", { css: Css.py2.px3.df.jcsb.aic.$, children: [
15712
15724
  /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("div", { children: [
15713
15725
  breadCrumb && /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(PageHeaderBreadcrumbs, { breadcrumb: breadCrumb }),
15714
15726
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("h1", { css: Css.xl3Sb.$, ...tids.pageTitle, children: pageTitle })
@@ -15741,14 +15753,15 @@ function PageHeader(props) {
15741
15753
  function FormSections(props) {
15742
15754
  const { sectionsWithRefs, formState } = props;
15743
15755
  const tids = useTestIds(props);
15744
- return /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("article", { css: Css.gr(2).gc("3 / 4").$, children: sectionsWithRefs.map(({ section, ref, sectionKey }, i) => (
15756
+ const bottomPaddingPx = sectionsWithRefs.length > 1 ? 200 : 0;
15757
+ return /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("article", { css: Css.gr(2).gc("2 / 3").pbPx(bottomPaddingPx).pr2.$, children: sectionsWithRefs.map(({ section, ref, sectionKey }, i) => (
15745
15758
  // Subgrid here allows for icon placement to the left of the section content
15746
15759
  /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)(
15747
15760
  "section",
15748
15761
  {
15749
15762
  id: sectionKey,
15750
15763
  ref,
15751
- css: Css.dg.gtc("50px 1fr").gtr("auto").mb3.add("scrollMarginTop", `${headerHeightPx}px`).$,
15764
+ css: Css.dg.gtc("50px 1fr").gtr("auto").mbPx(72).add("scrollMarginTop", `${headerHeightPx}px`).$,
15752
15765
  ...tids.formSection,
15753
15766
  children: [
15754
15767
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("div", { css: Css.gc(1).$, children: section.icon && /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(Icon, { icon: section.icon, inc: 3.5 }) }),
@@ -15770,7 +15783,7 @@ function LeftNav(props) {
15770
15783
  [sectionsWithRefs]
15771
15784
  );
15772
15785
  const activeSection = useActiveSection(sectionWithTitles);
15773
- return /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("aside", { css: Css.gr(2).gc("2 / 3").sticky.topPx(headerHeightPx).px3.df.fdc.gap1.$, ...tids.nav, children: sectionWithTitles.map((sectionWithRef) => /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(
15786
+ return /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("aside", { css: Css.gr(2).gc("1 / 2").sticky.topPx(headerHeightPx).px3.df.fdc.gap1.$, ...tids.nav, children: sectionWithTitles.map((sectionWithRef) => /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(
15774
15787
  SectionNavLink,
15775
15788
  {
15776
15789
  sectionWithRef,
@@ -15815,18 +15828,36 @@ function SectionNavLink(props) {
15815
15828
  }
15816
15829
  function useActiveSection(sectionsWithRefs) {
15817
15830
  const [activeSection, setActiveSection] = (0, import_react86.useState)(null);
15831
+ const debouncedIntersectionCallback = (0, import_use_debounce5.useDebouncedCallback)(
15832
+ (entries) => {
15833
+ const sectionsInView = entries.filter((entry) => entry.isIntersecting && entry.intersectionRatio > 0.2).sort((a, b) => {
15834
+ const ratioDiff = b.intersectionRatio - a.intersectionRatio;
15835
+ if (Math.abs(ratioDiff) > 0.05) return ratioDiff;
15836
+ const aTop = a.boundingClientRect.top;
15837
+ const bTop = b.boundingClientRect.top;
15838
+ return aTop - bTop;
15839
+ });
15840
+ if (sectionsInView[0]) {
15841
+ setActiveSection(sectionsInView[0].target.id);
15842
+ }
15843
+ },
15844
+ 200,
15845
+ { maxWait: 500 }
15846
+ );
15818
15847
  (0, import_react86.useEffect)(() => {
15819
15848
  if (!("IntersectionObserver" in window)) return;
15820
- const observer2 = new IntersectionObserver(
15821
- (entries) => {
15822
- const sectionsInView = entries.filter((entry) => entry.isIntersecting).sort((a, b) => b.intersectionRatio - a.intersectionRatio);
15823
- if (sectionsInView[0]) {
15824
- setActiveSection(sectionsInView[0].target.id);
15825
- }
15826
- },
15827
- // Threshold defines when the observer callback should be triggered, we may need to refine this based on more real word layouts
15828
- { rootMargin: `-${headerHeightPx}px 0px 0px 0px`, threshold: 0.4 }
15829
- );
15849
+ const observer2 = new IntersectionObserver((entries) => debouncedIntersectionCallback(entries), {
15850
+ /**
15851
+ * Creating rules to determine when a section is "in view" is a real challenge given the section sizes
15852
+ * are unknown and will likely be mixed (optimizing for large sections makes tracking small sections
15853
+ * more difficult and vice versa). This approach attempts to solve for this by creating a narrowed
15854
+ * "focus zone" trying to approximate where a users attention is likely to be. In this case, blocking
15855
+ * out the top 25% and bottom 35% of the viewport to focus on the middle-top 40%.
15856
+ */
15857
+ rootMargin: "-25% 0px -35% 0px",
15858
+ // Multiple threshold points allow for more granular detection of section visibility
15859
+ threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
15860
+ });
15830
15861
  sectionsWithRefs.forEach(({ ref }) => {
15831
15862
  if (ref.current) {
15832
15863
  observer2.observe(ref.current);
@@ -15839,7 +15870,7 @@ function useActiveSection(sectionsWithRefs) {
15839
15870
  }
15840
15871
  });
15841
15872
  };
15842
- }, [sectionsWithRefs]);
15873
+ }, [sectionsWithRefs, debouncedIntersectionCallback]);
15843
15874
  return activeSection;
15844
15875
  }
15845
15876
 
@@ -18215,6 +18246,7 @@ function useToast() {
18215
18246
  Palette,
18216
18247
  PresentationProvider,
18217
18248
  PreventBrowserScroll,
18249
+ RIGHT_SIDEBAR_MIN_WIDTH,
18218
18250
  RadioGroupField,
18219
18251
  ResponsiveGrid,
18220
18252
  ResponsiveGridItem,