Skip to content
This repository was archived by the owner on May 13, 2024. It is now read-only.

Commit a8cf15c

Browse files
Merge pull request #315 from shafin-deriv/shafin/DAPI-464/feat--api-application-screen
[DAPI] feat: app manager desktop
2 parents 07dffb8 + 0da7466 commit a8cf15c

38 files changed

+2169
-477
lines changed

package-lock.json

Lines changed: 1257 additions & 277 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"dependencies": {
2424
"@deriv/deriv-api": "^1.0.11",
2525
"@deriv/quill-design": "^1.2.18",
26+
"@deriv/quill-icons": "^1.21.3",
2627
"@deriv/ui": "^0.1.0",
2728
"@docusaurus/core": "^2.4.0",
2829
"@docusaurus/plugin-client-redirects": "^2.4.0",
@@ -31,8 +32,10 @@
3132
"@easyops-cn/docusaurus-search-local": "^0.35.0",
3233
"@hookform/resolvers": "^2.9.10",
3334
"@mdx-js/react": "^1.6.22",
35+
"@radix-ui/react-accordion": "^1.1.2",
3436
"@radix-ui/react-dropdown-menu": "^2.0.2",
3537
"@radix-ui/react-tabs": "^1.0.2",
38+
"@radix-ui/react-tooltip": "^1.0.7",
3639
"@react-spring/web": "^9.7.3",
3740
"@testing-library/react-hooks": "^8.0.1",
3841
"@use-gesture/react": "^10.3.0",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { cleanup, render, screen } from '@testing-library/react';
3+
import CustomAccordion from '..';
4+
import userEvent from '@testing-library/user-event';
5+
6+
const mock_accordion_items = [
7+
{ header: 'header_1', content: 'content 1' },
8+
{ header: 'header_2', content: 'content 2' },
9+
];
10+
11+
describe('CustomAccordion', () => {
12+
beforeEach(() => {
13+
render(<CustomAccordion items={mock_accordion_items} />);
14+
});
15+
16+
afterEach(() => {
17+
cleanup();
18+
});
19+
20+
it('should render the custom accordion', () => {
21+
const header = screen.getByText('header_1');
22+
expect(header).toBeInTheDocument();
23+
});
24+
25+
it('should open accordion content on click', async () => {
26+
const header = screen.getByText('header_2');
27+
await userEvent.click(header);
28+
const content = screen.getByText('content 2');
29+
expect(content).toBeInTheDocument();
30+
});
31+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
.accordion_root {
2+
margin: 16px;
3+
margin-top: 48px;
4+
display: flex;
5+
flex-direction: column;
6+
7+
&__item {
8+
overflow: hidden;
9+
margin-top: 2px;
10+
border-radius: 24px;
11+
}
12+
}
13+
14+
.accordion_header {
15+
display: flex;
16+
background-color: transparent;
17+
18+
[data-state='open'] {
19+
background-color: var(--opacity-black-75);
20+
}
21+
22+
&__trigger {
23+
font-family: inherit;
24+
padding: 24px;
25+
height: 42px;
26+
flex: 1;
27+
display: flex;
28+
align-items: center;
29+
justify-content: space-between;
30+
font-size: 16px;
31+
line-height: 1;
32+
}
33+
34+
.accordion_chevron {
35+
transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1);
36+
}
37+
38+
[data-state='open'] > .accordion_chevron {
39+
transform: rotate(180deg);
40+
}
41+
}
42+
43+
.accordion_content {
44+
overflow: hidden;
45+
background-color: var(--opacity-black-75);
46+
47+
&__text {
48+
padding: 16px 18px;
49+
font-size: 14px;
50+
font-weight: 400;
51+
}
52+
53+
&[data-state='open'] {
54+
animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1);
55+
}
56+
57+
&[data-state='closed'] {
58+
animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1);
59+
background-color: transparent;
60+
}
61+
}
62+
63+
@keyframes slideDown {
64+
from {
65+
height: 0;
66+
}
67+
to {
68+
height: var(--radix-accordion-content-height);
69+
}
70+
}
71+
72+
@keyframes slideUp {
73+
from {
74+
height: var(--radix-accordion-content-height);
75+
}
76+
to {
77+
height: 0;
78+
}
79+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { StandaloneChevronDownRegularIcon } from '@deriv/quill-icons';
3+
import * as Accordion from '@radix-ui/react-accordion';
4+
import './custom-accordion.scss';
5+
6+
type TCustomAccordionProps = {
7+
items: Array<{ header: string; content: React.ReactNode }>;
8+
};
9+
10+
const AccordionTrigger: React.FC = ({ children }) => (
11+
<Accordion.Header className='accordion_header'>
12+
<Accordion.Trigger className='accordion_header__trigger'>
13+
{children}
14+
<StandaloneChevronDownRegularIcon iconSize='md' className='accordion_chevron' />
15+
</Accordion.Trigger>
16+
</Accordion.Header>
17+
);
18+
19+
const AccordionContent: React.FC = ({ children }) => (
20+
<Accordion.Content className='accordion_content'>
21+
<div className='accordion_content__text'>{children}</div>
22+
</Accordion.Content>
23+
);
24+
25+
const CustomAccordion: React.FC<TCustomAccordionProps> = ({ items }) => (
26+
<Accordion.Root
27+
data-testid='dt_accordion_root'
28+
className='accordion_root'
29+
type='single'
30+
collapsible
31+
>
32+
{items.map((item) => (
33+
<Accordion.Item className='accordion_root__item' key={item.header} value={item.header}>
34+
<AccordionTrigger>{item.header}</AccordionTrigger>
35+
<AccordionContent>{item.content}</AccordionContent>
36+
</Accordion.Item>
37+
))}
38+
</Accordion.Root>
39+
);
40+
41+
export default CustomAccordion;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { cleanup, render, screen } from '@testing-library/react';
3+
import CustomTabs from '..';
4+
import userEvent from '@testing-library/user-event';
5+
6+
const mock_tabs = [
7+
{ label: 'tab_1', content: 'content 1' },
8+
{ label: 'tab_2', content: 'content 2' },
9+
];
10+
11+
describe('CustomTabs', () => {
12+
beforeEach(() => {
13+
render(<CustomTabs tabs={mock_tabs}></CustomTabs>);
14+
});
15+
16+
afterEach(() => {
17+
cleanup();
18+
});
19+
20+
it('should render the custom tabs', () => {
21+
const tab = screen.getByText('tab_1');
22+
expect(tab).toBeInTheDocument();
23+
});
24+
25+
it('should change tab content on different tab click', async () => {
26+
const tab = screen.getByText('tab_2');
27+
await userEvent.click(tab);
28+
const content = screen.getByText('content 2');
29+
expect(content).toBeInTheDocument();
30+
});
31+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.tabs {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
justify-content: center;
6+
7+
&_header {
8+
margin-block: 64px;
9+
background-color: var(--opacity-black-75);
10+
padding: 12px;
11+
border-radius: 24px;
12+
text-align: center;
13+
14+
&__items {
15+
display: flex;
16+
justify-content: space-between;
17+
align-items: center;
18+
}
19+
&__item {
20+
padding: 8px;
21+
min-width: 160px;
22+
cursor: pointer;
23+
24+
&.active {
25+
background-color: var(--solid-slate-50);
26+
border-radius: 12px;
27+
}
28+
}
29+
}
30+
}

src/components/CustomTabs/index.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { useState } from 'react';
2+
import './custom-tabs.scss';
3+
4+
const CustomTabs: React.FC<{
5+
tabs: Array<{
6+
label: string;
7+
content: React.ReactNode;
8+
}>;
9+
}> = ({ tabs }) => {
10+
const [activeTab, setActiveTab] = useState(0);
11+
12+
return (
13+
<div className='tabs'>
14+
<div className='tabs_header'>
15+
<div className='tabs_header__items'>
16+
{tabs.map((tab, index) => (
17+
<div
18+
key={index}
19+
className={`tabs_header__item ${activeTab === index ? 'active' : ''}`}
20+
onClick={() => setActiveTab(index)}
21+
>
22+
{tab.label}
23+
</div>
24+
))}
25+
</div>
26+
</div>
27+
<div className='tabs_content'>{tabs[activeTab].content}</div>
28+
</div>
29+
);
30+
};
31+
32+
export default CustomTabs;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
import { cleanup, render, screen } from '@testing-library/react';
3+
import CustomTooltip from '..';
4+
import userEvent from '@testing-library/user-event';
5+
6+
describe('CustomTooltip', () => {
7+
beforeEach(() => {
8+
render(
9+
<CustomTooltip text='tooltip text'>
10+
<div>outer text</div>
11+
</CustomTooltip>,
12+
);
13+
});
14+
15+
afterEach(() => {
16+
cleanup();
17+
});
18+
19+
it('should render the custom tooltip with children', () => {
20+
const text = screen.getByText('outer text');
21+
expect(text).toBeInTheDocument();
22+
});
23+
24+
it('should render the tooltip text on hover', async () => {
25+
const text = screen.getByText('outer text');
26+
await userEvent.hover(text);
27+
const tooltip_text = screen.getAllByText('tooltip text');
28+
expect(tooltip_text[0]).toBeInTheDocument();
29+
});
30+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.tooltip_content {
2+
border-radius: 4px;
3+
padding: 8px 0px;
4+
font-size: 12px;
5+
line-height: 14px;
6+
color: var(--ifm-color-emphasis-100);
7+
background-color: var(--ifm-color-emphasis-700);
8+
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
9+
user-select: none;
10+
animation-duration: 400ms;
11+
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
12+
will-change: transform, opacity;
13+
max-width: 96px;
14+
text-align: center;
15+
}
16+
17+
.tooltip_arrow {
18+
fill: var(--ifm-color-emphasis-700);
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import * as Tooltip from '@radix-ui/react-tooltip';
3+
import './custom-tooltip.scss';
4+
5+
const CustomTooltip: React.FC<{ text: React.ReactNode }> = ({ children, text }) => {
6+
return (
7+
<Tooltip.Provider delayDuration={0}>
8+
<Tooltip.Root>
9+
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
10+
<Tooltip.Portal>
11+
<Tooltip.Content side='bottom' className='tooltip_content'>
12+
{text}
13+
<Tooltip.Arrow className='tooltip_arrow' />
14+
</Tooltip.Content>
15+
</Tooltip.Portal>
16+
</Tooltip.Root>
17+
</Tooltip.Provider>
18+
);
19+
};
20+
21+
export default CustomTooltip;

src/contexts/app-manager/app-manager.provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type TAppManagerContextProps = {
1010

1111
const AppManagerContextProvider = ({ children }: TAppManagerContextProps) => {
1212
const [apps, setApps] = useState<ApplicationObject[]>([]);
13-
const [currentTab, setCurrentTab] = useState<TDashboardTab>('MANAGE_TOKENS');
13+
const [currentTab, setCurrentTab] = useState<TDashboardTab>('MANAGE_APPS');
1414
const [is_dashboard, setIsDashboard] = useState(false);
1515
const [app_register_modal_open, setAppRegisterModalOpen] = useState(false);
1616
const { getAllApps, apps: updatedApps } = useGetApps();

src/features/dashboard/__tests__/AppManager.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const mockUseAppManager = useAppManager as jest.MockedFunction<
3434
mockUseAppManager.mockImplementation(() => ({
3535
setIsDashboard: jest.fn(),
3636
getApps: jest.fn(),
37+
updateCurrentTab: jest.fn(),
3738
}));
3839

3940
jest.mock('react-table');
@@ -78,6 +79,7 @@ describe('AppManager', () => {
7879
setIsDashboard: jest.fn(),
7980
apps: [],
8081
getApps: jest.fn(),
82+
updateCurrentTab: jest.fn(),
8183
}));
8284
mockUseApiToken.mockImplementation(() => ({
8385
tokens: [],

src/features/dashboard/components/AppDashboardContainer/app-dashboard-container.module.scss renamed to src/features/dashboard/components/AppDashboardContainer/app-dashboard-container.scss

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
padding-block: 72px;
66
width: 100%;
77

8-
&_main {
9-
max-width: 608px;
10-
}
11-
128
&_top {
9+
max-width: 608px;
10+
margin: auto;
1311
text-align: center;
1412
padding-inline: 16px;
1513
h2 {
1614
margin-bottom: 16px;
1715
}
1816
}
17+
18+
&_main {
19+
width: 100%;
20+
}
1921
}

0 commit comments

Comments
 (0)