@servicetitan/marketing-ui 7.4.0 → 7.6.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.
@@ -28,6 +28,8 @@ export interface StatCardProps {
28
28
  valueOnly?: boolean;
29
29
  className?: string;
30
30
  diffPercentOnly?: boolean;
31
+ centered?: boolean;
32
+ loading?: boolean;
31
33
  }
32
34
  export declare const StatCard: FC<StatCardProps>;
33
35
  export {};
@@ -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,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
+ {"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../../src/components/stat/stat-card.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAY,SAAS,EAAY,MAAM,OAAO,CAAC;AAE1D,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;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CA6FtC,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { Fragment, useState } from 'react';
3
3
  import classNames from 'classnames';
4
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
- import { Icon } from '@servicetitan/anvil2';
7
+ import { Icon, Skeleton } 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
10
  import InfoSVG from '@servicetitan/anvil2/assets/icons/material/round/info.svg';
@@ -80,7 +80,7 @@ export const StatDiff = ({ value, prev, size, format, inverted, neutral, classNa
80
80
  ]
81
81
  });
82
82
  };
83
- export const StatCard = ({ title, description, popoverContent, value, percent, money, rate, prev, clean, inverted, neutral, fill, valueOnly, className, diffPercentOnly = false })=>{
83
+ export const StatCard = ({ title, description, popoverContent, value, percent, money, rate, prev, clean, inverted, neutral, fill, valueOnly, className, diffPercentOnly = false, centered, loading })=>{
84
84
  const [popoverShown, setPopoverShown] = useState(false);
85
85
  const format = money ? 'money' : percent ? 'percent' : rate ? 'rate' : 'number';
86
86
  const val = value === undefined ? '\u00A0' : formatValue(value, format);
@@ -93,15 +93,21 @@ export const StatCard = ({ title, description, popoverContent, value, percent, m
93
93
  svg: InfoSVG,
94
94
  color: tokens.colorNeutral100
95
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
96
+ const infoContent = popoverContent ? /*#__PURE__*/ _jsx("div", {
97
+ className: Styles.infoIcon,
98
+ children: /*#__PURE__*/ _jsx(Popover, {
99
+ open: popoverShown,
100
+ trigger: infoIcon,
101
+ onMouseEnter: ()=>setPopoverShown(true),
102
+ children: popoverContent
103
+ })
104
+ }) : description ? /*#__PURE__*/ _jsx("div", {
105
+ className: Styles.infoIcon,
106
+ children: /*#__PURE__*/ _jsx(Tooltip, {
107
+ text: description,
108
+ "data-cy": `marketing-stat-${title}-tooltip`,
109
+ children: infoIcon
110
+ })
105
111
  }) : null;
106
112
  return /*#__PURE__*/ _jsx(Stack, {
107
113
  direction: "column",
@@ -112,33 +118,42 @@ export const StatCard = ({ title, description, popoverContent, value, percent, m
112
118
  }, className),
113
119
  onMouseLeave: ()=>setPopoverShown(false),
114
120
  children: /*#__PURE__*/ _jsxs("div", {
115
- className: "p-3",
121
+ className: classNames('p-3', Styles.content),
116
122
  children: [
117
123
  /*#__PURE__*/ _jsxs("div", {
118
- className: Styles.titleRow,
124
+ className: classNames(Styles.titleRow, {
125
+ [Styles.centered]: centered
126
+ }),
119
127
  children: [
120
128
  eyebrow,
121
129
  hasInfo && infoContent
122
130
  ]
123
131
  }),
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
- ]
132
+ /*#__PURE__*/ _jsx("div", {
133
+ className: classNames(Styles.valueRow, {
134
+ [Styles.centered]: centered
135
+ }),
136
+ children: loading ? /*#__PURE__*/ _jsx(Skeleton.Text, {
137
+ variant: "headline",
138
+ className: "w-33"
139
+ }) : /*#__PURE__*/ _jsxs(Fragment, {
140
+ children: [
141
+ /*#__PURE__*/ _jsx(Headline, {
142
+ size: "xlarge",
143
+ className: "m-b-0-i",
144
+ "data-cy": `marketing-stat-${title}-value`,
145
+ children: val
146
+ }),
147
+ !valueOnly && /*#__PURE__*/ _jsx(StatDiff, {
148
+ value: value,
149
+ prev: prev,
150
+ format: format,
151
+ inverted: inverted,
152
+ neutral: neutral,
153
+ diffPercentOnly: diffPercentOnly
154
+ })
155
+ ]
156
+ })
142
157
  })
143
158
  ]
144
159
  })
@@ -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 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"}
1
+ {"version":3,"sources":["../../../src/components/stat/stat-card.tsx"],"sourcesContent":["import { FC, Fragment, 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, Skeleton } 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 centered?: boolean;\n loading?: 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 centered,\n loading,\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 <div className={Styles.infoIcon}>\n <Popover\n open={popoverShown}\n trigger={infoIcon}\n onMouseEnter={() => setPopoverShown(true)}\n >\n {popoverContent}\n </Popover>\n </div>\n ) : description ? (\n <div className={Styles.infoIcon}>\n <Tooltip text={description} data-cy={`marketing-stat-${title}-tooltip`}>\n {infoIcon}\n </Tooltip>\n </div>\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={classNames('p-3', Styles.content)}>\n <div className={classNames(Styles.titleRow, { [Styles.centered]: centered })}>\n {eyebrow}\n {hasInfo && infoContent}\n </div>\n <div className={classNames(Styles.valueRow, { [Styles.centered]: centered })}>\n {loading ? (\n <Skeleton.Text variant=\"headline\" className=\"w-33\" />\n ) : (\n <Fragment>\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 </Fragment>\n )}\n </div>\n </div>\n </Stack>\n );\n};\n"],"names":["Fragment","useState","classNames","BodyText","Eyebrow","Headline","Popover","Stack","Tooltip","Styles","formatValue","Icon","Skeleton","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","centered","loading","popoverShown","setPopoverShown","val","hasInfo","eyebrow","infoIcon","colorNeutral100","infoContent","div","open","trigger","onMouseEnter","direction","card","onMouseLeave","content","titleRow","valueRow","Text","variant"],"mappings":";AAAA,SAAaA,QAAQ,EAAaC,QAAQ,QAAQ,QAAQ;AAC1D,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,EAAEC,QAAQ,QAAQ,uBAAuB;AACtD,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,IAAKjB,YAAYQ,OAAOU;AACrD;AAEA,MAAMC,6BAA6B,CAACX,OAAeS;IAC/C,IAAI,CAACT,OAAO;QACR,OAAO;IACX;IAEA,OAAO,AAACS,CAAAA,SAAS,MAAM,GAAE,IAAKjB,YAAYQ,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,KAACX;QACG0B,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,MAACjC;QACG2B,WAAWhC,WAAWO,OAAOgC,QAAQ,EAAEP;QACvCQ,gBAAe;QACfC,YAAW;;0BAEX,KAACpC,MAAMqC,IAAI;gBAACV,WAAU;0BAClB,cAAA,KAACW;8BAAMxB;;;0BAEX,KAACd,MAAMqC,IAAI;0BACP,cAAA,KAACzC;oBACG4B,IAAI,EAAEA,iBAAAA,kBAAAA,OAAQ;oBACde,WAAQ;oBACRZ,WAAWhC,WAAW;wBAClB,aAAa,CAAC+B,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;AAsBF,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,EACvBuB,QAAQ,EACRC,OAAO,EACV;IACG,MAAM,CAACC,cAAcC,gBAAgB,GAAG5D,SAAS;IACjD,MAAM2B,SAASyB,QAAQ,UAAUD,UAAU,YAAYE,OAAO,SAAS;IACvE,MAAMQ,MAAM5C,UAAU6B,YAAY,WAAWrC,YAAYQ,OAAOU;IAChE,MAAMmC,UAAU,CAAC,CAACb,eAAe,CAAC,CAACC;IAEnC,MAAMa,wBAAU,KAAC5D;QAAQ0C,WAAS,CAAC,eAAe,EAAEG,MAAM,MAAM,CAAC;kBAAGA;;IAEpE,MAAMgB,yBAAW,KAACtD;QAAK0B,KAAKtB;QAASuB,OAAOtB,OAAOkD,eAAe;;IAElE,MAAMC,cAAchB,+BAChB,KAACiB;QAAIlC,WAAWzB,OAAOwD,QAAQ;kBAC3B,cAAA,KAAC3D;YACG+D,MAAMT;YACNU,SAASL;YACTM,cAAc,IAAMV,gBAAgB;sBAEnCV;;SAGTD,4BACA,KAACkB;QAAIlC,WAAWzB,OAAOwD,QAAQ;kBAC3B,cAAA,KAACzD;YAAQ+B,MAAMW;YAAaJ,WAAS,CAAC,eAAe,EAAEG,MAAM,QAAQ,CAAC;sBACjEgB;;SAGT;IAEJ,qBACI,KAAC1D;QACGiE,WAAU;QACVtC,WAAWhC,WACP,OACA;YACI,mBAAmB,CAACqD;YACpB,CAAC9C,OAAOgE,IAAI,CAAC,EAAE,CAAClB;YAChB,4BAA4BC;QAChC,GACAtB;QAEJwC,cAAc,IAAMb,gBAAgB;kBAEpC,cAAA,MAACO;YAAIlC,WAAWhC,WAAW,OAAOO,OAAOkE,OAAO;;8BAC5C,MAACP;oBAAIlC,WAAWhC,WAAWO,OAAOmE,QAAQ,EAAE;wBAAE,CAACnE,OAAOiD,QAAQ,CAAC,EAAEA;oBAAS;;wBACrEM;wBACAD,WAAWI;;;8BAEhB,KAACC;oBAAIlC,WAAWhC,WAAWO,OAAOoE,QAAQ,EAAE;wBAAE,CAACpE,OAAOiD,QAAQ,CAAC,EAAEA;oBAAS;8BACrEC,wBACG,KAAC/C,SAASkE,IAAI;wBAACC,SAAQ;wBAAW7C,WAAU;uCAE5C,MAAClC;;0CACG,KAACK;gCACG0B,MAAK;gCACLG,WAAU;gCACVY,WAAS,CAAC,eAAe,EAAEG,MAAM,MAAM,CAAC;0CAEvCa;;4BAEJ,CAACL,2BACE,KAAC3B;gCACGZ,OAAOA;gCACPC,MAAMA;gCACNS,QAAQA;gCACRI,UAAUA;gCACVC,SAASA;gCACTE,iBAAiBA;;;;;;;;AASrD,EAAE"}
@@ -11,9 +11,15 @@
11
11
  margin-bottom: 2px;
12
12
  }
13
13
 
14
+ .content {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 8px;
18
+ }
19
+
14
20
  .title-row {
15
21
  display: flex;
16
- align-items: flex-start;
22
+ align-items: center;
17
23
  gap: 8px;
18
24
  }
19
25
 
@@ -22,7 +28,17 @@
22
28
  flex-wrap: wrap;
23
29
  align-items: flex-end;
24
30
  gap: 8px;
25
- margin-top: @spacing-1;
31
+ min-height: 40px;
32
+ }
33
+
34
+ .info-icon {
35
+ display: flex;
36
+ align-items: center;
37
+ line-height: 0;
38
+ }
39
+
40
+ .centered {
41
+ justify-content: center;
26
42
  }
27
43
 
28
44
  .card {
@@ -1,5 +1,8 @@
1
1
  export const __esModule: true;
2
2
  export const card: string;
3
+ export const centered: string;
4
+ export const content: string;
5
+ export const infoIcon: string;
3
6
  export const statDiff: string;
4
7
  export const statExtendedDiff: string;
5
8
  export const titleRow: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/marketing-ui",
3
- "version": "7.4.0",
3
+ "version": "7.6.0",
4
4
  "description": "Marketing UI component and utils",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,7 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@servicetitan/anvil2": "^2.0.1",
34
34
  "@servicetitan/design-system": "~14.5.1",
35
- "@servicetitan/react-ioc": "^34.0.1",
35
+ "@servicetitan/react-ioc": "^34.2.0",
36
36
  "@servicetitan/tokens": ">=12.2.1",
37
37
  "@testing-library/react": "^14.2.1",
38
38
  "@types/accounting": "~0.4.2",
@@ -53,5 +53,5 @@
53
53
  "less": true,
54
54
  "webpack": false
55
55
  },
56
- "gitHead": "7e2b147613dc2f96854d2e2c4458a4891dc6aadc"
56
+ "gitHead": "0ce9b839d487b5520516bb11e16816b0ede0f6c0"
57
57
  }
@@ -11,9 +11,15 @@
11
11
  margin-bottom: 2px;
12
12
  }
13
13
 
14
+ .content {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 8px;
18
+ }
19
+
14
20
  .title-row {
15
21
  display: flex;
16
- align-items: flex-start;
22
+ align-items: center;
17
23
  gap: 8px;
18
24
  }
19
25
 
@@ -22,7 +28,17 @@
22
28
  flex-wrap: wrap;
23
29
  align-items: flex-end;
24
30
  gap: 8px;
25
- margin-top: @spacing-1;
31
+ min-height: 40px;
32
+ }
33
+
34
+ .info-icon {
35
+ display: flex;
36
+ align-items: center;
37
+ line-height: 0;
38
+ }
39
+
40
+ .centered {
41
+ justify-content: center;
26
42
  }
