Skip to content

Commit d0f21f2

Browse files
authored
[DTRA] / DRAFT/ Kate / DTRA-497 / Trade Type section Updates (#12984)
* feat: add dropdown to the contract description section * feat: add learn more button * refactor: removed info icons from menu and moved high low under ups and downs * feat: add learn more functionality to trading page * feat: add arrow to the trade type widget for mobile * feat: add info banner for unavailable contracts * feat: add optional title for some conditions * refactor: remove code smells * refactor: remove sonar cloud issue * refactor: finally remove sonarcloud issue * refactor: add tests for shared and components packages * refactor: add tests for tradr package * refactor: analytics * fix: analytics * refactor: apply suggestions * fix: failling test case * refactor: add more tests * refactor: apply suggestions * refactor: rename classnames * chore: empty commit to retrigger checks * refactor: revert changes * chore: rename according to the new convention * fix: height for mobile * refactor: increase chart space * fix: bug with switching between description and glossary tabs * fix: margin for mobile dropdown * fix: background color * fix: margin mobile * refactor: add fixed width for banner
1 parent fcf9d9f commit d0f21f2

34 files changed

+667
-143
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import ArrowButton from '../arrow-button';
5+
6+
const mockedTitle = 'mocked title';
7+
8+
const mockedDefaultProps = {
9+
is_collapsed: false,
10+
position: 'top' as React.ComponentProps<typeof ArrowButton>['position'],
11+
onClick: jest.fn(),
12+
title: mockedTitle,
13+
handle_button: false,
14+
};
15+
16+
describe('<ArrowButton/>', () => {
17+
it('should render icon with title for top position if title is defined', () => {
18+
render(<ArrowButton {...mockedDefaultProps} />);
19+
20+
expect(screen.getByText(mockedTitle)).toBeInTheDocument();
21+
});
22+
23+
it('should not render icon with title for top position if title is undefined', () => {
24+
render(<ArrowButton {...mockedDefaultProps} title={undefined} />);
25+
26+
expect(screen.queryByText(mockedTitle)).not.toBeInTheDocument();
27+
});
28+
29+
it('should render icon with title for bottom position if title is defined', () => {
30+
render(<ArrowButton {...mockedDefaultProps} position='bottom' />);
31+
32+
expect(screen.getByText(mockedTitle)).toBeInTheDocument();
33+
});
34+
35+
it('should not render icon with title for bottom position if title is undefined', () => {
36+
render(<ArrowButton {...mockedDefaultProps} title={undefined} position='bottom' />);
37+
38+
expect(screen.queryByText(mockedTitle)).not.toBeInTheDocument();
39+
});
40+
41+
it('should render icon_flat with if handle_button === true', () => {
42+
render(<ArrowButton {...mockedDefaultProps} handle_button />);
43+
44+
expect(screen.getByTestId('icon_handle')).toBeInTheDocument();
45+
});
46+
47+
it('should call onClick function if user clicks on icon', () => {
48+
render(<ArrowButton {...mockedDefaultProps} />);
49+
userEvent.click(screen.getByText(mockedTitle));
50+
51+
expect(mockedDefaultProps.onClick).toHaveBeenCalled();
52+
});
53+
});

packages/components/src/components/collapsible/arrow-button.tsx

+11-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type TArrowButton = {
1414
onClick: () => void;
1515
title?: string;
1616
position?: 'top' | 'bottom';
17+
handle_button?: boolean;
1718
};
1819

1920
const IconArrow = ({ className }: { className?: string }) => (
@@ -29,7 +30,7 @@ const IconArrowWithTitle = ({ title, ...props }: TIconArrowWithTitle) => (
2930
</React.Fragment>
3031
);
3132

32-
const ArrowButton = ({ is_collapsed = false, position, onClick, title }: TArrowButton) => {
33+
const ArrowButton = ({ is_collapsed = false, position, onClick, title, handle_button = false }: TArrowButton) => {
3334
const [is_open, expand] = React.useState(!is_collapsed);
3435

3536
const toggleExpand = () => {
@@ -82,8 +83,16 @@ const ArrowButton = ({ is_collapsed = false, position, onClick, title }: TArrowB
8283
);
8384
}
8485

86+
if (handle_button) icon_arrow = <div className='dc-collapsible__icon--handle' data-testid='icon_handle' />;
87+
8588
return (
86-
<div className='dc-collapsible__button' onClick={toggleExpand}>
89+
<div
90+
className={classNames('dc-collapsible__button', {
91+
'dc-collapsible__button--handle': handle_button,
92+
})}
93+
onClick={toggleExpand}
94+
onKeyDown={toggleExpand}
95+
>
8796
{icon_arrow}
8897
</div>
8998
);

packages/components/src/components/collapsible/collapsible.scss

+13
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,23 @@
3838
align-items: center;
3939
justify-content: center;
4040
height: 32px;
41+
42+
&--handle {
43+
align-items: baseline;
44+
height: 3.8rem;
45+
}
4146
}
4247
&__icon {
4348
transition: transform 0.3s ease-in-out;
4449

50+
&--handle {
51+
width: 4rem;
52+
height: 0.4rem;
53+
margin-top: 0.8rem;
54+
border-radius: 0.2rem;
55+
background-color: var(--general-active);
56+
}
57+
4558
&--top {
4659
transform: rotate(180deg);
4760

packages/components/src/components/collapsible/collapsible.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type TCollapsible = {
99
position?: 'top' | 'bottom';
1010
onClick: (state: boolean) => void;
1111
title?: string;
12+
handle_button?: boolean;
1213
};
1314

1415
const swipe_config = {
@@ -24,6 +25,7 @@ const Collapsible = ({
2425
children,
2526
onClick,
2627
title,
28+
handle_button,
2729
}: React.PropsWithChildren<TCollapsible>) => {
2830
const [is_open, expand] = React.useState(!is_collapsed);
2931
const [should_show_collapsible, setShouldShowCollapsible] = React.useState(false);
@@ -58,7 +60,13 @@ const Collapsible = ({
5860
});
5961

6062
const arrow_button = (
61-
<ArrowButton is_collapsed={!is_open} position={position} onClick={toggleExpand} title={title} />
63+
<ArrowButton
64+
is_collapsed={!is_open}
65+
position={position}
66+
onClick={toggleExpand}
67+
title={title}
68+
handle_button={handle_button}
69+
/>
6270
);
6371
const CustomTag = as || 'div';
6472
return (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import Items from '../items';
4+
5+
describe('<Items />', () => {
6+
let mockedDefaultProps: React.ComponentProps<typeof Items>;
7+
8+
beforeEach(() => {
9+
mockedDefaultProps = {
10+
handleSelect: jest.fn(),
11+
onKeyPressed: jest.fn(),
12+
value: 'multiplier',
13+
nodes: null,
14+
items: [
15+
{ text: 'Multipliers', value: 'multiplier' },
16+
{ text: 'Accumulators', value: 'accumulator' },
17+
],
18+
setScrollHeight: jest.fn(),
19+
};
20+
});
21+
22+
it('should render list of passed items', () => {
23+
render(<Items {...mockedDefaultProps} />);
24+
25+
expect(screen.getByText('Multipliers')).toBeInTheDocument();
26+
expect(screen.getByText('Accumulators')).toBeInTheDocument();
27+
});
28+
29+
it('should call setScrollHeight function if value === item.value', () => {
30+
render(<Items {...mockedDefaultProps} />);
31+
32+
expect(mockedDefaultProps.setScrollHeight).toBeCalled();
33+
});
34+
it('should not call setScrollHeight function if value !== item.value', () => {
35+
mockedDefaultProps.value = 'vanilla';
36+
render(<Items {...mockedDefaultProps} />);
37+
38+
expect(mockedDefaultProps.setScrollHeight).not.toBeCalled();
39+
});
40+
});

packages/components/src/components/dropdown/dropdown.scss

+2-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@
269269
min-width: 15rem;
270270
width: 100%;
271271

272-
&:not(.cfd-personal-details-modal__form *):not(.trade-container__multiplier-dropdown):not(.dc-dropdown--left) {
272+
&:not(.cfd-personal-details-modal__form *):not(.trade-container__multiplier-dropdown):not(
273+
.dc-dropdown--left):not(.contract-type-info__dropdown) {
273274
margin-top: unset;
274275
}
275276

packages/components/src/components/dropdown/dropdown.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type TDropdown = {
4444
onClick?: () => void;
4545
placeholder?: string;
4646
suffix_icon?: string;
47+
should_scroll_to_selected?: boolean;
48+
should_autohide?: boolean;
4749
test_id?: string;
4850
value?: string | number;
4951
classNameIcon?: string;
@@ -67,6 +69,8 @@ type TDropdownList = {
6769
parent_ref: React.RefObject<HTMLElement>;
6870
portal_id?: string;
6971
suffix_icon?: string;
72+
should_scroll_to_selected?: boolean;
73+
should_autohide?: boolean;
7074
value?: string | number;
7175
};
7276

@@ -89,11 +93,15 @@ const DropdownList = React.forwardRef<HTMLDivElement, TDropdownList>((props, lis
8993
parent_ref,
9094
portal_id,
9195
suffix_icon,
96+
should_scroll_to_selected,
97+
should_autohide,
9298
value,
9399
} = props;
94100

95101
const [list_dimensions, setListDimensions] = React.useState([initial_offset, 0]);
96102
const [style, setStyle] = React.useState({});
103+
const [scroll_height, setScrollHeight] = React.useState<number>();
104+
97105
const is_portal = !!portal_id;
98106

99107
React.useEffect(() => {
@@ -188,7 +196,12 @@ const DropdownList = React.forwardRef<HTMLDivElement, TDropdownList>((props, lis
188196
role='list'
189197
ref={list_ref}
190198
>
191-
<ThemedScrollbars height={list_dimensions[1] || '200px'}>
199+
<ThemedScrollbars
200+
height={list_dimensions[1] || '200px'}
201+
scroll_height={scroll_height}
202+
should_scroll_to_selected={should_scroll_to_selected}
203+
autohide={should_autohide}
204+
>
192205
{Array.isArray(list) ? (
193206
<Items
194207
onKeyPressed={onKeyPressed}
@@ -199,6 +212,7 @@ const DropdownList = React.forwardRef<HTMLDivElement, TDropdownList>((props, lis
199212
is_align_text_left={is_align_text_left}
200213
value={value}
201214
nodes={nodes.current}
215+
setScrollHeight={setScrollHeight}
202216
/>
203217
) : (
204218
Object.keys(list).map((key, idx) => (
@@ -261,6 +275,8 @@ const Dropdown = ({
261275
onClick,
262276
placeholder,
263277
suffix_icon,
278+
should_scroll_to_selected,
279+
should_autohide,
264280
test_id,
265281
value,
266282
classNameIcon,
@@ -503,6 +519,8 @@ const Dropdown = ({
503519
portal_id={list_portal_id}
504520
ref={list_ref}
505521
suffix_icon={suffix_icon}
522+
should_scroll_to_selected={should_scroll_to_selected}
523+
should_autohide={should_autohide}
506524
value={value}
507525
/>
508526
</div>

packages/components/src/components/dropdown/items.tsx

+20-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type TItem = {
1414
is_align_text_left?: boolean;
1515
nodes: Map<string, HTMLDivElement | null> | null;
1616
item: TListItem;
17+
setScrollHeight?: (new_value: number) => void;
1718
};
1819

1920
type TItems = Omit<TItem, 'item'> & {
@@ -30,10 +31,21 @@ const Items = ({ items, ...props }: TItems) => {
3031
);
3132
};
3233

33-
const Item = ({ onKeyPressed, value, item, handleSelect, nodes, has_symbol, is_align_text_left, className }: TItem) => {
34+
const Item = ({
35+
onKeyPressed,
36+
value,
37+
item,
38+
handleSelect,
39+
nodes,
40+
has_symbol,
41+
is_align_text_left,
42+
className,
43+
setScrollHeight,
44+
}: TItem) => {
3445
const item_ref = React.useRef<HTMLDivElement>(null);
3546
const symbol_type_class_name =
3647
item.text && typeof item.text === 'string' ? `symbols--${item.text.toLowerCase()}` : null;
48+
const is_selected = value === item.value;
3749

3850
React.useEffect(() => {
3951
const removeListeners = () => {
@@ -51,11 +63,17 @@ const Item = ({ onKeyPressed, value, item, handleSelect, nodes, has_symbol, is_a
5163
return () => removeListeners();
5264
}, [item, nodes, onKeyPressed]);
5365

66+
React.useEffect(() => {
67+
if (setScrollHeight && item_ref.current && is_selected) {
68+
setScrollHeight(item_ref.current.offsetTop - item_ref.current.scrollHeight);
69+
}
70+
}, [item_ref, setScrollHeight, is_selected]);
71+
5472
return (
5573
<div
5674
className={classNames(
5775
'dc-list__item',
58-
{ 'dc-list__item--selected': value === item.value },
76+
{ 'dc-list__item--selected': is_selected },
5977
{ 'dc-list__item--disabled': item.disabled }
6078
)}
6179
data-testid='dti_list_item'

packages/components/src/components/mobile-dialog/mobile-dialog.scss

+15-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,21 @@
9393
border-bottom: 0.01rem solid transparent;
9494

9595
.inline-message__information {
96-
margin: 1.6rem 0.8rem;
96+
margin: 1.6rem 0.8rem -0.8rem;
97+
}
98+
99+
.learn-more {
100+
height: 5rem;
101+
width: calc(100% - 1.6rem);
102+
margin: 1.6rem 0.8rem 0.8rem;
103+
padding: 1.6rem;
104+
display: flex;
105+
justify-content: space-between;
106+
align-items: center;
107+
border: none;
108+
background-color: var(--general-section-1);
109+
cursor: pointer;
110+
border-radius: $BORDER_RADIUS;
97111
}
98112
}
99113
}

packages/components/src/components/mobile-dialog/mobile-dialog.tsx

+16-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type TMobileDialog = {
2121
title?: React.ReactNode;
2222
visible?: boolean;
2323
wrapper_classname?: string;
24+
learn_more_banner?: React.ReactNode;
2425
};
2526

2627
const MobileDialog = (props: React.PropsWithChildren<TMobileDialog>) => {
@@ -36,6 +37,7 @@ const MobileDialog = (props: React.PropsWithChildren<TMobileDialog>) => {
3637
title,
3738
visible,
3839
wrapper_classname,
40+
learn_more_banner,
3941
} = props;
4042

4143
const footer_ref = React.useRef<HTMLDivElement>(null);
@@ -100,16 +102,25 @@ const MobileDialog = (props: React.PropsWithChildren<TMobileDialog>) => {
100102
<Div100vhContainer
101103
className={classNames('dc-mobile-dialog__container', {
102104
'dc-mobile-dialog__container--has-scroll': props.has_content_scroll,
103-
'dc-mobile-dialog__container--has-info-banner': info_banner,
105+
'dc-mobile-dialog__container--has-info-banner': info_banner || learn_more_banner,
104106
})}
105107
height_offset={props.content_height_offset || '8px'}
106108
>
107109
<ThemedScrollbars
108-
is_bypassed={!info_banner}
110+
is_bypassed={!info_banner && !learn_more_banner}
109111
is_scrollbar_hidden
110-
className={info_banner ? classNames('dc-mobile-dialog__header-wrapper', header_classname) : ''}
112+
className={
113+
info_banner || learn_more_banner
114+
? classNames('dc-mobile-dialog__header-wrapper', header_classname)
115+
: ''
116+
}
111117
>
112-
<div className={classNames('dc-mobile-dialog__header', !info_banner && header_classname)}>
118+
<div
119+
className={classNames(
120+
'dc-mobile-dialog__header',
121+
!info_banner && !learn_more_banner && header_classname
122+
)}
123+
>
113124
<Text
114125
as='h2'
115126
size='xs'
@@ -131,6 +142,7 @@ const MobileDialog = (props: React.PropsWithChildren<TMobileDialog>) => {
131142
)}
132143
</div>
133144
{info_banner}
145+
{learn_more_banner}
134146
</ThemedScrollbars>
135147
<div
136148
className={classNames('dc-mobile-dialog__content', {

0 commit comments

Comments
 (0)