@oanda/labs-crowd-view-widget 1.0.46 → 1.0.48

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 (57) hide show
  1. package/CHANGELOG.md +388 -0
  2. package/dist/main/CrowdViewWidget/Main.js +10 -6
  3. package/dist/main/CrowdViewWidget/Main.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +3 -2
  5. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
  7. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +12 -6
  9. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  10. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  11. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +5 -2
  12. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  13. package/dist/main/CrowdViewWidget/components/Legend/Legend.js +5 -2
  14. package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  15. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js +9 -5
  16. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  17. package/dist/main/CrowdViewWidget/constants.js +10 -2
  18. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  19. package/dist/main/translations/sources/en.json +4 -4
  20. package/dist/module/CrowdViewWidget/Main.js +10 -6
  21. package/dist/module/CrowdViewWidget/Main.js.map +1 -1
  22. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +3 -2
  23. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  24. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
  25. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  26. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +13 -7
  27. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  28. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  29. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +5 -2
  30. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  31. package/dist/module/CrowdViewWidget/components/Legend/Legend.js +5 -2
  32. package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  33. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js +9 -5
  34. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  35. package/dist/module/CrowdViewWidget/constants.js +10 -2
  36. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  37. package/dist/module/translations/sources/en.json +4 -4
  38. package/dist/types/CrowdViewWidget/components/Chart/Chart.d.ts +1 -1
  39. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +2 -1
  40. package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +1 -1
  41. package/dist/types/CrowdViewWidget/components/Legend/Legend.d.ts +2 -1
  42. package/dist/types/CrowdViewWidget/components/Legend/LegendBar.d.ts +2 -1
  43. package/dist/types/CrowdViewWidget/constants.d.ts +9 -2
  44. package/package.json +3 -3
  45. package/src/CrowdViewWidget/Main.tsx +46 -39
  46. package/src/CrowdViewWidget/components/Chart/Chart.tsx +2 -2
  47. package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +4 -26
  48. package/src/CrowdViewWidget/components/Chart/chartOptions.ts +21 -5
  49. package/src/CrowdViewWidget/components/Chart/types.ts +2 -0
  50. package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +17 -6
  51. package/src/CrowdViewWidget/components/Legend/Legend.tsx +5 -1
  52. package/src/CrowdViewWidget/components/Legend/LegendBar.tsx +9 -4
  53. package/src/CrowdViewWidget/constants.ts +11 -2
  54. package/src/translations/sources/en.json +4 -4
  55. package/test/components/Chart/utils/chartUtils.test.ts +24 -7
  56. package/test/components/Legend.test.tsx +46 -2
  57. package/test/components/LegendBar.test.tsx +76 -4
@@ -17,7 +17,7 @@ import { getInstrumentConfigForDivision } from './utils/instrumentUtils';
17
17
 
