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

[DAPI] feat: app manager desktop #315

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,534 changes: 1,257 additions & 277 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dependencies": {
"@deriv/deriv-api": "^1.0.11",
"@deriv/quill-design": "^1.2.18",
"@deriv/quill-icons": "^1.21.3",
"@deriv/ui": "^0.1.0",
"@docusaurus/core": "^2.4.0",
"@docusaurus/plugin-client-redirects": "^2.4.0",
Expand All @@ -31,8 +32,10 @@
"@easyops-cn/docusaurus-search-local": "^0.35.0",
"@hookform/resolvers": "^2.9.10",
"@mdx-js/react": "^1.6.22",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-tabs": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.7",
"@react-spring/web": "^9.7.3",
"@testing-library/react-hooks": "^8.0.1",
"@use-gesture/react": "^10.3.0",
Expand Down
31 changes: 31 additions & 0 deletions src/components/CustomAccordion/__tests__/custom-accordion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import CustomAccordion from '..';
import userEvent from '@testing-library/user-event';

const mock_accordion_items = [
{ header: 'header_1', content: 'content 1' },
{ header: 'header_2', content: 'content 2' },
];

describe('CustomAccordion', () => {
beforeEach(() => {
render(<CustomAccordion items={mock_accordion_items} />);
});

afterEach(() => {
cleanup();
});

it('should render the custom accordion', () => {
const header = screen.getByText('header_1');
expect(header).toBeInTheDocument();
});

it('should open accordion content on click', async () => {
const header = screen.getByText('header_2');
await userEvent.click(header);
const content = screen.getByText('content 2');
expect(content).toBeInTheDocument();
});
});
79 changes: 79 additions & 0 deletions src/components/CustomAccordion/custom-accordion.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.accordion_root {
margin: 16px;
margin-top: 48px;
display: flex;
flex-direction: column;

&__item {
overflow: hidden;
margin-top: 2px;
border-radius: 24px;
}
}

.accordion_header {
display: flex;
background-color: transparent;

[data-state='open'] {
background-color: var(--opacity-black-75);
}

&__trigger {
font-family: inherit;
padding: 24px;
height: 42px;
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
line-height: 1;
}

.accordion_chevron {
transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1);
}

[data-state='open'] > .accordion_chevron {
transform: rotate(180deg);
}
}

.accordion_content {
overflow: hidden;
background-color: var(--opacity-black-75);

&__text {
padding: 16px 18px;
font-size: 14px;
font-weight: 400;
}

&[data-state='open'] {
animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1);
}

&[data-state='closed'] {
animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1);
background-color: transparent;
}
}

@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}

@keyframes slideUp {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
41 changes: 41 additions & 0 deletions src/components/CustomAccordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { StandaloneChevronDownRegularIcon } from '@deriv/quill-icons';
import * as Accordion from '@radix-ui/react-accordion';
import './custom-accordion.scss';

type TCustomAccordionProps = {
items: Array<{ header: string; content: React.ReactNode }>;
};

const AccordionTrigger: React.FC = ({ children }) => (
<Accordion.Header className='accordion_header'>
<Accordion.Trigger className='accordion_header__trigger'>
{children}
<StandaloneChevronDownRegularIcon iconSize='md' className='accordion_chevron' />
</Accordion.Trigger>
</Accordion.Header>
);

const AccordionContent: React.FC = ({ children }) => (
<Accordion.Content className='accordion_content'>
<div className='accordion_content__text'>{children}</div>
</Accordion.Content>
);

const CustomAccordion: React.FC<TCustomAccordionProps> = ({ items }) => (
<Accordion.Root
data-testid='dt_accordion_root'
className='accordion_root'
type='single'
collapsible
>
{items.map((item) => (
<Accordion.Item className='accordion_root__item' key={item.header} value={item.header}>
<AccordionTrigger>{item.header}</AccordionTrigger>
<AccordionContent>{item.content}</AccordionContent>
</Accordion.Item>
))}
</Accordion.Root>
);

export default CustomAccordion;
31 changes: 31 additions & 0 deletions src/components/CustomTabs/__tests__/custom-tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import CustomTabs from '..';
import userEvent from '@testing-library/user-event';

const mock_tabs = [
{ label: 'tab_1', content: 'content 1' },
{ label: 'tab_2', content: 'content 2' },
];

describe('CustomTabs', () => {
beforeEach(() => {
render(<CustomTabs tabs={mock_tabs}></CustomTabs>);
});

afterEach(() => {
cleanup();
});

it('should render the custom tabs', () => {
const tab = screen.getByText('tab_1');
expect(tab).toBeInTheDocument();
});

it('should change tab content on different tab click', async () => {
const tab = screen.getByText('tab_2');
await userEvent.click(tab);
const content = screen.getByText('content 2');
expect(content).toBeInTheDocument();
});
});
30 changes: 30 additions & 0 deletions src/components/CustomTabs/custom-tabs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.tabs {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;

&_header {
margin-block: 64px;
background-color: var(--opacity-black-75);
padding: 12px;
border-radius: 24px;
text-align: center;

&__items {
display: flex;
justify-content: space-between;
align-items: center;
}
&__item {
padding: 8px;
min-width: 160px;
cursor: pointer;

&.active {
background-color: var(--solid-slate-50);
border-radius: 12px;
}
}
}
}
32 changes: 32 additions & 0 deletions src/components/CustomTabs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useState } from 'react';
import './custom-tabs.scss';

