@servicetitan/marketing-ui 6.0.1 → 6.1.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.
Files changed (40) hide show
  1. package/dist/components/charts/funnel-chart/components/funnel-chart.d.ts.map +1 -1
  2. package/dist/components/charts/funnel-chart/components/funnel-chart.js +1 -2
  3. package/dist/components/charts/funnel-chart/components/funnel-chart.js.map +1 -1
  4. package/dist/components/charts/funnel-chart/components/funnel-svg.js +3 -3
  5. package/dist/components/charts/funnel-chart/components/funnel-svg.js.map +1 -1
  6. package/dist/components/charts/line-chart/components/body.js +2 -2
  7. package/dist/components/charts/line-chart/components/body.js.map +1 -1
  8. package/dist/components/charts/line-chart/components/hover-popover.d.ts.map +1 -1
  9. package/dist/components/charts/line-chart/components/hover-popover.js +69 -44
  10. package/dist/components/charts/line-chart/components/hover-popover.js.map +1 -1
  11. package/dist/components/charts/line-chart/components/hover-popover.module.less +26 -1
  12. package/dist/components/charts/line-chart/components/hover-popover.module.less.d.ts +3 -0
  13. package/dist/components/charts/line-chart/components/stuff.js +2 -1
  14. package/dist/components/charts/line-chart/components/stuff.js.map +1 -1
  15. package/dist/components/charts/line-chart/components/svg-bars.js +1 -1
  16. package/dist/components/charts/line-chart/components/svg-bars.js.map +1 -1
  17. package/dist/components/charts/pie-chart/components/pie-chart.js +1 -0
  18. package/dist/components/charts/pie-chart/components/pie-chart.js.map +1 -1
  19. package/dist/components/charts/pie-chart/components/pie-chart.module.less +8 -0
  20. package/dist/components/charts/pie-chart/components/pie-chart.module.less.d.ts +2 -0
  21. package/dist/components/charts/pie-chart/components/pie.d.ts.map +1 -1
  22. package/dist/components/charts/pie-chart/components/pie.js +2 -0
  23. package/dist/components/charts/pie-chart/components/pie.js.map +1 -1
  24. package/dist/components/stat/stat-card.d.ts.map +1 -1
  25. package/dist/components/stat/stat-card.js +2 -2
  26. package/dist/components/stat/stat-card.js.map +1 -1
  27. package/package.json +2 -2
  28. package/src/components/charts/funnel-chart/components/funnel-chart.tsx +0 -1
  29. package/src/components/charts/funnel-chart/components/funnel-svg.tsx +3 -3
  30. package/src/components/charts/line-chart/components/body.tsx +1 -1
  31. package/src/components/charts/line-chart/components/hover-popover.module.less +26 -1
  32. package/src/components/charts/line-chart/components/hover-popover.module.less.d.ts +3 -0
  33. package/src/components/charts/line-chart/components/hover-popover.tsx +85 -47
  34. package/src/components/charts/line-chart/components/stuff.tsx +1 -1
  35. package/src/components/charts/line-chart/components/svg-bars.tsx +1 -1
  36. package/src/components/charts/pie-chart/components/pie-chart.module.less +8 -0
  37. package/src/components/charts/pie-chart/components/pie-chart.module.less.d.ts +2 -0
  38. package/src/components/charts/pie-chart/components/pie-chart.tsx +1 -1
  39. package/src/components/charts/pie-chart/components/pie.tsx +2 -0
  40. package/src/components/stat/stat-card.tsx +1 -2
@@ -1,4 +1,4 @@
1
- import { useCallback, useMemo, FC, Fragment } from 'react';
1
+ import { useCallback, useMemo, useRef, useLayoutEffect, useState, FC, CSSProperties } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { observer } from 'mobx-react';
4
4
  import { useDependencies } from '@servicetitan/react-ioc';
@@ -11,6 +11,9 @@ import * as Styles from './hover-popover.module.less';
11
11
  import { ColorTag } from '../../common';
12
12
  import { Text } from '@servicetitan/anvil2';
13
13
 
