@salesmind-ai/design-system 0.3.4 → 0.3.6
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/{StatsSection-wgd8Vge1.d.cts → StatsSection-Dihy3zml.d.cts} +2 -0
- package/dist/{StatsSection-B8iD9L-o.d.ts → StatsSection-MfKKyqL1.d.ts} +2 -0
- package/dist/admin/index.cjs +68 -2928
- package/dist/admin/index.cjs.map +1 -1
- package/dist/admin/index.js +5 -2915
- package/dist/admin/index.js.map +1 -1
- package/dist/blog/index.cjs +53 -1064
- package/dist/blog/index.cjs.map +1 -1
- package/dist/blog/index.js +8 -1054
- package/dist/blog/index.js.map +1 -1
- package/dist/charts/index.cjs +46 -2694
- package/dist/charts/index.cjs.map +1 -1
- package/dist/charts/index.js +3 -2680
- package/dist/charts/index.js.map +1 -1
- package/dist/chunk-2GARWEJK.js +17 -0
- package/dist/chunk-2GARWEJK.js.map +1 -0
- package/dist/chunk-2KQVZ5FB.js +485 -0
- package/dist/chunk-2KQVZ5FB.js.map +1 -0
- package/dist/chunk-3NKRFUAR.js +37 -0
- package/dist/chunk-3NKRFUAR.js.map +1 -0
- package/dist/chunk-3TGSIILM.cjs +201 -0
- package/dist/chunk-3TGSIILM.cjs.map +1 -0
- package/dist/chunk-4GM5BGBN.cjs +801 -0
- package/dist/chunk-4GM5BGBN.cjs.map +1 -0
- package/dist/chunk-5LGDEZWY.cjs +2434 -0
- package/dist/chunk-5LGDEZWY.cjs.map +1 -0
- package/dist/chunk-6H4DSTXR.js +786 -0
- package/dist/chunk-6H4DSTXR.js.map +1 -0
- package/dist/chunk-6HKQ5ILL.cjs +1624 -0
- package/dist/chunk-6HKQ5ILL.cjs.map +1 -0
- package/dist/chunk-6UNG76Y2.js +153 -0
- package/dist/chunk-6UNG76Y2.js.map +1 -0
- package/dist/chunk-7PX2AZ6Y.js +39 -0
- package/dist/chunk-7PX2AZ6Y.js.map +1 -0
- package/dist/chunk-ARC5KXBC.js +187 -0
- package/dist/chunk-ARC5KXBC.js.map +1 -0
- package/dist/chunk-B6AVAX4F.js +1415 -0
- package/dist/chunk-B6AVAX4F.js.map +1 -0
- package/dist/chunk-BILT5KD3.js +264 -0
- package/dist/chunk-BILT5KD3.js.map +1 -0
- package/dist/chunk-C2BCDNAV.js +24 -0
- package/dist/chunk-C2BCDNAV.js.map +1 -0
- package/dist/chunk-CH42VPWE.cjs +421 -0
- package/dist/chunk-CH42VPWE.cjs.map +1 -0
- package/dist/chunk-CJ2MKVAF.cjs +46 -0
- package/dist/chunk-CJ2MKVAF.cjs.map +1 -0
- package/dist/chunk-DP74LUXG.cjs +98 -0
- package/dist/chunk-DP74LUXG.cjs.map +1 -0
- package/dist/chunk-E7D6EKJ4.cjs +44 -0
- package/dist/chunk-E7D6EKJ4.cjs.map +1 -0
- package/dist/chunk-ECXBTUH6.cjs +584 -0
- package/dist/chunk-ECXBTUH6.cjs.map +1 -0
- package/dist/chunk-EFRAP5ES.js +157 -0
- package/dist/chunk-EFRAP5ES.js.map +1 -0
- package/dist/chunk-EM7JHRYW.cjs +69 -0
- package/dist/chunk-EM7JHRYW.cjs.map +1 -0
- package/dist/chunk-FAFAP4L5.js +183 -0
- package/dist/chunk-FAFAP4L5.js.map +1 -0
- package/dist/chunk-H2Y6BSTL.cjs +69 -0
- package/dist/chunk-H2Y6BSTL.cjs.map +1 -0
- package/dist/chunk-HN4PHABT.js +126 -0
- package/dist/chunk-HN4PHABT.js.map +1 -0
- package/dist/chunk-HRENHNDJ.js +211 -0
- package/dist/chunk-HRENHNDJ.js.map +1 -0
- package/dist/chunk-I75BFEYT.cjs +2561 -0
- package/dist/chunk-I75BFEYT.cjs.map +1 -0
- package/dist/chunk-IFRATNLU.js +562 -0
- package/dist/chunk-IFRATNLU.js.map +1 -0
- package/dist/chunk-JNASH4OQ.js +1022 -0
- package/dist/chunk-JNASH4OQ.js.map +1 -0
- package/dist/chunk-JPJN4YBC.js +409 -0
- package/dist/chunk-JPJN4YBC.js.map +1 -0
- package/dist/chunk-KCKUSU2M.cjs +166 -0
- package/dist/chunk-KCKUSU2M.cjs.map +1 -0
- package/dist/chunk-KDLH35OI.cjs +1042 -0
- package/dist/chunk-KDLH35OI.cjs.map +1 -0
- package/dist/chunk-KJ2OXQF4.js +287 -0
- package/dist/chunk-KJ2OXQF4.js.map +1 -0
- package/dist/chunk-KK5UO2P4.cjs +717 -0
- package/dist/chunk-KK5UO2P4.cjs.map +1 -0
- package/dist/chunk-KNQEIU7O.cjs +1202 -0
- package/dist/chunk-KNQEIU7O.cjs.map +1 -0
- package/dist/chunk-KVGSVGRK.cjs +569 -0
- package/dist/chunk-KVGSVGRK.cjs.map +1 -0
- package/dist/chunk-L352JRV6.cjs +105 -0
- package/dist/chunk-L352JRV6.cjs.map +1 -0
- package/dist/chunk-LGNMFBLF.cjs +502 -0
- package/dist/chunk-LGNMFBLF.cjs.map +1 -0
- package/dist/chunk-LJADZITX.cjs +298 -0
- package/dist/chunk-LJADZITX.cjs.map +1 -0
- package/dist/chunk-LSR7JYVH.cjs +196 -0
- package/dist/chunk-LSR7JYVH.cjs.map +1 -0
- package/dist/chunk-MDB2WCRQ.cjs +137 -0
- package/dist/chunk-MDB2WCRQ.cjs.map +1 -0
- package/dist/chunk-MQDEE7HC.cjs +283 -0
- package/dist/chunk-MQDEE7HC.cjs.map +1 -0
- package/dist/chunk-MQRB634A.cjs +34 -0
- package/dist/chunk-MQRB634A.cjs.map +1 -0
- package/dist/chunk-MU6GW5ZV.js +2317 -0
- package/dist/chunk-MU6GW5ZV.js.map +1 -0
- package/dist/chunk-NN3TUHIH.js +28 -0
- package/dist/chunk-NN3TUHIH.js.map +1 -0
- package/dist/chunk-NT4LBP7D.cjs +111 -0
- package/dist/chunk-NT4LBP7D.cjs.map +1 -0
- package/dist/chunk-OGKGIXFC.cjs +2162 -0
- package/dist/chunk-OGKGIXFC.cjs.map +1 -0
- package/dist/chunk-OXNXEQY7.js +2538 -0
- package/dist/chunk-OXNXEQY7.js.map +1 -0
- package/dist/chunk-P5BOFE5A.js +546 -0
- package/dist/chunk-P5BOFE5A.js.map +1 -0
- package/dist/chunk-Q2MFGYTE.cjs +1449 -0
- package/dist/chunk-Q2MFGYTE.cjs.map +1 -0
- package/dist/chunk-Q75DBVDY.cjs +68 -0
- package/dist/chunk-Q75DBVDY.cjs.map +1 -0
- package/dist/chunk-RQUFZAZ7.js +1608 -0
- package/dist/chunk-RQUFZAZ7.js.map +1 -0
- package/dist/chunk-SICKWUWB.js +62 -0
- package/dist/chunk-SICKWUWB.js.map +1 -0
- package/dist/chunk-T343CCH5.js +1190 -0
- package/dist/chunk-T343CCH5.js.map +1 -0
- package/dist/chunk-T5H5PNLN.js +701 -0
- package/dist/chunk-T5H5PNLN.js.map +1 -0
- package/dist/chunk-U3LK2GID.js +2122 -0
- package/dist/chunk-U3LK2GID.js.map +1 -0
- package/dist/chunk-UFAJY2DM.js +62 -0
- package/dist/chunk-UFAJY2DM.js.map +1 -0
- package/dist/chunk-VC5LMUVQ.cjs +20 -0
- package/dist/chunk-VC5LMUVQ.cjs.map +1 -0
- package/dist/chunk-VM7WFMKI.cjs +76 -0
- package/dist/chunk-VM7WFMKI.cjs.map +1 -0
- package/dist/chunk-W2WTP6HS.cjs +233 -0
- package/dist/chunk-W2WTP6HS.cjs.map +1 -0
- package/dist/chunk-WH7PYHZY.cjs +35 -0
- package/dist/chunk-WH7PYHZY.cjs.map +1 -0
- package/dist/chunk-XU3OMQ7V.js +98 -0
- package/dist/chunk-XU3OMQ7V.js.map +1 -0
- package/dist/chunk-XWPDRMZG.js +62 -0
- package/dist/chunk-XWPDRMZG.js.map +1 -0
- package/dist/chunk-Y3CPKNB7.js +67 -0
- package/dist/chunk-Y3CPKNB7.js.map +1 -0
- package/dist/chunk-YNVRDD2P.js +98 -0
- package/dist/chunk-YNVRDD2P.js.map +1 -0
- package/dist/chunk-YSYR54XR.js +92 -0
- package/dist/chunk-YSYR54XR.js.map +1 -0
- package/dist/chunk-YTYDQBVY.cjs +162 -0
- package/dist/chunk-YTYDQBVY.cjs.map +1 -0
- package/dist/core/index.cjs +807 -4333
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +14 -4130
- package/dist/core/index.js.map +1 -1
- package/dist/i18n/index.cjs +86 -558
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.js +1 -544
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.cjs +1432 -17140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +31 -16785
- package/dist/index.js.map +1 -1
- package/dist/marketing/index.cjs +142 -3072
- package/dist/marketing/index.cjs.map +1 -1
- package/dist/marketing/index.js +11 -3042
- package/dist/marketing/index.js.map +1 -1
- package/dist/motion/index.cjs +26 -1222
- package/dist/motion/index.cjs.map +1 -1
- package/dist/motion/index.js +2 -1215
- package/dist/motion/index.js.map +1 -1
- package/dist/nav/index.cjs +101 -1518
- package/dist/nav/index.cjs.map +1 -1
- package/dist/nav/index.js +4 -1498
- package/dist/nav/index.js.map +1 -1
- package/dist/report/index.cjs +171 -2403
- package/dist/report/index.cjs.map +1 -1
- package/dist/report/index.js +3 -2363
- package/dist/report/index.js.map +1 -1
- package/dist/sections/index.cjs +22 -377
- package/dist/sections/index.cjs.map +1 -1
- package/dist/sections/index.d.cts +1 -1
- package/dist/sections/index.d.ts +1 -1
- package/dist/sections/index.js +6 -369
- package/dist/sections/index.js.map +1 -1
- package/dist/social-proof/index.cjs +53 -1250
- package/dist/social-proof/index.cjs.map +1 -1
- package/dist/social-proof/index.js +6 -1235
- package/dist/social-proof/index.js.map +1 -1
- package/dist/theme/index.cjs +38 -565
- package/dist/theme/index.cjs.map +1 -1
- package/dist/theme/index.js +2 -555
- package/dist/theme/index.js.map +1 -1
- package/dist/web/client/index.cjs +38 -491
- package/dist/web/client/index.cjs.map +1 -1
- package/dist/web/client/index.js +4 -483
- package/dist/web/client/index.js.map +1 -1
- package/dist/web/index.cjs +158 -1346
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.js +9 -1305
- package/dist/web/index.js.map +1 -1
- package/dist/web/server/index.cjs +26 -563
- package/dist/web/server/index.cjs.map +1 -1
- package/dist/web/server/index.js +1 -560
- package/dist/web/server/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/components/BrowserFrame/BrowserFrame.tsx
|
|
6
|
+
var BrowserFrame = forwardRef(
|
|
7
|
+
({
|
|
8
|
+
variant = "browser",
|
|
9
|
+
children,
|
|
10
|
+
url,
|
|
11
|
+
showControls,
|
|
12
|
+
withGlow = false,
|
|
13
|
+
aspectRatio = "16/9",
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}, ref) => {
|
|
17
|
+
const hasControls = showControls ?? (variant === "browser" || variant === "app");
|
|
18
|
+
return /* @__PURE__ */ jsxs(
|
|
19
|
+
"div",
|
|
20
|
+
{
|
|
21
|
+
ref,
|
|
22
|
+
className: clsx(
|
|
23
|
+
"ds-browser-frame",
|
|
24
|
+
`ds-browser-frame--${variant}`,
|
|
25
|
+
withGlow && "ds-browser-frame--glow",
|
|
26
|
+
className
|
|
27
|
+
),
|
|
28
|
+
...props,
|
|
29
|
+
children: [
|
|
30
|
+
variant !== "minimal" && /* @__PURE__ */ jsxs("div", { className: "ds-browser-frame__chrome", children: [
|
|
31
|
+
hasControls && /* @__PURE__ */ jsxs("div", { className: "ds-browser-frame__controls", "aria-hidden": "true", children: [
|
|
32
|
+
/* @__PURE__ */ jsx("span", { className: "ds-browser-frame__dot ds-browser-frame__dot--red" }),
|
|
33
|
+
/* @__PURE__ */ jsx("span", { className: "ds-browser-frame__dot ds-browser-frame__dot--yellow" }),
|
|
34
|
+
/* @__PURE__ */ jsx("span", { className: "ds-browser-frame__dot ds-browser-frame__dot--green" })
|
|
35
|
+
] }),
|
|
36
|
+
variant === "browser" && url && /* @__PURE__ */ jsxs("div", { className: "ds-browser-frame__url-bar", children: [
|
|
37
|
+
/* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", className: "ds-browser-frame__lock", children: [
|
|
38
|
+
/* @__PURE__ */ jsx("path", { d: "M3 5V4a3 3 0 116 0v1", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
|
|
39
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "5", width: "8", height: "6", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" })
|
|
40
|
+
] }),
|
|
41
|
+
/* @__PURE__ */ jsx("span", { className: "ds-browser-frame__url-text", children: url })
|
|
42
|
+
] })
|
|
43
|
+
] }),
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
"div",
|
|
46
|
+
{
|
|
47
|
+
className: "ds-browser-frame__content",
|
|
48
|
+
style: aspectRatio !== "auto" ? { aspectRatio } : void 0,
|
|
49
|
+
children
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
variant === "mobile" && /* @__PURE__ */ jsx("div", { className: "ds-browser-frame__notch", "aria-hidden": "true" })
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
BrowserFrame.displayName = "BrowserFrame";
|
|
59
|
+
|
|
60
|
+
export { BrowserFrame };
|
|
61
|
+
//# sourceMappingURL=out.js.map
|
|
62
|
+
//# sourceMappingURL=chunk-XWPDRMZG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/BrowserFrame/BrowserFrame.tsx"],"names":[],"mappings":";AAAA,SAAgB,kBAAkB;AAClC,OAAO,UAAU;AAoEH,SACE,KADF;AA/BP,IAAM,eAAe;AAAA,EAC1B,CACE;AAAA,IACE,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,cAAc;AAAA,IACd;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,cAAc,iBAAiB,YAAY,aAAa,YAAY;AAE1E,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,qBAAqB,OAAO;AAAA,UAC5B,YAAY;AAAA,UACZ;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAGH;AAAA,sBAAY,aACX,qBAAC,SAAI,WAAU,4BACZ;AAAA,2BACC,qBAAC,SAAI,WAAU,8BAA6B,eAAY,QACtD;AAAA,kCAAC,UAAK,WAAU,oDAAmD;AAAA,cACnE,oBAAC,UAAK,WAAU,uDAAsD;AAAA,cACtE,oBAAC,UAAK,WAAU,sDAAqD;AAAA,eACvE;AAAA,YAED,YAAY,aAAa,OACxB,qBAAC,SAAI,WAAU,6BACb;AAAA,mCAAC,SAAI,OAAM,MAAK,QAAO,MAAK,SAAQ,aAAY,MAAK,QAAO,eAAY,QAAO,WAAU,0BACvF;AAAA,oCAAC,UAAK,GAAE,wBAAuB,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ;AAAA,gBAC7F,oBAAC,UAAK,GAAE,KAAI,GAAE,KAAI,OAAM,KAAI,QAAO,KAAI,IAAG,OAAM,QAAO,gBAAe,aAAY,OAAM;AAAA,iBAC1F;AAAA,cACA,oBAAC,UAAK,WAAU,8BAA8B,eAAI;AAAA,eACpD;AAAA,aAEJ;AAAA,UAIF;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,gBAAgB,SAAS,EAAE,YAAY,IAAI;AAAA,cAEjD;AAAA;AAAA,UACH;AAAA,UAGC,YAAY,YACX,oBAAC,SAAI,WAAU,2BAA0B,eAAY,QAAO;AAAA;AAAA;AAAA,IAEhE;AAAA,EAEJ;AACF;AAEA,aAAa,cAAc","sourcesContent":["import React, { forwardRef } from 'react';\nimport clsx from 'clsx';\nimport './BrowserFrame.css';\n\n/* ============================================================================\n BROWSER FRAME\n ============================================================================\n Realistic browser/device mockup frame for product screenshots and videos.\n Makes static screenshots feel like live product demos.\n\n Strategic objective: Trust (product visualization builds desire)\n ============================================================================ */\n\nexport interface BrowserFrameProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Frame type */\n variant?: 'browser' | 'app' | 'mobile' | 'minimal';\n /** Content to frame (image, video, or React element) */\n children: React.ReactNode;\n /** Optional URL bar text */\n url?: string;\n /** Show traffic light dots (default: true for browser/app) */\n showControls?: boolean;\n /** Optional glow effect behind frame */\n withGlow?: boolean;\n /** Aspect ratio */\n aspectRatio?: '16/9' | '4/3' | '3/2' | 'auto';\n}\n\n/**\n * Device mockup frame for product screenshots.\n *\n * @example\n * ```tsx\n * <BrowserFrame variant=\"browser\" url=\"app.sales-mind.ai\" withGlow>\n * <img src=\"/screenshot.png\" alt=\"SalesMind dashboard\" />\n * </BrowserFrame>\n * ```\n */\nexport const BrowserFrame = forwardRef<HTMLDivElement, BrowserFrameProps>(\n (\n {\n variant = 'browser',\n children,\n url,\n showControls,\n withGlow = false,\n aspectRatio = '16/9',\n className,\n ...props\n },\n ref,\n ) => {\n const hasControls = showControls ?? (variant === 'browser' || variant === 'app');\n\n return (\n <div\n ref={ref}\n className={clsx(\n 'ds-browser-frame',\n `ds-browser-frame--${variant}`,\n withGlow && 'ds-browser-frame--glow',\n className,\n )}\n {...props}\n >\n {/* Chrome header */}\n {variant !== 'minimal' && (\n <div className=\"ds-browser-frame__chrome\">\n {hasControls && (\n <div className=\"ds-browser-frame__controls\" aria-hidden=\"true\">\n <span className=\"ds-browser-frame__dot ds-browser-frame__dot--red\" />\n <span className=\"ds-browser-frame__dot ds-browser-frame__dot--yellow\" />\n <span className=\"ds-browser-frame__dot ds-browser-frame__dot--green\" />\n </div>\n )}\n {variant === 'browser' && url && (\n <div className=\"ds-browser-frame__url-bar\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\" className=\"ds-browser-frame__lock\">\n <path d=\"M3 5V4a3 3 0 116 0v1\" stroke=\"currentColor\" strokeWidth=\"1.2\" strokeLinecap=\"round\" />\n <rect x=\"2\" y=\"5\" width=\"8\" height=\"6\" rx=\"1.5\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n </svg>\n <span className=\"ds-browser-frame__url-text\">{url}</span>\n </div>\n )}\n </div>\n )}\n\n {/* Content area */}\n <div\n className=\"ds-browser-frame__content\"\n style={aspectRatio !== 'auto' ? { aspectRatio } : undefined}\n >\n {children}\n </div>\n\n {/* Mobile notch */}\n {variant === 'mobile' && (\n <div className=\"ds-browser-frame__notch\" aria-hidden=\"true\" />\n )}\n </div>\n );\n },\n);\n\nBrowserFrame.displayName = 'BrowserFrame';\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/components/Avatar/Avatar.tsx
|
|
6
|
+
var AvatarContext = React.createContext({
|
|
7
|
+
hasImage: false,
|
|
8
|
+
imageError: false,
|
|
9
|
+
onImageError: () => {
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var Avatar = React.forwardRef(
|
|
13
|
+
({ size = "md", className, children, ...props }, ref) => {
|
|
14
|
+
const [imageError, setImageError] = React.useState(false);
|
|
15
|
+
const [hasImage, setHasImage] = React.useState(false);
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
let found = false;
|
|
18
|
+
React.Children.forEach(children, (child) => {
|
|
19
|
+
if (React.isValidElement(child) && child.type === AvatarImage) {
|
|
20
|
+
found = true;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
setHasImage(found);
|
|
24
|
+
}, [children]);
|
|
25
|
+
const onImageError = React.useCallback(() => {
|
|
26
|
+
setImageError(true);
|
|
27
|
+
}, []);
|
|
28
|
+
return /* @__PURE__ */ jsx(AvatarContext.Provider, { value: { hasImage, imageError, onImageError }, children: /* @__PURE__ */ jsx(
|
|
29
|
+
"span",
|
|
30
|
+
{
|
|
31
|
+
ref,
|
|
32
|
+
className: clsx("ds-avatar", `ds-avatar--${size}`, className),
|
|
33
|
+
...props,
|
|
34
|
+
children
|
|
35
|
+
}
|
|
36
|
+
) });
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
Avatar.displayName = "Avatar";
|
|
40
|
+
var AvatarImage = React.forwardRef(({ className, onError, ...props }, ref) => {
|
|
41
|
+
const { imageError, onImageError } = React.useContext(AvatarContext);
|
|
42
|
+
const handleError = (e) => {
|
|
43
|
+
onImageError();
|
|
44
|
+
onError?.(e);
|
|
45
|
+
};
|
|
46
|
+
if (imageError) return null;
|
|
47
|
+
return /* @__PURE__ */ jsx(
|
|
48
|
+
"img",
|
|
49
|
+
{
|
|
50
|
+
ref,
|
|
51
|
+
className: clsx("ds-avatar__image", className),
|
|
52
|
+
onError: handleError,
|
|
53
|
+
...props
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
AvatarImage.displayName = "AvatarImage";
|
|
58
|
+
var AvatarFallback = React.forwardRef(({ className, ...props }, ref) => {
|
|
59
|
+
const { hasImage, imageError } = React.useContext(AvatarContext);
|
|
60
|
+
if (hasImage && !imageError) return null;
|
|
61
|
+
return /* @__PURE__ */ jsx("span", { ref, className: clsx("ds-avatar__fallback", className), ...props });
|
|
62
|
+
});
|
|
63
|
+
AvatarFallback.displayName = "AvatarFallback";
|
|
64
|
+
|
|
65
|
+
export { Avatar, AvatarFallback, AvatarImage };
|
|
66
|
+
//# sourceMappingURL=out.js.map
|
|
67
|
+
//# sourceMappingURL=chunk-Y3CPKNB7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Avatar/Avatar.tsx"],"names":[],"mappings":";AAAA,OAAO,WAAW;AAClB,OAAO,UAAU;AA2DT;AA9CR,IAAM,gBAAgB,MAAM,cAAkC;AAAA,EAC5D,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,cAAc,MAAM;AAAA,EAAC;AACvB,CAAC;AAoBM,IAAM,SAAS,MAAM;AAAA,EAC1B,CAAC,EAAE,OAAO,MAAM,WAAW,UAAU,GAAG,MAAM,GAAG,QAAQ;AACvD,UAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,UAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AAGpD,UAAM,UAAU,MAAM;AACpB,UAAI,QAAQ;AACZ,YAAM,SAAS,QAAQ,UAAU,CAAC,UAAU;AAC1C,YAAI,MAAM,eAAe,KAAK,KAAK,MAAM,SAAS,aAAa;AAC7D,kBAAQ;AAAA,QACV;AAAA,MACF,CAAC;AACD,kBAAY,KAAK;AAAA,IACnB,GAAG,CAAC,QAAQ,CAAC;AAEb,UAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,oBAAc,IAAI;AAAA,IACpB,GAAG,CAAC,CAAC;AAEL,WACE,oBAAC,cAAc,UAAd,EAAuB,OAAO,EAAE,UAAU,YAAY,aAAa,GAClE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,KAAK,aAAa,cAAc,IAAI,IAAI,SAAS;AAAA,QAC3D,GAAG;AAAA,QAEH;AAAA;AAAA,IACH,GACF;AAAA,EAEJ;AACF;AAEA,OAAO,cAAc;AAMd,IAAM,cAAc,MAAM,WAG/B,CAAC,EAAE,WAAW,SAAS,GAAG,MAAM,GAAG,QAAQ;AAC3C,QAAM,EAAE,YAAY,aAAa,IAAI,MAAM,WAAW,aAAa;AAEnE,QAAM,cAAc,CAAC,MAA8C;AACjE,iBAAa;AACb,cAAU,CAAC;AAAA,EACb;AAEA,MAAI,WAAY,QAAO;AAEvB,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,KAAK,oBAAoB,SAAS;AAAA,MAC7C,SAAS;AAAA,MACR,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AAED,YAAY,cAAc;AAMnB,IAAM,iBAAiB,MAAM,WAGlC,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAAQ;AAClC,QAAM,EAAE,UAAU,WAAW,IAAI,MAAM,WAAW,aAAa;AAG/D,MAAI,YAAY,CAAC,WAAY,QAAO;AAEpC,SACE,oBAAC,UAAK,KAAU,WAAW,KAAK,uBAAuB,SAAS,GAAI,GAAG,OAAO;AAElF,CAAC;AAED,eAAe,cAAc","sourcesContent":["import React from 'react';\nimport clsx from 'clsx';\nimport './Avatar.css';\n\n/* ==========================================================================\n Context\n ========================================================================== */\n\ninterface AvatarContextValue {\n hasImage: boolean;\n imageError: boolean;\n onImageError: () => void;\n}\n\nconst AvatarContext = React.createContext<AvatarContextValue>({\n hasImage: false,\n imageError: false,\n onImageError: () => {},\n});\n\n/* ==========================================================================\n Avatar (root)\n ========================================================================== */\n\nexport interface AvatarProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Size variant */\n size?: 'sm' | 'md' | 'lg';\n}\n\n/**\n * User avatar with image and fallback support.\n *\n * @example\n * <Avatar>\n * <AvatarImage src=\"/photo.jpg\" alt=\"User\" />\n * <AvatarFallback>JD</AvatarFallback>\n * </Avatar>\n */\nexport const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(\n ({ size = 'md', className, children, ...props }, ref) => {\n const [imageError, setImageError] = React.useState(false);\n const [hasImage, setHasImage] = React.useState(false);\n\n // Scan children to detect if an AvatarImage is present\n React.useEffect(() => {\n let found = false;\n React.Children.forEach(children, (child) => {\n if (React.isValidElement(child) && child.type === AvatarImage) {\n found = true;\n }\n });\n setHasImage(found);\n }, [children]);\n\n const onImageError = React.useCallback(() => {\n setImageError(true);\n }, []);\n\n return (\n <AvatarContext.Provider value={{ hasImage, imageError, onImageError }}>\n <span\n ref={ref}\n className={clsx('ds-avatar', `ds-avatar--${size}`, className)}\n {...props}\n >\n {children}\n </span>\n </AvatarContext.Provider>\n );\n },\n);\n\nAvatar.displayName = 'Avatar';\n\n/* ==========================================================================\n AvatarImage\n ========================================================================== */\n\nexport const AvatarImage = React.forwardRef<\n HTMLImageElement,\n React.ImgHTMLAttributes<HTMLImageElement>\n>(({ className, onError, ...props }, ref) => {\n const { imageError, onImageError } = React.useContext(AvatarContext);\n\n const handleError = (e: React.SyntheticEvent<HTMLImageElement>) => {\n onImageError();\n onError?.(e);\n };\n\n if (imageError) return null;\n\n return (\n <img\n ref={ref}\n className={clsx('ds-avatar__image', className)}\n onError={handleError}\n {...props}\n />\n );\n});\n\nAvatarImage.displayName = 'AvatarImage';\n\n/* ==========================================================================\n AvatarFallback\n ========================================================================== */\n\nexport const AvatarFallback = React.forwardRef<\n HTMLSpanElement,\n React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => {\n const { hasImage, imageError } = React.useContext(AvatarContext);\n\n // Show fallback when there is no image, or the image failed to load\n if (hasImage && !imageError) return null;\n\n return (\n <span ref={ref} className={clsx('ds-avatar__fallback', className)} {...props} />\n );\n});\n\nAvatarFallback.displayName = 'AvatarFallback';\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// src/tokens/motion.ts
|
|
2
|
+
var DURATION = {
|
|
3
|
+
micro: 0.15,
|
|
4
|
+
// 150ms
|
|
5
|
+
short: 0.25,
|
|
6
|
+
// 250ms
|
|
7
|
+
medium: 0.5,
|
|
8
|
+
// 500ms
|
|
9
|
+
long: 1
|
|
10
|
+
// 1000ms
|
|
11
|
+
};
|
|
12
|
+
var EASING = {
|
|
13
|
+
standard: [0.16, 1, 0.3, 1],
|
|
14
|
+
// Smooth, premium feel
|
|
15
|
+
emphasis: [0.34, 1.56, 0.64, 1],
|
|
16
|
+
// Slight spring/overshoot
|
|
17
|
+
exit: [0.4, 0, 0.2, 1],
|
|
18
|
+
// Quick, decisive exit
|
|
19
|
+
linear: [0, 0, 1, 1]
|
|
20
|
+
};
|
|
21
|
+
var TRANSITION = {
|
|
22
|
+
standard: {
|
|
23
|
+
duration: DURATION.medium,
|
|
24
|
+
ease: EASING.standard
|
|
25
|
+
},
|
|
26
|
+
emphasis: {
|
|
27
|
+
duration: DURATION.medium,
|
|
28
|
+
ease: EASING.emphasis
|
|
29
|
+
},
|
|
30
|
+
quick: {
|
|
31
|
+
duration: DURATION.short,
|
|
32
|
+
ease: EASING.standard
|
|
33
|
+
},
|
|
34
|
+
spring: {
|
|
35
|
+
type: "spring",
|
|
36
|
+
stiffness: 200,
|
|
37
|
+
damping: 20
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var VARIANTS = {
|
|
41
|
+
fadeIn: {
|
|
42
|
+
hidden: { opacity: 0 },
|
|
43
|
+
visible: { opacity: 1, transition: { duration: DURATION.medium, ease: EASING.standard } }
|
|
44
|
+
},
|
|
45
|
+
fadeUp: {
|
|
46
|
+
hidden: { opacity: 0, y: 12 },
|
|
47
|
+
visible: { opacity: 1, y: 0, transition: { duration: DURATION.medium, ease: EASING.standard } }
|
|
48
|
+
},
|
|
49
|
+
scaleIn: {
|
|
50
|
+
hidden: { opacity: 0, scale: 0.95 },
|
|
51
|
+
visible: {
|
|
52
|
+
opacity: 1,
|
|
53
|
+
scale: 1,
|
|
54
|
+
transition: { duration: DURATION.short, ease: EASING.emphasis }
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
staggerContainer: {
|
|
58
|
+
hidden: { opacity: 0 },
|
|
59
|
+
visible: {
|
|
60
|
+
opacity: 1,
|
|
61
|
+
transition: {
|
|
62
|
+
staggerChildren: 0.08,
|
|
63
|
+
delayChildren: 0.1
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
pulse: {
|
|
68
|
+
hidden: { scale: 1 },
|
|
69
|
+
visible: {
|
|
70
|
+
scale: [1, 1.05, 1],
|
|
71
|
+
transition: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
impact: {
|
|
75
|
+
hidden: { scale: 1 },
|
|
76
|
+
trigger: {
|
|
77
|
+
scale: [1, 0.95, 1.05, 1],
|
|
78
|
+
transition: { duration: 0.4, ease: "backOut" }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var DISTANCE = {
|
|
83
|
+
sm: 4,
|
|
84
|
+
md: 12,
|
|
85
|
+
lg: 24,
|
|
86
|
+
xl: 48
|
|
87
|
+
};
|
|
88
|
+
var MOTION = {
|
|
89
|
+
DURATION,
|
|
90
|
+
EASING,
|
|
91
|
+
TRANSITION,
|
|
92
|
+
VARIANTS,
|
|
93
|
+
DISTANCE
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { DISTANCE, DURATION, EASING, MOTION, TRANSITION, VARIANTS };
|
|
97
|
+
//# sourceMappingURL=out.js.map
|
|
98
|
+
//# sourceMappingURL=chunk-YNVRDD2P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tokens/motion.ts"],"names":[],"mappings":";AAOO,IAAM,WAAW;AAAA,EACtB,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,QAAQ;AAAA;AAAA,EACR,MAAM;AAAA;AACR;AAKO,IAAM,SAAS;AAAA,EACpB,UAAU,CAAC,MAAM,GAAG,KAAK,CAAC;AAAA;AAAA,EAC1B,UAAU,CAAC,MAAM,MAAM,MAAM,CAAC;AAAA;AAAA,EAC9B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;AAAA;AAAA,EACrB,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC;AACrB;AAKO,IAAM,aAAa;AAAA,EACxB,UAAU;AAAA,IACR,UAAU,SAAS;AAAA,IACnB,MAAM,OAAO;AAAA,EACf;AAAA,EACA,UAAU;AAAA,IACR,UAAU,SAAS;AAAA,IACnB,MAAM,OAAO;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,UAAU,SAAS;AAAA,IACnB,MAAM,OAAO;AAAA,EACf;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AACF;AAKO,IAAM,WAAW;AAAA,EACtB,QAAQ;AAAA,IACN,QAAQ,EAAE,SAAS,EAAE;AAAA,IACrB,SAAS,EAAE,SAAS,GAAG,YAAY,EAAE,UAAU,SAAS,QAAQ,MAAM,OAAO,SAAS,EAAE;AAAA,EAC1F;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,IAC5B,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG,YAAY,EAAE,UAAU,SAAS,QAAQ,MAAM,OAAO,SAAS,EAAE;AAAA,EAChG;AAAA,EACA,SAAS;AAAA,IACP,QAAQ,EAAE,SAAS,GAAG,OAAO,KAAK;AAAA,IAClC,SAAS;AAAA,MACP,SAAS;AAAA,MACT,OAAO;AAAA,MACP,YAAY,EAAE,UAAU,SAAS,OAAO,MAAM,OAAO,SAAS;AAAA,IAChE;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA,IAChB,QAAQ,EAAE,SAAS,EAAE;AAAA,IACrB,SAAS;AAAA,MACP,SAAS;AAAA,MACT,YAAY;AAAA,QACV,iBAAiB;AAAA,QACjB,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,QAAQ,EAAE,OAAO,EAAE;AAAA,IACnB,SAAS;AAAA,MACP,OAAO,CAAC,GAAG,MAAM,CAAC;AAAA,MAClB,YAAY,EAAE,UAAU,KAAK,QAAQ,UAAU,MAAM,YAAY;AAAA,IACnE;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ,EAAE,OAAO,EAAE;AAAA,IACnB,SAAS;AAAA,MACP,OAAO,CAAC,GAAG,MAAM,MAAM,CAAC;AAAA,MACxB,YAAY,EAAE,UAAU,KAAK,MAAM,UAAU;AAAA,IAC/C;AAAA,EACF;AACF;AAKO,IAAM,WAAW;AAAA,EACtB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEO,IAAM,SAAS;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF","sourcesContent":["/* ============================================================================\n MOTION TOKENS & FRAMER CONFIG\n ============================================================================ */\n\n/**\n * Duration tokens (seconds) for Framer Motion\n */\nexport const DURATION = {\n micro: 0.15, // 150ms\n short: 0.25, // 250ms\n medium: 0.5, // 500ms\n long: 1.0, // 1000ms\n} as const;\n\n/**\n * Easing definitions (cubic-bezier arrays) for Framer Motion\n */\nexport const EASING = {\n standard: [0.16, 1, 0.3, 1], // Smooth, premium feel\n emphasis: [0.34, 1.56, 0.64, 1], // Slight spring/overshoot\n exit: [0.4, 0, 0.2, 1], // Quick, decisive exit\n linear: [0, 0, 1, 1],\n} as const;\n\n/**\n * Standard transitions for Framer Motion\n */\nexport const TRANSITION = {\n standard: {\n duration: DURATION.medium,\n ease: EASING.standard,\n },\n emphasis: {\n duration: DURATION.medium,\n ease: EASING.emphasis,\n },\n quick: {\n duration: DURATION.short,\n ease: EASING.standard,\n },\n spring: {\n type: 'spring',\n stiffness: 200,\n damping: 20,\n },\n} as const;\n\n/**\n * Reusable Variants for Framer Motion\n */\nexport const VARIANTS = {\n fadeIn: {\n hidden: { opacity: 0 },\n visible: { opacity: 1, transition: { duration: DURATION.medium, ease: EASING.standard } },\n },\n fadeUp: {\n hidden: { opacity: 0, y: 12 },\n visible: { opacity: 1, y: 0, transition: { duration: DURATION.medium, ease: EASING.standard } },\n },\n scaleIn: {\n hidden: { opacity: 0, scale: 0.95 },\n visible: {\n opacity: 1,\n scale: 1,\n transition: { duration: DURATION.short, ease: EASING.emphasis },\n },\n },\n staggerContainer: {\n hidden: { opacity: 0 },\n visible: {\n opacity: 1,\n transition: {\n staggerChildren: 0.08,\n delayChildren: 0.1,\n },\n },\n },\n pulse: {\n hidden: { scale: 1 },\n visible: {\n scale: [1, 1.05, 1],\n transition: { duration: 1.5, repeat: Infinity, ease: 'easeInOut' },\n },\n },\n impact: {\n hidden: { scale: 1 },\n trigger: {\n scale: [1, 0.95, 1.05, 1],\n transition: { duration: 0.4, ease: 'backOut' },\n },\n },\n} as const;\n\n/**\n * Layout Tokens\n */\nexport const DISTANCE = {\n sm: 4,\n md: 12,\n lg: 24,\n xl: 48,\n} as const;\n\nexport const MOTION = {\n DURATION,\n EASING,\n TRANSITION,\n VARIANTS,\n DISTANCE,\n} as const;\n"]}
|
|
@@ -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"]}
|