const CustomTabs: React.FC<{
tabs: Array<{
label: string;
content: React.ReactNode;
}>;
}> = ({ tabs }) => {
const [activeTab, setActiveTab] = useState(0);

return (
<div className='tabs'>
<div className='tabs_header'>
<div className='tabs_header__items'>
{tabs.map((tab, index) => (
<div
key={index}
className={`tabs_header__item ${activeTab === index ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.label}
</div>
))}
</div>
</div>
<div className='tabs_content'>{tabs[activeTab].content}</div>
</div>
);
};

export default CustomTabs;
30 changes: 30 additions & 0 deletions src/components/CustomTooltip/__tests__/custom-tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import CustomTooltip from '..';
import userEvent from '@testing-library/user-event';

describe('CustomTooltip', () => {
beforeEach(() => {
render(
<CustomTooltip text='tooltip text'>
<div>outer text</div>
</CustomTooltip>,
);
});

afterEach(() => {
cleanup();
});

it('should render the custom tooltip with children', () => {
const text = screen.getByText('outer text');
expect(text).toBeInTheDocument();
});

it('should render the tooltip text on hover', async () => {
const text = screen.getByText('outer text');
await userEvent.hover(text);
const tooltip_text = screen.getAllByText('tooltip text');
expect(tooltip_text[0]).toBeInTheDocument();
});
});
19 changes: 19 additions & 0 deletions src/components/CustomTooltip/custom-tooltip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.tooltip_content {
border-radius: 4px;
padding: 8px 0px;
font-size: 12px;
line-height: 14px;
color: var(--ifm-color-emphasis-100);
background-color: var(--ifm-color-emphasis-700);
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
user-select: none;
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
max-width: 96px;
text-align: center;
}

.tooltip_arrow {
fill: var(--ifm-color-emphasis-700);
}
21 changes: 21 additions & 0 deletions src/components/CustomTooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';
import './custom-tooltip.scss';

const CustomTooltip: React.FC<{ text: React.ReactNode }> = ({ children, text }) => {
return (
<Tooltip.Provider delayDuration={0}>
<Tooltip.Root>
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content side='bottom' className='tooltip_content'>
{text}
<Tooltip.Arrow className='tooltip_arrow' />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
};

export default CustomTooltip;
2 changes: 1 addition & 1 deletion src/contexts/app-manager/app-manager.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type TAppManagerContextProps = {

const AppManagerContextProvider = ({ children }: TAppManagerContextProps) => {
const [apps, setApps] = useState<ApplicationObject[]>([]);
const [currentTab, setCurrentTab] = useState<TDashboardTab>('MANAGE_TOKENS');
const [currentTab, setCurrentTab] = useState<TDashboardTab>('MANAGE_APPS');
const [is_dashboard, setIsDashboard] = useState(false);
const [app_register_modal_open, setAppRegisterModalOpen] = useState(false);
const { getAllApps, apps: updatedApps } = useGetApps();
Expand Down
2 changes: 2 additions & 0 deletions src/features/dashboard/__tests__/AppManager.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const mockUseAppManager = useAppManager as jest.MockedFunction<
mockUseAppManager.mockImplementation(() => ({
setIsDashboard: jest.fn(),
getApps: jest.fn(),
updateCurrentTab: jest.fn(),
}));

jest.mock('react-table');
Expand Down Expand Up @@ -78,6 +79,7 @@ describe('AppManager', () => {
setIsDashboard: jest.fn(),
apps: [],
getApps: jest.fn(),
updateCurrentTab: jest.fn(),
}));
mockUseApiToken.mockImplementation(() => ({
tokens: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
padding-block: 72px;
width: 100%;

&_main {
max-width: 608px;
}

&_top {
max-width: 608px;
margin: auto;
text-align: center;
padding-inline: 16px;
h2 {
margin-bottom: 16px;
}
}

&_main {
width: 100%;
}
}
Loading