@salesmind-ai/design-system 0.3.3 → 0.3.4
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/{SectionShell-BfBw5q0Y.d.cts → SectionShell-GlglHCzz.d.cts} +1 -0
- package/dist/{SectionShell-BfBw5q0Y.d.ts → SectionShell-GlglHCzz.d.ts} +1 -0
- package/dist/StatsSection-B8iD9L-o.d.ts +68 -0
- package/dist/StatsSection-wgd8Vge1.d.cts +68 -0
- package/dist/admin/index.cjs +2928 -68
- package/dist/admin/index.cjs.map +1 -1
- package/dist/admin/index.js +2915 -5
- package/dist/admin/index.js.map +1 -1
- package/dist/blog/index.cjs +1064 -53
- package/dist/blog/index.cjs.map +1 -1
- package/dist/blog/index.d.cts +1 -1
- package/dist/blog/index.d.ts +1 -1
- package/dist/blog/index.js +1054 -8
- package/dist/blog/index.js.map +1 -1
- package/dist/charts/index.cjs +2694 -46
- package/dist/charts/index.cjs.map +1 -1
- package/dist/charts/index.js +2680 -3
- package/dist/charts/index.js.map +1 -1
- package/dist/core/index.cjs +4333 -807
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +4130 -14
- package/dist/core/index.js.map +1 -1
- package/dist/i18n/index.cjs +558 -86
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.js +544 -1
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.cjs +17140 -1432
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +24 -13
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16785 -31
- package/dist/index.js.map +1 -1
- package/dist/marketing/index.cjs +3072 -142
- package/dist/marketing/index.cjs.map +1 -1
- package/dist/marketing/index.d.cts +1 -1
- package/dist/marketing/index.d.ts +1 -1
- package/dist/marketing/index.js +3042 -11
- package/dist/marketing/index.js.map +1 -1
- package/dist/motion/index.cjs +1222 -26
- package/dist/motion/index.cjs.map +1 -1
- package/dist/motion/index.js +1215 -2
- package/dist/motion/index.js.map +1 -1
- package/dist/nav/index.cjs +1518 -101
- package/dist/nav/index.cjs.map +1 -1
- package/dist/nav/index.css +24 -13
- package/dist/nav/index.css.map +1 -1
- package/dist/nav/index.js +1498 -4
- package/dist/nav/index.js.map +1 -1
- package/dist/report/index.cjs +2403 -171
- package/dist/report/index.cjs.map +1 -1
- package/dist/report/index.js +2363 -3
- package/dist/report/index.js.map +1 -1
- package/dist/sections/index.cjs +382 -28
- package/dist/sections/index.cjs.map +1 -1
- package/dist/sections/index.d.cts +15 -69
- package/dist/sections/index.d.ts +15 -69
- package/dist/sections/index.js +376 -4
- package/dist/sections/index.js.map +1 -1
- package/dist/social-proof/index.cjs +1250 -53
- package/dist/social-proof/index.cjs.map +1 -1
- package/dist/social-proof/index.d.cts +1 -1
- package/dist/social-proof/index.d.ts +1 -1
- package/dist/social-proof/index.js +1235 -6
- package/dist/social-proof/index.js.map +1 -1
- package/dist/theme/index.cjs +565 -38
- package/dist/theme/index.cjs.map +1 -1
- package/dist/theme/index.js +555 -2
- package/dist/theme/index.js.map +1 -1
- package/dist/web/client/index.cjs +491 -38
- package/dist/web/client/index.cjs.map +1 -1
- package/dist/web/client/index.js +483 -4
- package/dist/web/client/index.js.map +1 -1
- package/dist/web/index.cjs +1346 -158
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.js +1305 -9
- package/dist/web/index.js.map +1 -1
- package/dist/web/server/index.cjs +563 -26
- package/dist/web/server/index.cjs.map +1 -1
- package/dist/web/server/index.js +560 -1
- package/dist/web/server/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2GARWEJK.js +0 -17
- package/dist/chunk-2GARWEJK.js.map +0 -1
- package/dist/chunk-3NKRFUAR.js +0 -37
- package/dist/chunk-3NKRFUAR.js.map +0 -1
- package/dist/chunk-3TGSIILM.cjs +0 -201
- package/dist/chunk-3TGSIILM.cjs.map +0 -1
- package/dist/chunk-4GM5BGBN.cjs +0 -801
- package/dist/chunk-4GM5BGBN.cjs.map +0 -1
- package/dist/chunk-5LGDEZWY.cjs +0 -2434
- package/dist/chunk-5LGDEZWY.cjs.map +0 -1
- package/dist/chunk-6H4DSTXR.js +0 -786
- package/dist/chunk-6H4DSTXR.js.map +0 -1
- package/dist/chunk-6UNG76Y2.js +0 -153
- package/dist/chunk-6UNG76Y2.js.map +0 -1
- package/dist/chunk-7PX2AZ6Y.js +0 -39
- package/dist/chunk-7PX2AZ6Y.js.map +0 -1
- package/dist/chunk-B6AVAX4F.js +0 -1415
- package/dist/chunk-B6AVAX4F.js.map +0 -1
- package/dist/chunk-BILT5KD3.js +0 -264
- package/dist/chunk-BILT5KD3.js.map +0 -1
- package/dist/chunk-C2BCDNAV.js +0 -24
- package/dist/chunk-C2BCDNAV.js.map +0 -1
- package/dist/chunk-CH42VPWE.cjs +0 -421
- package/dist/chunk-CH42VPWE.cjs.map +0 -1
- package/dist/chunk-CJ2MKVAF.cjs +0 -46
- package/dist/chunk-CJ2MKVAF.cjs.map +0 -1
- package/dist/chunk-DP74LUXG.cjs +0 -98
- package/dist/chunk-DP74LUXG.cjs.map +0 -1
- package/dist/chunk-E7D6EKJ4.cjs +0 -44
- package/dist/chunk-E7D6EKJ4.cjs.map +0 -1
- package/dist/chunk-ECXBTUH6.cjs +0 -584
- package/dist/chunk-ECXBTUH6.cjs.map +0 -1
- package/dist/chunk-EFRAP5ES.js +0 -157
- package/dist/chunk-EFRAP5ES.js.map +0 -1
- package/dist/chunk-F6YYWMME.js +0 -485
- package/dist/chunk-F6YYWMME.js.map +0 -1
- package/dist/chunk-FAFAP4L5.js +0 -183
- package/dist/chunk-FAFAP4L5.js.map +0 -1
- package/dist/chunk-GUZIMHWS.js +0 -1608
- package/dist/chunk-GUZIMHWS.js.map +0 -1
- package/dist/chunk-H2Y6BSTL.cjs +0 -69
- package/dist/chunk-H2Y6BSTL.cjs.map +0 -1
- package/dist/chunk-HN4PHABT.js +0 -126
- package/dist/chunk-HN4PHABT.js.map +0 -1
- package/dist/chunk-HRENHNDJ.js +0 -211
- package/dist/chunk-HRENHNDJ.js.map +0 -1
- package/dist/chunk-I75BFEYT.cjs +0 -2561
- package/dist/chunk-I75BFEYT.cjs.map +0 -1
- package/dist/chunk-IFRATNLU.js +0 -562
- package/dist/chunk-IFRATNLU.js.map +0 -1
- package/dist/chunk-IYPXJ6YC.cjs +0 -69
- package/dist/chunk-IYPXJ6YC.cjs.map +0 -1
- package/dist/chunk-JPJN4YBC.js +0 -409
- package/dist/chunk-JPJN4YBC.js.map +0 -1
- package/dist/chunk-KBA2LFBG.js +0 -62
- package/dist/chunk-KBA2LFBG.js.map +0 -1
- package/dist/chunk-KCKUSU2M.cjs +0 -166
- package/dist/chunk-KCKUSU2M.cjs.map +0 -1
- package/dist/chunk-KJ2OXQF4.js +0 -287
- package/dist/chunk-KJ2OXQF4.js.map +0 -1
- package/dist/chunk-KNQEIU7O.cjs +0 -1202
- package/dist/chunk-KNQEIU7O.cjs.map +0 -1
- package/dist/chunk-KVGSVGRK.cjs +0 -569
- package/dist/chunk-KVGSVGRK.cjs.map +0 -1
- package/dist/chunk-L352JRV6.cjs +0 -105
- package/dist/chunk-L352JRV6.cjs.map +0 -1
- package/dist/chunk-LJADZITX.cjs +0 -298
- package/dist/chunk-LJADZITX.cjs.map +0 -1
- package/dist/chunk-LMJPWXTZ.cjs +0 -194
- package/dist/chunk-LMJPWXTZ.cjs.map +0 -1
- package/dist/chunk-LOWEAQST.js +0 -701
- package/dist/chunk-LOWEAQST.js.map +0 -1
- package/dist/chunk-MDB2WCRQ.cjs +0 -137
- package/dist/chunk-MDB2WCRQ.cjs.map +0 -1
- package/dist/chunk-MQDEE7HC.cjs +0 -283
- package/dist/chunk-MQDEE7HC.cjs.map +0 -1
- package/dist/chunk-MQRB634A.cjs +0 -34
- package/dist/chunk-MQRB634A.cjs.map +0 -1
- package/dist/chunk-MTI27RDV.js +0 -185
- package/dist/chunk-MTI27RDV.js.map +0 -1
- package/dist/chunk-MU6GW5ZV.js +0 -2317
- package/dist/chunk-MU6GW5ZV.js.map +0 -1
- package/dist/chunk-NN3TUHIH.js +0 -28
- package/dist/chunk-NN3TUHIH.js.map +0 -1
- package/dist/chunk-NT4LBP7D.cjs +0 -111
- package/dist/chunk-NT4LBP7D.cjs.map +0 -1
- package/dist/chunk-OLV7OD3X.cjs +0 -502
- package/dist/chunk-OLV7OD3X.cjs.map +0 -1
- package/dist/chunk-OXNXEQY7.js +0 -2538
- package/dist/chunk-OXNXEQY7.js.map +0 -1
- package/dist/chunk-P5BOFE5A.js +0 -546
- package/dist/chunk-P5BOFE5A.js.map +0 -1
- package/dist/chunk-Q2MFGYTE.cjs +0 -1449
- package/dist/chunk-Q2MFGYTE.cjs.map +0 -1
- package/dist/chunk-Q75DBVDY.cjs +0 -68
- package/dist/chunk-Q75DBVDY.cjs.map +0 -1
- package/dist/chunk-REQ5Q6ZI.js +0 -1022
- package/dist/chunk-REQ5Q6ZI.js.map +0 -1
- package/dist/chunk-SICKWUWB.js +0 -62
- package/dist/chunk-SICKWUWB.js.map +0 -1
- package/dist/chunk-T343CCH5.js +0 -1190
- package/dist/chunk-T343CCH5.js.map +0 -1
- package/dist/chunk-TEC62D4A.cjs +0 -1624
- package/dist/chunk-TEC62D4A.cjs.map +0 -1
- package/dist/chunk-TW5JB35D.js +0 -2122
- package/dist/chunk-TW5JB35D.js.map +0 -1
- package/dist/chunk-VC5LMUVQ.cjs +0 -20
- package/dist/chunk-VC5LMUVQ.cjs.map +0 -1
- package/dist/chunk-VM7WFMKI.cjs +0 -76
- package/dist/chunk-VM7WFMKI.cjs.map +0 -1
- package/dist/chunk-W2WTP6HS.cjs +0 -233
- package/dist/chunk-W2WTP6HS.cjs.map +0 -1
- package/dist/chunk-WH7PYHZY.cjs +0 -35
- package/dist/chunk-WH7PYHZY.cjs.map +0 -1
- package/dist/chunk-XQZVY7JJ.cjs +0 -717
- package/dist/chunk-XQZVY7JJ.cjs.map +0 -1
- package/dist/chunk-XU3OMQ7V.js +0 -98
- package/dist/chunk-XU3OMQ7V.js.map +0 -1
- package/dist/chunk-XWPDRMZG.js +0 -62
- package/dist/chunk-XWPDRMZG.js.map +0 -1
- package/dist/chunk-Y3CPKNB7.js +0 -67
- package/dist/chunk-Y3CPKNB7.js.map +0 -1
- package/dist/chunk-YNVRDD2P.js +0 -98
- package/dist/chunk-YNVRDD2P.js.map +0 -1
- package/dist/chunk-YSYR54XR.js +0 -92
- package/dist/chunk-YSYR54XR.js.map +0 -1
- package/dist/chunk-YTYDQBVY.cjs +0 -162
- package/dist/chunk-YTYDQBVY.cjs.map +0 -1
- package/dist/chunk-ZDLOA2UT.cjs +0 -1042
- package/dist/chunk-ZDLOA2UT.cjs.map +0 -1
- package/dist/chunk-ZWUKRCOJ.cjs +0 -2162
- package/dist/chunk-ZWUKRCOJ.cjs.map +0 -1
package/dist/chunk-YSYR54XR.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { forwardRef, useState, useRef, useEffect } from 'react';
|
|
2
|
-
import clsx from 'clsx';
|
|
3
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
-
|
|
5
|
-
// src/components/MetricCounter/MetricCounter.tsx
|
|
6
|
-
var MetricCounter = forwardRef(
|
|
7
|
-
({
|
|
8
|
-
value,
|
|
9
|
-
prefix = "",
|
|
10
|
-
suffix = "",
|
|
11
|
-
duration = 1500,
|
|
12
|
-
decimals = 0,
|
|
13
|
-
label,
|
|
14
|
-
className,
|
|
15
|
-
...props
|
|
16
|
-
}, ref) => {
|
|
17
|
-
const [displayValue, setDisplayValue] = useState(0);
|
|
18
|
-
const [hasAnimated, setHasAnimated] = useState(false);
|
|
19
|
-
const containerRef = useRef(null);
|
|
20
|
-
const prefersReducedMotion = useRef(false);
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
prefersReducedMotion.current = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
23
|
-
if (prefersReducedMotion.current) {
|
|
24
|
-
setDisplayValue(value);
|
|
25
|
-
setHasAnimated(true);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const observer = new IntersectionObserver(
|
|
29
|
-
(entries) => {
|
|
30
|
-
if (entries[0].isIntersecting && !hasAnimated) {
|
|
31
|
-
startAnimation();
|
|
32
|
-
observer.disconnect();
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
{ threshold: 0.5 }
|
|
36
|
-
);
|
|
37
|
-
if (containerRef.current) {
|
|
38
|
-
observer.observe(containerRef.current);
|
|
39
|
-
}
|
|
40
|
-
return () => observer.disconnect();
|
|
41
|
-
}, [value, hasAnimated]);
|
|
42
|
-
const startAnimation = () => {
|
|
43
|
-
let startTime;
|
|
44
|
-
setHasAnimated(true);
|
|
45
|
-
const easeOutExpo = (t) => {
|
|
46
|
-
return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
|
47
|
-
};
|
|
48
|
-
const animate = (currentTime) => {
|
|
49
|
-
if (!startTime) startTime = currentTime;
|
|
50
|
-
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
51
|
-
const currentMultiplier = easeOutExpo(progress);
|
|
52
|
-
const nextValue = value * currentMultiplier;
|
|
53
|
-
setDisplayValue(nextValue);
|
|
54
|
-
if (progress < 1) {
|
|
55
|
-
requestAnimationFrame(animate);
|
|
56
|
-
} else {
|
|
57
|
-
setDisplayValue(value);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
requestAnimationFrame(animate);
|
|
61
|
-
};
|
|
62
|
-
const formattedValue = displayValue.toLocaleString(void 0, {
|
|
63
|
-
minimumFractionDigits: decimals,
|
|
64
|
-
maximumFractionDigits: decimals
|
|
65
|
-
});
|
|
66
|
-
return /* @__PURE__ */ jsxs(
|
|
67
|
-
"div",
|
|
68
|
-
{
|
|
69
|
-
ref: (node) => {
|
|
70
|
-
containerRef.current = node;
|
|
71
|
-
if (typeof ref === "function") ref(node);
|
|
72
|
-
else if (ref) ref.current = node;
|
|
73
|
-
},
|
|
74
|
-
className: clsx("ds-metric-counter", className),
|
|
75
|
-
...props,
|
|
76
|
-
children: [
|
|
77
|
-
/* @__PURE__ */ jsxs("div", { className: "ds-metric-counter__value-wrapper", children: [
|
|
78
|
-
prefix && /* @__PURE__ */ jsx("span", { className: "ds-metric-counter__prefix", children: prefix }),
|
|
79
|
-
/* @__PURE__ */ jsx("span", { className: "ds-metric-counter__value", children: formattedValue }),
|
|
80
|
-
suffix && /* @__PURE__ */ jsx("span", { className: "ds-metric-counter__suffix", children: suffix })
|
|
81
|
-
] }),
|
|
82
|
-
label && /* @__PURE__ */ jsx("div", { className: "ds-metric-counter__label", children: label })
|
|
83
|
-
]
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
MetricCounter.displayName = "MetricCounter";
|
|
89
|
-
|
|
90
|
-
export { MetricCounter };
|
|
91
|
-
//# sourceMappingURL=out.js.map
|
|
92
|
-
//# sourceMappingURL=chunk-YSYR54XR.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/MetricCounter/MetricCounter.tsx"],"names":[],"mappings":";AAAA,SAAgB,YAAY,WAAW,QAAQ,gBAAgB;AAC/D,OAAO,UAAU;AA+GT,SACa,KADb;AAxFD,IAAM,gBAAgB;AAAA,EAC3B,CACE;AAAA,IACE;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW;AAAA,IACX,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,CAAC,cAAc,eAAe,IAAI,SAAS,CAAC;AAClD,UAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,UAAM,eAAe,OAAuB,IAAI;AAChD,UAAM,uBAAuB,OAAO,KAAK;AAEzC,cAAU,MAAM;AACd,2BAAqB,UAAU,OAAO,WAAW,kCAAkC,EAAE;AAErF,UAAI,qBAAqB,SAAS;AAChC,wBAAgB,KAAK;AACrB,uBAAe,IAAI;AACnB;AAAA,MACF;AAEA,YAAM,WAAW,IAAI;AAAA,QACnB,CAAC,YAAY;AACX,cAAI,QAAQ,CAAC,EAAE,kBAAkB,CAAC,aAAa;AAC7C,2BAAe;AACf,qBAAS,WAAW;AAAA,UACtB;AAAA,QACF;AAAA,QACA,EAAE,WAAW,IAAI;AAAA,MACnB;AAEA,UAAI,aAAa,SAAS;AACxB,iBAAS,QAAQ,aAAa,OAAO;AAAA,MACvC;AAEA,aAAO,MAAM,SAAS,WAAW;AAAA,IAEnC,GAAG,CAAC,OAAO,WAAW,CAAC;AAEvB,UAAM,iBAAiB,MAAM;AAC3B,UAAI;AACJ,qBAAe,IAAI;AAEnB,YAAM,cAAc,CAAC,MAAsB;AACzC,eAAO,MAAM,IAAI,IAAI,IAAI,KAAK,IAAI,GAAG,MAAM,CAAC;AAAA,MAC9C;AAEA,YAAM,UAAU,CAAC,gBAAwB;AACvC,YAAI,CAAC,UAAW,aAAY;AAC5B,cAAM,WAAW,KAAK,KAAK,cAAc,aAAa,UAAU,CAAC;AAEjE,cAAM,oBAAoB,YAAY,QAAQ;AAC9C,cAAM,YAAY,QAAQ;AAE1B,wBAAgB,SAAS;AAEzB,YAAI,WAAW,GAAG;AAChB,gCAAsB,OAAO;AAAA,QAC/B,OAAO;AACL,0BAAgB,KAAK;AAAA,QACvB;AAAA,MACF;AAEA,4BAAsB,OAAO;AAAA,IAC/B;AAEA,UAAM,iBAAiB,aAAa,eAAe,QAAW;AAAA,MAC5D,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC;AAED,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,CAAC,SAAS;AACb,UAAC,aAA+D,UAAU;AAC1E,cAAI,OAAO,QAAQ,WAAY,KAAI,IAAI;AAAA,mBAC9B,IAAK,CAAC,IAAsD,UAAU;AAAA,QACjF;AAAA,QACA,WAAW,KAAK,qBAAqB,SAAS;AAAA,QAC7C,GAAG;AAAA,QAEJ;AAAA,+BAAC,SAAI,WAAU,oCACZ;AAAA,sBAAU,oBAAC,UAAK,WAAU,6BAA6B,kBAAO;AAAA,YAC/D,oBAAC,UAAK,WAAU,4BAA4B,0BAAe;AAAA,YAC1D,UAAU,oBAAC,UAAK,WAAU,6BAA6B,kBAAO;AAAA,aACjE;AAAA,UACC,SAAS,oBAAC,SAAI,WAAU,4BAA4B,iBAAM;AAAA;AAAA;AAAA,IAC7D;AAAA,EAEJ;AACF;AAEA,cAAc,cAAc","sourcesContent":["import React, { forwardRef, useEffect, useRef, useState } from 'react';\nimport clsx from 'clsx';\nimport './MetricCounter.css';\n\nexport interface MetricCounterProps extends React.HTMLAttributes<HTMLDivElement> {\n /** The target number to count up to */\n value: number;\n /** Optional prefix (e.g. \"$\", \"+\") */\n prefix?: string;\n /** Optional suffix (e.g. \"%\", \"M\") */\n suffix?: string;\n /** Duration of the animation in milliseconds (default: 1500) */\n duration?: number;\n /** Number of decimal places to show (default: 0) */\n decimals?: number;\n /** A label to display below the number */\n label?: React.ReactNode;\n}\n\n/**\n * An animated counter for displaying key metrics and social proof.\n * Uses requestAnimationFrame for smooth counting and IntersectionObserver\n * to trigger only when visible. Respects prefers-reduced-motion.\n */\nexport const MetricCounter = forwardRef<HTMLDivElement, MetricCounterProps>(\n (\n {\n value,\n prefix = '',\n suffix = '',\n duration = 1500,\n decimals = 0,\n label,\n className,\n ...props\n },\n ref\n ) => {\n const [displayValue, setDisplayValue] = useState(0);\n const [hasAnimated, setHasAnimated] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const prefersReducedMotion = useRef(false);\n\n useEffect(() => {\n prefersReducedMotion.current = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n \n if (prefersReducedMotion.current) {\n setDisplayValue(value);\n setHasAnimated(true);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0].isIntersecting && !hasAnimated) {\n startAnimation();\n observer.disconnect();\n }\n },\n { threshold: 0.5 }\n );\n\n if (containerRef.current) {\n observer.observe(containerRef.current);\n }\n\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps -- startAnimation is stable and intentionally omitted to avoid re-triggering\n }, [value, hasAnimated]);\n\n const startAnimation = () => {\n let startTime: number;\n setHasAnimated(true);\n\n const easeOutExpo = (t: number): number => {\n return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);\n };\n\n const animate = (currentTime: number) => {\n if (!startTime) startTime = currentTime;\n const progress = Math.min((currentTime - startTime) / duration, 1);\n \n const currentMultiplier = easeOutExpo(progress);\n const nextValue = value * currentMultiplier;\n \n setDisplayValue(nextValue);\n\n if (progress < 1) {\n requestAnimationFrame(animate);\n } else {\n setDisplayValue(value);\n }\n };\n\n requestAnimationFrame(animate);\n };\n\n const formattedValue = displayValue.toLocaleString(undefined, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n });\n\n return (\n <div\n ref={(node) => {\n (containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n if (typeof ref === 'function') ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }}\n className={clsx('ds-metric-counter', className)}\n {...props}\n >\n <div className=\"ds-metric-counter__value-wrapper\">\n {prefix && <span className=\"ds-metric-counter__prefix\">{prefix}</span>}\n <span className=\"ds-metric-counter__value\">{formattedValue}</span>\n {suffix && <span className=\"ds-metric-counter__suffix\">{suffix}</span>}\n </div>\n {label && <div className=\"ds-metric-counter__label\">{label}</div>}\n </div>\n );\n }\n);\n\nMetricCounter.displayName = 'MetricCounter';\n"]}
|
package/dist/chunk-YTYDQBVY.cjs
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkH2Y6BSTL_cjs = require('./chunk-H2Y6BSTL.cjs');
|
|
4
|
-
var chunkVC5LMUVQ_cjs = require('./chunk-VC5LMUVQ.cjs');
|
|
5
|
-
var chunkLJADZITX_cjs = require('./chunk-LJADZITX.cjs');
|
|
6
|
-
var react = require('react');
|
|
7
|
-
var lucideReact = require('lucide-react');
|
|
8
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
-
|
|
10
|
-
// src/web/analytics/create-analytics-loader.ts
|
|
11
|
-
function createAnalyticsLoader(config) {
|
|
12
|
-
return (trackingId) => {
|
|
13
|
-
if (typeof document === "undefined") return;
|
|
14
|
-
if (document.getElementById(config.id)) return;
|
|
15
|
-
const script = document.createElement("script");
|
|
16
|
-
script.id = config.id;
|
|
17
|
-
if (config.inlineScript) {
|
|
18
|
-
script.textContent = config.inlineScript(trackingId);
|
|
19
|
-
} else {
|
|
20
|
-
script.async = config.async ?? true;
|
|
21
|
-
script.src = config.src(trackingId);
|
|
22
|
-
}
|
|
23
|
-
document.head.appendChild(script);
|
|
24
|
-
config.onLoad?.(trackingId);
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
var loadGoogleAnalytics = createAnalyticsLoader({
|
|
28
|
-
id: "ga-script",
|
|
29
|
-
src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,
|
|
30
|
-
onLoad: (gaId) => {
|
|
31
|
-
window.dataLayer = window.dataLayer || [];
|
|
32
|
-
window.dataLayer.push(["js", /* @__PURE__ */ new Date()]);
|
|
33
|
-
window.dataLayer.push(["config", gaId]);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
var loadClarity = createAnalyticsLoader({
|
|
37
|
-
id: "clarity-script",
|
|
38
|
-
src: (clarityId) => `https://www.clarity.ms/tag/${clarityId}`,
|
|
39
|
-
onLoad: () => {
|
|
40
|
-
if (!window.clarity) {
|
|
41
|
-
const queue = [];
|
|
42
|
-
window.clarity = (...args) => {
|
|
43
|
-
queue.push(args);
|
|
44
|
-
};
|
|
45
|
-
window.clarity.q = queue;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
var COOKIE_CONSENT_EVENT = "cookie_consent_granted";
|
|
50
|
-
var COOKIE_CONSENT_KEY = "cookie_consent";
|
|
51
|
-
function CookieConsent({
|
|
52
|
-
delay = 1e3,
|
|
53
|
-
privacyUrl = "/legal/privacy",
|
|
54
|
-
onAccept,
|
|
55
|
-
onDecline,
|
|
56
|
-
labels,
|
|
57
|
-
className
|
|
58
|
-
}) {
|
|
59
|
-
const [state, setState] = react.useState("idle");
|
|
60
|
-
react.useEffect(() => {
|
|
61
|
-
const consent = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
62
|
-
if (!consent) {
|
|
63
|
-
const timer = setTimeout(() => setState("open"), delay);
|
|
64
|
-
return () => clearTimeout(timer);
|
|
65
|
-
} else {
|
|
66
|
-
setState("closed");
|
|
67
|
-
}
|
|
68
|
-
}, [delay]);
|
|
69
|
-
const handleDismiss = react.useCallback(() => {
|
|
70
|
-
setState("closing");
|
|
71
|
-
}, []);
|
|
72
|
-
const handleAccept = react.useCallback(() => {
|
|
73
|
-
localStorage.setItem(COOKIE_CONSENT_KEY, "granted");
|
|
74
|
-
handleDismiss();
|
|
75
|
-
if (typeof window !== "undefined") {
|
|
76
|
-
window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));
|
|
77
|
-
}
|
|
78
|
-
onAccept?.();
|
|
79
|
-
}, [onAccept, handleDismiss]);
|
|
80
|
-
const handleDecline = react.useCallback(() => {
|
|
81
|
-
localStorage.setItem(COOKIE_CONSENT_KEY, "denied");
|
|
82
|
-
handleDismiss();
|
|
83
|
-
onDecline?.();
|
|
84
|
-
}, [onDecline, handleDismiss]);
|
|
85
|
-
const handleTransitionEnd = react.useCallback(() => {
|
|
86
|
-
if (state === "closing") {
|
|
87
|
-
setState("closed");
|
|
88
|
-
}
|
|
89
|
-
}, [state]);
|
|
90
|
-
if (state === "idle" || state === "closed") return null;
|
|
91
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
92
|
-
"div",
|
|
93
|
-
{
|
|
94
|
-
className: `ds-cookie-consent ${className ?? ""}`,
|
|
95
|
-
"data-state": state === "open" ? "open" : "closed",
|
|
96
|
-
onTransitionEnd: handleTransitionEnd,
|
|
97
|
-
role: "dialog",
|
|
98
|
-
"aria-label": labels?.title ?? "Cookie consent",
|
|
99
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ds-cookie-consent__inner", children: [
|
|
100
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ds-cookie-consent__content", children: [
|
|
101
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "ds-cookie-consent__title", children: labels?.title ?? "We use cookies" }),
|
|
102
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "ds-cookie-consent__description", children: [
|
|
103
|
-
labels?.description ?? "We use tracking cookies to understand how you use the product and help us improve it.",
|
|
104
|
-
/* @__PURE__ */ jsxRuntime.jsx(chunkLJADZITX_cjs.OutboundLink, { href: privacyUrl, context: "cookie-consent-privacy", className: "ds-cookie-consent__link", children: labels?.privacyLinkText ?? "Privacy Policy" })
|
|
105
|
-
] })
|
|
106
|
-
] }),
|
|
107
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ds-cookie-consent__actions", children: [
|
|
108
|
-
/* @__PURE__ */ jsxRuntime.jsx(chunkH2Y6BSTL_cjs.Button, { variant: "outline", size: "sm", onClick: handleDecline, children: labels?.declineLabel ?? "Decline" }),
|
|
109
|
-
/* @__PURE__ */ jsxRuntime.jsx(chunkH2Y6BSTL_cjs.Button, { variant: "primary", size: "sm", onClick: handleAccept, children: labels?.acceptLabel ?? "Accept" })
|
|
110
|
-
] }),
|
|
111
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
112
|
-
"button",
|
|
113
|
-
{
|
|
114
|
-
onClick: handleDecline,
|
|
115
|
-
className: "ds-cookie-consent__close",
|
|
116
|
-
"aria-label": "Close",
|
|
117
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16 })
|
|
118
|
-
}
|
|
119
|
-
)
|
|
120
|
-
] })
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
function useCookieConsent() {
|
|
125
|
-
const [status, setStatus] = react.useState(() => {
|
|
126
|
-
if (typeof window === "undefined") return null;
|
|
127
|
-
const stored = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
128
|
-
if (stored === "granted") return "granted";
|
|
129
|
-
if (stored === "denied") return "denied";
|
|
130
|
-
return null;
|
|
131
|
-
});
|
|
132
|
-
react.useEffect(() => {
|
|
133
|
-
const handleConsent = () => setStatus("granted");
|
|
134
|
-
window.addEventListener(COOKIE_CONSENT_EVENT, handleConsent);
|
|
135
|
-
return () => window.removeEventListener(COOKIE_CONSENT_EVENT, handleConsent);
|
|
136
|
-
}, []);
|
|
137
|
-
return status;
|
|
138
|
-
}
|
|
139
|
-
function AnalyticsProvider({ onTrack, debug = false, children }) {
|
|
140
|
-
const track = react.useCallback(
|
|
141
|
-
(event, props) => {
|
|
142
|
-
if (debug && typeof console !== "undefined") {
|
|
143
|
-
console.log("[DS Analytics]", event, props);
|
|
144
|
-
}
|
|
145
|
-
onTrack(event, props);
|
|
146
|
-
},
|
|
147
|
-
[onTrack, debug]
|
|
148
|
-
);
|
|
149
|
-
const value = react.useMemo(() => ({ track }), [track]);
|
|
150
|
-
return /* @__PURE__ */ jsxRuntime.jsx(chunkVC5LMUVQ_cjs.AnalyticsContext.Provider, { value, children });
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
exports.AnalyticsProvider = AnalyticsProvider;
|
|
154
|
-
exports.COOKIE_CONSENT_EVENT = COOKIE_CONSENT_EVENT;
|
|
155
|
-
exports.COOKIE_CONSENT_KEY = COOKIE_CONSENT_KEY;
|
|
156
|
-
exports.CookieConsent = CookieConsent;
|
|
157
|
-
exports.createAnalyticsLoader = createAnalyticsLoader;
|
|
158
|
-
exports.loadClarity = loadClarity;
|
|
159
|
-
exports.loadGoogleAnalytics = loadGoogleAnalytics;
|
|
160
|
-
exports.useCookieConsent = useCookieConsent;
|
|
161
|
-
//# sourceMappingURL=out.js.map
|
|
162
|
-
//# sourceMappingURL=chunk-YTYDQBVY.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web/analytics/create-analytics-loader.ts","../src/web/CookieConsent/CookieConsent.tsx","../src/web/analytics/use-cookie-consent.ts","../src/web/analytics/analytics-provider.tsx"],"names":["useState","useEffect","useCallback","jsx"],"mappings":"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","sourcesContent":["/**\n * Generic analytics script loader.\n *\n * Framework-agnostic: injects a `<script>` tag into `<head>` once,\n * guards against double-loading, and respects cookie consent.\n *\n * @example\n * ```ts\n * import { createAnalyticsLoader } from '@salesmind-ai/design-system/web';\n *\n * const loadGA = createAnalyticsLoader({\n * id: 'ga-script',\n * src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n * onLoad: (gaId) => {\n * window.dataLayer = window.dataLayer || [];\n * function gtag(...args: unknown[]) { window.dataLayer.push(args); }\n * gtag('js', new Date());\n * gtag('config', gaId);\n * },\n * });\n *\n * // Later, after consent:\n * loadGA('G-XXXXXX');\n * ```\n */\n\nexport interface AnalyticsLoaderConfig<TId extends string = string> {\n /** Unique DOM id for the script element (prevents double-loading) */\n id: string;\n /** Build the script src URL from the tracking ID */\n src: (trackingId: TId) => string;\n /** Async attribute on the script tag (default: true) */\n async?: boolean;\n /** Called after the script is appended to the DOM */\n onLoad?: (trackingId: TId) => void;\n /** Custom inline script content instead of an external src */\n inlineScript?: (trackingId: TId) => string;\n}\n\n/**\n * Create a reusable analytics loader function.\n * The returned function injects the script once and is safe to call multiple times.\n */\nexport function createAnalyticsLoader<TId extends string = string>(\n config: AnalyticsLoaderConfig<TId>,\n): (trackingId: TId) => void {\n return (trackingId: TId) => {\n if (typeof document === 'undefined') return;\n if (document.getElementById(config.id)) return;\n\n const script = document.createElement('script');\n script.id = config.id;\n\n if (config.inlineScript) {\n script.textContent = config.inlineScript(trackingId);\n } else {\n script.async = config.async ?? true;\n script.src = config.src(trackingId);\n }\n\n document.head.appendChild(script);\n config.onLoad?.(trackingId);\n };\n}\n\n// ─── Pre-built Loaders ──────────────────────────────────────────────────────\n\ndeclare global {\n interface Window {\n dataLayer: unknown[];\n clarity?: (...args: unknown[]) => void;\n }\n}\n\n/**\n * Load Google Analytics (gtag.js).\n * Call with your GA measurement ID after cookie consent.\n */\nexport const loadGoogleAnalytics = createAnalyticsLoader({\n id: 'ga-script',\n src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n onLoad: (gaId) => {\n window.dataLayer = window.dataLayer || [];\n // Use array push directly to avoid @ts-ignore issues with gtag signature\n window.dataLayer.push(['js', new Date()]);\n window.dataLayer.push(['config', gaId]);\n },\n});\n\n/**\n * Load Microsoft Clarity.\n * Call with your Clarity project ID after cookie consent.\n */\nexport const loadClarity = createAnalyticsLoader({\n id: 'clarity-script',\n src: (clarityId) => `https://www.clarity.ms/tag/${clarityId}`,\n onLoad: () => {\n // Clarity auto-initializes from the script tag.\n // Expose the global `clarity` queue function for custom events.\n if (!window.clarity) {\n const queue: unknown[][] = [];\n window.clarity = (...args: unknown[]) => {\n queue.push(args);\n };\n (window.clarity as unknown as { q: unknown[][] }).q = queue;\n }\n },\n});\n","\"use client\";\nimport { useState, useEffect, useCallback } from 'react';\nimport { Button } from '../../components/Button/Button';\nimport { OutboundLink } from '../../components/OutboundLink/OutboundLink';\nimport { X } from 'lucide-react';\nimport './CookieConsent.css';\n\n/** Cookie consent state */\nexport type ConsentStatus = 'granted' | 'denied' | null;\n\n/** Event name dispatched on window when consent changes */\nexport const COOKIE_CONSENT_EVENT = 'cookie_consent_granted';\n\n/** localStorage key for cookie consent */\nexport const COOKIE_CONSENT_KEY = 'cookie_consent';\n\n/** Labels for i18n support */\nexport interface CookieConsentLabels {\n title?: string;\n description?: string;\n privacyLinkText?: string;\n acceptLabel?: string;\n declineLabel?: string;\n}\n\nexport interface CookieConsentProps {\n /** Delay in ms before showing the banner (default: 1000) */\n delay?: number;\n /** URL to the privacy policy page (default: \"/legal/privacy\") */\n privacyUrl?: string;\n /** Called when the user accepts cookies */\n onAccept?: () => void;\n /** Called when the user declines cookies */\n onDecline?: () => void;\n /** Override default labels for i18n */\n labels?: CookieConsentLabels;\n /** Custom className for the container */\n className?: string;\n}\n\n/**\n * CookieConsent — GDPR-compliant cookie consent banner.\n *\n * - Animated entrance/exit via CSS transitions (no framer-motion)\n * - Respects prior consent stored in localStorage\n * - Dispatches a `cookie_consent_granted` event on the window for analytics loaders\n * - Uses DS Button component\n *\n * @example\n * ```tsx\n * import { CookieConsent } from '@salesmind-ai/design-system/web';\n *\n * <CookieConsent\n * privacyUrl=\"/legal/privacy\"\n * onAccept={() => loadGoogleAnalytics('G-XXXX')}\n * />\n * ```\n */\nexport function CookieConsent({\n delay = 1000,\n privacyUrl = '/legal/privacy',\n onAccept,\n onDecline,\n labels,\n className,\n}: CookieConsentProps) {\n // 'idle' = not yet determined, 'open' = visible, 'closing' = exit animation, 'closed' = hidden\n const [state, setState] = useState<'idle' | 'open' | 'closing' | 'closed'>('idle');\n\n useEffect(() => {\n const consent = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (!consent) {\n const timer = setTimeout(() => setState('open'), delay);\n return () => clearTimeout(timer);\n } else {\n setState('closed');\n }\n }, [delay]);\n\n const handleDismiss = useCallback(() => {\n setState('closing');\n }, []);\n\n const handleAccept = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'granted');\n handleDismiss();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));\n }\n onAccept?.();\n }, [onAccept, handleDismiss]);\n\n const handleDecline = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'denied');\n handleDismiss();\n onDecline?.();\n }, [onDecline, handleDismiss]);\n\n const handleTransitionEnd = useCallback(() => {\n if (state === 'closing') {\n setState('closed');\n }\n }, [state]);\n\n // Don't render anything until we know consent status, or after fully closed\n if (state === 'idle' || state === 'closed') return null;\n\n return (\n <div\n className={`ds-cookie-consent ${className ?? ''}`}\n data-state={state === 'open' ? 'open' : 'closed'}\n onTransitionEnd={handleTransitionEnd}\n role=\"dialog\"\n aria-label={labels?.title ?? 'Cookie consent'}\n >\n <div className=\"ds-cookie-consent__inner\">\n <div className=\"ds-cookie-consent__content\">\n <h3 className=\"ds-cookie-consent__title\">\n {labels?.title ?? 'We use cookies'}\n </h3>\n <p className=\"ds-cookie-consent__description\">\n {labels?.description ??\n 'We use tracking cookies to understand how you use the product and help us improve it.'}\n <OutboundLink href={privacyUrl} context=\"cookie-consent-privacy\" className=\"ds-cookie-consent__link\">\n {labels?.privacyLinkText ?? 'Privacy Policy'}\n </OutboundLink>\n </p>\n </div>\n <div className=\"ds-cookie-consent__actions\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleDecline}>\n {labels?.declineLabel ?? 'Decline'}\n </Button>\n <Button variant=\"primary\" size=\"sm\" onClick={handleAccept}>\n {labels?.acceptLabel ?? 'Accept'}\n </Button>\n </div>\n <button\n onClick={handleDecline}\n className=\"ds-cookie-consent__close\"\n aria-label=\"Close\"\n >\n <X size={16} />\n </button>\n </div>\n </div>\n );\n}\n","\"use client\";\nimport { useState, useEffect } from 'react';\nimport { COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus } from '../CookieConsent/CookieConsent';\n\n/**\n * React hook that tracks cookie consent status.\n *\n * Returns `true` once the user has granted cookie consent,\n * `false` if denied, `null` if not yet decided.\n *\n * Listens for the `cookie_consent_granted` window event dispatched\n * by the CookieConsent component.\n *\n * @example\n * ```tsx\n * const hasConsent = useCookieConsent();\n *\n * useEffect(() => {\n * if (hasConsent) loadGoogleAnalytics('G-XXXX');\n * }, [hasConsent]);\n * ```\n */\nexport function useCookieConsent(): ConsentStatus {\n const [status, setStatus] = useState<ConsentStatus>(() => {\n if (typeof window === 'undefined') return null;\n const stored = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (stored === 'granted') return 'granted';\n if (stored === 'denied') return 'denied';\n return null;\n });\n\n useEffect(() => {\n const handleConsent = () => setStatus('granted');\n window.addEventListener(COOKIE_CONSENT_EVENT, handleConsent);\n return () => window.removeEventListener(COOKIE_CONSENT_EVENT, handleConsent);\n }, []);\n\n return status;\n}\n","\"use client\";\nimport { useCallback, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { AnalyticsContext } from './analytics-context';\nimport type { TrackFn } from './analytics-context';\n\n/* ============================================================================\n ANALYTICS PROVIDER — Event Tracking Context\n ============================================================================\n\n Provides a unified event tracking interface for all marketing/conversion\n components. Components call `useAnalytics().track(...)` without knowing\n which analytics backend is active.\n\n When no provider is present, all calls are no-ops — safe for Storybook,\n tests, and SSR.\n\n Usage:\n // In your app root:\n <AnalyticsProvider onTrack={(event, props) => gtag('event', event, props)}>\n <App />\n </AnalyticsProvider>\n\n // In any DS component:\n const { track } = useAnalytics();\n track('cta_click', { location: 'hero', label: 'Book Demo' });\n\n ============================================================================ */\n\n/** Props for the AnalyticsProvider component */\nexport interface AnalyticsProviderProps {\n /** Callback invoked on every track() call. Wire this to your analytics backend. */\n onTrack: TrackFn;\n /** Enable console logging in development (default: false) */\n debug?: boolean;\n children: ReactNode;\n}\n\n/**\n * Provides analytics event tracking to all descendant DS components.\n *\n * @example\n * ```tsx\n * <AnalyticsProvider\n * onTrack={(event, props) => {\n * window.gtag?.('event', event, props);\n * }}\n * >\n * <App />\n * </AnalyticsProvider>\n * ```\n */\nexport function AnalyticsProvider({ onTrack, debug = false, children }: AnalyticsProviderProps) {\n const track: TrackFn = useCallback(\n (event, props) => {\n if (debug && typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.log('[DS Analytics]', event, props);\n }\n onTrack(event, props);\n },\n [onTrack, debug],\n );\n\n const value = useMemo(() => ({ track }), [track]);\n\n return <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>;\n}\n"]}
|