@dreamtree-org/twreact-ui 2.1.2 → 2.2.5

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.
Files changed (93) hide show
  1. package/README.md +47 -4
  2. package/ai-skills/dreamtree-ui.md +20 -3
  3. package/dist/_shared/{cn-DPPQUJu1.js → cn-TMJ-_w1a.js} +14 -1
  4. package/dist/_shared/useLatestRef-CdtlnPUz.js +21 -0
  5. package/dist/_shared/useReducedMotion-B_PMM5ER.js +77 -0
  6. package/dist/ai-schema.json +513 -0
  7. package/dist/charts/index.js +6 -76
  8. package/dist/components/motion/BentoExpandGrid.d.ts +10 -0
  9. package/dist/components/motion/BentoExpandGrid.d.ts.map +1 -0
  10. package/dist/components/motion/CountUp.d.ts +9 -0
  11. package/dist/components/motion/CountUp.d.ts.map +1 -0
  12. package/dist/components/motion/DeviceFrame.d.ts +8 -0
  13. package/dist/components/motion/DeviceFrame.d.ts.map +1 -0
  14. package/dist/components/motion/DeviceMorphHero.d.ts +11 -0
  15. package/dist/components/motion/DeviceMorphHero.d.ts.map +1 -0
  16. package/dist/components/motion/LogoMarquee.d.ts +9 -0
  17. package/dist/components/motion/LogoMarquee.d.ts.map +1 -0
  18. package/dist/components/motion/PhotoCollage.d.ts +9 -0
  19. package/dist/components/motion/PhotoCollage.d.ts.map +1 -0
  20. package/dist/components/motion/Reveal.d.ts +9 -0
  21. package/dist/components/motion/Reveal.d.ts.map +1 -0
  22. package/dist/components/motion/RevealGroup.d.ts +5 -0
  23. package/dist/components/motion/RevealGroup.d.ts.map +1 -0
  24. package/dist/components/motion/RotatingBadge.d.ts +9 -0
  25. package/dist/components/motion/RotatingBadge.d.ts.map +1 -0
  26. package/dist/components/motion/ScrollStage.d.ts +11 -0
  27. package/dist/components/motion/ScrollStage.d.ts.map +1 -0
  28. package/dist/components/motion/SmoothScrollProvider.d.ts +18 -0
  29. package/dist/components/motion/SmoothScrollProvider.d.ts.map +1 -0
  30. package/dist/components/motion/StackedCards.d.ts +10 -0
  31. package/dist/components/motion/StackedCards.d.ts.map +1 -0
  32. package/dist/components/motion/StatStrip.d.ts +8 -0
  33. package/dist/components/motion/StatStrip.d.ts.map +1 -0
  34. package/dist/components/motion/TestimonialSpotlight.d.ts +10 -0
  35. package/dist/components/motion/TestimonialSpotlight.d.ts.map +1 -0
  36. package/dist/components/motion/storyData.d.ts +52 -0
  37. package/dist/components/motion/storyData.d.ts.map +1 -0
  38. package/dist/esm/components/motion/BentoExpandGrid.js +144 -0
  39. package/dist/esm/components/motion/CountUp.js +97 -0
  40. package/dist/esm/components/motion/DeviceFrame.js +102 -0
  41. package/dist/esm/components/motion/DeviceMorphHero.js +228 -0
  42. package/dist/esm/components/motion/LogoMarquee.js +144 -0
  43. package/dist/esm/components/motion/PhotoCollage.js +92 -0
  44. package/dist/esm/components/motion/Reveal.js +115 -0
  45. package/dist/esm/components/motion/RevealGroup.js +43 -0
  46. package/dist/esm/components/motion/RotatingBadge.js +66 -0
  47. package/dist/esm/components/motion/ScrollStage.js +62 -0
  48. package/dist/esm/components/motion/SmoothScrollProvider.js +63 -0
  49. package/dist/esm/components/motion/StackedCards.js +166 -0
  50. package/dist/esm/components/motion/StatStrip.js +58 -0
  51. package/dist/esm/components/motion/TestimonialSpotlight.js +209 -0
  52. package/dist/esm/hooks/useScrollProgress.js +107 -0
  53. package/dist/esm/hooks/useSmoothScroll.js +33 -0
  54. package/dist/esm/motion/engine/inView.js +54 -0
  55. package/dist/esm/motion/engine/progress.js +45 -0
  56. package/dist/esm/motion/engine/scheduler.js +67 -0
  57. package/dist/esm/motion/engine/useHydrated.js +17 -0
  58. package/dist/esm/motion/engine/useIsomorphicLayoutEffect.js +7 -0
  59. package/dist/esm/motion/engine/useMergedRef.js +12 -0
  60. package/dist/esm/motion/index.js +16 -0
  61. package/dist/esm/motion/tokens.js +10 -0
  62. package/dist/hooks/useScrollProgress.d.ts +17 -0
  63. package/dist/hooks/useScrollProgress.d.ts.map +1 -0
  64. package/dist/hooks/useSmoothScroll.d.ts +11 -0
  65. package/dist/hooks/useSmoothScroll.d.ts.map +1 -0
  66. package/dist/index.css +1 -1
  67. package/dist/index.esm.css +1 -1
  68. package/dist/motion/engine/inView.d.ts +5 -0
  69. package/dist/motion/engine/inView.d.ts.map +1 -0
  70. package/dist/motion/engine/progress.d.ts +18 -0
  71. package/dist/motion/engine/progress.d.ts.map +1 -0
  72. package/dist/motion/engine/scheduler.d.ts +10 -0
  73. package/dist/motion/engine/scheduler.d.ts.map +1 -0
  74. package/dist/motion/engine/useHydrated.d.ts +3 -0
  75. package/dist/motion/engine/useHydrated.d.ts.map +1 -0
  76. package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts +4 -0
  77. package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts.map +1 -0
  78. package/dist/motion/engine/useMergedRef.d.ts +3 -0
  79. package/dist/motion/engine/useMergedRef.d.ts.map +1 -0
  80. package/dist/motion/index.d.ts +20 -0
  81. package/dist/motion/index.d.ts.map +1 -0
  82. package/dist/motion/index.js +1813 -0
  83. package/dist/motion/tokens.d.ts +6 -0
  84. package/dist/motion/tokens.d.ts.map +1 -0
  85. package/dist/rich-text/index.js +3 -18
  86. package/dist/tokens/tokens.json +21 -0
  87. package/dist/types/motion.d.ts +290 -0
  88. package/dist/types/motion.d.ts.map +1 -0
  89. package/doc/README.md +17 -1
  90. package/mcp/catalog.snapshot.json +952 -5
  91. package/motion/package.json +5 -0
  92. package/package.json +14 -3
  93. package/tokens/tokens.json +6 -0
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import React, { useId } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+
7
+ var _excluded = ["text", "speed", "size", "icon", "className", "style"];
8
+ var RADIUS = 38;
9
+ var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
10
+ /**
11
+ * A circular badge with text set around its edge, slowly turning (SVG `textPath`). The
12
+ * text is spread to fill the circle exactly. Exposed as one image named by `text`
13
+ * (override with `aria-label`); it stops turning under reduced motion.
14
+ */
15
+ var RotatingBadge = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
16
+ var text = _ref.text,
17
+ _ref$speed = _ref.speed,
18
+ speed = _ref$speed === void 0 ? 14 : _ref$speed,
19
+ _ref$size = _ref.size,
20
+ size = _ref$size === void 0 ? 120 : _ref$size,
21
+ icon = _ref.icon,
22
+ className = _ref.className,
23
+ style = _ref.style,
24
+ rest = _objectWithoutProperties(_ref, _excluded);
25
+ var pathId = "dt-badge-".concat(useId().replace(/:/g, ""));
26
+ return jsxs("div", _objectSpread2(_objectSpread2({
27
+ ref: ref,
28
+ role: "img",
29
+ "aria-label": text,
30
+ className: cn("relative inline-flex shrink-0 items-center justify-center rounded-full text-foreground", className),
31
+ style: _objectSpread2({
32
+ width: size,
33
+ height: size
34
+ }, style)
35
+ }, rest), {}, {
36
+ children: [jsxs("svg", {
37
+ viewBox: "0 0 100 100",
38
+ "aria-hidden": "true",
39
+ className: "absolute inset-0 h-full w-full animate-spin motion-reduce:animate-none",
40
+ style: {
41
+ animationDuration: "".concat(speed, "s")
42
+ },
43
+ children: [jsx("defs", {
44
+ children: jsx("path", {
45
+ id: pathId,
46
+ d: "M 50,50 m -".concat(RADIUS, ",0 a ").concat(RADIUS, ",").concat(RADIUS, " 0 1,1 ").concat(RADIUS * 2, ",0 a ").concat(RADIUS, ",").concat(RADIUS, " 0 1,1 -").concat(RADIUS * 2, ",0")
47
+ })
48
+ }), jsx("text", {
49
+ className: "fill-current text-[8.5px] font-semibold uppercase",
50
+ children: jsx("textPath", {
51
+ href: "#".concat(pathId),
52
+ textLength: CIRCUMFERENCE - 2,
53
+ lengthAdjust: "spacing",
54
+ children: text
55
+ })
56
+ })]
57
+ }), icon ? jsx("span", {
58
+ "aria-hidden": "true",
59
+ className: "relative",
60
+ children: icon
61
+ }) : null]
62
+ }));
63
+ });
64
+ RotatingBadge.displayName = "RotatingBadge";
65
+
66
+ export { RotatingBadge as default };
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import React, { useRef } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
7
+ import { useMediaQuery } from '../../hooks/useMediaQuery.js';
8
+ import { useScrollProgress } from '../../hooks/useScrollProgress.js';
9
+ import { useMergedRef } from '../../motion/engine/useMergedRef.js';
10
+ import { MOBILE_QUERY, SCENE_LENGTH_MOBILE, SCENE_LENGTH } from '../../motion/tokens.js';
11
+
12
+ var _excluded = ["length", "mobileLength", "pin", "children", "onProgress", "className", "contentClassName", "style", "as"];
13
+ /**
14
+ * A pinned scroll scene: a tall section whose content sticks to the viewport while the
15
+ * user scrolls through `length` vh. Progress (0–1) is written to `--dt-p` on the root, so
16
+ * children animate with CSS (`opacity: var(--dt-p, 1)`), and passed to `children(progress)`
17
+ * and `onProgress`. Under reduced motion, or with `pin={false}`, it renders in normal flow
18
+ * at its final state.
19
+ */
20
+ var ScrollStage = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
21
+ var _ref$length = _ref.length,
22
+ length = _ref$length === void 0 ? SCENE_LENGTH : _ref$length,
23
+ _ref$mobileLength = _ref.mobileLength,
24
+ mobileLength = _ref$mobileLength === void 0 ? SCENE_LENGTH_MOBILE : _ref$mobileLength,
25
+ _ref$pin = _ref.pin,
26
+ pin = _ref$pin === void 0 ? true : _ref$pin,
27
+ children = _ref.children,
28
+ onProgress = _ref.onProgress,
29
+ className = _ref.className,
30
+ contentClassName = _ref.contentClassName,
31
+ style = _ref.style,
32
+ _ref$as = _ref.as,
33
+ Tag = _ref$as === void 0 ? "section" : _ref$as,
34
+ rest = _objectWithoutProperties(_ref, _excluded);
35
+ var local = useRef(null);
36
+ var setRef = useMergedRef(ref, local);
37
+ var reduced = useReducedMotion();
38
+ var mobile = useMediaQuery(MOBILE_QUERY);
39
+ var pinned = pin && !reduced;
40
+ var progress = useScrollProgress(local, {
41
+ mode: "pin",
42
+ enabled: pinned,
43
+ onChange: onProgress
44
+ });
45
+ var vh = mobile ? mobileLength : length;
46
+ return jsx(Tag, _objectSpread2(_objectSpread2({
47
+ ref: setRef,
48
+ "data-dt-stage": pinned ? "pinned" : "static",
49
+ className: cn("relative", className),
50
+ style: pinned ? _objectSpread2({
51
+ height: "".concat(Math.max(vh, 100), "vh")
52
+ }, style) : style
53
+ }, rest), {}, {
54
+ children: jsx("div", {
55
+ className: cn(pinned && "sticky top-0 h-screen overflow-hidden", contentClassName),
56
+ children: typeof children === "function" ? children(progress) : children
57
+ })
58
+ }));
59
+ });
60
+ ScrollStage.displayName = "ScrollStage";
61
+
62
+ export { ScrollStage as default };
@@ -0,0 +1,63 @@
1
+ 'use client';
2
+ import { slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { useState, useEffect, useMemo } from 'react';
5
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
6
+ import { NATIVE_SCROLL, SmoothScrollContext } from '../../hooks/useSmoothScroll.js';
7
+ import { subscribe } from '../../motion/engine/scheduler.js';
8
+
9
+ /**
10
+ * Opt-in smooth (inertial) scrolling with Lenis. Lenis is an optional peer dependency
11
+ * and is never imported by this package: install it and pass the class —
12
+ * `<SmoothScrollProvider lenis={Lenis}>`. Without `lenis`, with `enabled={false}` or when
13
+ * the user prefers reduced motion, the page keeps native scrolling. Lenis runs on the same
14
+ * frame loop as the other motion components.
15
+ */
16
+ function SmoothScrollProvider(_ref) {
17
+ var LenisClass = _ref.lenis,
18
+ options = _ref.options,
19
+ _ref$enabled = _ref.enabled,
20
+ enabled = _ref$enabled === void 0 ? true : _ref$enabled,
21
+ children = _ref.children;
22
+ var reduced = useReducedMotion();
23
+ var _useState = useState(null),
24
+ _useState2 = _slicedToArray(_useState, 2),
25
+ instance = _useState2[0],
26
+ setInstance = _useState2[1];
27
+ useEffect(function () {
28
+ if (!enabled || reduced || typeof LenisClass !== "function") return undefined;
29
+ var lenis = new LenisClass(_objectSpread2({
30
+ autoRaf: false
31
+ }, options));
32
+ var unsubscribe = subscribe({
33
+ loop: true,
34
+ write: function write(_, now) {
35
+ return lenis.raf(now);
36
+ }
37
+ });
38
+ setInstance(lenis);
39
+ return function () {
40
+ unsubscribe();
41
+ lenis.destroy();
42
+ setInstance(null);
43
+ };
44
+ // `options` is read once per Lenis instance; change `enabled` or `lenis` to rebuild it.
45
+ // eslint-disable-next-line react-hooks/exhaustive-deps
46
+ }, [LenisClass, enabled, reduced]);
47
+ var value = useMemo(function () {
48
+ return instance ? {
49
+ lenis: instance,
50
+ scrollTo: function scrollTo(target) {
51
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
52
+ return instance.scrollTo(target, opts);
53
+ }
54
+ } : NATIVE_SCROLL;
55
+ }, [instance]);
56
+ return jsx(SmoothScrollContext.Provider, {
57
+ value: value,
58
+ children: children
59
+ });
60
+ }
61
+ SmoothScrollProvider.displayName = "SmoothScrollProvider";
62
+
63
+ export { SmoothScrollProvider as default };
@@ -0,0 +1,166 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2, typeof as _typeof } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import React, { useRef, useEffect } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
7
+ import { observe } from '../../motion/engine/inView.js';
8
+ import { subscribe } from '../../motion/engine/scheduler.js';
9
+ import { clamp01 } from '../../motion/engine/progress.js';
10
+ import { useMergedRef } from '../../motion/engine/useMergedRef.js';
11
+ import { SCROLL_STICKY_TOP } from '../../motion/tokens.js';
12
+
13
+ var _excluded = ["items", "stickyTop", "tilt", "scaleBack", "dimBack", "mediaSide", "renderItem", "className", "style"];
14
+ var isImage = function isImage(media) {
15
+ return media && _typeof(media) === "object" && ! /*#__PURE__*/React.isValidElement(media) && "src" in media;
16
+ };
17
+ function CardBody(_ref) {
18
+ var item = _ref.item,
19
+ flip = _ref.flip;
20
+ return jsxs("div", {
21
+ className: "grid gap-8 md:grid-cols-2 md:items-center",
22
+ children: [jsxs("div", {
23
+ className: cn("flex flex-col gap-4", flip && "md:order-2"),
24
+ children: [item.kicker ? jsx("p", {
25
+ className: "text-micro uppercase tracking-widest text-primary-600 dark:text-primary-400",
26
+ children: item.kicker
27
+ }) : null, jsx("h3", {
28
+ className: "text-section-title font-semibold tracking-tight text-foreground",
29
+ children: item.title
30
+ }), item.body ? jsx("div", {
31
+ className: "text-body text-muted",
32
+ children: item.body
33
+ }) : null, item.bullets && item.bullets.length ? jsx("ul", {
34
+ className: "grid gap-2 sm:grid-cols-2",
35
+ children: item.bullets.map(function (b, i) {
36
+ return jsxs("li", {
37
+ className: "flex items-start gap-2 text-caption text-foreground",
38
+ children: [jsx("span", {
39
+ "aria-hidden": "true",
40
+ className: "mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-primary-500"
41
+ }), b]
42
+ }, i);
43
+ })
44
+ }) : null, item.metric || item.cta ? jsxs("div", {
45
+ className: "mt-2 flex flex-wrap items-end justify-between gap-4",
46
+ children: [item.metric ? jsxs("p", {
47
+ className: "flex flex-col",
48
+ children: [jsx("span", {
49
+ className: "text-kpi font-semibold text-foreground",
50
+ children: item.metric.value
51
+ }), jsx("span", {
52
+ className: "text-caption text-muted",
53
+ children: item.metric.label
54
+ })]
55
+ }) : null, item.cta]
56
+ }) : null]
57
+ }), item.media ? jsx("div", {
58
+ className: cn("overflow-hidden rounded-card", flip && "md:order-1"),
59
+ children: isImage(item.media) ? jsx("img", {
60
+ src: item.media.src,
61
+ alt: item.media.alt,
62
+ loading: "lazy",
63
+ className: "aspect-[4/3] h-full w-full object-cover"
64
+ }) : item.media
65
+ }) : null]
66
+ });
67
+ }
68
+ /**
69
+ * Cards that stick one over another as the page scrolls (sticky stacking). Cards
70
+ * underneath shrink and dim as the next one covers them, and each has a slight
71
+ * alternating tilt so the stack looks hand-placed. Tilt is off below `md` and under
72
+ * reduced motion; the scale/dim effect is off under reduced motion.
73
+ */
74
+ var StackedCards = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
75
+ var _ref2$items = _ref2.items,
76
+ items = _ref2$items === void 0 ? [] : _ref2$items,
77
+ _ref2$stickyTop = _ref2.stickyTop,
78
+ stickyTop = _ref2$stickyTop === void 0 ? SCROLL_STICKY_TOP : _ref2$stickyTop,
79
+ _ref2$tilt = _ref2.tilt,
80
+ tilt = _ref2$tilt === void 0 ? 1 : _ref2$tilt,
81
+ _ref2$scaleBack = _ref2.scaleBack,
82
+ scaleBack = _ref2$scaleBack === void 0 ? 0.94 : _ref2$scaleBack,
83
+ _ref2$dimBack = _ref2.dimBack,
84
+ dimBack = _ref2$dimBack === void 0 ? 0.15 : _ref2$dimBack,
85
+ _ref2$mediaSide = _ref2.mediaSide,
86
+ mediaSide = _ref2$mediaSide === void 0 ? "alternate" : _ref2$mediaSide,
87
+ renderItem = _ref2.renderItem,
88
+ className = _ref2.className,
89
+ style = _ref2.style,
90
+ rest = _objectWithoutProperties(_ref2, _excluded);
91
+ var local = useRef(null);
92
+ var setRef = useMergedRef(ref, local);
93
+ var reduced = useReducedMotion();
94
+ useEffect(function () {
95
+ var list = local.current;
96
+ if (!list || reduced || scaleBack >= 1 && dimBack <= 0) return undefined;
97
+ var slots = Array.from(list.children);
98
+ var cards = slots.map(function (li) {
99
+ return li.firstElementChild;
100
+ });
101
+ var measure = function measure() {
102
+ return slots.map(function (li) {
103
+ return li.getBoundingClientRect();
104
+ });
105
+ };
106
+ var write = function write(rects) {
107
+ cards.forEach(function (card, i) {
108
+ var next = rects[i + 1];
109
+ // 0 while the next card is a full card-height away, 1 once it has reached the sticky line.
110
+ var cover = next ? clamp01(1 - (next.top - stickyTop) / Math.max(rects[i].height, 1)) : 0;
111
+ card.style.setProperty("--dt-scale", String(1 - (1 - scaleBack) * cover));
112
+ card.style.setProperty("--dt-dim", String(1 - dimBack * cover));
113
+ });
114
+ };
115
+ var unsubscribe = null;
116
+ var stop = observe(list, function (inView) {
117
+ if (inView && !unsubscribe) unsubscribe = subscribe({
118
+ measure: measure,
119
+ write: write
120
+ });else if (!inView && unsubscribe) {
121
+ unsubscribe();
122
+ unsubscribe = null;
123
+ }
124
+ });
125
+ return function () {
126
+ stop();
127
+ if (unsubscribe) unsubscribe();
128
+ cards.forEach(function (card) {
129
+ card.style.removeProperty("--dt-scale");
130
+ card.style.removeProperty("--dt-dim");
131
+ });
132
+ };
133
+ }, [items, stickyTop, scaleBack, dimBack, reduced]);
134
+ return jsx("ol", _objectSpread2(_objectSpread2({
135
+ ref: setRef,
136
+ className: cn("relative flex flex-col gap-8", className),
137
+ style: style
138
+ }, rest), {}, {
139
+ children: items.map(function (item, i) {
140
+ var _item$id;
141
+ var deg = tilt ? (i % 2 === 0 ? -1 : 1) * tilt * (1 + i % 3 * 0.5) : 0;
142
+ var flip = mediaSide === "alternate" && i % 2 === 1;
143
+ return jsx("li", {
144
+ className: "sticky",
145
+ style: {
146
+ top: "".concat(stickyTop, "px")
147
+ },
148
+ children: jsx("article", {
149
+ className: cn("origin-top rounded-card border border-line bg-surface p-6 shadow-lg dark:bg-background md:p-10",
150
+ // motion-safe: none of this applies when the OS asks for reduced motion (even before hydration).
151
+ "motion-safe:[filter:brightness(var(--dt-dim,1))] motion-safe:[transform:scale(var(--dt-scale,1))]", "motion-safe:md:[transform:rotate(var(--dt-tilt,0deg))_scale(var(--dt-scale,1))]"),
152
+ style: deg ? {
153
+ "--dt-tilt": "".concat(deg, "deg")
154
+ } : undefined,
155
+ children: renderItem ? renderItem(item, i) : jsx(CardBody, {
156
+ item: item,
157
+ flip: flip
158
+ })
159
+ })
160
+ }, (_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : i);
161
+ })
162
+ }));
163
+ });
164
+ StackedCards.displayName = "StackedCards";
165
+
166
+ export { StackedCards as default };
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import React from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import CountUp from './CountUp.js';
7
+
8
+ var _excluded = ["items", "variant", "duration", "locale", "className"];
9
+ var COLS = {
10
+ 1: "sm:grid-cols-1",
11
+ 2: "sm:grid-cols-2",
12
+ 3: "sm:grid-cols-3",
13
+ 4: "sm:grid-cols-2 lg:grid-cols-4"
14
+ };
15
+ /**
16
+ * A row of headline numbers that count up on view — as bordered cards or one band with
17
+ * dividers. A description list: each label is a `<dt>`, each number its `<dd>`.
18
+ */
19
+ var StatStrip = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
20
+ var _ref$items = _ref.items,
21
+ items = _ref$items === void 0 ? [] : _ref$items,
22
+ _ref$variant = _ref.variant,
23
+ variant = _ref$variant === void 0 ? "cards" : _ref$variant,
24
+ duration = _ref.duration,
25
+ locale = _ref.locale,
26
+ className = _ref.className,
27
+ rest = _objectWithoutProperties(_ref, _excluded);
28
+ var cards = variant !== "band";
29
+ var cols = COLS[Math.min(items.length, 4)] || COLS[4];
30
+ return jsx("dl", _objectSpread2(_objectSpread2({
31
+ ref: ref,
32
+ className: cn("grid grid-cols-2", cols, cards ? "gap-4" : "gap-y-6 rounded-card border border-line bg-surface py-6 dark:bg-background sm:divide-x sm:divide-line", className)
33
+ }, rest), {}, {
34
+ children: items.map(function (item, i) {
35
+ return jsxs("div", {
36
+ className: cn("flex flex-col-reverse gap-1", cards ? "rounded-card border border-line bg-surface p-6 shadow-xs dark:bg-background" : "px-6 text-center"),
37
+ children: [jsx("dt", {
38
+ className: "text-caption text-muted",
39
+ children: item.label
40
+ }), jsx("dd", {
41
+ className: "text-kpi font-semibold tracking-tight text-foreground",
42
+ children: jsx(CountUp, {
43
+ value: item.value,
44
+ prefix: item.prefix,
45
+ suffix: item.suffix,
46
+ decimals: item.decimals,
47
+ format: item.format,
48
+ duration: duration,
49
+ locale: locale
50
+ })
51
+ })]
52
+ }, i);
53
+ })
54
+ }));
55
+ });
56
+ StatStrip.displayName = "StatStrip";
57
+
58
+ export { StatStrip as default };
@@ -0,0 +1,209 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2, typeof as _typeof } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import React, { useRef, useId, useState, useEffect } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
7
+ import { observe } from '../../motion/engine/inView.js';
8
+ import { useMergedRef } from '../../motion/engine/useMergedRef.js';
9
+
10
+ var _excluded = ["items", "autoplay", "grid", "label", "className"];
11
+ var isImage = function isImage(logo) {
12
+ return logo && _typeof(logo) === "object" && ! /*#__PURE__*/React.isValidElement(logo) && "src" in logo;
13
+ };
14
+ function Logo(_ref) {
15
+ var logo = _ref.logo,
16
+ decorative = _ref.decorative;
17
+ if (!logo) return null;
18
+ if (isImage(logo)) {
19
+ return jsx("img", {
20
+ src: logo.src,
21
+ alt: decorative ? "" : logo.alt,
22
+ className: "max-h-10 max-w-[70%] object-contain"
23
+ });
24
+ }
25
+ return decorative ? jsx("span", {
26
+ "aria-hidden": "true",
27
+ children: logo
28
+ }) : logo;
29
+ }
30
+ /**
31
+ * Client quotes with a 3×3 tile grid of logos: the active client's logo sits in the centre
32
+ * tile and the quote beside it. Dots are a tab list (arrow keys, Home/End). Autoplay pauses
33
+ * on hover, while focus is inside, while off screen and under reduced motion, and has a
34
+ * visible pause button (WCAG 2.2.2).
35
+ */
36
+ var TestimonialSpotlight = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
37
+ var _ref2$items = _ref2.items,
38
+ items = _ref2$items === void 0 ? [] : _ref2$items,
39
+ _ref2$autoplay = _ref2.autoplay,
40
+ autoplay = _ref2$autoplay === void 0 ? 6000 : _ref2$autoplay,
41
+ _ref2$grid = _ref2.grid,
42
+ grid = _ref2$grid === void 0 ? true : _ref2$grid,
43
+ _ref2$label = _ref2.label,
44
+ label = _ref2$label === void 0 ? "Testimonials" : _ref2$label,
45
+ className = _ref2.className,
46
+ rest = _objectWithoutProperties(_ref2, _excluded);
47
+ var local = useRef(null);
48
+ var setRef = useMergedRef(ref, local);
49
+ var reduced = useReducedMotion();
50
+ var id = useId();
51
+ var _useState = useState(0),
52
+ _useState2 = _slicedToArray(_useState, 2),
53
+ index = _useState2[0],
54
+ setIndex = _useState2[1];
55
+ var _useState3 = useState(false),
56
+ _useState4 = _slicedToArray(_useState3, 2),
57
+ paused = _useState4[0],
58
+ setPaused = _useState4[1];
59
+ var _useState5 = useState(false),
60
+ _useState6 = _slicedToArray(_useState5, 2),
61
+ hovered = _useState6[0],
62
+ setHovered = _useState6[1];
63
+ var _useState7 = useState(false),
64
+ _useState8 = _slicedToArray(_useState7, 2),
65
+ focused = _useState8[0],
66
+ setFocused = _useState8[1];
67
+ var _useState9 = useState(false),
68
+ _useState0 = _slicedToArray(_useState9, 2),
69
+ inView = _useState0[0],
70
+ setInView = _useState0[1];
71
+ var count = items.length;
72
+ var playing = autoplay > 0 && count > 1 && !reduced && !paused && !hovered && !focused && inView;
73
+ useEffect(function () {
74
+ return observe(local.current, setInView);
75
+ }, []);
76
+ useEffect(function () {
77
+ if (!playing) return undefined;
78
+ var timer = setTimeout(function () {
79
+ return setIndex(function (i) {
80
+ return (i + 1) % count;
81
+ });
82
+ }, autoplay);
83
+ return function () {
84
+ return clearTimeout(timer);
85
+ };
86
+ }, [playing, index, autoplay, count]);
87
+ if (!count) return null;
88
+ var current = items[Math.min(index, count - 1)];
89
+ var tabs = items.map(function (_, i) {
90
+ return "".concat(id, "-tab-").concat(i);
91
+ });
92
+ var onTabKey = function onTabKey(event) {
93
+ var map = {
94
+ ArrowRight: index + 1,
95
+ ArrowDown: index + 1,
96
+ ArrowLeft: index - 1,
97
+ ArrowUp: index - 1,
98
+ Home: 0,
99
+ End: count - 1
100
+ };
101
+ if (!(event.key in map)) return;
102
+ event.preventDefault();
103
+ var next = (map[event.key] + count) % count;
104
+ setIndex(next);
105
+ var el = document.getElementById(tabs[next]);
106
+ if (el) el.focus();
107
+ };
108
+ return jsxs("section", _objectSpread2(_objectSpread2({
109
+ ref: setRef,
110
+ "aria-roledescription": "carousel",
111
+ "aria-label": label,
112
+ className: cn("grid items-center gap-10 md:grid-cols-2", className),
113
+ onMouseEnter: function onMouseEnter() {
114
+ return setHovered(true);
115
+ },
116
+ onMouseLeave: function onMouseLeave() {
117
+ return setHovered(false);
118
+ },
119
+ onFocus: function onFocus() {
120
+ return setFocused(true);
121
+ },
122
+ onBlur: function onBlur(e) {
123
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocused(false);
124
+ }
125
+ }, rest), {}, {
126
+ children: [grid ? jsx("div", {
127
+ className: "grid aspect-square grid-cols-3 gap-3",
128
+ children: Array.from({
129
+ length: 9
130
+ }, function (_, tile) {
131
+ var centre = tile === 4;
132
+ var other = items[(index + tile + 1) % count];
133
+ return jsx("div", {
134
+ className: cn("flex items-center justify-center rounded-card border border-line", centre ? "bg-surface shadow-md dark:bg-background" : "bg-background"),
135
+ children: centre ? jsx("span", {
136
+ className: "flex w-full justify-center animate-fade-in motion-reduce:animate-none",
137
+ children: jsx(Logo, {
138
+ logo: current.logo
139
+ })
140
+ }, index) : tile % 2 === 0 ? jsx("span", {
141
+ className: "flex w-full justify-center opacity-30 grayscale",
142
+ "aria-hidden": "true",
143
+ children: jsx(Logo, {
144
+ logo: other.logo,
145
+ decorative: true
146
+ })
147
+ }) : null
148
+ }, tile);
149
+ })
150
+ }) : null, jsxs("div", {
151
+ className: "flex flex-col gap-6",
152
+ children: [jsxs("figure", {
153
+ id: "".concat(id, "-panel"),
154
+ role: "tabpanel",
155
+ "aria-labelledby": tabs[index],
156
+ "aria-live": playing ? "off" : "polite",
157
+ className: "flex flex-col gap-4 animate-fade-in motion-reduce:animate-none",
158
+ children: [jsx("blockquote", {
159
+ className: "text-section-title font-medium leading-snug text-foreground",
160
+ children: current.quote
161
+ }), jsxs("figcaption", {
162
+ className: "text-body text-muted",
163
+ children: [jsx("span", {
164
+ className: "font-semibold text-foreground",
165
+ children: current.name
166
+ }), current.role ? jsxs("span", {
167
+ children: [", ", current.role]
168
+ }) : null]
169
+ })]
170
+ }, index), count > 1 ? jsxs("div", {
171
+ className: "flex items-center gap-4",
172
+ children: [jsx("div", {
173
+ role: "tablist",
174
+ "aria-label": "".concat(label, ": choose a quote"),
175
+ className: "flex gap-2",
176
+ onKeyDown: onTabKey,
177
+ children: items.map(function (item, i) {
178
+ var _item$id;
179
+ return jsx("button", {
180
+ id: tabs[i],
181
+ type: "button",
182
+ role: "tab",
183
+ "aria-selected": i === index,
184
+ "aria-controls": "".concat(id, "-panel"),
185
+ tabIndex: i === index ? 0 : -1,
186
+ "aria-label": "Quote ".concat(i + 1, " of ").concat(count),
187
+ onClick: function onClick() {
188
+ return setIndex(i);
189
+ },
190
+ className: cn("h-2.5 rounded-pill transition-[width,background-color] duration-base motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2", i === index ? "w-6 bg-primary-600" : "w-2.5 bg-line hover:bg-muted/60")
191
+ }, (_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : i);
192
+ })
193
+ }), autoplay > 0 && !reduced ? jsx("button", {
194
+ type: "button",
195
+ onClick: function onClick() {
196
+ return setPaused(function (p) {
197
+ return !p;
198
+ });
199
+ },
200
+ className: "rounded-pill px-2 py-1 text-caption text-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
201
+ children: paused ? "Play" : "Pause"
202
+ }) : null]
203
+ }) : null]
204
+ })]
205
+ }));
206
+ });
207
+ TestimonialSpotlight.displayName = "TestimonialSpotlight";
208
+
209
+ export { TestimonialSpotlight as default };