@salesmind-ai/design-system 0.3.1 → 0.3.3

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 (197) hide show
  1. package/dist/admin/index.cjs +68 -2928
  2. package/dist/admin/index.cjs.map +1 -1
  3. package/dist/admin/index.js +5 -2915
  4. package/dist/admin/index.js.map +1 -1
  5. package/dist/blog/index.cjs +53 -1064
  6. package/dist/blog/index.cjs.map +1 -1
  7. package/dist/blog/index.js +8 -1054
  8. package/dist/blog/index.js.map +1 -1
  9. package/dist/charts/index.cjs +46 -2694
  10. package/dist/charts/index.cjs.map +1 -1
  11. package/dist/charts/index.js +3 -2680
  12. package/dist/charts/index.js.map +1 -1
  13. package/dist/chunk-2GARWEJK.js +17 -0
  14. package/dist/chunk-2GARWEJK.js.map +1 -0
  15. package/dist/chunk-3NKRFUAR.js +37 -0
  16. package/dist/chunk-3NKRFUAR.js.map +1 -0
  17. package/dist/chunk-3TGSIILM.cjs +201 -0
  18. package/dist/chunk-3TGSIILM.cjs.map +1 -0
  19. package/dist/chunk-4GM5BGBN.cjs +801 -0
  20. package/dist/chunk-4GM5BGBN.cjs.map +1 -0
  21. package/dist/chunk-5LGDEZWY.cjs +2434 -0
  22. package/dist/chunk-5LGDEZWY.cjs.map +1 -0
  23. package/dist/chunk-6H4DSTXR.js +786 -0
  24. package/dist/chunk-6H4DSTXR.js.map +1 -0
  25. package/dist/chunk-6UNG76Y2.js +153 -0
  26. package/dist/chunk-6UNG76Y2.js.map +1 -0
  27. package/dist/chunk-7PX2AZ6Y.js +39 -0
  28. package/dist/chunk-7PX2AZ6Y.js.map +1 -0
  29. package/dist/chunk-B6AVAX4F.js +1415 -0
  30. package/dist/chunk-B6AVAX4F.js.map +1 -0
  31. package/dist/chunk-BILT5KD3.js +264 -0
  32. package/dist/chunk-BILT5KD3.js.map +1 -0
  33. package/dist/chunk-C2BCDNAV.js +24 -0
  34. package/dist/chunk-C2BCDNAV.js.map +1 -0
  35. package/dist/chunk-CH42VPWE.cjs +421 -0
  36. package/dist/chunk-CH42VPWE.cjs.map +1 -0
  37. package/dist/chunk-CJ2MKVAF.cjs +46 -0
  38. package/dist/chunk-CJ2MKVAF.cjs.map +1 -0
  39. package/dist/chunk-DP74LUXG.cjs +98 -0
  40. package/dist/chunk-DP74LUXG.cjs.map +1 -0
  41. package/dist/chunk-E7D6EKJ4.cjs +44 -0
  42. package/dist/chunk-E7D6EKJ4.cjs.map +1 -0
  43. package/dist/chunk-ECXBTUH6.cjs +584 -0
  44. package/dist/chunk-ECXBTUH6.cjs.map +1 -0
  45. package/dist/chunk-EFRAP5ES.js +157 -0
  46. package/dist/chunk-EFRAP5ES.js.map +1 -0
  47. package/dist/chunk-F6YYWMME.js +485 -0
  48. package/dist/chunk-F6YYWMME.js.map +1 -0
  49. package/dist/chunk-FAFAP4L5.js +183 -0
  50. package/dist/chunk-FAFAP4L5.js.map +1 -0
  51. package/dist/chunk-GUZIMHWS.js +1608 -0
  52. package/dist/chunk-GUZIMHWS.js.map +1 -0
  53. package/dist/chunk-H2Y6BSTL.cjs +69 -0
  54. package/dist/chunk-H2Y6BSTL.cjs.map +1 -0
  55. package/dist/chunk-HN4PHABT.js +126 -0
  56. package/dist/chunk-HN4PHABT.js.map +1 -0
  57. package/dist/chunk-HRENHNDJ.js +211 -0
  58. package/dist/chunk-HRENHNDJ.js.map +1 -0
  59. package/dist/chunk-I75BFEYT.cjs +2561 -0
  60. package/dist/chunk-I75BFEYT.cjs.map +1 -0
  61. package/dist/chunk-IFRATNLU.js +562 -0
  62. package/dist/chunk-IFRATNLU.js.map +1 -0
  63. package/dist/chunk-IYPXJ6YC.cjs +69 -0
  64. package/dist/chunk-IYPXJ6YC.cjs.map +1 -0
  65. package/dist/chunk-JPJN4YBC.js +409 -0
  66. package/dist/chunk-JPJN4YBC.js.map +1 -0
  67. package/dist/chunk-KBA2LFBG.js +62 -0
  68. package/dist/chunk-KBA2LFBG.js.map +1 -0
  69. package/dist/chunk-KCKUSU2M.cjs +166 -0
  70. package/dist/chunk-KCKUSU2M.cjs.map +1 -0
  71. package/dist/chunk-KJ2OXQF4.js +287 -0
  72. package/dist/chunk-KJ2OXQF4.js.map +1 -0
  73. package/dist/chunk-KNQEIU7O.cjs +1202 -0
  74. package/dist/chunk-KNQEIU7O.cjs.map +1 -0
  75. package/dist/chunk-KVGSVGRK.cjs +569 -0
  76. package/dist/chunk-KVGSVGRK.cjs.map +1 -0
  77. package/dist/chunk-L352JRV6.cjs +105 -0
  78. package/dist/chunk-L352JRV6.cjs.map +1 -0
  79. package/dist/chunk-LJADZITX.cjs +298 -0
  80. package/dist/chunk-LJADZITX.cjs.map +1 -0
  81. package/dist/chunk-LMJPWXTZ.cjs +194 -0
  82. package/dist/chunk-LMJPWXTZ.cjs.map +1 -0
  83. package/dist/chunk-LOWEAQST.js +701 -0
  84. package/dist/chunk-LOWEAQST.js.map +1 -0
  85. package/dist/chunk-MDB2WCRQ.cjs +137 -0
  86. package/dist/chunk-MDB2WCRQ.cjs.map +1 -0
  87. package/dist/chunk-MQDEE7HC.cjs +283 -0
  88. package/dist/chunk-MQDEE7HC.cjs.map +1 -0
  89. package/dist/chunk-MQRB634A.cjs +34 -0
  90. package/dist/chunk-MQRB634A.cjs.map +1 -0
  91. package/dist/chunk-MTI27RDV.js +185 -0
  92. package/dist/chunk-MTI27RDV.js.map +1 -0
  93. package/dist/chunk-MU6GW5ZV.js +2317 -0
  94. package/dist/chunk-MU6GW5ZV.js.map +1 -0
  95. package/dist/chunk-NN3TUHIH.js +28 -0
  96. package/dist/chunk-NN3TUHIH.js.map +1 -0
  97. package/dist/chunk-NT4LBP7D.cjs +111 -0
  98. package/dist/chunk-NT4LBP7D.cjs.map +1 -0
  99. package/dist/chunk-OLV7OD3X.cjs +502 -0
  100. package/dist/chunk-OLV7OD3X.cjs.map +1 -0
  101. package/dist/chunk-OXNXEQY7.js +2538 -0
  102. package/dist/chunk-OXNXEQY7.js.map +1 -0
  103. package/dist/chunk-P5BOFE5A.js +546 -0
  104. package/dist/chunk-P5BOFE5A.js.map +1 -0
  105. package/dist/chunk-Q2MFGYTE.cjs +1449 -0
  106. package/dist/chunk-Q2MFGYTE.cjs.map +1 -0
  107. package/dist/chunk-Q75DBVDY.cjs +68 -0
  108. package/dist/chunk-Q75DBVDY.cjs.map +1 -0
  109. package/dist/chunk-REQ5Q6ZI.js +1022 -0
  110. package/dist/chunk-REQ5Q6ZI.js.map +1 -0
  111. package/dist/chunk-SICKWUWB.js +62 -0
  112. package/dist/chunk-SICKWUWB.js.map +1 -0
  113. package/dist/chunk-T343CCH5.js +1190 -0
  114. package/dist/chunk-T343CCH5.js.map +1 -0
  115. package/dist/chunk-TEC62D4A.cjs +1624 -0
  116. package/dist/chunk-TEC62D4A.cjs.map +1 -0
  117. package/dist/chunk-TW5JB35D.js +2122 -0
  118. package/dist/chunk-TW5JB35D.js.map +1 -0
  119. package/dist/chunk-VC5LMUVQ.cjs +20 -0
  120. package/dist/chunk-VC5LMUVQ.cjs.map +1 -0
  121. package/dist/chunk-VM7WFMKI.cjs +76 -0
  122. package/dist/chunk-VM7WFMKI.cjs.map +1 -0
  123. package/dist/chunk-W2WTP6HS.cjs +233 -0
  124. package/dist/chunk-W2WTP6HS.cjs.map +1 -0
  125. package/dist/chunk-WH7PYHZY.cjs +35 -0
  126. package/dist/chunk-WH7PYHZY.cjs.map +1 -0
  127. package/dist/chunk-XQZVY7JJ.cjs +717 -0
  128. package/dist/chunk-XQZVY7JJ.cjs.map +1 -0
  129. package/dist/chunk-XU3OMQ7V.js +98 -0
  130. package/dist/chunk-XU3OMQ7V.js.map +1 -0
  131. package/dist/chunk-XWPDRMZG.js +62 -0
  132. package/dist/chunk-XWPDRMZG.js.map +1 -0
  133. package/dist/chunk-Y3CPKNB7.js +67 -0
  134. package/dist/chunk-Y3CPKNB7.js.map +1 -0
  135. package/dist/chunk-YNVRDD2P.js +98 -0
  136. package/dist/chunk-YNVRDD2P.js.map +1 -0
  137. package/dist/chunk-YSYR54XR.js +92 -0
  138. package/dist/chunk-YSYR54XR.js.map +1 -0
  139. package/dist/chunk-YTYDQBVY.cjs +162 -0
  140. package/dist/chunk-YTYDQBVY.cjs.map +1 -0
  141. package/dist/chunk-ZDLOA2UT.cjs +1042 -0
  142. package/dist/chunk-ZDLOA2UT.cjs.map +1 -0
  143. package/dist/chunk-ZWUKRCOJ.cjs +2162 -0
  144. package/dist/chunk-ZWUKRCOJ.cjs.map +1 -0
  145. package/dist/core/index.cjs +807 -4333
  146. package/dist/core/index.cjs.map +1 -1
  147. package/dist/core/index.js +14 -4130
  148. package/dist/core/index.js.map +1 -1
  149. package/dist/i18n/index.cjs +86 -558
  150. package/dist/i18n/index.cjs.map +1 -1
  151. package/dist/i18n/index.js +1 -544
  152. package/dist/i18n/index.js.map +1 -1
  153. package/dist/index.cjs +1432 -17140
  154. package/dist/index.cjs.map +1 -1
  155. package/dist/index.js +31 -16785
  156. package/dist/index.js.map +1 -1
  157. package/dist/marketing/index.cjs +142 -3072
  158. package/dist/marketing/index.cjs.map +1 -1
  159. package/dist/marketing/index.js +11 -3042
  160. package/dist/marketing/index.js.map +1 -1
  161. package/dist/motion/index.cjs +26 -1222
  162. package/dist/motion/index.cjs.map +1 -1
  163. package/dist/motion/index.js +2 -1215
  164. package/dist/motion/index.js.map +1 -1
  165. package/dist/nav/index.cjs +101 -1518
  166. package/dist/nav/index.cjs.map +1 -1
  167. package/dist/nav/index.js +4 -1498
  168. package/dist/nav/index.js.map +1 -1
  169. package/dist/report/index.cjs +171 -2403
  170. package/dist/report/index.cjs.map +1 -1
  171. package/dist/report/index.js +3 -2363
  172. package/dist/report/index.js.map +1 -1
  173. package/dist/sections/index.cjs +28 -378
  174. package/dist/sections/index.cjs.map +1 -1
  175. package/dist/sections/index.js +4 -372
  176. package/dist/sections/index.js.map +1 -1
  177. package/dist/social-proof/index.cjs +53 -1250
  178. package/dist/social-proof/index.cjs.map +1 -1
  179. package/dist/social-proof/index.js +6 -1235
  180. package/dist/social-proof/index.js.map +1 -1
  181. package/dist/theme/index.cjs +38 -565
  182. package/dist/theme/index.cjs.map +1 -1
  183. package/dist/theme/index.js +2 -555
  184. package/dist/theme/index.js.map +1 -1
  185. package/dist/web/client/index.cjs +38 -491
  186. package/dist/web/client/index.cjs.map +1 -1
  187. package/dist/web/client/index.js +4 -483
  188. package/dist/web/client/index.js.map +1 -1
  189. package/dist/web/index.cjs +158 -1346
  190. package/dist/web/index.cjs.map +1 -1
  191. package/dist/web/index.js +9 -1305
  192. package/dist/web/index.js.map +1 -1
  193. package/dist/web/server/index.cjs +26 -563
  194. package/dist/web/server/index.cjs.map +1 -1
  195. package/dist/web/server/index.js +1 -560
  196. package/dist/web/server/index.js.map +1 -1
  197. package/package.json +11 -1
@@ -0,0 +1,92 @@
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
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,162 @@
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
@@ -0,0 +1 @@
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"]}