@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.cjs +108 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -7
- package/dist/index.d.ts +20 -7
- package/dist/index.js +108 -31
- package/dist/index.js.map +1 -1
- package/dist/styles.css +4 -0
- package/package.json +1 -1
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.
|
|
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
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
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={
|
|
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(
|
|
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.
|
|
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
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
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={
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
/* @__PURE__ */ jsx(
|
|
1796
|
-
|
|
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",
|