14
+ const CHART_HEIGHT_PX = 400;
15
+ const OFFSET_PX = 16;
16
+
14
17
  export const HoverPopover: FC = observer(() => {
15
18
  const [
16
19
  {
@@ -25,6 +28,7 @@ export const HoverPopover: FC = observer(() => {
25
28
  },
26
29
  svgStore,
27
30
  ] = useDependencies(LineChartStore, SvgStore);
31
+ const isChartLeftSide = hoveredIndex < periods.length / 2;
28
32
 
29
33
  const formatDateTitle = useMemo(() => periodDateTitleFormatter[resolution], [resolution]);
30
34
  const formatValue = useCallback(
@@ -34,15 +38,48 @@ export const HoverPopover: FC = observer(() => {
34
38
  title,
35
39
  [display]
36
40
  );
37
- const popoverStyle = useMemo(() => {
38
- const pos = svgStore.periodX(hoveredIndex);
39
41
 
40
- if (hoveredIndex < periods.length / 2) {
41
- return { left: `${svgStore.fpx(pos + 2)}%` };
42
+ const popRef = useRef<HTMLDivElement | null>(null);
43
+ const [popH, setPopH] = useState(0);
44
+
45
+ useLayoutEffect(() => {
46
+ if (!popRef.current) {
47
+ return;
48
+ }
49
+ const rect = popRef.current.getBoundingClientRect();
50
+ if (rect.height && Math.abs(rect.height - popH) > 0.5) {
51
+ setPopH(rect.height);
42
52
  }
53
+ }, [hoveredIndex, metrics, display, popH]);
54
+
55
+ const popoverStyle = useMemo<CSSProperties>(() => {
56
+ const posX = svgStore.periodX(hoveredIndex);
57
+
58
+ const yHeights = metrics
59
+ .filter(m => m.values[hoveredIndex] !== undefined)
60
+ .map(m => svgStore.periodY(m, hoveredIndex));
61
+
62
+ const barHeight = yHeights.length
63
+ ? stackedTotals
64
+ ? yHeights.reduce((a, b) => a + b, 0)
65
+ : Math.max(...yHeights)
66
+ : 0;
67
+ const barHeightPercentRaw = svgStore.fpy(Math.max(0, Math.min(100, barHeight)));
68
+ const barHeightPercent = Math.max(0, Math.min(100, Number(barHeightPercentRaw) || 0));
43
69
 
44
- return { right: `${svgStore.fpx(102 - pos)}%` };
45
- }, [svgStore, hoveredIndex, periods.length]);
70
+ const barTopPositionPx = (barHeightPercent / 100) * CHART_HEIGHT_PX;
71
+ const availableSpaceBelow = Math.max(0, CHART_HEIGHT_PX - barTopPositionPx - popH);
72
+ const popoverOffsetPx = Math.min(OFFSET_PX, availableSpaceBelow);
73
+
74
+ return {
75
+ top: `${barHeightPercent}%`,
76
+ transform: `translateY(${popoverOffsetPx}px)`,
77
+ position: 'absolute',
78
+ ...(isChartLeftSide
79
+ ? { left: `${svgStore.fpx(posX + 2)}%` }
80
+ : { right: `${svgStore.fpx(102 - posX)}%` }),
81
+ };
82
+ }, [svgStore, hoveredIndex, isChartLeftSide, metrics, stackedTotals, popH]);
46
83
 
47
84
  if (hoveredIndex < 0 || hoveredIndex >= periods.length) {
48
85
  return null;
@@ -52,47 +89,48 @@ export const HoverPopover: FC = observer(() => {
52
89
  const partialWeek = !!period.partial;
53
90
 
54
91
  return (
55
- <Fragment>
56
- <div
57
- className={Styles.line}
58
- style={{ left: svgStore.fpx(svgStore.periodX(hoveredIndex)) + '%' }}
59
- />
92
+ <div
93
+ ref={popRef}
94
+ className={classNames(Styles.popover, 'border border-radius-1 p-1')}
95
+ style={popoverStyle}
96
+ >
60
97
  <div
61
- className={classNames(Styles.popover, 'bg-white border border-radius-1 p-1')}
62
- style={popoverStyle}
63
- >
64
- <Text size="small" variant="headline" el="h6">
65
- {stackedTotals
66
- ? `${formattedTotalAt(hoveredIndex)} ${totalLabel} | ${formatDateTitle(period)}`
67
- : formatDateTitle(period)}
68
- </Text>
69
- {partialWeek && (
70
- <BodyText size="xsmall" subdued>
71
- Partial week
72
- </BodyText>
98
+ className={classNames(
99
+ Styles.arrow,
100
+ isChartLeftSide ? Styles.arrowLeft : Styles.arrowRight
73
101
  )}
74
- {metrics.map(
75
- m =>
76
- m.values[hoveredIndex] !== undefined && (
77
- <ColorTag
78
- small
79
- label={formatValue(m.title, m.values[hoveredIndex], m.isRight)}
80
- color={m.color}
81
- key={m.title}
82
- className="m-t-1"
83
- dashed={m.opts?.dashed}
84
- pattern={m.opts?.pattern}
85
- outlineColor={m.opts?.outlineColor}
86
- strokeColor={m.opts?.strokeColor}
87
- colorTagClassName={
88
- m.opts?.pattern === 'outline'
89
- ? Styles.colorTagOutlined
90
- : Styles.colorTag
91
- }
92
- />
93
- )
94
- )}
95
- </div>
96
- </Fragment>
102
+ />
103
+ <Text size="small" variant="headline" el="h6">
104
+ {stackedTotals
105
+ ? `${formattedTotalAt(hoveredIndex)} ${totalLabel} | ${formatDateTitle(period)}`
106
+ : formatDateTitle(period)}
107
+ </Text>
108
+ {partialWeek && (
109
+ <BodyText size="xsmall" subdued>
110
+ Partial week
111
+ </BodyText>
112
+ )}
113
+ {metrics.map(
114
+ m =>
115
+ m.values[hoveredIndex] !== undefined && (
116
+ <ColorTag
117
+ small
118
+ label={formatValue(m.title, m.values[hoveredIndex], m.isRight)}
119
+ color={m.color}
120
+ key={m.title}
121
+ className="m-t-1"
122
+ dashed={m.opts?.dashed}
123
+ pattern={m.opts?.pattern}
124
+ outlineColor={m.opts?.outlineColor}
125
+ strokeColor={m.opts?.strokeColor}
126
+ colorTagClassName={
127
+ m.opts?.pattern === 'outline'
128
+ ? Styles.colorTagOutlined
129
+ : Styles.colorTag
130
+ }
131
+ />
132
+ )
133
+ )}
134
+ </div>
97
135
  );
98
136
  });
@@ -11,7 +11,7 @@ interface MetricsTitleProps {
11
11
  }
12
12
 
13
13
  export const MetricsTitle: FC<MetricsTitleProps> = ({ metrics }) => (
14
- <Stack alignItems="center">
14
+ <Stack alignItems="flex-start" className="m-l-half">
15
15
  {metrics.map(m => (
16
16
  <ColorTag
17
17
  key={m.id}
@@ -99,7 +99,7 @@ export const SvgBars: FC<SvgBarsProps> = observer(
99
99
  const TOP_RADIUS = 1;
100
100
  const xLeft = +fpx(x - barWidth / 2);
101
101
  const yTop = +fpy(stackedBarHeight) + (values.length - 2);
102
- const height = +fpx(value.val);
102
+ const height = j === values.length - 1 ? +fpx(value.val - 2) : +fpx(value.val);
103
103
  const width = +fpx(barWidth);
104
104
 
105
105
  const r = j === 0 ? TOP_RADIUS : 0; // radius must be numeric
@@ -4,6 +4,10 @@
4
4
  max-width: 120px;
5
5
  }
6
6
 
7
+ .title-wrapper-color-tags {
8
+ max-width: 380px;
9
+ }
10
+
7
11
  .percent-text-wrapper {
8
12
  width: 32px;
9
13
  height: 32px;
@@ -16,3 +20,7 @@
16
20
  margin: @spacing-0 auto;
17
21
  box-shadow: 0 0 0 1px @color-neutral-0 inset;
18
22
  }
23
+
24
+ .popover {
25
+ background-color: @color-neutral-30;
26
+ }
@@ -1,4 +1,6 @@
1
1
  export const __esModule: true;
2
2
  export const percentTextWrapper: string;
3
+ export const popover: string;
3
4
  export const titleWrapper: string;
5
+ export const titleWrapperColorTags: string;
4
6
 
@@ -28,7 +28,7 @@ const PieTitles: FC<{ title: string; pieces: PiePiece[] }> = ({ title, pieces })
28
28
  </div>
29
29
  </div>
30
30
  ) : (
31
- <Flex direction="row" gap={8}>
31
+ <Flex direction="row" gap={8} className={Styles.titleWrapperColorTags}>
32
32
  {pieces.map(piece => (
33
33
  <ColorTag key={piece.title} label={piece.title} color={piece.color} />
34
34
  ))}
@@ -5,6 +5,7 @@ import { BodyText, Popover, Stack } from '@servicetitan/design-system';
5
5
  import { useClientRect } from '../../../../utils/use-client-rect';
6
6
  import { PieChartPopoverContentType, PiePiece, PopoverDirection } from '../utils/interface';
7
7
  import { ColorTag } from '../../common';
8
+ import * as Styles from './pie-chart.module.less';
8
9
 
9
10
  const chartPadding = 8;
10
11
  const px = (value?: number) => `${value ?? 0}px`;
@@ -250,6 +251,7 @@ export const Pie: FC<{
250
251
  direction={popoverDirection}
251
252
  padding="s"
252
253
  width="auto"
254
+ className={Styles.popover}
253
255
  >
254
256
  {selectedIndex === ind && (
255
257
  <Stack
@@ -84,7 +84,6 @@ export const StatDiff: FC<StatDiffProps> = ({
84
84
  ? 'green'
85
85
  : 'red'
86
86
  }
87
- className="m-r-half m-t-half"
88
87
  />
89
88
  );
90
89
  let text = '';
@@ -111,7 +110,7 @@ export const StatDiff: FC<StatDiffProps> = ({
111
110
  justifyContent="center"
112
111
  alignItems="center"
113
112
  >
114
- <Stack.Item>
113
+ <Stack.Item className="m-r-half m-t-half">
115
114
  <span>{diff}</span>
116
115
  </Stack.Item>
117
116
  <Stack.Item>