27
43
 
28
44
  .card {
@@ -1,5 +1,8 @@
1
1
  export const __esModule: true;
2
2
  export const card: string;
3
+ export const centered: string;
4
+ export const content: string;
5
+ export const infoIcon: string;
3
6
  export const statDiff: string;
4
7
  export const statExtendedDiff: string;
5
8
  export const titleRow: string;
@@ -1,4 +1,4 @@
1
- import { FC, ReactNode, useState } from 'react';
1
+ import { FC, Fragment, ReactNode, useState } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import {
4
4
  BodyText,
@@ -11,7 +11,7 @@ import {
11
11
  } from '@servicetitan/design-system';
12
12
  import * as Styles from './stat-card.module.less';
13
13
  import { formatValue, NumberFormatter } from '../../utils/formatters';
14
- import { Icon } from '@servicetitan/anvil2';
14
+ import { Icon, Skeleton } from '@servicetitan/anvil2';
15
15
  import TrendingUpSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_up.svg';
16
16
  import TrendingDownSVG from '@servicetitan/anvil2/assets/icons/material/round/trending_down.svg';
17
17
  import InfoSVG from '@servicetitan/anvil2/assets/icons/material/round/info.svg';
@@ -149,6 +149,8 @@ export interface StatCardProps {
149
149
  valueOnly?: boolean;
150
150
  className?: string;
151
151
  diffPercentOnly?: boolean;
152
+ centered?: boolean;
153
+ loading?: boolean;
152
154
  }
153
155
 
154
156
  export const StatCard: FC<StatCardProps> = ({
@@ -167,6 +169,8 @@ export const StatCard: FC<StatCardProps> = ({
167
169
  valueOnly,
168
170
  className,
169
171
  diffPercentOnly = false,
172
+ centered,
173
+ loading,
170
174
  }) => {
171
175
  const [popoverShown, setPopoverShown] = useState(false);
172
176
  const format = money ? 'money' : percent ? 'percent' : rate ? 'rate' : 'number';
@@ -178,13 +182,21 @@ export const StatCard: FC<StatCardProps> = ({
178
182
  const infoIcon = <Icon svg={InfoSVG} color={tokens.colorNeutral100} />;
179
183
 
180
184
  const infoContent = popoverContent ? (
181
- <Popover open={popoverShown} trigger={infoIcon} onMouseEnter={() => setPopoverShown(true)}>
182
- {popoverContent}
183
- </Popover>
185
+ <div className={Styles.infoIcon}>
186
+ <Popover
187
+ open={popoverShown}
188
+ trigger={infoIcon}
189
+ onMouseEnter={() => setPopoverShown(true)}
190
+ >
191
+ {popoverContent}
192
+ </Popover>
193
+ </div>
184
194
  ) : description ? (
185
- <Tooltip text={description} data-cy={`marketing-stat-${title}-tooltip`}>
186
- {infoIcon}
187
- </Tooltip>
195
+ <div className={Styles.infoIcon}>
196
+ <Tooltip text={description} data-cy={`marketing-stat-${title}-tooltip`}>
197
+ {infoIcon}
198
+ </Tooltip>
199
+ </div>
188
200
  ) : null;
189
201
 
190
202
  return (
@@ -201,28 +213,34 @@ export const StatCard: FC<StatCardProps> = ({
201
213
  )}
202
214
  onMouseLeave={() => setPopoverShown(false)}
203
215
  >
204
- <div className="p-3">
205
- <div className={Styles.titleRow}>
216
+ <div className={classNames('p-3', Styles.content)}>
217
+ <div className={classNames(Styles.titleRow, { [Styles.centered]: centered })}>
206
218
  {eyebrow}
207
219
  {hasInfo && infoContent}
208
220
  </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
- />
221
+ <div className={classNames(Styles.valueRow, { [Styles.centered]: centered })}>
222
+ {loading ? (
223
+ <Skeleton.Text variant="headline" className="w-33" />
224
+ ) : (
225
+ <Fragment>
226
+ <Headline
227
+ size="xlarge"
228
+ className="m-b-0-i"
229
+ data-cy={`marketing-stat-${title}-value`}
230
+ >
231
+ {val}
232
+ </Headline>
233
+ {!valueOnly && (
234
+ <StatDiff
235
+ value={value}
236
+ prev={prev}
237
+ format={format}
238
+ inverted={inverted}
239
+ neutral={neutral}
240
+ diffPercentOnly={diffPercentOnly}
241
+ />
242
+ )}
243
+ </Fragment>
226
244
  )}
227
245
  </div>
228
246
  </div>