@servicetitan/marketing-ui 7.3.0 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/charts/common/color-tag.d.ts.map +1 -1
- package/dist/components/charts/common/color-tag.js +1 -0
- package/dist/components/charts/common/color-tag.js.map +1 -1
- package/dist/components/charts/line-chart/components/hover-popover.d.ts.map +1 -1
- package/dist/components/charts/line-chart/components/hover-popover.js +22 -13
- package/dist/components/charts/line-chart/components/hover-popover.js.map +1 -1
- package/dist/components/charts/line-chart/components/hover-popover.module.less +5 -0
- package/dist/components/charts/line-chart/components/hover-popover.module.less.d.ts +1 -0
- package/dist/components/charts/line-chart/components/svg-bars.d.ts.map +1 -1
- package/dist/components/charts/line-chart/components/svg-bars.js +16 -22
- package/dist/components/charts/line-chart/components/svg-bars.js.map +1 -1
- package/dist/components/stat/stat-card.d.ts.map +1 -1
- package/dist/components/stat/stat-card.js +53 -33
- package/dist/components/stat/stat-card.js.map +1 -1
- package/dist/components/stat/stat-card.module.less +17 -2
- package/dist/components/stat/stat-card.module.less.d.ts +3 -1
- package/package.json +2 -2
- package/src/components/charts/common/color-tag.tsx +5 -1
- package/src/components/charts/line-chart/components/hover-popover.module.less +5 -0
- package/src/components/charts/line-chart/components/hover-popover.module.less.d.ts +1 -0
- package/src/components/charts/line-chart/components/hover-popover.tsx +23 -12
- package/src/components/charts/line-chart/components/svg-bars.tsx +20 -37
- package/src/components/stat/stat-card.module.less +17 -2
- package/src/components/stat/stat-card.module.less.d.ts +3 -1
- package/src/components/stat/stat-card.tsx +44 -37
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"color-tag.d.ts","sourceRoot":"","sources":["../../../../src/components/charts/common/color-tag.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAS,MAAM,OAAO,CAAC;AAKlC,UAAU,aAAa;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;CAC7C;AAED,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,
|
|
1
|
+
{"version":3,"file":"color-tag.d.ts","sourceRoot":"","sources":["../../../../src/components/charts/common/color-tag.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAS,MAAM,OAAO,CAAC;AAKlC,UAAU,aAAa;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;CAC7C;AAED,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CA8EtC,CAAC"}
|
|
@@ -62,6 +62,7 @@ export const ColorTag = ({ label, color, className, small, dashed, outlineColor,
|
|
|
62
62
|
borderColor: strokeColor !== null && strokeColor !== void 0 ? strokeColor : color
|
|
63
63
|
} : pattern === 'outline' ? {
|
|
64
64
|
borderColor: outlineColor !== null && outlineColor !== void 0 ? outlineColor : color,
|
|
65
|
+
backgroundColor: color,
|
|
65
66
|
borderRadius: radius
|
|
66
67
|
} : {
|
|
67
68
|
backgroundColor: color,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/charts/common/color-tag.tsx"],"sourcesContent":["import { FC, useId } from 'react';\nimport classNames from 'classnames';\nimport { BodyText, Stack } from '@servicetitan/design-system';\nimport * as Styles from './color-tag.module.less';\n\ninterface ColorTagProps {\n label: string;\n color: string;\n className?: string;\n colorTagClassName?: string;\n small?: boolean;\n dashed?: boolean;\n strokeColor?: string;\n outlineColor?: string;\n pattern?: 'solid' | 'striped' | 'outline';\n}\n\nexport const ColorTag: FC<ColorTagProps> = ({\n label,\n color,\n className,\n small,\n dashed,\n outlineColor,\n pattern,\n strokeColor,\n colorTagClassName,\n}) => {\n const uid = useId();\n const patternId = `pattern-${uid}`;\n\n const width = small ? 50 : 22;\n const height = small ? 10 : 25;\n const radius = small ? 0 : 3;\n\n return (\n <Stack alignItems=\"center\" className={className} gap={1}>\n {pattern === 'striped' ? (\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n className={classNames(Styles.colorTag, colorTagClassName)}\n aria-hidden\n >\n <defs>\n <pattern\n id={patternId}\n patternUnits=\"userSpaceOnUse\"\n width=\"8\"\n height=\"8\"\n patternTransform=\"rotate(45)\"\n >\n <rect width=\"4\" height=\"8\" fill={color} opacity=\"0.08\" />\n <rect width=\"2\" height=\"8\" fill={color} />\n </pattern>\n </defs>\n <rect\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n rx={radius}\n ry={radius}\n fill={`url(#${patternId})`}\n stroke={strokeColor ?? color}\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n ) : (\n <div\n className={classNames(\n Styles.colorTag,\n small && Styles.colorTagSmall,\n dashed && Styles.colorTagDashed,\n pattern === 'outline' && Styles.colorTagOutline,\n colorTagClassName\n )}\n style={\n dashed\n ? { borderColor: strokeColor ?? color }\n : pattern === 'outline'\n ? {
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/charts/common/color-tag.tsx"],"sourcesContent":["import { FC, useId } from 'react';\nimport classNames from 'classnames';\nimport { BodyText, Stack } from '@servicetitan/design-system';\nimport * as Styles from './color-tag.module.less';\n\ninterface ColorTagProps {\n label: string;\n color: string;\n className?: string;\n colorTagClassName?: string;\n small?: boolean;\n dashed?: boolean;\n strokeColor?: string;\n outlineColor?: string;\n pattern?: 'solid' | 'striped' | 'outline';\n}\n\nexport const ColorTag: FC<ColorTagProps> = ({\n label,\n color,\n className,\n small,\n dashed,\n outlineColor,\n pattern,\n strokeColor,\n colorTagClassName,\n}) => {\n const uid = useId();\n const patternId = `pattern-${uid}`;\n\n const width = small ? 50 : 22;\n const height = small ? 10 : 25;\n const radius = small ? 0 : 3;\n\n return (\n <Stack alignItems=\"center\" className={className} gap={1}>\n {pattern === 'striped' ? (\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n className={classNames(Styles.colorTag, colorTagClassName)}\n aria-hidden\n >\n <defs>\n <pattern\n id={patternId}\n patternUnits=\"userSpaceOnUse\"\n width=\"8\"\n height=\"8\"\n patternTransform=\"rotate(45)\"\n >\n <rect width=\"4\" height=\"8\" fill={color} opacity=\"0.08\" />\n <rect width=\"2\" height=\"8\" fill={color} />\n </pattern>\n </defs>\n <rect\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n rx={radius}\n ry={radius}\n fill={`url(#${patternId})`}\n stroke={strokeColor ?? color}\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n ) : (\n <div\n className={classNames(\n Styles.colorTag,\n small && Styles.colorTagSmall,\n dashed && Styles.colorTagDashed,\n pattern === 'outline' && Styles.colorTagOutline,\n colorTagClassName\n )}\n style={\n dashed\n ? { borderColor: strokeColor ?? color }\n : pattern === 'outline'\n ? {\n borderColor: outlineColor ?? color,\n backgroundColor: color,\n borderRadius: radius,\n }\n : { backgroundColor: color, borderRadius: radius }\n }\n />\n )}\n <BodyText size={small ? 'xsmall' : 'small'}>{label}</BodyText>\n </Stack>\n );\n};\n"],"names":["useId","classNames","BodyText","Stack","Styles","ColorTag","label","color","className","small","dashed","outlineColor","pattern","strokeColor","colorTagClassName","uid","patternId","width","height","radius","alignItems","gap","svg","viewBox","colorTag","aria-hidden","defs","id","patternUnits","patternTransform","rect","fill","opacity","x","y","rx","ry","stroke","strokeWidth","vectorEffect","div","colorTagSmall","colorTagDashed","colorTagOutline","style","borderColor","backgroundColor","borderRadius","size"],"mappings":";AAAA,SAAaA,KAAK,QAAQ,QAAQ;AAClC,OAAOC,gBAAgB,aAAa;AACpC,SAASC,QAAQ,EAAEC,KAAK,QAAQ,8BAA8B;AAC9D,YAAYC,YAAY,0BAA0B;AAclD,OAAO,MAAMC,WAA8B,CAAC,EACxCC,KAAK,EACLC,KAAK,EACLC,SAAS,EACTC,KAAK,EACLC,MAAM,EACNC,YAAY,EACZC,OAAO,EACPC,WAAW,EACXC,iBAAiB,EACpB;IACG,MAAMC,MAAMf;IACZ,MAAMgB,YAAY,CAAC,QAAQ,EAAED,KAAK;IAElC,MAAME,QAAQR,QAAQ,KAAK;IAC3B,MAAMS,SAAST,QAAQ,KAAK;IAC5B,MAAMU,SAASV,QAAQ,IAAI;IAE3B,qBACI,MAACN;QAAMiB,YAAW;QAASZ,WAAWA;QAAWa,KAAK;;YACjDT,YAAY,0BACT,MAACU;gBACGL,OAAOA;gBACPC,QAAQA;gBACRK,SAAS,CAAC,IAAI,EAAEN,MAAM,CAAC,EAAEC,QAAQ;gBACjCV,WAAWP,WAAWG,OAAOoB,QAAQ,EAAEV;gBACvCW,aAAW;;kCAEX,KAACC;kCACG,cAAA,MAACd;4BACGe,IAAIX;4BACJY,cAAa;4BACbX,OAAM;4BACNC,QAAO;4BACPW,kBAAiB;;8CAEjB,KAACC;oCAAKb,OAAM;oCAAIC,QAAO;oCAAIa,MAAMxB;oCAAOyB,SAAQ;;8CAChD,KAACF;oCAAKb,OAAM;oCAAIC,QAAO;oCAAIa,MAAMxB;;;;;kCAGzC,KAACuB;wBACGG,GAAE;wBACFC,GAAE;wBACFjB,OAAOA;wBACPC,QAAQA;wBACRiB,IAAIhB;wBACJiB,IAAIjB;wBACJY,MAAM,CAAC,KAAK,EAAEf,UAAU,CAAC,CAAC;wBAC1BqB,MAAM,EAAExB,wBAAAA,yBAAAA,cAAeN;wBACvB+B,aAAa;wBACbC,cAAa;;;+BAIrB,KAACC;gBACGhC,WAAWP,WACPG,OAAOoB,QAAQ,EACff,SAASL,OAAOqC,aAAa,EAC7B/B,UAAUN,OAAOsC,cAAc,EAC/B9B,YAAY,aAAaR,OAAOuC,eAAe,EAC/C7B;gBAEJ8B,OACIlC,SACM;oBAAEmC,WAAW,EAAEhC,wBAAAA,yBAAAA,cAAeN;gBAAM,IACpCK,YAAY,YACV;oBACIiC,WAAW,EAAElC,yBAAAA,0BAAAA,eAAgBJ;oBAC7BuC,iBAAiBvC;oBACjBwC,cAAc5B;gBAClB,IACA;oBAAE2B,iBAAiBvC;oBAAOwC,cAAc5B;gBAAO;;0BAInE,KAACjB;gBAAS8C,MAAMvC,QAAQ,WAAW;0BAAUH;;;;AAGzD,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hover-popover.d.ts","sourceRoot":"","sources":["../../../../../src/components/charts/line-chart/components/hover-popover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2D,EAAE,
|
|
1
|
+
{"version":3,"file":"hover-popover.d.ts","sourceRoot":"","sources":["../../../../../src/components/charts/line-chart/components/hover-popover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2D,EAAE,EAAE,MAAM,OAAO,CAAC;AAgBpF,eAAO,MAAM,YAAY,EAAE,EAkIzB,CAAC"}
|
|
@@ -38,24 +38,30 @@ export const HoverPopover = observer(()=>{
|
|
|
38
38
|
display,
|
|
39
39
|
popH
|
|
40
40
|
]);
|
|
41
|
-
const popoverStyle = useMemo(()=>{
|
|
41
|
+
const { popoverStyle, arrowPosition } = useMemo(()=>{
|
|
42
42
|
const posX = svgStore.periodX(hoveredIndex);
|
|
43
43
|
const yHeights = metrics.filter((m)=>m.values[hoveredIndex] !== undefined).map((m)=>svgStore.periodY(m, hoveredIndex));
|
|
44
44
|
const barHeight = yHeights.length ? stackedTotals ? yHeights.reduce((a, b)=>a + b, 0) : Math.max(...yHeights) : 0;
|
|
45
45
|
const barHeightPercentRaw = svgStore.fpy(Math.max(0, Math.min(100, barHeight)));
|
|
46
46
|
const barHeightPercent = Math.max(0, Math.min(100, Number(barHeightPercentRaw) || 0));
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
47
|
+
const barTopPx = barHeightPercent / 100 * CHART_HEIGHT_PX;
|
|
48
|
+
const idealTopPx = barTopPx + OFFSET_PX;
|
|
49
|
+
const maxTopPx = Math.max(0, CHART_HEIGHT_PX - popH);
|
|
50
|
+
const popoverRepositioned = idealTopPx > maxTopPx;
|
|
51
|
+
const clampedTopPx = Math.min(idealTopPx, maxTopPx);
|
|
52
|
+
const topPercent = clampedTopPx / CHART_HEIGHT_PX * 100;
|
|
50
53
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
popoverStyle: {
|
|
55
|
+
top: `${topPercent.toFixed(1)}%`,
|
|
56
|
+
transform: 'translateY(0)',
|
|
57
|
+
position: 'absolute',
|
|
58
|
+
...isChartLeftSide ? {
|
|
59
|
+
left: `${svgStore.fpx(posX + 2)}%`
|
|
60
|
+
} : {
|
|
61
|
+
right: `${svgStore.fpx(102 - posX)}%`
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
arrowPosition: popoverRepositioned ? 'bottom' : 'top'
|
|
59
65
|
};
|
|
60
66
|
}, [
|
|
61
67
|
svgStore,
|
|
@@ -68,6 +74,9 @@ export const HoverPopover = observer(()=>{
|
|
|
68
74
|
if (hoveredIndex < 0 || hoveredIndex >= periods.length) {
|
|
69
75
|
return null;
|
|
70
76
|
}
|
|
77
|
+
if ((stackedTotals === null || stackedTotals === void 0 ? void 0 : stackedTotals[hoveredIndex]) === 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
71
80
|
const period = periods[hoveredIndex];
|
|
72
81
|
const partialWeek = !!period.partial;
|
|
73
82
|
return /*#__PURE__*/ _jsxs("div", {
|
|
@@ -76,7 +85,7 @@ export const HoverPopover = observer(()=>{
|
|
|
76
85
|
style: popoverStyle,
|
|
77
86
|
children: [
|
|
78
87
|
/*#__PURE__*/ _jsx("div", {
|
|
79
|
-
className: classNames(Styles.arrow, isChartLeftSide ? Styles.arrowLeft : Styles.arrowRight)
|
|
88
|
+
className: classNames(Styles.arrow, isChartLeftSide ? Styles.arrowLeft : Styles.arrowRight, arrowPosition === 'bottom' && Styles.arrowBottom)
|
|
80
89
|
}),
|
|
81
90
|
/*#__PURE__*/ _jsx(Text, {
|
|
82
91
|
size: "small",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/charts/line-chart/components/hover-popover.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useLayoutEffect, useState, FC, CSSProperties } from 'react';\nimport classNames from 'classnames';\nimport { observer } from 'mobx-react';\nimport { useDependencies } from '@servicetitan/react-ioc';\nimport { BodyText } from '@servicetitan/design-system';\nimport { LineChartStore } from '../stores/line-chart.store';\nimport { SvgStore } from '../stores/svg.store';\nimport { getFormatter } from '../utils/formatters';\nimport { periodDateTitleFormatter } from '../utils/labels';\nimport * as Styles from './hover-popover.module.less';\nimport { ColorTag } from '../../common';\nimport { Text } from '@servicetitan/anvil2';\n\nconst CHART_HEIGHT_PX = 400;\nconst OFFSET_PX = 16;\n\nexport const HoverPopover: FC = observer(() => {\n const [\n {\n periods,\n resolution,\n hoveredIndex,\n metrics,\n display,\n formattedTotalAt,\n totalLabel,\n stackedTotals,\n },\n svgStore,\n ] = useDependencies(LineChartStore, SvgStore);\n const isChartLeftSide = hoveredIndex < periods.length / 2;\n\n const formatDateTitle = useMemo(() => periodDateTitleFormatter[resolution], [resolution]);\n const formatValue = useCallback(\n (title: string, value: number, isRight: boolean) =>\n getFormatter(isRight ? display.metricsRightFormat : display.metricsLeftFormat)(value) +\n ' ' +\n title,\n [display]\n );\n\n const popRef = useRef<HTMLDivElement | null>(null);\n const [popH, setPopH] = useState(0);\n\n useLayoutEffect(() => {\n if (!popRef.current) {\n return;\n }\n const rect = popRef.current.getBoundingClientRect();\n if (rect.height && Math.abs(rect.height - popH) > 0.5) {\n setPopH(rect.height);\n }\n }, [hoveredIndex, metrics, display, popH]);\n\n const popoverStyle = useMemo<CSSProperties>(() => {\n const posX = svgStore.periodX(hoveredIndex);\n\n const yHeights = metrics\n .filter(m => m.values[hoveredIndex] !== undefined)\n .map(m => svgStore.periodY(m, hoveredIndex));\n\n const barHeight = yHeights.length\n ? stackedTotals\n ? yHeights.reduce((a, b) => a + b, 0)\n : Math.max(...yHeights)\n : 0;\n const barHeightPercentRaw = svgStore.fpy(Math.max(0, Math.min(100, barHeight)));\n const barHeightPercent = Math.max(0, Math.min(100, Number(barHeightPercentRaw) || 0));\n\n const barTopPositionPx = (barHeightPercent / 100) * CHART_HEIGHT_PX;\n const availableSpaceBelow = Math.max(0, CHART_HEIGHT_PX - barTopPositionPx - popH);\n const popoverOffsetPx = Math.min(OFFSET_PX, availableSpaceBelow);\n\n return {\n top: `${barHeightPercent}%`,\n transform: `translateY(${popoverOffsetPx}px)`,\n position: 'absolute',\n ...(isChartLeftSide\n ? { left: `${svgStore.fpx(posX + 2)}%` }\n : { right: `${svgStore.fpx(102 - posX)}%` }),\n };\n }, [svgStore, hoveredIndex, isChartLeftSide, metrics, stackedTotals, popH]);\n\n if (hoveredIndex < 0 || hoveredIndex >= periods.length) {\n return null;\n }\n\n const period = periods[hoveredIndex]!;\n const partialWeek = !!period.partial;\n\n return (\n <div\n ref={popRef}\n className={classNames(Styles.popover, 'border border-radius-1 p-1')}\n style={popoverStyle}\n >\n <div\n className={classNames(\n Styles.arrow,\n isChartLeftSide ? Styles.arrowLeft : Styles.arrowRight\n )}\n />\n <Text size=\"small\" variant=\"headline\" el=\"h6\">\n {stackedTotals\n ? `${formattedTotalAt(hoveredIndex)} ${totalLabel} | ${formatDateTitle(period)}`\n : formatDateTitle(period)}\n </Text>\n {partialWeek && (\n <BodyText size=\"xsmall\" subdued>\n Partial week\n </BodyText>\n )}\n {metrics.map(\n m =>\n m.values[hoveredIndex] !== undefined && (\n <ColorTag\n small\n label={formatValue(m.title, m.values[hoveredIndex], m.isRight)}\n color={m.color}\n key={m.title}\n className=\"m-t-1\"\n dashed={m.opts?.dashed}\n pattern={m.opts?.pattern}\n outlineColor={m.opts?.outlineColor}\n strokeColor={m.opts?.strokeColor}\n colorTagClassName={\n m.opts?.pattern === 'outline'\n ? Styles.colorTagOutlined\n : Styles.colorTag\n }\n />\n )\n )}\n </div>\n );\n});\n"],"names":["useCallback","useMemo","useRef","useLayoutEffect","useState","classNames","observer","useDependencies","BodyText","LineChartStore","SvgStore","getFormatter","periodDateTitleFormatter","Styles","ColorTag","Text","CHART_HEIGHT_PX","OFFSET_PX","HoverPopover","periods","resolution","hoveredIndex","metrics","display","formattedTotalAt","totalLabel","stackedTotals","svgStore","isChartLeftSide","length","formatDateTitle","formatValue","title","value","isRight","metricsRightFormat","metricsLeftFormat","popRef","popH","setPopH","current","rect","getBoundingClientRect","height","Math","abs","popoverStyle","posX","periodX","yHeights","filter","m","values","undefined","map","periodY","barHeight","reduce","a","b","max","barHeightPercentRaw","fpy","min","barHeightPercent","Number","barTopPositionPx","availableSpaceBelow","popoverOffsetPx","top","transform","position","left","fpx","right","period","partialWeek","partial","div","ref","className","popover","style","arrow","arrowLeft","arrowRight","size","variant","el","subdued","small","label","color","dashed","opts","pattern","outlineColor","strokeColor","colorTagClassName","colorTagOutlined","colorTag"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/charts/line-chart/components/hover-popover.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useLayoutEffect, useState, FC } from 'react';\nimport classNames from 'classnames';\nimport { observer } from 'mobx-react';\nimport { useDependencies } from '@servicetitan/react-ioc';\nimport { BodyText } from '@servicetitan/design-system';\nimport { LineChartStore } from '../stores/line-chart.store';\nimport { SvgStore } from '../stores/svg.store';\nimport { getFormatter } from '../utils/formatters';\nimport { periodDateTitleFormatter } from '../utils/labels';\nimport * as Styles from './hover-popover.module.less';\nimport { ColorTag } from '../../common';\nimport { Text } from '@servicetitan/anvil2';\n\nconst CHART_HEIGHT_PX = 400;\nconst OFFSET_PX = 16;\n\nexport const HoverPopover: FC = observer(() => {\n const [\n {\n periods,\n resolution,\n hoveredIndex,\n metrics,\n display,\n formattedTotalAt,\n totalLabel,\n stackedTotals,\n },\n svgStore,\n ] = useDependencies(LineChartStore, SvgStore);\n const isChartLeftSide = hoveredIndex < periods.length / 2;\n\n const formatDateTitle = useMemo(() => periodDateTitleFormatter[resolution], [resolution]);\n const formatValue = useCallback(\n (title: string, value: number, isRight: boolean) =>\n getFormatter(isRight ? display.metricsRightFormat : display.metricsLeftFormat)(value) +\n ' ' +\n title,\n [display]\n );\n\n const popRef = useRef<HTMLDivElement | null>(null);\n const [popH, setPopH] = useState(0);\n\n useLayoutEffect(() => {\n if (!popRef.current) {\n return;\n }\n const rect = popRef.current.getBoundingClientRect();\n if (rect.height && Math.abs(rect.height - popH) > 0.5) {\n setPopH(rect.height);\n }\n }, [hoveredIndex, metrics, display, popH]);\n\n const { popoverStyle, arrowPosition } = useMemo(() => {\n const posX = svgStore.periodX(hoveredIndex);\n\n const yHeights = metrics\n .filter(m => m.values[hoveredIndex] !== undefined)\n .map(m => svgStore.periodY(m, hoveredIndex));\n\n const barHeight = yHeights.length\n ? stackedTotals\n ? yHeights.reduce((a, b) => a + b, 0)\n : Math.max(...yHeights)\n : 0;\n const barHeightPercentRaw = svgStore.fpy(Math.max(0, Math.min(100, barHeight)));\n const barHeightPercent = Math.max(0, Math.min(100, Number(barHeightPercentRaw) || 0));\n\n const barTopPx = (barHeightPercent / 100) * CHART_HEIGHT_PX;\n const idealTopPx = barTopPx + OFFSET_PX;\n const maxTopPx = Math.max(0, CHART_HEIGHT_PX - popH);\n const popoverRepositioned = idealTopPx > maxTopPx;\n const clampedTopPx = Math.min(idealTopPx, maxTopPx);\n const topPercent = (clampedTopPx / CHART_HEIGHT_PX) * 100;\n\n return {\n popoverStyle: {\n top: `${topPercent.toFixed(1)}%`,\n transform: 'translateY(0)',\n position: 'absolute' as const,\n ...(isChartLeftSide\n ? { left: `${svgStore.fpx(posX + 2)}%` }\n : { right: `${svgStore.fpx(102 - posX)}%` }),\n },\n arrowPosition: popoverRepositioned ? 'bottom' : 'top',\n };\n }, [svgStore, hoveredIndex, isChartLeftSide, metrics, stackedTotals, popH]);\n\n if (hoveredIndex < 0 || hoveredIndex >= periods.length) {\n return null;\n }\n\n if (stackedTotals?.[hoveredIndex] === 0) {\n return null;\n }\n\n const period = periods[hoveredIndex]!;\n const partialWeek = !!period.partial;\n\n return (\n <div\n ref={popRef}\n className={classNames(Styles.popover, 'border border-radius-1 p-1')}\n style={popoverStyle}\n >\n <div\n className={classNames(\n Styles.arrow,\n isChartLeftSide ? Styles.arrowLeft : Styles.arrowRight,\n arrowPosition === 'bottom' && Styles.arrowBottom\n )}\n />\n <Text size=\"small\" variant=\"headline\" el=\"h6\">\n {stackedTotals\n ? `${formattedTotalAt(hoveredIndex)} ${totalLabel} | ${formatDateTitle(period)}`\n : formatDateTitle(period)}\n </Text>\n {partialWeek && (\n <BodyText size=\"xsmall\" subdued>\n Partial week\n </BodyText>\n )}\n {metrics.map(\n m =>\n m.values[hoveredIndex] !== undefined && (\n <ColorTag\n small\n label={formatValue(m.title, m.values[hoveredIndex], m.isRight)}\n color={m.color}\n key={m.title}\n className=\"m-t-1\"\n dashed={m.opts?.dashed}\n pattern={m.opts?.pattern}\n outlineColor={m.opts?.outlineColor}\n strokeColor={m.opts?.strokeColor}\n colorTagClassName={\n m.opts?.pattern === 'outline'\n ? Styles.colorTagOutlined\n : Styles.colorTag\n }\n />\n )\n )}\n </div>\n );\n});\n"],"names":["useCallback","useMemo","useRef","useLayoutEffect","useState","classNames","observer","useDependencies","BodyText","LineChartStore","SvgStore","getFormatter","periodDateTitleFormatter","Styles","ColorTag","Text","CHART_HEIGHT_PX","OFFSET_PX","HoverPopover","periods","resolution","hoveredIndex","metrics","display","formattedTotalAt","totalLabel","stackedTotals","svgStore","isChartLeftSide","length","formatDateTitle","formatValue","title","value","isRight","metricsRightFormat","metricsLeftFormat","popRef","popH","setPopH","current","rect","getBoundingClientRect","height","Math","abs","popoverStyle","arrowPosition","posX","periodX","yHeights","filter","m","values","undefined","map","periodY","barHeight","reduce","a","b","max","barHeightPercentRaw","fpy","min","barHeightPercent","Number","barTopPx","idealTopPx","maxTopPx","popoverRepositioned","clampedTopPx","topPercent","top","toFixed","transform","position","left","fpx","right","period","partialWeek","partial","div","ref","className","popover","style","arrow","arrowLeft","arrowRight","arrowBottom","size","variant","el","subdued","small","label","color","dashed","opts","pattern","outlineColor","strokeColor","colorTagClassName","colorTagOutlined","colorTag"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg-bars.d.ts","sourceRoot":"","sources":["../../../../../src/components/charts/line-chart/components/svg-bars.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAG3B,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI3D,UAAU,YAAY;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,
|
|
1
|
+
{"version":3,"file":"svg-bars.d.ts","sourceRoot":"","sources":["../../../../../src/components/charts/line-chart/components/svg-bars.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAG3B,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI3D,UAAU,YAAY;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CAsLpC,CAAC;AAEF,UAAU,iBAAiB;IACvB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAyB7C,CAAC"}
|
|
@@ -55,23 +55,13 @@ export const SvgBars = observer(({ metrics, isStackedBarChart, isGroupedBarChart
|
|
|
55
55
|
};
|
|
56
56
|
});
|
|
57
57
|
if (isStackedBarChart) {
|
|
58
|
-
// Use ORIGINAL calculations - keep all spacing/positioning unchanged
|
|
59
58
|
const spacingBetweenSegments = 1;
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
// Find first/last non-zero indices for visual styling
|
|
59
|
+
const totalValue = values.reduce((sum, curr)=>sum + curr.value, 0);
|
|
60
|
+
const totalYValue = values.reduce((sum, curr)=>sum + curr.val, 0);
|
|
63
61
|
const firstNonZeroIdx = values.findIndex((v)=>v.value > 0);
|
|
64
62
|
const lastNonZeroIdx = values.reduce((last, v, idx)=>v.value > 0 ? idx : last, -1);
|
|
65
|
-
// Count 0-value segments below first non-zero (for text position adjustment)
|
|
66
|
-
const zeroSegmentsBelowFirst = firstNonZeroIdx >= 0 ? values.slice(firstNonZeroIdx + 1).filter((v)=>v.value <= 0).length : 0;
|
|
67
|
-
const totalValue = values.reduce((sum, curr)=>sum + curr.value, 0);
|
|
68
63
|
if (totalValue > 0) {
|
|
69
|
-
|
|
70
|
-
* Adjust text position to maintain consistent gap with first rendered segment:
|
|
71
|
-
* 1. Subtract spacing for skipped segments from fpy argument
|
|
72
|
-
* 2. Add pixel offset for zeros below first non-zero
|
|
73
|
-
*/ const textStackedBarHeight = stackedBarHeight - (firstNonZeroIdx > 0 ? firstNonZeroIdx : 0) * spacingBetweenSegments;
|
|
74
|
-
const yTop = +fpy(textStackedBarHeight) + zeroSegmentsBelowFirst * spacingBetweenSegments;
|
|
64
|
+
const yTop = +fpy(totalYValue) - 2;
|
|
75
65
|
const scaleX = 0.3;
|
|
76
66
|
const scaleY = 1;
|
|
77
67
|
paths.push(/*#__PURE__*/ _jsx("g", {
|
|
@@ -93,21 +83,25 @@ export const SvgBars = observer(({ metrics, isStackedBarChart, isGroupedBarChart
|
|
|
93
83
|
})
|
|
94
84
|
}, `total-${i}`));
|
|
95
85
|
}
|
|
86
|
+
let stackedBarHeight = totalYValue;
|
|
87
|
+
let isFirstRendered = true;
|
|
88
|
+
const nonZeroCount = values.filter((v)=>v.value > 0).length;
|
|
89
|
+
const bottomTrim = Math.max(0, nonZeroCount - 1) * spacingBetweenSegments;
|
|
96
90
|
for(let j = 0; j < values.length; j++){
|
|
97
91
|
var _value_strokeColor;
|
|
98
92
|
const value = values[j];
|
|
99
|
-
stackedBarHeight -= spacingBetweenSegments;
|
|
100
|
-
const TOP_RADIUS = 1;
|
|
101
|
-
const xLeft = +fpx(x - barWidth / 2);
|
|
102
|
-
const width = +fpx(barWidth);
|
|
103
93
|
if (value.value <= 0) {
|
|
104
|
-
stackedBarHeight -= value.val;
|
|
105
94
|
continue;
|
|
106
95
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
if (!isFirstRendered) {
|
|
97
|
+
stackedBarHeight -= spacingBetweenSegments;
|
|
98
|
+
}
|
|
99
|
+
isFirstRendered = false;
|
|
100
|
+
const TOP_RADIUS = 1;
|
|
101
|
+
const xLeft = +fpx(x - barWidth / 2);
|
|
102
|
+
const width = +fpx(barWidth);
|
|
103
|
+
const yTop = +fpy(stackedBarHeight);
|
|
104
|
+
const height = j === lastNonZeroIdx ? +fpx(value.val - bottomTrim) : +fpx(value.val);
|
|
111
105
|
const r = j === firstNonZeroIdx ? TOP_RADIUS : 0;
|
|
112
106
|
const d = [
|
|
113
107
|
`M ${xLeft} ${yTop + height}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/charts/line-chart/components/svg-bars.tsx"],"sourcesContent":["import { FC } from 'react';\nimport { observer } from 'mobx-react';\nimport { useDependencies } from '@servicetitan/react-ioc';\nimport { ChartMetric } from '../utils/internal-interfaces';\nimport { keyVal } from '../utils/key';\nimport { SvgStore } from '../stores/svg.store';\n\ninterface SvgBarsProps {\n metrics: ChartMetric[];\n isStackedBarChart?: boolean;\n isGroupedBarChart?: boolean;\n}\n\nexport const SvgBars: FC<SvgBarsProps> = observer(\n ({ metrics, isStackedBarChart, isGroupedBarChart }) => {\n const [store] = useDependencies(SvgStore);\n const { fpx, fpy, barWidth, length } = store;\n const barWidthHalf = barWidth / 2;\n const paths = [];\n\n const patternDefs = metrics\n .filter(m => m.opts?.pattern === 'striped')\n .map(m => {\n const rotation = 20;\n const tileW = 0.6;\n const tileH = 0.6;\n const stripeWidth = Math.max(0.1, Math.floor(tileW / 20));\n const tintOpacity = 0.06;\n\n return (\n <pattern\n key={`pattern-${m.id}`}\n id={`stripe-pattern-${m.id}`}\n patternUnits=\"userSpaceOnUse\"\n width={tileW}\n height={tileH}\n patternTransform={`rotate(${rotation})`}\n >\n <rect width={tileW} height={tileH} fill={m.color} opacity={tintOpacity} />\n <rect width={stripeWidth} height={tileH} fill={m.color} />\n </pattern>\n );\n });\n\n for (let i = 0; i < length; i++) {\n const x = store.periodX(i);\n const values = metrics.map(m => ({\n id: m.id,\n color: m.valuesOpts?.[i]?.color ?? m.color,\n opacity: m.opacity,\n val: store.periodY(m, i),\n pattern: m.opts?.pattern,\n strokeColor: m.opts?.strokeColor,\n outlineColor: m.opts?.outlineColor,\n value: m.values[i],\n }));\n\n if (isStackedBarChart) {\n // Use ORIGINAL calculations - keep all spacing/positioning unchanged\n const spacingBetweenSegments = 1;\n const totalSpacing = (values.length - 1) * spacingBetweenSegments;\n let stackedBarHeight =\n values.reduce((sum, curr) => sum + curr.val, 0) + totalSpacing;\n\n // Find first/last non-zero indices for visual styling\n const firstNonZeroIdx = values.findIndex(v => v.value > 0);\n const lastNonZeroIdx = values.reduce(\n (last, v, idx) => (v.value > 0 ? idx : last),\n -1\n );\n\n // Count 0-value segments below first non-zero (for text position adjustment)\n const zeroSegmentsBelowFirst =\n firstNonZeroIdx >= 0\n ? values.slice(firstNonZeroIdx + 1).filter(v => v.value <= 0).length\n : 0;\n\n const totalValue = values.reduce((sum, curr) => sum + curr.value, 0);\n if (totalValue > 0) {\n /*\n * Adjust text position to maintain consistent gap with first rendered segment:\n * 1. Subtract spacing for skipped segments from fpy argument\n * 2. Add pixel offset for zeros below first non-zero\n */\n const textStackedBarHeight =\n stackedBarHeight -\n (firstNonZeroIdx > 0 ? firstNonZeroIdx : 0) * spacingBetweenSegments;\n const yTop =\n +fpy(textStackedBarHeight) +\n zeroSegmentsBelowFirst * spacingBetweenSegments;\n const scaleX = 0.3;\n const scaleY = 1;\n\n paths.push(\n <g\n key={`total-${i}`}\n transform={`translate(${x},${yTop}) scale(${scaleX},${scaleY})`}\n pointerEvents=\"none\"\n >\n <text\n x={0}\n y={0}\n textAnchor=\"middle\"\n dominantBaseline=\"alphabetic\"\n fontSize=\"2.5\"\n fontWeight={600}\n fill=\"#111827\"\n stroke=\"white\"\n strokeWidth={0.8}\n paintOrder=\"stroke\"\n fontFamily=\"Nunito Sans\"\n >\n {Math.round(totalValue)}\n </text>\n </g>\n );\n }\n\n for (let j = 0; j < values.length; j++) {\n const value = values[j];\n stackedBarHeight -= spacingBetweenSegments;\n\n const TOP_RADIUS = 1;\n const xLeft = +fpx(x - barWidth / 2);\n const width = +fpx(barWidth);\n\n if (value.value <= 0) {\n stackedBarHeight -= value.val;\n continue;\n }\n const zeroSegments = values.slice(j + 1).filter(v => v.value <= 0).length;\n\n // Adjust yTop: move down by the space 0-value segments would occupy\n const yTop =\n +fpy(stackedBarHeight) +\n (values.length - 2) +\n zeroSegments * spacingBetweenSegments;\n const height = j === lastNonZeroIdx ? +fpx(value.val - 2) : +fpx(value.val);\n const r = j === firstNonZeroIdx ? TOP_RADIUS : 0;\n\n const d = [\n `M ${xLeft} ${yTop + height}`, // bottom-left\n `L ${xLeft} ${yTop + r}`, // up left edge\n `Q ${xLeft} ${yTop} ${xLeft + r / 2} ${yTop}`, // top-left corner\n `L ${xLeft + width - r / 2} ${yTop}`, // across top\n `Q ${xLeft + width} ${yTop} ${xLeft + width} ${yTop + r}`, // top-right corner\n `L ${xLeft + width} ${yTop + height}`, // down right edge\n 'Z',\n ].join(' ');\n paths.push(\n <path\n key={keyVal(value.id, i)}\n d={d}\n fill={\n value.pattern === 'striped'\n ? `url(#stripe-pattern-${value.id})`\n : value.color\n }\n stroke={\n value.pattern === 'outline'\n ? value.outlineColor\n : (value.strokeColor ?? value.color)\n }\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n strokeLinejoin=\"round\"\n />\n );\n\n stackedBarHeight -= value.val;\n }\n } else if (isGroupedBarChart) {\n for (let j = 0; j < values.length; j++) {\n const groupedBarX = (j * barWidth) / values.length;\n const value = values[j];\n\n paths.push(\n <rect\n key={keyVal(value.id, i)}\n x={x + groupedBarX - barWidthHalf}\n y={fpy(value.val)}\n width={barWidth / values.length - 0.1}\n height={fpx(value.val)}\n fill={value.color}\n opacity={value.opacity}\n />\n );\n }\n } else {\n values.sort((a, b) => b.val - a.val);\n for (const value of values) {\n paths.push(\n <rect\n key={keyVal(value.id, i)}\n x={fpx(x - barWidthHalf)}\n y={fpy(value.val)}\n width={fpx(barWidth)}\n height={fpx(value.val)}\n fill={value.color}\n />\n );\n }\n }\n }\n\n return (\n <g>\n {patternDefs.length > 0 && <defs>{patternDefs}</defs>}\n {paths}\n </g>\n );\n }\n);\n\ninterface SvgBarsHoverProps {\n onHover(ind: number): void;\n onLeave(ind: number): void;\n}\n\nexport const SvgBarsHover: FC<SvgBarsHoverProps> = observer(({ onHover, onLeave }) => {\n const [store] = useDependencies(SvgStore);\n const { fpx, fpy, barWidth, length } = store;\n const barWidthHalf = barWidth / 2;\n const paths = [];\n\n for (let i = 0; i < length; i++) {\n const x = store.periodX(i);\n\n paths.push(\n <rect\n key={keyVal('_', i)}\n onMouseEnter={() => onHover(i)}\n onMouseLeave={() => onLeave(i)}\n x={fpx(x - barWidthHalf)}\n y={fpy(100)}\n width={fpx(barWidth)}\n height=\"100%\"\n fill=\"white\"\n fillOpacity=\"0\"\n />\n );\n }\n\n return <g>{paths}</g>;\n});\n"],"names":["observer","useDependencies","keyVal","SvgStore","SvgBars","metrics","isStackedBarChart","isGroupedBarChart","store","fpx","fpy","barWidth","length","barWidthHalf","paths","patternDefs","filter","m","opts","pattern","map","rotation","tileW","tileH","stripeWidth","Math","max","floor","tintOpacity","id","patternUnits","width","height","patternTransform","rect","fill","color","opacity","i","x","periodX","values","valuesOpts","val","periodY","strokeColor","outlineColor","value","spacingBetweenSegments","totalSpacing","stackedBarHeight","reduce","sum","curr","firstNonZeroIdx","findIndex","v","lastNonZeroIdx","last","idx","zeroSegmentsBelowFirst","slice","totalValue","textStackedBarHeight","yTop","scaleX","scaleY","push","g","transform","pointerEvents","text","y","textAnchor","dominantBaseline","fontSize","fontWeight","stroke","strokeWidth","paintOrder","fontFamily","round","j","TOP_RADIUS","xLeft","zeroSegments","r","d","join","path","vectorEffect","strokeLinejoin","groupedBarX","sort","a","b","defs","SvgBarsHover","onHover","onLeave","onMouseEnter","onMouseLeave","fillOpacity"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/charts/line-chart/components/svg-bars.tsx"],"sourcesContent":["import { FC } from 'react';\nimport { observer } from 'mobx-react';\nimport { useDependencies } from '@servicetitan/react-ioc';\nimport { ChartMetric } from '../utils/internal-interfaces';\nimport { keyVal } from '../utils/key';\nimport { SvgStore } from '../stores/svg.store';\n\ninterface SvgBarsProps {\n metrics: ChartMetric[];\n isStackedBarChart?: boolean;\n isGroupedBarChart?: boolean;\n}\n\nexport const SvgBars: FC<SvgBarsProps> = observer(\n ({ metrics, isStackedBarChart, isGroupedBarChart }) => {\n const [store] = useDependencies(SvgStore);\n const { fpx, fpy, barWidth, length } = store;\n const barWidthHalf = barWidth / 2;\n const paths = [];\n\n const patternDefs = metrics\n .filter(m => m.opts?.pattern === 'striped')\n .map(m => {\n const rotation = 20;\n const tileW = 0.6;\n const tileH = 0.6;\n const stripeWidth = Math.max(0.1, Math.floor(tileW / 20));\n const tintOpacity = 0.06;\n\n return (\n <pattern\n key={`pattern-${m.id}`}\n id={`stripe-pattern-${m.id}`}\n patternUnits=\"userSpaceOnUse\"\n width={tileW}\n height={tileH}\n patternTransform={`rotate(${rotation})`}\n >\n <rect width={tileW} height={tileH} fill={m.color} opacity={tintOpacity} />\n <rect width={stripeWidth} height={tileH} fill={m.color} />\n </pattern>\n );\n });\n\n for (let i = 0; i < length; i++) {\n const x = store.periodX(i);\n const values = metrics.map(m => ({\n id: m.id,\n color: m.valuesOpts?.[i]?.color ?? m.color,\n opacity: m.opacity,\n val: store.periodY(m, i),\n pattern: m.opts?.pattern,\n strokeColor: m.opts?.strokeColor,\n outlineColor: m.opts?.outlineColor,\n value: m.values[i],\n }));\n\n if (isStackedBarChart) {\n const spacingBetweenSegments = 1;\n const totalValue = values.reduce((sum, curr) => sum + curr.value, 0);\n const totalYValue = values.reduce((sum, curr) => sum + curr.val, 0);\n\n const firstNonZeroIdx = values.findIndex(v => v.value > 0);\n const lastNonZeroIdx = values.reduce(\n (last, v, idx) => (v.value > 0 ? idx : last),\n -1\n );\n\n if (totalValue > 0) {\n const yTop = +fpy(totalYValue) - 2;\n const scaleX = 0.3;\n const scaleY = 1;\n\n paths.push(\n <g\n key={`total-${i}`}\n transform={`translate(${x},${yTop}) scale(${scaleX},${scaleY})`}\n pointerEvents=\"none\"\n >\n <text\n x={0}\n y={0}\n textAnchor=\"middle\"\n dominantBaseline=\"alphabetic\"\n fontSize=\"2.5\"\n fontWeight={600}\n fill=\"#111827\"\n stroke=\"white\"\n strokeWidth={0.8}\n paintOrder=\"stroke\"\n fontFamily=\"Nunito Sans\"\n >\n {Math.round(totalValue)}\n </text>\n </g>\n );\n }\n\n let stackedBarHeight = totalYValue;\n let isFirstRendered = true;\n const nonZeroCount = values.filter(v => v.value > 0).length;\n const bottomTrim = Math.max(0, nonZeroCount - 1) * spacingBetweenSegments;\n\n for (let j = 0; j < values.length; j++) {\n const value = values[j];\n\n if (value.value <= 0) {\n continue;\n }\n\n if (!isFirstRendered) {\n stackedBarHeight -= spacingBetweenSegments;\n }\n isFirstRendered = false;\n\n const TOP_RADIUS = 1;\n const xLeft = +fpx(x - barWidth / 2);\n const width = +fpx(barWidth);\n const yTop = +fpy(stackedBarHeight);\n const height =\n j === lastNonZeroIdx ? +fpx(value.val - bottomTrim) : +fpx(value.val);\n const r = j === firstNonZeroIdx ? TOP_RADIUS : 0;\n\n const d = [\n `M ${xLeft} ${yTop + height}`, // bottom-left\n `L ${xLeft} ${yTop + r}`, // up left edge\n `Q ${xLeft} ${yTop} ${xLeft + r / 2} ${yTop}`, // top-left corner\n `L ${xLeft + width - r / 2} ${yTop}`, // across top\n `Q ${xLeft + width} ${yTop} ${xLeft + width} ${yTop + r}`, // top-right corner\n `L ${xLeft + width} ${yTop + height}`, // down right edge\n 'Z',\n ].join(' ');\n paths.push(\n <path\n key={keyVal(value.id, i)}\n d={d}\n fill={\n value.pattern === 'striped'\n ? `url(#stripe-pattern-${value.id})`\n : value.color\n }\n stroke={\n value.pattern === 'outline'\n ? value.outlineColor\n : (value.strokeColor ?? value.color)\n }\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n strokeLinejoin=\"round\"\n />\n );\n\n stackedBarHeight -= value.val;\n }\n } else if (isGroupedBarChart) {\n for (let j = 0; j < values.length; j++) {\n const groupedBarX = (j * barWidth) / values.length;\n const value = values[j];\n\n paths.push(\n <rect\n key={keyVal(value.id, i)}\n x={x + groupedBarX - barWidthHalf}\n y={fpy(value.val)}\n width={barWidth / values.length - 0.1}\n height={fpx(value.val)}\n fill={value.color}\n opacity={value.opacity}\n />\n );\n }\n } else {\n values.sort((a, b) => b.val - a.val);\n for (const value of values) {\n paths.push(\n <rect\n key={keyVal(value.id, i)}\n x={fpx(x - barWidthHalf)}\n y={fpy(value.val)}\n width={fpx(barWidth)}\n height={fpx(value.val)}\n fill={value.color}\n />\n );\n }\n }\n }\n\n return (\n <g>\n {patternDefs.length > 0 && <defs>{patternDefs}</defs>}\n {paths}\n </g>\n );\n }\n);\n\ninterface SvgBarsHoverProps {\n onHover(ind: number): void;\n onLeave(ind: number): void;\n}\n\nexport const SvgBarsHover: FC<SvgBarsHoverProps> = observer(({ onHover, onLeave }) => {\n const [store] = useDependencies(SvgStore);\n const { fpx, fpy, barWidth, length } = store;\n const barWidthHalf = barWidth / 2;\n const paths = [];\n\n for (let i = 0; i < length; i++) {\n const x = store.periodX(i);\n\n paths.push(\n <rect\n key={keyVal('_', i)}\n onMouseEnter={() => onHover(i)}\n onMouseLeave={() => onLeave(i)}\n x={fpx(x - barWidthHalf)}\n y={fpy(100)}\n width={fpx(barWidth)}\n height=\"100%\"\n fill=\"white\"\n fillOpacity=\"0\"\n />\n );\n }\n\n return <g>{paths}</g>;\n});\n"],"names":["observer","useDependencies","keyVal","SvgStore","SvgBars","metrics","isStackedBarChart","isGroupedBarChart","store","fpx","fpy","barWidth","length","barWidthHalf","paths","patternDefs","filter","m","opts","pattern","map","rotation","tileW","tileH","stripeWidth","Math","max","floor","tintOpacity","id","patternUnits","width","height","patternTransform","rect","fill","color","opacity","i","x","periodX","values","valuesOpts","val","periodY","strokeColor","outlineColor","value","spacingBetweenSegments","totalValue","reduce","sum","curr","totalYValue","firstNonZeroIdx","findIndex","v","lastNonZeroIdx","last","idx","yTop","scaleX","scaleY","push","g","transform","pointerEvents","text","y","textAnchor","dominantBaseline","fontSize","fontWeight","stroke","strokeWidth","paintOrder","fontFamily","round","stackedBarHeight","isFirstRendered","nonZeroCount","bottomTrim","j","TOP_RADIUS","xLeft","r","d","join","path","vectorEffect","strokeLinejoin","groupedBarX","sort","a","b","defs","SvgBarsHover","onHover","onLeave","onMouseEnter","onMouseLeave","fillOpacity"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../../src/components/stat/stat-card.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;AAEhD,OAAO,EAEH,mBAAmB,
|
|
1
|
+
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../../src/components/stat/stat-card.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;AAEhD,OAAO,EAEH,mBAAmB,EAMtB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAe,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAqCtE,UAAU,aAAa;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CAyEtC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CA6EtC,CAAC"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
|
-
import { BodyText, Eyebrow, Popover, Stack, Tooltip } from '@servicetitan/design-system';
|
|
4
|
+
import { BodyText, Eyebrow, Headline, Popover, Stack, Tooltip } from '@servicetitan/design-system';
|
|
5
5
|
import * as Styles from './stat-card.module.less';
|
|
6
6
|
import { formatValue } from '../../utils/formatters';
|
|
7
7
|
import { Icon } from '@servicetitan/anvil2';
|
|
8
8
|
import TrendingUpSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_up.svg';
|
|
9
9
|
import TrendingDownSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_down.svg';
|
|
10
|
+
import InfoSVG from '@servicetitan/anvil2/assets/icons/material/round/info.svg';
|
|
11
|
+
import { tokens } from '@servicetitan/tokens/core/index';
|
|
10
12
|
const calculateDiff = (value, prev, percents)=>{
|
|
11
13
|
const diff = (value - prev) * (percents ? 100 : 1);
|
|
12
14
|
const absDiff = Math.abs(diff);
|
|
@@ -82,46 +84,64 @@ export const StatCard = ({ title, description, popoverContent, value, percent, m
|
|
|
82
84
|
const [popoverShown, setPopoverShown] = useState(false);
|
|
83
85
|
const format = money ? 'money' : percent ? 'percent' : rate ? 'rate' : 'number';
|
|
84
86
|
const val = value === undefined ? '\u00A0' : formatValue(value, format);
|
|
87
|
+
const hasInfo = !!description || !!popoverContent;
|
|
85
88
|
const eyebrow = /*#__PURE__*/ _jsx(Eyebrow, {
|
|
86
|
-
className: classNames(Styles.title, 'ta-center'),
|
|
87
89
|
"data-cy": `marketing-stat-${title}-title`,
|
|
88
|
-
onMouseEnter: ()=>{
|
|
89
|
-
setPopoverShown(true);
|
|
90
|
-
},
|
|
91
90
|
children: title
|
|
92
91
|
});
|
|
93
|
-
|
|
92
|
+
const infoIcon = /*#__PURE__*/ _jsx(Icon, {
|
|
93
|
+
svg: InfoSVG,
|
|
94
|
+
color: tokens.colorNeutral100
|
|
95
|
+
});
|
|
96
|
+
const infoContent = popoverContent ? /*#__PURE__*/ _jsx(Popover, {
|
|
97
|
+
open: popoverShown,
|
|
98
|
+
trigger: infoIcon,
|
|
99
|
+
onMouseEnter: ()=>setPopoverShown(true),
|
|
100
|
+
children: popoverContent
|
|
101
|
+
}) : description ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
102
|
+
text: description,
|
|
103
|
+
"data-cy": `marketing-stat-${title}-tooltip`,
|
|
104
|
+
children: infoIcon
|
|
105
|
+
}) : null;
|
|
106
|
+
return /*#__PURE__*/ _jsx(Stack, {
|
|
94
107
|
direction: "column",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
className: classNames('p-2', {
|
|
109
|
+
'bg-white border': !clean,
|
|
110
|
+
[Styles.card]: !clean,
|
|
98
111
|
'flex-grow-1 flex-basis-0': fill
|
|
99
112
|
}, className),
|
|
100
113
|
onMouseLeave: ()=>setPopoverShown(false),
|
|
101
|
-
children:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
115
|
+
className: "p-3",
|
|
116
|
+
children: [
|
|
117
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
118
|
+
className: Styles.titleRow,
|
|
119
|
+
children: [
|
|
120
|
+
eyebrow,
|
|
121
|
+
hasInfo && infoContent
|
|
122
|
+
]
|
|
123
|
+
}),
|
|
124
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
125
|
+
className: Styles.valueRow,
|
|
126
|
+
children: [
|
|
127
|
+
/*#__PURE__*/ _jsx(Headline, {
|
|
128
|
+
size: "xlarge",
|
|
129
|
+
className: "m-b-0-i",
|
|
130
|
+
"data-cy": `marketing-stat-${title}-value`,
|
|
131
|
+
children: val
|
|
132
|
+
}),
|
|
133
|
+
!valueOnly && /*#__PURE__*/ _jsx(StatDiff, {
|
|
134
|
+
value: value,
|
|
135
|
+
prev: prev,
|
|
136
|
+
format: format,
|
|
137
|
+
inverted: inverted,
|
|
138
|
+
neutral: neutral,
|
|
139
|
+
diffPercentOnly: diffPercentOnly
|
|
140
|
+
})
|
|
141
|
+
]
|
|
142
|
+
})
|
|
143
|
+
]
|
|
144
|
+
})
|
|
125
145
|
});
|
|
126
146
|
};
|
|
127
147
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/stat/stat-card.tsx"],"sourcesContent":["import { FC, ReactNode, useState } from 'react';\nimport classNames from 'classnames';\nimport {\n BodyText,\n BodyTextPropsStrict,\n Eyebrow,\n Popover,\n Stack,\n Tooltip,\n} from '@servicetitan/design-system';\nimport * as Styles from './stat-card.module.less';\nimport { formatValue, NumberFormatter } from '../../utils/formatters';\nimport { Icon } from '@servicetitan/anvil2';\nimport TrendingUpSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_up.svg';\nimport TrendingDownSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_down.svg';\n\nconst calculateDiff = (\n value: number,\n prev: number,\n percents?: boolean\n): [number, number, boolean] => {\n const diff = (value - prev) * (percents ? 100 : 1);\n const absDiff = Math.abs(diff);\n let diffPercent = 0;\n\n if (percents) {\n diffPercent = diff;\n } else if (absDiff) {\n diffPercent = prev ? (100 * absDiff) / prev : 100;\n }\n\n return [absDiff, diffPercent, diff >= 0];\n};\n\nconst formatDifference = (value: number, isPlus: boolean, format: NumberFormatter): string => {\n return (isPlus ? '+' : '-') + formatValue(value, format);\n};\n\nconst formatDifferencePercentage = (value: number, isPlus: boolean): string => {\n if (!value) {\n return '';\n }\n\n return (isPlus ? '+' : '-') + formatValue(value, 'percent-100');\n};\n\ninterface StatDiffProps {\n value?: number;\n prev?: number;\n size?: BodyTextPropsStrict['size'];\n format: NumberFormatter;\n inverted?: boolean;\n neutral?: boolean;\n className?: string;\n diffPercentOnly?: boolean;\n}\n\nexport const StatDiff: FC<StatDiffProps> = ({\n value,\n prev,\n size,\n format,\n inverted,\n neutral,\n className,\n diffPercentOnly = false,\n}) => {\n const percents = format === 'percent';\n const [absDiff, diffPercent, isIncrease] = calculateDiff(value ?? 0, prev ?? 0, percents);\n const diff =\n absDiff === 0 ? (\n ''\n ) : (\n <Icon\n svg={isIncrease ? TrendingUpSVG : TrendingDownSVG}\n color={\n neutral\n ? 'neutral-200'\n : inverted\n ? isIncrease\n ? 'red'\n : 'green'\n : isIncrease\n ? 'green'\n : 'red'\n }\n />\n );\n let text = '';\n\n if (percents) {\n text += formatDifferencePercentage(absDiff, isIncrease);\n } else {\n const diffPercentage = formatDifferencePercentage(diffPercent, isIncrease);\n\n if (diffPercentOnly) {\n text += `${diffPercentage}`;\n } else {\n text += `${formatDifference(absDiff, isIncrease, format)}`;\n\n if (diffPercent !== 0) {\n text += ` (${diffPercentage})`;\n }\n }\n }\n\n return (\n <Stack\n className={classNames(Styles.statDiff, className)}\n justifyContent=\"center\"\n alignItems=\"center\"\n >\n <Stack.Item className=\"m-r-half m-t-half\">\n <span>{diff}</span>\n </Stack.Item>\n <Stack.Item>\n <BodyText\n size={size ?? 'small'}\n data-cy=\"stat-diff-value\"\n className={classNames({\n 'c-red-500': !neutral && (inverted ? isIncrease : !isIncrease),\n 'c-green-500': !neutral && (inverted ? !isIncrease : isIncrease),\n 'c-neutral-200': !!neutral,\n })}\n >\n {value === undefined ? '\\u00A0' : text}\n </BodyText>\n </Stack.Item>\n </Stack>\n );\n};\n\nexport interface StatCardProps {\n title: string;\n description?: string;\n popoverContent?: ReactNode;\n value?: number;\n prev?: number;\n percent?: boolean;\n money?: boolean;\n rate?: boolean;\n clean?: boolean;\n inverted?: boolean;\n neutral?: boolean;\n fill?: boolean;\n valueOnly?: boolean;\n className?: string;\n diffPercentOnly?: boolean;\n}\n\nexport const StatCard: FC<StatCardProps> = ({\n title,\n description,\n popoverContent,\n value,\n percent,\n money,\n rate,\n prev,\n clean,\n inverted,\n neutral,\n fill,\n valueOnly,\n className,\n diffPercentOnly = false,\n}) => {\n const [popoverShown, setPopoverShown] = useState(false);\n const format = money ? 'money' : percent ? 'percent' : rate ? 'rate' : 'number';\n const val = value === undefined ? '\\u00A0' : formatValue(value, format);\n\n const eyebrow = (\n <Eyebrow\n className={classNames(Styles.title, 'ta-center')}\n data-cy={`marketing-stat-${title}-title`}\n onMouseEnter={() => {\n setPopoverShown(true);\n }}\n >\n {title}\n </Eyebrow>\n );\n\n return (\n <Stack\n direction=\"column\"\n alignItems=\"center\"\n className={classNames(\n 'p-y-3',\n {\n 'bg-white border-radius-2 border': !clean,\n 'flex-grow-1 flex-basis-0': fill,\n },\n className\n )}\n onMouseLeave={() => setPopoverShown(false)}\n >\n {popoverContent ? (\n <Popover open={popoverShown} trigger={eyebrow}>\n {popoverContent}\n </Popover>\n ) : description ? (\n <Tooltip text={description} data-cy={`marketing-stat-${title}-tooltip`}>\n {eyebrow}\n </Tooltip>\n ) : (\n eyebrow\n )}\n <BodyText className=\"fs-6-i ff-display\" data-cy={`marketing-stat-${title}-value`}>\n {val}\n </BodyText>\n {!valueOnly && (\n <StatDiff\n value={value}\n prev={prev}\n format={format}\n inverted={inverted}\n neutral={neutral}\n diffPercentOnly={diffPercentOnly}\n />\n )}\n </Stack>\n );\n};\n"],"names":["useState","classNames","BodyText","Eyebrow","Popover","Stack","Tooltip","Styles","formatValue","Icon","TrendingUpSVG","TrendingDownSVG","calculateDiff","value","prev","percents","diff","absDiff","Math","abs","diffPercent","formatDifference","isPlus","format","formatDifferencePercentage","StatDiff","size","inverted","neutral","className","diffPercentOnly","isIncrease","svg","color","text","diffPercentage","statDiff","justifyContent","alignItems","Item","span","data-cy","undefined","StatCard","title","description","popoverContent","percent","money","rate","clean","fill","valueOnly","popoverShown","setPopoverShown","val","eyebrow","onMouseEnter","direction","onMouseLeave","open","trigger"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/stat/stat-card.tsx"],"sourcesContent":["import { FC, ReactNode, useState } from 'react';\nimport classNames from 'classnames';\nimport {\n BodyText,\n BodyTextPropsStrict,\n Eyebrow,\n Headline,\n Popover,\n Stack,\n Tooltip,\n} from '@servicetitan/design-system';\nimport * as Styles from './stat-card.module.less';\nimport { formatValue, NumberFormatter } from '../../utils/formatters';\nimport { Icon } from '@servicetitan/anvil2';\nimport TrendingUpSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_up.svg';\nimport TrendingDownSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_down.svg';\nimport InfoSVG from '@servicetitan/anvil2/assets/icons/material/round/info.svg';\nimport { tokens } from '@servicetitan/tokens/core/index';\n\nconst calculateDiff = (\n value: number,\n prev: number,\n percents?: boolean\n): [number, number, boolean] => {\n const diff = (value - prev) * (percents ? 100 : 1);\n const absDiff = Math.abs(diff);\n let diffPercent = 0;\n\n if (percents) {\n diffPercent = diff;\n } else if (absDiff) {\n diffPercent = prev ? (100 * absDiff) / prev : 100;\n }\n\n return [absDiff, diffPercent, diff >= 0];\n};\n\nconst formatDifference = (value: number, isPlus: boolean, format: NumberFormatter): string => {\n return (isPlus ? '+' : '-') + formatValue(value, format);\n};\n\nconst formatDifferencePercentage = (value: number, isPlus: boolean): string => {\n if (!value) {\n return '';\n }\n\n return (isPlus ? '+' : '-') + formatValue(value, 'percent-100');\n};\n\ninterface StatDiffProps {\n value?: number;\n prev?: number;\n size?: BodyTextPropsStrict['size'];\n format: NumberFormatter;\n inverted?: boolean;\n neutral?: boolean;\n className?: string;\n diffPercentOnly?: boolean;\n}\n\nexport const StatDiff: FC<StatDiffProps> = ({\n value,\n prev,\n size,\n format,\n inverted,\n neutral,\n className,\n diffPercentOnly = false,\n}) => {\n const percents = format === 'percent';\n const [absDiff, diffPercent, isIncrease] = calculateDiff(value ?? 0, prev ?? 0, percents);\n const diff =\n absDiff === 0 ? (\n ''\n ) : (\n <Icon\n svg={isIncrease ? TrendingUpSVG : TrendingDownSVG}\n color={\n neutral\n ? 'neutral-200'\n : inverted\n ? isIncrease\n ? 'red'\n : 'green'\n : isIncrease\n ? 'green'\n : 'red'\n }\n />\n );\n let text = '';\n\n if (percents) {\n text += formatDifferencePercentage(absDiff, isIncrease);\n } else {\n const diffPercentage = formatDifferencePercentage(diffPercent, isIncrease);\n\n if (diffPercentOnly) {\n text += `${diffPercentage}`;\n } else {\n text += `${formatDifference(absDiff, isIncrease, format)}`;\n\n if (diffPercent !== 0) {\n text += ` (${diffPercentage})`;\n }\n }\n }\n\n return (\n <Stack\n className={classNames(Styles.statDiff, className)}\n justifyContent=\"center\"\n alignItems=\"center\"\n >\n <Stack.Item className=\"m-r-half m-t-half\">\n <span>{diff}</span>\n </Stack.Item>\n <Stack.Item>\n <BodyText\n size={size ?? 'small'}\n data-cy=\"stat-diff-value\"\n className={classNames({\n 'c-red-500': !neutral && (inverted ? isIncrease : !isIncrease),\n 'c-green-500': !neutral && (inverted ? !isIncrease : isIncrease),\n 'c-neutral-200': !!neutral,\n })}\n >\n {value === undefined ? '\\u00A0' : text}\n </BodyText>\n </Stack.Item>\n </Stack>\n );\n};\n\nexport interface StatCardProps {\n title: string;\n description?: string;\n popoverContent?: ReactNode;\n value?: number;\n prev?: number;\n percent?: boolean;\n money?: boolean;\n rate?: boolean;\n clean?: boolean;\n inverted?: boolean;\n neutral?: boolean;\n fill?: boolean;\n valueOnly?: boolean;\n className?: string;\n diffPercentOnly?: boolean;\n}\n\nexport const StatCard: FC<StatCardProps> = ({\n title,\n description,\n popoverContent,\n value,\n percent,\n money,\n rate,\n prev,\n clean,\n inverted,\n neutral,\n fill,\n valueOnly,\n className,\n diffPercentOnly = false,\n}) => {\n const [popoverShown, setPopoverShown] = useState(false);\n const format = money ? 'money' : percent ? 'percent' : rate ? 'rate' : 'number';\n const val = value === undefined ? '\\u00A0' : formatValue(value, format);\n const hasInfo = !!description || !!popoverContent;\n\n const eyebrow = <Eyebrow data-cy={`marketing-stat-${title}-title`}>{title}</Eyebrow>;\n\n const infoIcon = <Icon svg={InfoSVG} color={tokens.colorNeutral100} />;\n\n const infoContent = popoverContent ? (\n <Popover open={popoverShown} trigger={infoIcon} onMouseEnter={() => setPopoverShown(true)}>\n {popoverContent}\n </Popover>\n ) : description ? (\n <Tooltip text={description} data-cy={`marketing-stat-${title}-tooltip`}>\n {infoIcon}\n </Tooltip>\n ) : null;\n\n return (\n <Stack\n direction=\"column\"\n className={classNames(\n 'p-2',\n {\n 'bg-white border': !clean,\n [Styles.card]: !clean,\n 'flex-grow-1 flex-basis-0': fill,\n },\n className\n )}\n onMouseLeave={() => setPopoverShown(false)}\n >\n <div className=\"p-3\">\n <div className={Styles.titleRow}>\n {eyebrow}\n {hasInfo && infoContent}\n </div>\n <div className={Styles.valueRow}>\n <Headline\n size=\"xlarge\"\n className=\"m-b-0-i\"\n data-cy={`marketing-stat-${title}-value`}\n >\n {val}\n </Headline>\n {!valueOnly && (\n <StatDiff\n value={value}\n prev={prev}\n format={format}\n inverted={inverted}\n neutral={neutral}\n diffPercentOnly={diffPercentOnly}\n />\n )}\n </div>\n </div>\n </Stack>\n );\n};\n"],"names":["useState","classNames","BodyText","Eyebrow","Headline","Popover","Stack","Tooltip","Styles","formatValue","Icon","TrendingUpSVG","TrendingDownSVG","InfoSVG","tokens","calculateDiff","value","prev","percents","diff","absDiff","Math","abs","diffPercent","formatDifference","isPlus","format","formatDifferencePercentage","StatDiff","size","inverted","neutral","className","diffPercentOnly","isIncrease","svg","color","text","diffPercentage","statDiff","justifyContent","alignItems","Item","span","data-cy","undefined","StatCard","title","description","popoverContent","percent","money","rate","clean","fill","valueOnly","popoverShown","setPopoverShown","val","hasInfo","eyebrow","infoIcon","colorNeutral100","infoContent","open","trigger","onMouseEnter","direction","card","onMouseLeave","div","titleRow","valueRow"],"mappings":";AAAA,SAAwBA,QAAQ,QAAQ,QAAQ;AAChD,OAAOC,gBAAgB,aAAa;AACpC,SACIC,QAAQ,EAERC,OAAO,EACPC,QAAQ,EACRC,OAAO,EACPC,KAAK,EACLC,OAAO,QACJ,8BAA8B;AACrC,YAAYC,YAAY,0BAA0B;AAClD,SAASC,WAAW,QAAyB,yBAAyB;AACtE,SAASC,IAAI,QAAQ,uBAAuB;AAC5C,OAAOC,mBAAmB,mEAAmE;AAC7F,OAAOC,qBAAqB,qEAAqE;AACjG,OAAOC,aAAa,4DAA4D;AAChF,SAASC,MAAM,QAAQ,kCAAkC;AAEzD,MAAMC,gBAAgB,CAClBC,OACAC,MACAC;IAEA,MAAMC,OAAO,AAACH,CAAAA,QAAQC,IAAG,IAAMC,CAAAA,WAAW,MAAM,CAAA;IAChD,MAAME,UAAUC,KAAKC,GAAG,CAACH;IACzB,IAAII,cAAc;IAElB,IAAIL,UAAU;QACVK,cAAcJ;IAClB,OAAO,IAAIC,SAAS;QAChBG,cAAcN,OAAO,AAAC,MAAMG,UAAWH,OAAO;IAClD;IAEA,OAAO;QAACG;QAASG;QAAaJ,QAAQ;KAAE;AAC5C;AAEA,MAAMK,mBAAmB,CAACR,OAAeS,QAAiBC;IACtD,OAAO,AAACD,CAAAA,SAAS,MAAM,GAAE,IAAKhB,YAAYO,OAAOU;AACrD;AAEA,MAAMC,6BAA6B,CAACX,OAAeS;IAC/C,IAAI,CAACT,OAAO;QACR,OAAO;IACX;IAEA,OAAO,AAACS,CAAAA,SAAS,MAAM,GAAE,IAAKhB,YAAYO,OAAO;AACrD;AAaA,OAAO,MAAMY,WAA8B,CAAC,EACxCZ,KAAK,EACLC,IAAI,EACJY,IAAI,EACJH,MAAM,EACNI,QAAQ,EACRC,OAAO,EACPC,SAAS,EACTC,kBAAkB,KAAK,EAC1B;IACG,MAAMf,WAAWQ,WAAW;IAC5B,MAAM,CAACN,SAASG,aAAaW,WAAW,GAAGnB,cAAcC,kBAAAA,mBAAAA,QAAS,GAAGC,iBAAAA,kBAAAA,OAAQ,GAAGC;IAChF,MAAMC,OACFC,YAAY,IACR,mBAEA,KAACV;QACGyB,KAAKD,aAAavB,gBAAgBC;QAClCwB,OACIL,UACM,gBACAD,WACEI,aACI,QACA,UACJA,aACE,UACA;;IAI1B,IAAIG,OAAO;IAEX,IAAInB,UAAU;QACVmB,QAAQV,2BAA2BP,SAASc;IAChD,OAAO;QACH,MAAMI,iBAAiBX,2BAA2BJ,aAAaW;QAE/D,IAAID,iBAAiB;YACjBI,QAAQ,GAAGC,gBAAgB;QAC/B,OAAO;YACHD,QAAQ,GAAGb,iBAAiBJ,SAASc,YAAYR,SAAS;YAE1D,IAAIH,gBAAgB,GAAG;gBACnBc,QAAQ,CAAC,EAAE,EAAEC,eAAe,CAAC,CAAC;YAClC;QACJ;IACJ;IAEA,qBACI,MAAChC;QACG0B,WAAW/B,WAAWO,OAAO+B,QAAQ,EAAEP;QACvCQ,gBAAe;QACfC,YAAW;;0BAEX,KAACnC,MAAMoC,IAAI;gBAACV,WAAU;0BAClB,cAAA,KAACW;8BAAMxB;;;0BAEX,KAACb,MAAMoC,IAAI;0BACP,cAAA,KAACxC;oBACG2B,IAAI,EAAEA,iBAAAA,kBAAAA,OAAQ;oBACde,WAAQ;oBACRZ,WAAW/B,WAAW;wBAClB,aAAa,CAAC8B,WAAYD,CAAAA,WAAWI,aAAa,CAACA,UAAS;wBAC5D,eAAe,CAACH,WAAYD,CAAAA,WAAW,CAACI,aAAaA,UAAS;wBAC9D,iBAAiB,CAAC,CAACH;oBACvB;8BAECf,UAAU6B,YAAY,WAAWR;;;;;AAKtD,EAAE;AAoBF,OAAO,MAAMS,WAA8B,CAAC,EACxCC,KAAK,EACLC,WAAW,EACXC,cAAc,EACdjC,KAAK,EACLkC,OAAO,EACPC,KAAK,EACLC,IAAI,EACJnC,IAAI,EACJoC,KAAK,EACLvB,QAAQ,EACRC,OAAO,EACPuB,IAAI,EACJC,SAAS,EACTvB,SAAS,EACTC,kBAAkB,KAAK,EAC1B;IACG,MAAM,CAACuB,cAAcC,gBAAgB,GAAGzD,SAAS;IACjD,MAAM0B,SAASyB,QAAQ,UAAUD,UAAU,YAAYE,OAAO,SAAS;IACvE,MAAMM,MAAM1C,UAAU6B,YAAY,WAAWpC,YAAYO,OAAOU;IAChE,MAAMiC,UAAU,CAAC,CAACX,eAAe,CAAC,CAACC;IAEnC,MAAMW,wBAAU,KAACzD;QAAQyC,WAAS,CAAC,eAAe,EAAEG,MAAM,MAAM,CAAC;kBAAGA;;IAEpE,MAAMc,yBAAW,KAACnD;QAAKyB,KAAKtB;QAASuB,OAAOtB,OAAOgD,eAAe;;IAElE,MAAMC,cAAcd,+BAChB,KAAC5C;QAAQ2D,MAAMR;QAAcS,SAASJ;QAAUK,cAAc,IAAMT,gBAAgB;kBAC/ER;SAELD,4BACA,KAACzC;QAAQ8B,MAAMW;QAAaJ,WAAS,CAAC,eAAe,EAAEG,MAAM,QAAQ,CAAC;kBACjEc;SAEL;IAEJ,qBACI,KAACvD;QACG6D,WAAU;QACVnC,WAAW/B,WACP,OACA;YACI,mBAAmB,CAACoD;YACpB,CAAC7C,OAAO4D,IAAI,CAAC,EAAE,CAACf;YAChB,4BAA4BC;QAChC,GACAtB;QAEJqC,cAAc,IAAMZ,gBAAgB;kBAEpC,cAAA,MAACa;YAAItC,WAAU;;8BACX,MAACsC;oBAAItC,WAAWxB,OAAO+D,QAAQ;;wBAC1BX;wBACAD,WAAWI;;;8BAEhB,MAACO;oBAAItC,WAAWxB,OAAOgE,QAAQ;;sCAC3B,KAACpE;4BACGyB,MAAK;4BACLG,WAAU;4BACVY,WAAS,CAAC,eAAe,EAAEG,MAAM,MAAM,CAAC;sCAEvCW;;wBAEJ,CAACH,2BACE,KAAC3B;4BACGZ,OAAOA;4BACPC,MAAMA;4BACNS,QAAQA;4BACRI,UAAUA;4BACVC,SAASA;4BACTE,iBAAiBA;;;;;;;AAO7C,EAAE"}
|
|
@@ -11,6 +11,21 @@
|
|
|
11
11
|
margin-bottom: 2px;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
.title {
|
|
15
|
-
|
|
14
|
+
.title-row {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: flex-start;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.value-row {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
align-items: flex-end;
|
|
24
|
+
gap: 8px;
|
|
25
|
+
margin-top: @spacing-1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.card {
|
|
29
|
+
border-radius: 12px;
|
|
30
|
+
min-width: min-content;
|
|
16
31
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/marketing-ui",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"description": "Marketing UI component and utils",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"less": true,
|
|
54
54
|
"webpack": false
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "7e2b147613dc2f96854d2e2c4458a4891dc6aadc"
|
|
57
57
|
}
|
|
@@ -81,7 +81,11 @@ export const ColorTag: FC<ColorTagProps> = ({
|
|
|
81
81
|
dashed
|
|
82
82
|
? { borderColor: strokeColor ?? color }
|
|
83
83
|
: pattern === 'outline'
|
|
84
|
-
? {
|
|
84
|
+
? {
|
|
85
|
+
borderColor: outlineColor ?? color,
|
|
86
|
+
backgroundColor: color,
|
|
87
|
+
borderRadius: radius,
|
|
88
|
+
}
|
|
85
89
|
: { backgroundColor: color, borderRadius: radius }
|
|
86
90
|
}
|
|
87
91
|
/>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef, useLayoutEffect, useState, FC
|
|
1
|
+
import { useCallback, useMemo, useRef, useLayoutEffect, useState, FC } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
4
|
import { useDependencies } from '@servicetitan/react-ioc';
|
|
@@ -52,7 +52,7 @@ export const HoverPopover: FC = observer(() => {
|
|
|
52
52
|
}
|
|
53
53
|
}, [hoveredIndex, metrics, display, popH]);
|
|
54
54
|
|
|
55
|
-
const popoverStyle = useMemo
|
|
55
|
+
const { popoverStyle, arrowPosition } = useMemo(() => {
|
|
56
56
|
const posX = svgStore.periodX(hoveredIndex);
|
|
57
57
|
|
|
58
58
|
const yHeights = metrics
|
|
@@ -67,17 +67,23 @@ export const HoverPopover: FC = observer(() => {
|
|
|
67
67
|
const barHeightPercentRaw = svgStore.fpy(Math.max(0, Math.min(100, barHeight)));
|
|
68
68
|
const barHeightPercent = Math.max(0, Math.min(100, Number(barHeightPercentRaw) || 0));
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
70
|
+
const barTopPx = (barHeightPercent / 100) * CHART_HEIGHT_PX;
|
|
71
|
+
const idealTopPx = barTopPx + OFFSET_PX;
|
|
72
|
+
const maxTopPx = Math.max(0, CHART_HEIGHT_PX - popH);
|
|
73
|
+
const popoverRepositioned = idealTopPx > maxTopPx;
|
|
74
|
+
const clampedTopPx = Math.min(idealTopPx, maxTopPx);
|
|
75
|
+
const topPercent = (clampedTopPx / CHART_HEIGHT_PX) * 100;
|
|
73
76
|
|
|
74
77
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
popoverStyle: {
|
|
79
|
+
top: `${topPercent.toFixed(1)}%`,
|
|
80
|
+
transform: 'translateY(0)',
|
|
81
|
+
position: 'absolute' as const,
|
|
82
|
+
...(isChartLeftSide
|
|
83
|
+
? { left: `${svgStore.fpx(posX + 2)}%` }
|
|
84
|
+
: { right: `${svgStore.fpx(102 - posX)}%` }),
|
|
85
|
+
},
|
|
86
|
+
arrowPosition: popoverRepositioned ? 'bottom' : 'top',
|
|
81
87
|
};
|
|
82
88
|
}, [svgStore, hoveredIndex, isChartLeftSide, metrics, stackedTotals, popH]);
|
|
83
89
|
|
|
@@ -85,6 +91,10 @@ export const HoverPopover: FC = observer(() => {
|
|
|
85
91
|
return null;
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
if (stackedTotals?.[hoveredIndex] === 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
const period = periods[hoveredIndex]!;
|
|
89
99
|
const partialWeek = !!period.partial;
|
|
90
100
|
|
|
@@ -97,7 +107,8 @@ export const HoverPopover: FC = observer(() => {
|
|
|
97
107
|
<div
|
|
98
108
|
className={classNames(
|
|
99
109
|
Styles.arrow,
|
|
100
|
-
isChartLeftSide ? Styles.arrowLeft : Styles.arrowRight
|
|
110
|
+
isChartLeftSide ? Styles.arrowLeft : Styles.arrowRight,
|
|
111
|
+
arrowPosition === 'bottom' && Styles.arrowBottom
|
|
101
112
|
)}
|
|
102
113
|
/>
|
|
103
114
|
<Text size="small" variant="headline" el="h6">
|
|
@@ -56,38 +56,18 @@ export const SvgBars: FC<SvgBarsProps> = observer(
|
|
|
56
56
|
}));
|
|
57
57
|
|
|
58
58
|
if (isStackedBarChart) {
|
|
59
|
-
// Use ORIGINAL calculations - keep all spacing/positioning unchanged
|
|
60
59
|
const spacingBetweenSegments = 1;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
values.reduce((sum, curr) => sum + curr.val, 0) + totalSpacing;
|
|
60
|
+
const totalValue = values.reduce((sum, curr) => sum + curr.value, 0);
|
|
61
|
+
const totalYValue = values.reduce((sum, curr) => sum + curr.val, 0);
|
|
64
62
|
|
|
65
|
-
// Find first/last non-zero indices for visual styling
|
|
66
63
|
const firstNonZeroIdx = values.findIndex(v => v.value > 0);
|
|
67
64
|
const lastNonZeroIdx = values.reduce(
|
|
68
65
|
(last, v, idx) => (v.value > 0 ? idx : last),
|
|
69
66
|
-1
|
|
70
67
|
);
|
|
71
68
|
|
|
72
|
-
// Count 0-value segments below first non-zero (for text position adjustment)
|
|
73
|
-
const zeroSegmentsBelowFirst =
|
|
74
|
-
firstNonZeroIdx >= 0
|
|
75
|
-
? values.slice(firstNonZeroIdx + 1).filter(v => v.value <= 0).length
|
|
76
|
-
: 0;
|
|
77
|
-
|
|
78
|
-
const totalValue = values.reduce((sum, curr) => sum + curr.value, 0);
|
|
79
69
|
if (totalValue > 0) {
|
|
80
|
-
|
|
81
|
-
* Adjust text position to maintain consistent gap with first rendered segment:
|
|
82
|
-
* 1. Subtract spacing for skipped segments from fpy argument
|
|
83
|
-
* 2. Add pixel offset for zeros below first non-zero
|
|
84
|
-
*/
|
|
85
|
-
const textStackedBarHeight =
|
|
86
|
-
stackedBarHeight -
|
|
87
|
-
(firstNonZeroIdx > 0 ? firstNonZeroIdx : 0) * spacingBetweenSegments;
|
|
88
|
-
const yTop =
|
|
89
|
-
+fpy(textStackedBarHeight) +
|
|
90
|
-
zeroSegmentsBelowFirst * spacingBetweenSegments;
|
|
70
|
+
const yTop = +fpy(totalYValue) - 2;
|
|
91
71
|
const scaleX = 0.3;
|
|
92
72
|
const scaleY = 1;
|
|
93
73
|
|
|
@@ -116,26 +96,29 @@ export const SvgBars: FC<SvgBarsProps> = observer(
|
|
|
116
96
|
);
|
|
117
97
|
}
|
|
118
98
|
|
|
99
|
+
let stackedBarHeight = totalYValue;
|
|
100
|
+
let isFirstRendered = true;
|
|
101
|
+
const nonZeroCount = values.filter(v => v.value > 0).length;
|
|
102
|
+
const bottomTrim = Math.max(0, nonZeroCount - 1) * spacingBetweenSegments;
|
|
103
|
+
|
|
119
104
|
for (let j = 0; j < values.length; j++) {
|
|
120
105
|
const value = values[j];
|
|
121
|
-
stackedBarHeight -= spacingBetweenSegments;
|
|
122
|
-
|
|
123
|
-
const TOP_RADIUS = 1;
|
|
124
|
-
const xLeft = +fpx(x - barWidth / 2);
|
|
125
|
-
const width = +fpx(barWidth);
|
|
126
106
|
|
|
127
107
|
if (value.value <= 0) {
|
|
128
|
-
stackedBarHeight -= value.val;
|
|
129
108
|
continue;
|
|
130
109
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
110
|
+
|
|
111
|
+
if (!isFirstRendered) {
|
|
112
|
+
stackedBarHeight -= spacingBetweenSegments;
|
|
113
|
+
}
|
|
114
|
+
isFirstRendered = false;
|
|
115
|
+
|
|
116
|
+
const TOP_RADIUS = 1;
|
|
117
|
+
const xLeft = +fpx(x - barWidth / 2);
|
|
118
|
+
const width = +fpx(barWidth);
|
|
119
|
+
const yTop = +fpy(stackedBarHeight);
|
|
120
|
+
const height =
|
|
121
|
+
j === lastNonZeroIdx ? +fpx(value.val - bottomTrim) : +fpx(value.val);
|
|
139
122
|
const r = j === firstNonZeroIdx ? TOP_RADIUS : 0;
|
|
140
123
|
|
|
141
124
|
const d = [
|
|
@@ -11,6 +11,21 @@
|
|
|
11
11
|
margin-bottom: 2px;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
.title {
|
|
15
|
-
|
|
14
|
+
.title-row {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: flex-start;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.value-row {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
align-items: flex-end;
|
|
24
|
+
gap: 8px;
|
|
25
|
+
margin-top: @spacing-1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.card {
|
|
29
|
+
border-radius: 12px;
|
|
30
|
+
min-width: min-content;
|
|
16
31
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
BodyText,
|
|
5
5
|
BodyTextPropsStrict,
|
|
6
6
|
Eyebrow,
|
|
7
|
+
Headline,
|
|
7
8
|
Popover,
|
|
8
9
|
Stack,
|
|
9
10
|
Tooltip,
|
|
@@ -13,6 +14,8 @@ import { formatValue, NumberFormatter } from '../../utils/formatters';
|
|
|
13
14
|
import { Icon } from '@servicetitan/anvil2';
|
|
14
15
|
import TrendingUpSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_up.svg';
|
|
15
16
|
import TrendingDownSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_down.svg';
|
|
17
|
+
import InfoSVG from '@servicetitan/anvil2/assets/icons/material/round/info.svg';
|
|
18
|
+
import { tokens } from '@servicetitan/tokens/core/index';
|
|
16
19
|
|
|
17
20
|
const calculateDiff = (
|
|
18
21
|
value: number,
|
|
@@ -168,57 +171,61 @@ export const StatCard: FC<StatCardProps> = ({
|
|
|
168
171
|
const [popoverShown, setPopoverShown] = useState(false);
|
|
169
172
|
const format = money ? 'money' : percent ? 'percent' : rate ? 'rate' : 'number';
|
|
170
173
|
const val = value === undefined ? '\u00A0' : formatValue(value, format);
|
|
174
|
+
const hasInfo = !!description || !!popoverContent;
|
|
171
175
|
|
|
172
|
-
const eyebrow =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
const eyebrow = <Eyebrow data-cy={`marketing-stat-${title}-title`}>{title}</Eyebrow>;
|
|
177
|
+
|
|
178
|
+
const infoIcon = <Icon svg={InfoSVG} color={tokens.colorNeutral100} />;
|
|
179
|
+
|
|
180
|
+
const infoContent = popoverContent ? (
|
|
181
|
+
<Popover open={popoverShown} trigger={infoIcon} onMouseEnter={() => setPopoverShown(true)}>
|
|
182
|
+
{popoverContent}
|
|
183
|
+
</Popover>
|
|
184
|
+
) : description ? (
|
|
185
|
+
<Tooltip text={description} data-cy={`marketing-stat-${title}-tooltip`}>
|
|
186
|
+
{infoIcon}
|
|
187
|
+
</Tooltip>
|
|
188
|
+
) : null;
|
|
183
189
|
|
|
184
190
|
return (
|
|
185
191
|
<Stack
|
|
186
192
|
direction="column"
|
|
187
|
-
alignItems="center"
|
|
188
193
|
className={classNames(
|
|
189
|
-
'p-
|
|
194
|
+
'p-2',
|
|
190
195
|
{
|
|
191
|
-
'bg-white border
|
|
196
|
+
'bg-white border': !clean,
|
|
197
|
+
[Styles.card]: !clean,
|
|
192
198
|
'flex-grow-1 flex-basis-0': fill,
|
|
193
199
|
},
|
|
194
200
|
className
|
|
195
201
|
)}
|
|
196
202
|
onMouseLeave={() => setPopoverShown(false)}
|
|
197
203
|
>
|
|
198
|
-
|
|
199
|
-
<
|
|
200
|
-
{popoverContent}
|
|
201
|
-
</Popover>
|
|
202
|
-
) : description ? (
|
|
203
|
-
<Tooltip text={description} data-cy={`marketing-stat-${title}-tooltip`}>
|
|
204
|
+
<div className="p-3">
|
|
205
|
+
<div className={Styles.titleRow}>
|
|
204
206
|
{eyebrow}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
207
|
+
{hasInfo && infoContent}
|
|
208
|
+
</div>
|
|
209
|
+
<div className={Styles.valueRow}>
|
|
210
|
+
<Headline
|
|
211
|
+
size="xlarge"
|
|
212
|
+
className="m-b-0-i"
|
|
213
|
+
data-cy={`marketing-stat-${title}-value`}
|
|
214
|
+
>
|
|
215
|
+
{val}
|
|
216
|
+
</Headline>
|
|
217
|
+
{!valueOnly && (
|
|
218
|
+
<StatDiff
|
|
219
|
+
value={value}
|
|
220
|
+
prev={prev}
|
|
221
|
+
format={format}
|
|
222
|
+
inverted={inverted}
|
|
223
|
+
neutral={neutral}
|
|
224
|
+
diffPercentOnly={diffPercentOnly}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
222
229
|
</Stack>
|
|
223
230
|
);
|
|
224
231
|
};
|