@geomak/ui 7.3.4 → 7.4.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
@@ -4577,6 +4577,10 @@ function List2({
4577
4577
  );
4578
4578
  }) });
4579
4579
  }
4580
+ var rectOf = (el) => {
4581
+ const r = el.getBoundingClientRect();
4582
+ return { left: r.left, top: r.top, width: r.width, height: r.height };
4583
+ };
4580
4584
  var TOGGLE_POSITION_CLASS = {
4581
4585
  "top-left": "top-2 left-2",
4582
4586
  "top-right": "top-2 right-2",
@@ -4595,17 +4599,56 @@ function ScalableContainer({
4595
4599
  expandIcon,
4596
4600
  collapseIcon,
4597
4601
  togglePosition = "top-right",
4602
+ expandContainerRef,
4598
4603
  className = ""
4599
4604
  }) {
4600
4605
  const containerRef = React30.useRef(null);
4601
4606
  const [internalScaled, setInternalScaled] = React30.useState(false);
4602
4607
  const isScaled = expanded ?? internalScaled;
4603
4608
  const reduced = framerMotion.useReducedMotion();
4609
+ const usePortal = expandContainerRef != null;
4610
+ const [overlay, setOverlay] = React30.useState("closed");
4611
+ const [fromRect, setFromRect] = React30.useState(null);
4612
+ const [targetRect, setTargetRect] = React30.useState(null);
4613
+ const prevScaled = React30.useRef(isScaled);
4614
+ React30.useEffect(() => {
4615
+ if (!usePortal || isScaled === prevScaled.current) return;
4616
+ prevScaled.current = isScaled;
4617
+ if (isScaled) {
4618
+ const src = containerRef.current ? rectOf(containerRef.current) : null;
4619
+ const tgt = expandContainerRef.current ? rectOf(expandContainerRef.current) : null;
4620
+ if (src && tgt) {
4621
+ setFromRect(src);
4622
+ setTargetRect(tgt);
4623
+ setOverlay("open");
4624
+ }
4625
+ } else if (containerRef.current) {
4626
+ setTargetRect(rectOf(containerRef.current));
4627
+ setOverlay("closing");
4628
+ }
4629
+ }, [isScaled, usePortal, expandContainerRef]);
4630
+ React30.useEffect(() => {
4631
+ if (overlay !== "closing") return;
4632
+ const t = window.setTimeout(() => setOverlay("closed"), reduced ? 0 : 360);
4633
+ return () => window.clearTimeout(t);
4634
+ }, [overlay, reduced]);
4635
+ React30.useEffect(() => {
4636
+ if (overlay !== "open" || !expandContainerRef?.current) return;
4637
+ const update = () => {
4638
+ if (expandContainerRef.current) setTargetRect(rectOf(expandContainerRef.current));
4639
+ };
4640
+ window.addEventListener("resize", update);
4641
+ window.addEventListener("scroll", update, true);
4642
+ return () => {
4643
+ window.removeEventListener("resize", update);
4644
+ window.removeEventListener("scroll", update, true);
4645
+ };
4646
+ }, [overlay, expandContainerRef]);
4604
4647
  const onToggle = () => {
4605
4648
  const next = !isScaled;
4606
4649
  if (expanded === void 0) setInternalScaled(next);
4607
4650
  onExpandedChange?.(next);
4608
- if (next) {
4651
+ if (next && !usePortal) {
4609
4652
  window.setTimeout(
4610
4653
  () => containerRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" }),
4611
4654
  reduced ? 0 : 340
@@ -4613,50 +4656,79 @@ function ScalableContainer({
4613
4656
  }
4614
4657
  };
4615
4658
  const wrapperClass = isScaled ? assignClassOnClick : void 0;
4616
- return /* @__PURE__ */ jsxRuntime.jsxs(
4617
- framerMotion.motion.div,
4659
+ const overlayActive = usePortal && overlay !== "closed";
4660
+ const toggleButton = (scaled) => /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { placement: "bottom", title: scaled ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
4661
+ "button",
4618
4662
  {
4619
- ref: containerRef,
4620
- animate: {
4621
- width: isScaled ? expandedWidth : width,
4622
- height: isScaled ? expandedHeight : height
4623
- },
4624
- transition: reduced ? { duration: 0 } : {
4625
- width: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] },
4626
- height: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] }
4627
- },
4628
- className: cx(
4629
- "relative rounded-lg overflow-hidden",
4630
- // OS-window aesthetic: subtle elevation at rest, lifted shadow
4631
- // when expanded. No background colour change.
4632
- isScaled ? "shadow-2xl" : "shadow-md",
4633
- "transition-shadow duration-300",
4634
- className
4635
- ),
4636
- children: [
4637
- /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { placement: "bottom", title: isScaled ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
4638
- "button",
4639
- {
4640
- type: "button",
4641
- onClick: onToggle,
4642
- "aria-label": isScaled ? "Collapse container" : "Expand container",
4643
- "aria-expanded": isScaled,
4644
- className: [
4645
- "absolute z-10",
4646
- TOGGLE_POSITION_CLASS[togglePosition],
4647
- "w-7 h-7 inline-flex items-center justify-center",
4648
- "rounded-md bg-surface/80 backdrop-blur-sm border border-border",
4649
- "text-foreground-secondary hover:text-foreground hover:bg-surface",
4650
- "shadow-sm transition-colors duration-150",
4651
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
4652
- ].join(" "),
4653
- children: isScaled ? collapseIcon ?? /* @__PURE__ */ jsxRuntime.jsx(CollapseIcon, {}) : expandIcon ?? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, {})
4654
- }
4655
- ) }),
4656
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClass, children })
4657
- ]
4663
+ type: "button",
4664
+ onClick: onToggle,
4665
+ "aria-label": scaled ? "Collapse container" : "Expand container",
4666
+ "aria-expanded": scaled,
4667
+ className: [
4668
+ "absolute z-10",
4669
+ TOGGLE_POSITION_CLASS[togglePosition],
4670
+ "w-7 h-7 inline-flex items-center justify-center",
4671
+ "rounded-md bg-surface/80 backdrop-blur-sm border border-border",
4672
+ "text-foreground-secondary hover:text-foreground hover:bg-surface",
4673
+ "shadow-sm transition-colors duration-150",
4674
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
4675
+ ].join(" "),
4676
+ children: scaled ? collapseIcon ?? /* @__PURE__ */ jsxRuntime.jsx(CollapseIcon, {}) : expandIcon ?? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, {})
4658
4677
  }
