@geomak/ui 6.18.0 → 6.20.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
@@ -1131,12 +1131,20 @@ interface CardFooterProps {
1131
1131
  }
1132
1132
  declare function CardFooter({ children, noDivider, className }: CardFooterProps): react_jsx_runtime.JSX.Element;
1133
1133
 
1134
+ type CardCarouselVariant = 'flat' | 'rotating';
1134
1135
  interface CardCarouselProps {
1135
- /** The slides — typically `Card`s. Each becomes a snap target. */
1136
+ /** The slides — typically `Card`s. */
1136
1137
  children: React__default.ReactNode;
1138
+ /**
1139
+ * Layout style. Default `'flat'`.
1140
+ * - `'flat'` — a horizontal scroll-snap row (trackpad / touch / wheel).
1141
+ * - `'rotating'` — a coverflow stack: the active card sits centre-stage,
1142
+ * prominent, with its neighbours scaled back and faded behind it.
1143
+ */
1144
+ variant?: CardCarouselVariant;
1137
1145
  /** Width of each slide. Number → px. Default `280`. */
1138
1146
  itemWidth?: number | string;
1139
- /** Gap between slides in px. Default `16`. */
1147
+ /** Gap between slides in px (`flat` only). Default `16`. */
1140
1148
  gap?: number;
1141
1149
  /** Show prev / next arrow buttons. Default `true`. */
1142
1150
  showArrows?: boolean;
@@ -1148,18 +1156,23 @@ interface CardCarouselProps {
1148
1156
  style?: React__default.CSSProperties;
1149
1157
  }
1150
1158
  /**
1151
- * A horizontal, scroll-snap carousel for cards. Native scrolling drives it
1152
- * (trackpad / touch / wheel), with optional arrow buttons and position dots.
1153
- * Arrows disable at the ends; the scrollbar is hidden but scrolling is intact.
1159
+ * A carousel for cards, in two flavours via `variant`:
1160
+ *
1161
+ * - **`flat`** (default) a native scroll-snap row driven by trackpad / touch /
1162
+ * wheel, with optional arrows and dots. Arrows disable at the ends.
1163
+ * - **`rotating`** — a coverflow stack. The active card is centre-stage and
1164
+ * prominent; neighbours are scaled back and faded behind it. Stepping animates
1165
+ * the stack with a Framer Motion spring (transform + opacity only, so it stays
1166
+ * smooth). Click a side card, use the arrows/dots, or press ←/→.
1154
1167
  *
1155
1168
  * @example
1156
1169
  * ```tsx
1157
- * <CardCarousel itemWidth={300} showDots>
1170
+ * <CardCarousel variant="rotating" itemWidth={320} showDots>
1158
1171
  * {vessels.map((v) => <Card key={v.id}>…</Card>)}
1159
1172
  * </CardCarousel>
1160
1173
  * ```
1161
1174
  */
1162
- declare function CardCarousel({ children, itemWidth, gap, showArrows, showDots, 'aria-label': ariaLabel, className, style, }: CardCarouselProps): react_jsx_runtime.JSX.Element;
1175
+ declare function CardCarousel(props: CardCarouselProps): react_jsx_runtime.JSX.Element;
1163
1176
 
1164
1177
  type StatisticSize = 'sm' | 'md' | 'lg';
1165
1178
  type DeltaDirection = 'up' | 'down' | 'neutral';
package/dist/index.d.ts CHANGED
@@ -1131,12 +1131,20 @@ interface CardFooterProps {
1131
1131
  }
1132
1132
  declare function CardFooter({ children, noDivider, className }: CardFooterProps): react_jsx_runtime.JSX.Element;
1133
1133
 
1134
+ type CardCarouselVariant = 'flat' | 'rotating';
1134
1135
  interface CardCarouselProps {
1135
- /** The slides — typically `Card`s. Each becomes a snap target. */
1136
+ /** The slides — typically `Card`s. */
1136
1137
  children: React__default.ReactNode;
1138
+ /**
1139
+ * Layout style. Default `'flat'`.
1140
+ * - `'flat'` — a horizontal scroll-snap row (trackpad / touch / wheel).
1141
+ * - `'rotating'` — a coverflow stack: the active card sits centre-stage,
1142
+ * prominent, with its neighbours scaled back and faded behind it.
1143
+ */
1144
+ variant?: CardCarouselVariant;
1137
1145
  /** Width of each slide. Number → px. Default `280`. */
1138
1146
  itemWidth?: number | string;
1139
- /** Gap between slides in px. Default `16`. */
1147
+ /** Gap between slides in px (`flat` only). Default `16`. */
1140
1148
  gap?: number;
1141
1149
  /** Show prev / next arrow buttons. Default `true`. */
1142
1150
  showArrows?: boolean;
@@ -1148,18 +1156,23 @@ interface CardCarouselProps {
1148
1156
  style?: React__default.CSSProperties;
1149
1157
  }
1150
1158
  /**
1151
- * A horizontal, scroll-snap carousel for cards. Native scrolling drives it
1152
- * (trackpad / touch / wheel), with optional arrow buttons and position dots.
1153
- * Arrows disable at the ends; the scrollbar is hidden but scrolling is intact.
1159
+ * A carousel for cards, in two flavours via `variant`:
1160
+ *
1161
+ * - **`flat`** (default) a native scroll-snap row driven by trackpad / touch /
1162
+ * wheel, with optional arrows and dots. Arrows disable at the ends.
1163
+ * - **`rotating`** — a coverflow stack. The active card is centre-stage and
1164
+ * prominent; neighbours are scaled back and faded behind it. Stepping animates
1165
+ * the stack with a Framer Motion spring (transform + opacity only, so it stays
1166
+ * smooth). Click a side card, use the arrows/dots, or press ←/→.
1154
1167
  *
1155
1168
  * @example
1156
1169
  * ```tsx
1157
- * <CardCarousel itemWidth={300} showDots>
1170
+ * <CardCarousel variant="rotating" itemWidth={320} showDots>
1158
1171
  * {vessels.map((v) => <Card key={v.id}>…</Card>)}
1159
1172
  * </CardCarousel>
1160
1173
  * ```
1161
1174
  */
1162
- declare function CardCarousel({ children, itemWidth, gap, showArrows, showDots, 'aria-label': ariaLabel, className, style, }: CardCarouselProps): react_jsx_runtime.JSX.Element;
1175
+ declare function CardCarousel(props: CardCarouselProps): react_jsx_runtime.JSX.Element;
1163
1176
 
1164
1177
  type StatisticSize = 'sm' | 'md' | 'lg';
1165
1178
  type DeltaDirection = 'up' | 'down' | 'neutral';
package/dist/index.js CHANGED
@@ -1738,7 +1738,25 @@ Card.Body = CardBody;
1738
1738
  Card.Footer = CardFooter;
1739
1739
  var Card_default = Card;
1740
1740
  var Arrow2 = ({ dir }) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": "true", className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: dir === "left" ? "M15 19l-7-7 7-7" : "M9 5l7 7-7 7" }) });
1741
- function CardCarousel({
1741
+ var arrowBtn = "absolute top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 items-center justify-center rounded-full border border-border bg-surface text-foreground-secondary shadow-md transition hover:text-foreground hover:bg-surface-raised disabled:opacity-0 disabled:pointer-events-none focus:outline-none focus-visible:ring-2 focus-visible:ring-accent";
1742
+ var Dots = ({ count, active, onSelect }) => /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center justify-center gap-1.5", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx(
1743
+ "button",
1744
+ {
1745
+ type: "button",
1746
+ "aria-label": `Go to slide ${i + 1}`,
1747
+ "aria-current": i === active,
1748
+ onClick: () => onSelect(i),
1749
+ className: [
1750
+ "h-1.5 rounded-full transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
1751
+ i === active ? "w-5 bg-accent" : "w-1.5 bg-border hover:bg-foreground-muted"
1752
+ ].join(" ")
1753
+ },
1754
+ i
1755
+ )) });
1756
+ function CardCarousel(props) {
1757
+ return props.variant === "rotating" ? /* @__PURE__ */ jsx(RotatingCarousel, { ...props }) : /* @__PURE__ */ jsx(FlatCarousel, { ...props });
1758
+ }
1759
+ function FlatCarousel({
1742
1760
  children,
1743
1761
  itemWidth = 280,
1744
1762
  gap = 16,
@@ -1757,7 +1775,6 @@ function CardCarousel({
1757
1775
  const update = useCallback(() => {
1758
1776
  const el = scrollerRef.current;
1759
1777
  if (!el) return;
1760
- el.clientWidth;
1761
1778
  setAtStart(el.scrollLeft <= 1);
1762
1779
  setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 1);
1763
1780
  const first = el.firstElementChild;
@@ -1775,49 +1792,109 @@ function CardCarousel({
1775
1792
  window.removeEventListener("resize", update);
1776
1793
  };
1777
1794
  }, [update]);
1778
- const scrollByDir = (dir) => {
1795
+ const slideStep = (dir) => {
1779
1796
  const el = scrollerRef.current;
1780
1797
  if (!el) return;
1781
1798
  const first = el.firstElementChild;
1782
1799
  const slideW = first ? first.getBoundingClientRect().width + gap : el.clientWidth;
1783
1800
  el.scrollBy({ left: dir * slideW, behavior: "smooth" });
1784
1801
  };
1785
- const scrollTo = (i) => {
1802
+ const scrollToIndex = (i) => {
1786
1803
  const el = scrollerRef.current;
1787
1804
  if (!el) return;
1788
1805
  const first = el.firstElementChild;
1789
1806
  const slideW = first ? first.getBoundingClientRect().width + gap : el.clientWidth;
1790
1807
  el.scrollTo({ left: i * slideW, behavior: "smooth" });
1791
1808
  };
1792
- const arrowBtn = "absolute top-1/2 -translate-y-1/2 z-10 flex h-9 w-9 items-center justify-center rounded-full border border-border bg-surface text-foreground-secondary shadow-md transition hover:text-foreground hover:bg-surface-raised disabled:opacity-0 disabled:pointer-events-none focus:outline-none focus-visible:ring-2 focus-visible:ring-accent";
1793
- return /* @__PURE__ */ jsxs("section", { "aria-label": ariaLabel, className: ["relative", className].filter(Boolean).join(" "), style, children: [
1794
- showArrows && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Previous", onClick: () => scrollByDir(-1), disabled: atStart, className: `${arrowBtn} left-1`, children: /* @__PURE__ */ jsx(Arrow2, { dir: "left" }) }),
1795
- /* @__PURE__ */ jsx(
1796
- "div",
1797
- {
1798
- ref: scrollerRef,
1799
- className: "flex overflow-x-auto snap-x snap-mandatory hidden-scrollbar scroll-smooth",
1800
- style: { gap },
1801
- children: slides.map((slide, i) => /* @__PURE__ */ jsx("div", { className: "snap-start flex-shrink-0", style: { width }, children: slide }, i))
1802
- }
1803
- ),
1804
- showArrows && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Next", onClick: () => scrollByDir(1), disabled: atEnd, className: `${arrowBtn} right-1`, children: /* @__PURE__ */ jsx(Arrow2, { dir: "right" }) }),
1805
- showDots && slides.length > 1 && /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center justify-center gap-1.5", children: slides.map((_, i) => /* @__PURE__ */ jsx(
1806
- "button",
1807
- {
1808
- type: "button",
1809
- "aria-label": `Go to slide ${i + 1}`,
1810
- "aria-current": i === active,
1811
- onClick: () => scrollTo(i),
1812
- className: [
1813
- "h-1.5 rounded-full transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
1814
- i === active ? "w-5 bg-accent" : "w-1.5 bg-border hover:bg-foreground-muted"
1815
- ].join(" ")
1816
- },
1817
- i
1818
- )) })
1809
+ return /* @__PURE__ */ jsxs("section", { "aria-label": ariaLabel, "aria-roledescription": "carousel", className: ["relative", className].filter(Boolean).join(" "), style, children: [
1810
+ showArrows && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Previous", onClick: () => slideStep(-1), disabled: atStart, className: `${arrowBtn} left-1`, children: /* @__PURE__ */ jsx(Arrow2, { dir: "left" }) }),
1811
+ /* @__PURE__ */ jsx("div", { ref: scrollerRef, className: "flex overflow-x-auto snap-x snap-mandatory hidden-scrollbar scroll-smooth", style: { gap }, children: slides.map((slide, i) => /* @__PURE__ */ jsx("div", { className: "snap-start flex-shrink-0", style: { width }, children: slide }, i)) }),
1812
+ showArrows && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Next", onClick: () => slideStep(1), disabled: atEnd, className: `${arrowBtn} right-1`, children: /* @__PURE__ */ jsx(Arrow2, { dir: "right" }) }),
1813
+ showDots && slides.length > 1 && /* @__PURE__ */ jsx(Dots, { count: slides.length, active, onSelect: scrollToIndex })
1819
1814
  ] });
1820
1815
  }
1816
+ function RotatingCarousel({
1817
+ children,
1818
+ itemWidth = 280,
1819
+ showArrows = true,
1820
+ showDots = false,
1821
+ "aria-label": ariaLabel = "Carousel",
1822
+ className = "",
1823
+ style
1824
+ }) {
1825
+ const slides = React24.Children.toArray(children);
1826
+ const count = slides.length;
1827
+ const [active, setActive] = useState(0);
1828
+ const reduced = useReducedMotion();
1829
+ const wrap = (n) => count > 0 ? (n % count + count) % count : 0;
1830
+ const idx = wrap(active);
1831
+ const step = (dir) => setActive((a) => wrap(a + dir));
1832
+ const w = typeof itemWidth === "number" ? itemWidth : parseInt(String(itemWidth), 10) || 320;
1833
+ const widthCss = typeof itemWidth === "number" ? `${itemWidth}px` : itemWidth;
1834
+ const SPACING = w * 0.6;
1835
+ const SIDE_SCALE = 0.82;
1836
+ const SIDE_OPACITY = 0.5;
1837
+ const transition = reduced ? { duration: 0 } : { type: "spring", stiffness: 280, damping: 32, mass: 0.9 };
1838
+ const onKeyDown = (e) => {
1839
+ if (e.key === "ArrowLeft") {
1840
+ e.preventDefault();
1841
+ step(-1);
1842
+ } else if (e.key === "ArrowRight") {
1843
+ e.preventDefault();
1844
+ step(1);
1845
+ }
1846
+ };
1847
+ return /* @__PURE__ */ jsxs(
1848
+ "section",
1849
+ {
1850
+ "aria-label": ariaLabel,
1851
+ "aria-roledescription": "carousel",
1852
+ className: ["relative", className].filter(Boolean).join(" "),
1853
+ style,
1854
+ onKeyDown,
1855
+ children: [
1856
+ showArrows && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Previous", onClick: () => step(-1), className: `${arrowBtn} left-1`, children: /* @__PURE__ */ jsx(Arrow2, { dir: "left" }) }),
1857
+ /* @__PURE__ */ jsxs("div", { className: "relative mx-auto overflow-hidden py-6", children: [
1858
+ count > 0 && /* @__PURE__ */ jsx("div", { className: "invisible mx-auto", style: { width: widthCss }, "aria-hidden": "true", children: slides[idx] }),
1859
+ slides.map((slide, i) => {
1860
+ let offset = wrap(i - idx);
1861
+ if (offset > count / 2) offset -= count;
1862
+ const abs = Math.abs(offset);
1863
+ if (abs > 2) return null;
1864
+ const isCenter = offset === 0;
1865
+ const isPeek = abs === 1;
1866
+ return /* @__PURE__ */ jsx(
1867
+ "div",
1868
+ {
1869
+ className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2",
1870
+ style: { width: widthCss, zIndex: 30 - abs * 10, pointerEvents: isCenter || isPeek ? "auto" : "none" },
1871
+ children: /* @__PURE__ */ jsx(
1872
+ motion.div,
1873
+ {
1874
+ initial: false,
1875
+ animate: {
1876
+ x: offset * (abs <= 1 ? SPACING : SPACING * 1.7),
1877
+ scale: isCenter ? 1 : SIDE_SCALE,
1878
+ opacity: abs <= 1 ? isCenter ? 1 : SIDE_OPACITY : 0
1879
+ },
1880
+ transition,
1881
+ className: isCenter ? void 0 : "cursor-pointer",
1882
+ onClick: isCenter ? void 0 : () => setActive(i),
1883
+ "aria-hidden": isCenter ? void 0 : true,
1884
+ children: slide
1885
+ }
1886
+ )
1887
+ },
1888
+ i
1889
+ );
1890
+ })
1891
+ ] }),
1892
+ showArrows && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Next", onClick: () => step(1), className: `${arrowBtn} right-1`, children: /* @__PURE__ */ jsx(Arrow2, { dir: "right" }) }),
1893
+ showDots && count > 1 && /* @__PURE__ */ jsx(Dots, { count, active: idx, onSelect: setActive })
1894
+ ]
1895
+ }
1896
+ );
1897
+ }
1821
1898
  var VALUE_SIZE = {
1822
1899
  sm: "text-xl",
1823
1900
  md: "text-3xl",