@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.
- package/dist/components/stat/stat-card.d.ts +2 -0
- package/dist/components/stat/stat-card.d.ts.map +1 -1
- package/dist/components/stat/stat-card.js +47 -32
- package/dist/components/stat/stat-card.js.map +1 -1
- package/dist/components/stat/stat-card.module.less +18 -2
- package/dist/components/stat/stat-card.module.less.d.ts +3 -0
- package/package.json +3 -3
- package/src/components/stat/stat-card.module.less +18 -2
- package/src/components/stat/stat-card.module.less.d.ts +3 -0
- package/src/components/stat/stat-card.tsx +45 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../../src/components/stat/stat-card.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,
|
|
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(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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:
|
|
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__*/
|
|
125
|
-
className: Styles.valueRow,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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":"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"}
|
|
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":"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"}
|
|
@@ -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:
|
|
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
|
-
|
|
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 {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/marketing-ui",
|
|
3
|
-
"version": "7.
|
|
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
|
|
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": "
|
|
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:
|
|
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
|
-
|
|
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,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
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
<
|
|
186
|
-
{
|
|
187
|
-
|
|
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=
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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>
|