18
18
  const Main = ({ division }: MainProps) => {
19
19
  const { lang } = useLocale();
20
- const { size } = useLayoutProvider();
20
+ const { size, isDark } = useLayoutProvider();
21
21
  const isDesktop = size === Size.DESKTOP;
22
22
 
23
23
  const [bookType, setBookType] = useState(BookType.Order);
@@ -52,48 +52,55 @@ const Main = ({ division }: MainProps) => {
52
52
  className="lw-text-sm lw-tracking-normal"
53
53
  data-testid="crowd-view-widget"
54
54
  >
55
- <Tabs
56
- mobileFullWidth
57
- activeTab={bookType}
58
- handleClick={(e) => setBookType(e.currentTarget.value as BookType)}
59
- items={navigationConfig}
60
- labelCallback={lang}
61
- />
62
- <div className="lw-mb-6 lw-mt-12 lw-flex">
63
- <div
64
- className={cn('lw-mr-2', {
65
- 'lw-w-full': !isDesktop,
66
- 'lw-w-[280px]': isDesktop,
67
- })}
68
- >
69
- <Select
70
- options={instrumentSelectConfigWithDivision}
71
- searchPlaceholder={lang('search')}
72
- selectLabel={lang('instrument')}
73
- selectedOption={instrument}
74
- setSelectedOption={(val) =>
75
- setInstrument(val as { id: InstrumentId; label: string })
76
- }
77
- />
78
- </div>
55
+ <div>
56
+ <Tabs
57
+ mobileFullWidth
58
+ activeTab={bookType}
59
+ handleClick={(e) =>
60
+ setBookType(e.currentTarget.value as BookType)
61
+ }
62
+ items={navigationConfig}
63
+ labelCallback={lang}
64
+ />
79
65
  <div
80
- className={cn({
81
- 'lw-w-full': !isDesktop,
82
- 'lw-w-[280px]': isDesktop,
66
+ className={cn('lw-mb-6 lw-mt-12', {
67
+ 'lw-flex': isDesktop,
83
68
  })}
84
69
  >
85
- <Select
86
- options={granularitySelectConfigWithLang}
87
- searchPlaceholder={lang('search')}
88
- selectLabel={lang('granularity')}
89
- selectedOption={granularity}
90
- setSelectedOption={(val) =>
91
- setGranularity(val as { id: Granularity; label: string })
92
- }
93
- />
70
+ <div
71
+ className={cn('lw-mr-2', {
72
+ 'lw-w-full lw-mb-2': !isDesktop,
73
+ 'lw-w-[280px]': isDesktop,
74
+ })}
75
+ >
76
+ <Select
77
+ options={instrumentSelectConfigWithDivision}
78
+ searchPlaceholder={lang('search')}
79
+ selectLabel={lang('instrument')}
80
+ selectedOption={instrument}
81
+ setSelectedOption={(val) =>
82
+ setInstrument(val as { id: InstrumentId; label: string })
83
+ }
84
+ />
85
+ </div>
86
+ <div
87
+ className={cn({
88
+ 'lw-w-full': !isDesktop,
89
+ 'lw-w-[280px]': isDesktop,
90
+ })}
91
+ >
92
+ <Select
93
+ options={granularitySelectConfigWithLang}
94
+ searchPlaceholder={lang('search')}
95
+ selectLabel={lang('granularity')}
96
+ selectedOption={granularity}
97
+ setSelectedOption={(val) =>
98
+ setGranularity(val as { id: Granularity; label: string })
99
+ }
100
+ />
101
+ </div>
94
102
  </div>
95
103
  </div>
96
-
97
104
  <ChartWithData
98
105
  bookType={bookType}
99
106
  division={division}
@@ -101,7 +108,7 @@ const Main = ({ division }: MainProps) => {
101
108
  instrument={instrument.id}
102
109
  />
103
110
 
104
- <Legend bookType={bookType} />
111
+ <Legend bookType={bookType} isDark={isDark} />
105
112
  </div>
106
113
  )}
107
114
  </>
@@ -48,7 +48,7 @@ echarts.use([
48
48
  echarts.registerTheme('dark_theme', getChartTheme(Theme.Dark));
49
49
  echarts.registerTheme('light_theme', getChartTheme(Theme.Light));
50
50
 
51
- const Chart = ({ data }: ChartProps) => {
51
+ const Chart = ({ data, isDesktop }: ChartProps) => {
52
52
  const { isDark } = useLayoutProvider();
53
53
  const { lang } = useLocale();
54
54
 
@@ -58,7 +58,7 @@ const Chart = ({ data }: ChartProps) => {
58
58
  echarts={echarts}
59
59
  isDark={isDark}
60
60
  lazyUpdate={true}
61
- option={getOption(data, isDark, lang)}
61
+ option={getOption(data, isDark, isDesktop, lang)}
62
62
  opts={{ renderer: 'canvas' }}
63
63
  onEvents={{
64
64
  datazoom: (_params: unknown, instance: EChartsType) => {
@@ -5,7 +5,6 @@ import {
5
5
  SpinnerSize,
6
6
  useLayoutProvider,
7
7
  } from '@oanda/labs-widget-common';
8
- import classnames from 'classnames';
9
8
  import React from 'react';
10
9
 
11
10
  import { Chart } from './Chart';
@@ -30,41 +29,20 @@ const ChartWithData = ({
30
29
 
31
30
  return (
32
31
  <>
33
- <div
34
- className={classnames('lw-relative lw-w-full', {
35
- 'lw-h-[450px]': isDesktop,
36
- 'lw-h-[390px]': !isDesktop,
37
- })}
38
- >
32
+ <div className="lw-relative lw-h-[450px] lw-w-full">
39
33
  {error && (
40
- <div
41
- className={classnames(
42
- 'lw-absolute lw-left-0 lw-top-0 lw-flex lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary',
43
- {
44
- 'lw-h-full': isDesktop,
45
- 'lw-h-[calc(100%-40px)]': !isDesktop,
46
- }
47
- )}
48
- >
34
+ <div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-[calc(100%-30px)] lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary">
49
35
  <ChartError />
50
36
  </div>
51
37
  )}
52
38
  {loading && (
53
- <div
54
- className={classnames(
55
- 'lw-absolute lw-left-0 lw-top-0 lw-flex lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary',
56
- {
57
- 'lw-h-full': isDesktop,
58
- 'lw-h-[calc(100%-40px)]': !isDesktop,
59
- }
60
- )}
61
- >
39
+ <div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-[calc(100%-30px)] lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary">
62
40
  <Spinner size={SpinnerSize.lg} />
63
41
  </div>
64
42
  )}
65
43
  {!loading && !error && !!data && (
66
44
  <div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-full lw-w-full">
67
- <Chart data={data} />
45
+ <Chart data={data} isDesktop={isDesktop} />
68
46
  </div>
69
47
  )}
70
48
  </div>
@@ -2,6 +2,7 @@ import {
2
2
  colorPalette,
3
3
  getGridLines,
4
4
  getLineCommons,
5
+ themeColors,
5
6
  } from '@oanda/labs-widget-common';
6
7
 
7
8
  import { CHART_CONFIG } from '../../constants';
@@ -26,6 +27,7 @@ export const getOption: GetOptionType = (
26
27
  bookType,
27
28
  },
28
29
  isDark,
30
+ isDesktop,
29
31
  labelCallback
30
32
  ) => {
31
33
  let selectedPrice: number;
@@ -49,7 +51,9 @@ export const getOption: GetOptionType = (
49
51
  chartWidth: CHART_CONFIG.WIDTH,
50
52
  chartHeight: CHART_CONFIG.HEIGHT,
51
53
  xLabelsSize: CHART_CONFIG.X_LABEL_SIZE,
52
- yLabelSize: CHART_CONFIG.Y_LABEL_SIZE_DESKTOP,
54
+ yLabelSize: isDesktop
55
+ ? CHART_CONFIG.Y_LABEL_SIZE_DESKTOP
56
+ : CHART_CONFIG.Y_LABEL_SIZE_MOBILE,
53
57
  bottomLeftBox: false,
54
58
  });
55
59
 
@@ -68,6 +72,9 @@ export const getOption: GetOptionType = (
68
72
  axisPointer: {
69
73
  type: 'cross',
70
74
  axis: 'x',
75
+ crossStyle: {
76
+ color: isDark ? colorPalette.orange : themeColors.borderPrimary.light,
77
+ },
71
78
  label: {
72
79
  formatter: (params) => {
73
80
  if (params.axisDimension === 'y') {
@@ -124,6 +131,7 @@ export const getOption: GetOptionType = (
124
131
  axisLabel: {
125
132
  showMaxLabel: false,
126
133
  showMinLabel: false,
134
+ margin: isDesktop ? 4 : 2,
127
135
  formatter: (value: number) => value.toFixed(precision - 1),
128
136
  },
129
137
  },
@@ -133,8 +141,16 @@ export const getOption: GetOptionType = (
133
141
  id: 'candlestick',
134
142
  data: candlesSeriesData,
135
143
  itemStyle: {
136
- color: colorPalette.raspberryLight,
137
- color0: colorPalette.bottleGreenLight,
144
+ color: isDark ? colorPalette.orange : colorPalette.raspberryLight,
145
+ color0: isDark
146
+ ? colorPalette.bottleGreenDark
147
+ : colorPalette.bottleGreenLight,
148
+ borderColor: isDark
149
+ ? colorPalette.orange
150
+ : colorPalette.raspberryLight,
151
+ borderColor0: isDark
152
+ ? colorPalette.bottleGreenDark
153
+ : colorPalette.bottleGreenLight,
138
154
  },
139
155
 
140
156
  markPoint: {
@@ -171,7 +187,7 @@ export const getOption: GetOptionType = (
171
187
  height: rectHeight,
172
188
  },
173
189
  style: {
174
- fill: getRectColor(sentiment),
190
+ fill: getRectColor(sentiment, isDark),
175
191
  },
176
192
  silent: true,
177
193
  emphasisDisabled: true,
@@ -193,7 +209,7 @@ export const getOption: GetOptionType = (
193
209
  name: 'main-grid',
194
210
  top: '0px',
195
211
  left: '0px',
196
- right: `${CHART_CONFIG.Y_LABEL_SIZE_DESKTOP}px`,
212
+ right: `${isDesktop ? CHART_CONFIG.Y_LABEL_SIZE_DESKTOP : CHART_CONFIG.Y_LABEL_SIZE_MOBILE}px`,
197
213
  bottom: `${CHART_CONFIG.X_LABEL_SIZE}px`,
198
214
  },
199
215
  ],
@@ -40,11 +40,13 @@ export interface UseCrowdViewDataReturn {
40
40
  export type GetOptionType = (
41
41
  props: CrowdViewData,
42
42
  isDark: boolean,
43
+ isDesktop: boolean,
43
44
  labelCallback: (key: string, params?: Record<string, unknown>) => string
44
45
  ) => EChartsOption;
45
46
 
46
47
  export interface ChartProps {
47
48
  data: CrowdViewData;
49
+ isDesktop: boolean;
48
50
  }
49
51
 
50
52
  export interface ChartWithDataProps {
@@ -87,10 +87,17 @@ const getGradientColor = (
87
87
  return colorScale(value).hex();
88
88
  };
89
89
 
90
- export const getRectColor = (sentiment: number) =>
91
- sentiment < 0
92
- ? getGradientColor(sentiment * -1, COLOR_MAP.short[0], COLOR_MAP.short[1])
93
- : getGradientColor(sentiment, COLOR_MAP.long[0], COLOR_MAP.long[1]);
90
+ export const getRectColor = (sentiment: number, isDark: boolean) => {
91
+ const colorPalette = isDark ? COLOR_MAP.dark : COLOR_MAP.light;
92
+
93
+ return sentiment < 0
94
+ ? getGradientColor(
95
+ sentiment * -1,
96
+ colorPalette.short[0],
97
+ colorPalette.short[1]
98
+ )
99
+ : getGradientColor(sentiment, colorPalette.long[0], colorPalette.long[1]);
100
+ };
94
101
 
95
102
  export const getTooltipFormatter = ({
96
103
  params,
@@ -160,10 +167,14 @@ ${
160
167
  <p>${
161
168
  matchedBucket.sentiment < 0
162
169
  ? labelCallback(
163
- bookType === BookType.Order ? 'sell_advantage' : 'short_advantage'
170
+ bookType === BookType.Order
171
+ ? 'sell_overbalance'
172
+ : 'short_overbalance'
164
173
  )
165
174
  : labelCallback(
166
- bookType === BookType.Order ? 'buy_advantage' : 'long_advantage'
175
+ bookType === BookType.Order
176
+ ? 'buy_overbalance'
177
+ : 'long_overbalance'
167
178
  )
168
179
  }: ${Math.abs(matchedBucket.sentiment)}% </p>`
169
180
  : ''
@@ -9,23 +9,27 @@ interface LegendProps {
9
9
  longValues?: [number, number];
10
10
  shortValues?: [number, number];
11
11
  bookType: BookType;
12
+ isDark: boolean;
12
13
  }
13
14
 
14
15
  export const Legend = ({
15
16
  longValues = [BOOKS_THRESHOLDS.MIN, BOOKS_THRESHOLDS.MAX],
16
17
  shortValues = [BOOKS_THRESHOLDS.MIN, BOOKS_THRESHOLDS.MAX],
17
18
  bookType,
19
+ isDark,
18
20
  }: LegendProps) => {
19
21
  const { lang } = useLocale();
20
22
 
21
23
  return (
22
- <div className="lw-mx-auto lw-flex lw-w-full lw-flex-col lw-items-center lw-space-y-4 lw-py-6 sm:lw-max-w-md lg:lw-max-w-xl">
24
+ <div className="lw-mx-auto lw-flex lw-w-full lw-flex-col lw-items-center lw-space-y-4 lw-pb-6 lw-pt-0 sm:lw-max-w-md lg:lw-max-w-xl">
23
25
  <LegendBar
26
+ isDark={isDark}
24
27
  label={lang(bookType === BookType.Order ? 'buy' : 'long')}
25
28
  type="long"
26
29
  values={longValues}
27
30
  />
28
31
  <LegendBar
32
+ isDark={isDark}
29
33
  label={lang(bookType === BookType.Order ? 'sell' : 'short')}
30
34
  type="short"
31
35
  values={shortValues}
@@ -8,13 +8,18 @@ interface LegendBarProps {
8
8
  values: number[];
9
9
  type: LegendType;
10
10
  label: string;
11
+ isDark: boolean;
11
12
  }
12
13
 
13
- export const LegendBar = ({ values, type, label }: LegendBarProps) => {
14
- const colors = COLOR_MAP[type];
14
+ export const LegendBar = ({ values, type, label, isDark }: LegendBarProps) => {
15
+ const colorPalette = isDark ? COLOR_MAP.dark : COLOR_MAP.light;
16
+ const colors = type === 'long' ? colorPalette.long : colorPalette.short;
15
17
 
16
18
  return (
17
19
  <div className="lw-flex lw-w-full lw-flex-col lw-space-y-1 lw-border-border-primary">
20
+ <span className="lw-whitespace-nowrap lw-font-sans lw-text-xs lw-text-text-primary">
21
+ {label}
22
+ </span>
18
23
  <div className="lw-flex lw-h-2 lw-w-full lw-overflow-hidden lw-border lw-border-border-primary">
19
24
  <div
20
25
  className="lw-h-full lw-flex-1"
@@ -27,10 +32,10 @@ export const LegendBar = ({ values, type, label }: LegendBarProps) => {
27
32
 
28
33
  <div className="lw-flex lw-w-full lw-justify-between">
29
34
  <span className="lw-whitespace-nowrap lw-font-sans lw-text-xs lw-text-text-primary">
30
- {values[0].toFixed(2)}% {label}
35
+ {values[0].toFixed(2)}%
31
36
  </span>
32
37
  <span className="lw-whitespace-nowrap lw-font-sans lw-text-xs lw-text-text-primary">
33
- {values[1].toFixed(2)}% {label}
38
+ {`≤ ${values[1].toFixed(2)}`}%
34
39
  </span>
35
40
  </div>
36
41
  </div>
@@ -1,3 +1,5 @@
1
+ import { colorPalette } from '@oanda/labs-widget-common';
2
+
1
3
  import { InstrumentId } from './types';
2
4
 
3
5
  export const BOOKS_THRESHOLDS = {
@@ -19,14 +21,21 @@ export const CHART_CONFIG = {
19
21
  WIDTH: 9999,
20
22
  X_LABEL_SIZE: 40,
21
23
  Y_LABEL_SIZE_DESKTOP: 60,
24
+ Y_LABEL_SIZE_MOBILE: 40,
22
25
  INITIAL_START_ZOOM: 80,
23
26
  INITIAL_END_ZOOM: 100,
24
27
  X_AXIS_DATE_PADDING: ' ',
25
28
  } as const;
26
29
 
27
30
  export const COLOR_MAP = {
28
- long: ['#eaf5fa', '#83c4e0'],
29
- short: ['#fef7e7', '#fcd171'],
31
+ dark: {
32
+ long: [colorPalette.darkBlue10, colorPalette.darkBlue90],
33
+ short: [colorPalette.darkYellow10, colorPalette.darkYellow90],
34
+ },
35
+ light: {
36
+ long: [colorPalette.lightBlue10, colorPalette.lightBlue90],
37
+ short: [colorPalette.lightYellow10, colorPalette.lightYellow90],
38
+ },
30
39
  } as const;
31
40
 
32
41
  export const INSTRUMENTS_CONFIG: Record<
@@ -3,7 +3,7 @@
3
3
  "15_minutes": "15 minutes",
4
4
  "4_hours": "4 hours",
5
5
  "5_minutes": "5 minutes",
6
- "buy_advantage": "Buy advantage",
6
+ "buy_overbalance": "Buy overbalance",
7
7
  "buy": "Buy",
8
8
  "candle": "Candle",
9
9
  "close_price": "Close price",
@@ -11,7 +11,7 @@
11
11
  "granularity": "Granularity",
12
12
  "high": "High",
13
13
  "instrument": "Instrument",
14
- "long_advantage": "Sell advantage",
14
+ "long_overbalance": "Long overbalance",
15
15
  "long": "Long",
16
16
  "low": "Low",
17
17
  "no_matching_results": "No matching results",
@@ -23,9 +23,9 @@
23
23
  "positions": "Positions",
24
24
  "price_range": "Price range",
25
25
  "search": "Search",
26
- "sell_advantage": "Sell advantage",
26
+ "sell_overbalance": "Sell overbalance",
27
27
  "sell": "Sell",
28
28
  "sentiment": "Sentiment",
29
- "short_advantage": "Buy advantage",
29
+ "short_overbalance": "Short overbalance",
30
30
  "short": "Short"
31
31
  }
@@ -48,20 +48,37 @@ describe('chartUtils', () => {
48
48
  });
49
49
 
50
50
  describe('getRectColor', () => {
51
- it('uses long color scale for positive sentiment', () => {
52
- const color = getRectColor(BOOKS_THRESHOLDS.MAX);
51
+ it('uses long color scale for positive sentiment in light mode', () => {
52
+ const color = getRectColor(BOOKS_THRESHOLDS.MAX, false);
53
53
  expect(typeof color).toBe('string');
54
54
  // At max threshold, should be at or near target color
55
55
  expect(color.toLowerCase()).toContain(
56
- COLOR_MAP.long[1].slice(1).toLowerCase().substring(0, 3)
56
+ COLOR_MAP.light.long[1].slice(1).toLowerCase().substring(0, 3)
57
57
  );
58
58
  });
59
59
 
60
- it('uses short color scale for negative sentiment', () => {
61
- const color = getRectColor(-BOOKS_THRESHOLDS.MAX);
60
+ it('uses short color scale for negative sentiment in light mode', () => {
61
+ const color = getRectColor(-BOOKS_THRESHOLDS.MAX, false);
62
62
  expect(typeof color).toBe('string');
63
63
  expect(color.toLowerCase()).toContain(
64
- COLOR_MAP.short[1].slice(1).toLowerCase().substring(0, 3)
64
+ COLOR_MAP.light.short[1].slice(1).toLowerCase().substring(0, 3)
65
+ );
66
+ });
67
+
68
+ it('uses long color scale for positive sentiment in dark mode', () => {
69
+ const color = getRectColor(BOOKS_THRESHOLDS.MAX, true);
70
+ expect(typeof color).toBe('string');
71
+ // At max threshold, should be at or near target color
72
+ expect(color.toLowerCase()).toContain(
73
+ COLOR_MAP.dark.long[1].slice(1).toLowerCase().substring(0, 3)
74
+ );
75
+ });
76
+
77
+ it('uses short color scale for negative sentiment in dark mode', () => {
78
+ const color = getRectColor(-BOOKS_THRESHOLDS.MAX, true);
79
+ expect(typeof color).toBe('string');
80
+ expect(color.toLowerCase()).toContain(
81
+ COLOR_MAP.dark.short[1].slice(1).toLowerCase().substring(0, 3)
65
82
  );
66
83
  });
67
84
  });
@@ -152,7 +169,7 @@ describe('chartUtils', () => {
152
169
  expect(html).toContain('orders');
153
170
  expect(html).toContain('price_range');
154
171
  // Selected price 1.3306 falls into second bucket 1.3305 - 1.3310 which has negative sentiment
155
- expect(html).toContain('sell_advantage');
172
+ expect(html).toContain('sell_overbalance');
156
173
  });
157
174
  });
158
175
  });
@@ -6,6 +6,7 @@ import { render } from '@testing-library/react';
6
6
  import React from 'react';
7
7
 
8
8
  import { Legend } from '../../src/CrowdViewWidget/components';
9
+ import { COLOR_MAP } from '../../src/CrowdViewWidget/constants';
9
10
  import { BookType } from '../../src/gql/types/graphql';
10
11
 
11
12
  describe('Crowd View Widget', () => {
@@ -15,13 +16,56 @@ describe('Crowd View Widget', () => {
15
16
  const { getAllByText } = render(
16
17
  <Legend
17
18
  bookType={BookType.Position}
19
+ isDark={false}
18
20
  longValues={[0.15, 0.55]}
19
21
  shortValues={[0.15, 0.55]}
20
22
  />
21
23
  );
22
24
 
23
- expect(getAllByText(/long/)).toHaveLength(2);
24
- expect(getAllByText(/short/)).toHaveLength(2);
25
+ expect(getAllByText(/long/)).toHaveLength(1);
26
+ expect(getAllByText(/short/)).toHaveLength(1);
27
+ });
28
+
29
+ it('passes isDark prop to LegendBar components in light mode', () => {
30
+ const { getAllByTestId } = render(
31
+ <Legend
32
+ bookType={BookType.Position}
33
+ isDark={false}
34
+ longValues={[0.15, 0.55]}
35
+ shortValues={[0.15, 0.55]}
36
+ />
37
+ );
38
+ const segments = getAllByTestId('legend-bar-segment');
39
+ expect(segments.length).toBe(2);
40
+ // Check that segments use light mode colors (either long or short)
41
+ const styles = segments.map((segment) => segment.getAttribute('style'));
42
+ const hasLightColors = styles.some(
43
+ (style) =>
44
+ style?.includes(COLOR_MAP.light.long[0]) ||
45
+ style?.includes(COLOR_MAP.light.short[0])
46
+ );
47
+ expect(hasLightColors).toBe(true);
48
+ });
49
+
50
+ it('passes isDark prop to LegendBar components in dark mode', () => {
51
+ const { getAllByTestId } = render(
52
+ <Legend
53
+ bookType={BookType.Position}
54
+ isDark={true}
55
+ longValues={[0.15, 0.55]}
56
+ shortValues={[0.15, 0.55]}
57
+ />
58
+ );
59
+ const segments = getAllByTestId('legend-bar-segment');
60
+ expect(segments.length).toBe(2);
61
+ // Check that segments use dark mode colors (either long or short)
62
+ const styles = segments.map((segment) => segment.getAttribute('style'));
63
+ const hasDarkColors = styles.some(
64
+ (style) =>
65
+ style?.includes(COLOR_MAP.dark.long[0]) ||
66
+ style?.includes(COLOR_MAP.dark.short[0])
67
+ );
68
+ expect(hasDarkColors).toBe(true);
25
69
  });
26
70
  });
27
71
  });
@@ -6,6 +6,7 @@ import { render } from '@testing-library/react';
6
6
  import React from 'react';
7
7
 
8
8
  import { LegendBar } from '../../src/CrowdViewWidget/components';
9
+ import { COLOR_MAP } from '../../src/CrowdViewWidget/constants';
9
10
 
10
11
  describe('Crowd View Widget', () => {
11
12
  describe('components', () => {
@@ -14,20 +15,91 @@ describe('Crowd View Widget', () => {
14
15
 
15
16
  it('renders LegendBar with min and max values', () => {
16
17
  const { getByText } = render(
17
- <LegendBar label="long" type="long" values={mockValues} />
18
+ <LegendBar
19
+ isDark={false}
20
+ label="long"
21
+ type="long"
22
+ values={mockValues}
23
+ />
18
24
  );
19
25
 
20
- expect(getByText('0.15% long')).toBeInTheDocument();
21
- expect(getByText('0.55% long')).toBeInTheDocument();
26
+ expect(getByText('long')).toBeInTheDocument();
27
+ expect(getByText('0.15%')).toBeInTheDocument();
28
+ expect(getByText('≤ 0.55%')).toBeInTheDocument();
22
29
  });
23
30
 
24
31
  it('renders exactly 1 segment', () => {
25
32
  const { getAllByTestId } = render(
26
- <LegendBar label="short" type="short" values={mockValues} />
33
+ <LegendBar
34
+ isDark={false}
35
+ label="short"
36
+ type="short"
37
+ values={mockValues}
38
+ />
27
39
  );
28
40
  const segments = getAllByTestId('legend-bar-segment');
29
41
  expect(segments.length).toBe(1);
30
42
  });
43
+
44
+ it('uses light mode colors when isDark is false', () => {
45
+ const { getByTestId } = render(
46
+ <LegendBar
47
+ isDark={false}
48
+ label="long"
49
+ type="long"
50
+ values={mockValues}
51
+ />
52
+ );
53
+ const segment = getByTestId('legend-bar-segment');
54
+ const style = segment.getAttribute('style');
55
+ expect(style).toContain(COLOR_MAP.light.long[0]);
56
+ expect(style).toContain(COLOR_MAP.light.long[1]);
57
+ });
58
+
59
+ it('uses dark mode colors when isDark is true', () => {
60
+ const { getByTestId } = render(
61
+ <LegendBar
62
+ isDark={true}
63
+ label="long"
64
+ type="long"
65
+ values={mockValues}
66
+ />
67
+ );
68
+ const segment = getByTestId('legend-bar-segment');
69
+ const style = segment.getAttribute('style');
70
+ expect(style).toContain(COLOR_MAP.dark.long[0]);
71
+ expect(style).toContain(COLOR_MAP.dark.long[1]);
72
+ });
73
+
74
+ it('uses light mode short colors when isDark is false', () => {
75
+ const { getByTestId } = render(
76
+ <LegendBar
77
+ isDark={false}
78
+ label="short"
79
+ type="short"
80
+ values={mockValues}
81
+ />
82
+ );
83
+ const segment = getByTestId('legend-bar-segment');
84
+ const style = segment.getAttribute('style');
85
+ expect(style).toContain(COLOR_MAP.light.short[0]);
86
+ expect(style).toContain(COLOR_MAP.light.short[1]);
87
+ });
88
+
89
+ it('uses dark mode short colors when isDark is true', () => {
90
+ const { getByTestId } = render(
91
+ <LegendBar
92
+ isDark={true}
93
+ label="short"
94
+ type="short"
95
+ values={mockValues}
96
+ />
97
+ );
98
+ const segment = getByTestId('legend-bar-segment');
99
+ const style = segment.getAttribute('style');
100
+ expect(style).toContain(COLOR_MAP.dark.short[0]);
101
+ expect(style).toContain(COLOR_MAP.dark.short[1]);
102
+ });
31
103
  });
32
104
  });
33
105
  });