4659
- );
4678
+ ) });
4679
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4680
+ /* @__PURE__ */ jsxRuntime.jsxs(
4681
+ framerMotion.motion.div,
4682
+ {
4683
+ ref: containerRef,
4684
+ animate: {
4685
+ // Breakout mode never grows in place — the in-flow box stays
4686
+ // at its resting size and acts as the collapse target.
4687
+ width: isScaled && !usePortal ? expandedWidth : width,
4688
+ height: isScaled && !usePortal ? expandedHeight : height
4689
+ },
4690
+ transition: reduced ? { duration: 0 } : {
4691
+ width: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] },
4692
+ height: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] }
4693
+ },
4694
+ className: cx(
4695
+ "relative rounded-lg overflow-hidden",
4696
+ // OS-window aesthetic: subtle elevation at rest, lifted shadow
4697
+ // when expanded. No background colour change.
4698
+ isScaled && !usePortal ? "shadow-2xl" : "shadow-md",
4699
+ "transition-shadow duration-300",
4700
+ className
4701
+ ),
4702
+ children: [
4703
+ !overlayActive && toggleButton(isScaled),
4704
+ !overlayActive && /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClass, children })
4705
+ ]
4706
+ }
4707
+ ),
4708
+ overlayActive && fromRect && targetRect && reactDom.createPortal(
4709
+ /* @__PURE__ */ jsxRuntime.jsxs(
4710
+ framerMotion.motion.div,
4711
+ {
4712
+ initial: { ...fromRect },
4713
+ animate: { ...targetRect },
4714
+ transition: reduced ? { duration: 0 } : { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] },
4715
+ onAnimationComplete: () => {
4716
+ if (overlay === "closing") setOverlay("closed");
4717
+ },
4718
+ style: { position: "fixed" },
4719
+ className: cx(
4720
+ "z-dropdown rounded-lg overflow-hidden bg-surface shadow-2xl",
4721
+ className
4722
+ ),
4723
+ children: [
4724
+ toggleButton(isScaled),
4725
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("h-full w-full", wrapperClass), children })
4726
+ ]
4727
+ }
4728
+ ),
4729
+ document.body
4730
+ )
4731
+ ] });
4660
4732
  }
4661
4733
  function CollapseIcon() {
4662
4734
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "w-4 h-4", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 9L4 4M9 9V4M9 9H4M15 9L20 4M15 9V4M15 9H20M9 15L4 20M9 15V20M9 15H4M15 15L20 20M15 15V20M15 15H20" }) });