chore: align ui latest from repo

This commit is contained in:
Juan Picado 2021-04-15 21:10:28 +02:00
parent 64f0921477
commit 7c0a0c106d
139 changed files with 2183 additions and 708 deletions

View File

@ -33,6 +33,7 @@ module.exports = Object.assign({}, config, {
// note: this section has to be on sync with webpack configuration // note: this section has to be on sync with webpack configuration
'verdaccio-ui/components/(.*)': '<rootDir>/src/components/$1', 'verdaccio-ui/components/(.*)': '<rootDir>/src/components/$1',
'verdaccio-ui/utils/(.*)': '<rootDir>/src/utils/$1', 'verdaccio-ui/utils/(.*)': '<rootDir>/src/utils/$1',
'verdaccio-ui/providers/(.*)': '<rootDir>/src/providers/$1',
'verdaccio-ui/design-tokens/(.*)': '<rootDir>/src/design-tokens/$1', 'verdaccio-ui/design-tokens/(.*)': '<rootDir>/src/design-tokens/$1',
}, },
}); });

View File

@ -5,12 +5,20 @@
import { GlobalWithFetchMock } from 'jest-fetch-mock'; import { GlobalWithFetchMock } from 'jest-fetch-mock';
import 'mutationobserver-shim'; import 'mutationobserver-shim';
// @ts-ignore : Property '__APP_VERSION__' does not exist on type 'Global'.
global.__APP_VERSION__ = '1.0.0';
// @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'. // @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'.
global.__VERDACCIO_BASENAME_UI_OPTIONS = { base: 'http://localhost' }; global.__VERDACCIO_BASENAME_UI_OPTIONS = {
// @ts-ignore : Property 'VERDACCIO_API_URL' does not exist on type 'Global'. base: 'http://localhost',
global.VERDACCIO_API_URL = 'https://verdaccio.tld'; protocol: 'http',
host: 'localhost',
primaryColor: '#4b5e40',
url_prefix: '',
darkMode: false,
language: 'en-US',
uri: 'http://localhost:4873',
title: 'Verdaccio Dev UI',
scope: '',
version: 'v1.0.0',
};
const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock; const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
customGlobal.fetch = require('jest-fetch-mock'); customGlobal.fetch = require('jest-fetch-mock');
@ -19,8 +27,7 @@ customGlobal.fetchMock = customGlobal.fetch;
// mocking few DOM methods // mocking few DOM methods
// @ts-ignore : Property 'document' does not exist on type 'Global'. // @ts-ignore : Property 'document' does not exist on type 'Global'.
if (global.document) { if (global.document) {
// @ts-ignore : Type 'Mock<{ selectNodeContents: () => void; }, []>' // @ts-ignore : Type 'Mock<{ selectNodeContents: () => void; }, []>' is not assignable to type '() => Range'.
// is not assignable to type '() => Range'.
document.createRange = jest.fn((): void => ({ document.createRange = jest.fn((): void => ({
selectNodeContents: (): void => {}, selectNodeContents: (): void => {},
})); }));

View File

@ -35,7 +35,7 @@
"babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-emotion": "10.0.33", "babel-plugin-emotion": "10.0.33",
"bundlesize": "0.18.0", "bundlesize": "0.18.0",
"css-loader": "4.3.0", "css-loader": "5.2.1",
"dayjs": "1.9.7", "dayjs": "1.9.7",
"emotion": "10.0.27", "emotion": "10.0.27",
"emotion-theming": "10.0.27", "emotion-theming": "10.0.27",

View File

@ -30,7 +30,7 @@ jest.mock('verdaccio-ui/utils/storage', () => {
return new LocalStorageMock(); return new LocalStorageMock();
}); });
jest.mock('verdaccio-ui/utils/api', () => ({ jest.mock('verdaccio-ui/providers/API/api', () => ({
// eslint-disable-next-line jest/no-mocks-import // eslint-disable-next-line jest/no-mocks-import
request: require('../../jest/unit/components/__mocks__/api').default.request, request: require('../../jest/unit/components/__mocks__/api').default.request,
})); }));
@ -70,7 +70,7 @@ describe('<App />', () => {
expect(queryByTestId('greetings-label')).toBeFalsy(); expect(queryByTestId('greetings-label')).toBeFalsy();
} }
} }
}, 20000); });
test('isUserAlreadyLoggedIn: token already available in storage', async () => { test('isUserAlreadyLoggedIn: token already available in storage', async () => {
storage.setItem('username', 'verdaccio'); storage.setItem('username', 'verdaccio');
@ -93,5 +93,5 @@ describe('<App />', () => {
expect(queryAllByText('verdaccio')).toBeTruthy(); expect(queryAllByText('verdaccio')).toBeTruthy();
} }
} }
}, 20000); });
}); });

View File

@ -35,7 +35,7 @@ const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
/* eslint-disable react/jsx-no-bind */ /* eslint-disable react/jsx-no-bind */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
const App: React.FC = () => { const App: React.FC = () => {
const [user, setUser] = useState<undefined | { username: string }>(); const [user, setUser] = useState<undefined | { username: string | null }>();
/** /**
* Logout user * Logout user
* Required by: <Header /> * Required by: <Header />

View File

@ -6,7 +6,7 @@ export interface AppProps {
} }
export interface User { export interface User {
username: string; username: string | null;
} }
export interface AppContextProps extends AppProps { export interface AppContextProps extends AppProps {

View File

@ -1,5 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useConfig } from 'verdaccio-ui/providers/config';
import AppContext, { AppProps, User } from './AppContext'; import AppContext, { AppProps, User } from './AppContext';
interface Props { interface Props {
@ -8,8 +10,9 @@ interface Props {
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
const AppContextProvider: React.FC<Props> = ({ children, user }) => { const AppContextProvider: React.FC<Props> = ({ children, user }) => {
const { configOptions } = useConfig();
const [state, setState] = useState<AppProps>({ const [state, setState] = useState<AppProps>({
scope: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.scope ?? '', scope: configOptions.scope ?? '',
user, user,
}); });

View File

@ -24,8 +24,7 @@ enum Route {
} }
export const history = createBrowserHistory({ export const history = createBrowserHistory({
// basename is deprecated and already removed in a major released basename: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.url_prefix,
basename: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.basename,
}); });
const AppRoute: React.FC = () => { const AppRoute: React.FC = () => {
@ -38,7 +37,7 @@ const AppRoute: React.FC = () => {
const { user } = appContext; const { user } = appContext;
const isUserLoggedIn = user && user.username; const isUserLoggedIn = user?.username;
return ( return (
<Router history={history}> <Router history={history}>

View File

@ -6,19 +6,15 @@ import Footer from './Footer';
describe('<Footer /> component', () => { describe('<Footer /> component', () => {
beforeAll(() => { beforeAll(() => {
window.__VERDACCIO_BASENAME_UI_OPTIONS = { window.__VERDACCIO_BASENAME_UI_OPTIONS.version = 'v.1.0.0';
version: 'v.1.0.0',
};
}); });
afterAll(() => { afterAll(() => {
// @ts-ignore delete window.__VERDACCIO_BASENAME_UI_OPTIONS.version;
delete window.__VERDACCIO_BASENAME_UI_OPTIONS;
}); });
test('should load the initial state of Footer component', () => { test('should load the initial state of Footer component', () => {
render(<Footer />); const { container } = render(<Footer />);
// FIXME: this match does not work expect(container.firstChild).toMatchSnapshot();
// expect(screen.getByText('Powered by')).toBeInTheDocument();
}); });
}); });

View File

@ -15,6 +15,7 @@ import {
} from 'verdaccio-ui/components/Icons'; } from 'verdaccio-ui/components/Icons';
import Logo from 'verdaccio-ui/components/Logo'; import Logo from 'verdaccio-ui/components/Logo';
import { Theme } from 'verdaccio-ui/design-tokens/theme'; import { Theme } from 'verdaccio-ui/design-tokens/theme';
import { useConfig } from 'verdaccio-ui/providers/config';
import { goToVerdaccioWebsite } from 'verdaccio-ui/utils/windows'; import { goToVerdaccioWebsite } from 'verdaccio-ui/utils/windows';
import { Wrapper, Left, Right, Love, Inner } from './styles'; import { Wrapper, Left, Right, Love, Inner } from './styles';
@ -22,6 +23,7 @@ import { Wrapper, Left, Right, Love, Inner } from './styles';
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
const Footer = () => { const Footer = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { configOptions } = useConfig();
return ( return (
<Wrapper> <Wrapper>
<Inner> <Inner>
@ -42,9 +44,13 @@ const Footer = () => {
</ToolTip> </ToolTip>
</Left> </Left>
<Right> <Right>
{t('footer.powered-by')} {configOptions?.version && (
<Logo onClick={goToVerdaccioWebsite} size="x-small" /> <>
{`/ ${window?.__VERDACCIO_BASENAME_UI_OPTIONS?.version}`} {t('footer.powered-by')}
<Logo onClick={goToVerdaccioWebsite} size="x-small" />
{`/ ${configOptions.version}`}
</>
)}
</Right> </Right>
</Inner> </Inner>
</Wrapper> </Wrapper>

View File

@ -0,0 +1,566 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Footer /> component should load the initial state of Footer component 1`] = `
.emotion-0 {
background: #f9f9f9;
border-top: 1px solid #e3e3e3;
color: #999999;
font-size: 14px;
padding: 20px;
}
.emotion-2 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
width: 100%;
}
@media (min-width:768px) {
.emotion-2 {
min-width: 400px;
max-width: 800px;
margin: auto;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
}
@media (min-width:1024px) {
.emotion-2 {
max-width: 1240px;
}
}
.emotion-4 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: none;
}
@media (min-width:768px) {
.emotion-4 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
.emotion-6 {
color: #e25555;
padding: 0 5px;
}
.emotion-8 {
position: relative;
height: 18px;
}
.emotion-8:hover .emotion-14 {
visibility: visible;
}
.emotion-11 {
width: 18px;
height: 18px;
margin: 0px 8px;
}
.emotion-13 {
display: inline-grid;
grid-template-columns: repeat(8,max-content);
grid-gap: 0px 8px;
position: absolute;
background: #d3dddd;
padding: 1px 4px;
border-radius: 3px;
height: 20px;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
visibility: hidden;
top: -2px;
}
.emotion-13:before {
content: '';
position: absolute;
top: 29%;
left: -4px;
margin-left: -5px;
border: 5px solid;
border-color: #d3dddd transparent transparent transparent;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.emotion-15 {
width: 18px;
height: 18px;
}
.emotion-31 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: none;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
@media (min-width:768px) {
.emotion-31 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
.emotion-33 {
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
background-position: center;
background-size: contain;
background-image: url([object Object]);
background-repeat: no-repeat;
width: 30px;
height: 30px;
}
<div
class="emotion-0 emotion-1"
>
<div
class="emotion-2 emotion-3"
>
<div
class="emotion-4 emotion-5"
>
Made with
<span
class="emotion-6 emotion-7"
>
</span>
on
<span
class="emotion-8 emotion-9"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root emotion-10 emotion-11 emotion-12"
focusable="false"
viewBox="0 0 45 45"
>
<defs>
<clippath
id="prefix__a"
>
<path
d="M0 36h36V0H0v36z"
/>
</clippath>
<clippath
id="prefix__b"
>
<path
d="M18 36C8.059 36 0 27.941 0 18S8.059 0 18 0s18 8.059 18 18-8.059 18-18 18z"
/>
</clippath>
</defs>
<g
clip-path="url(#prefix__a)"
transform="matrix(1.25 0 0 -1.25 0 45)"
>
<path
d="M36 18c0-9.941-8.059-18-18-18S0 8.059 0 18s8.059 18 18 18 18-8.059 18-18"
fill="#88c9f9"
/>
</g>
<g
clip-path="url(#prefix__b)"
transform="matrix(1.25 0 0 -1.25 0 45)"
>
<path
d="M3.627 28.952c-.45 2.93 2.195 4.156 3.607 4.47 1.412.314 2.776.62 2.933-.006.156-.628.311-1.46 1.173-1.148.862.314 3.043.56 4.063 1.342 1.02.783 2.244.787 3.264.473 1.02-.313 3.877-.227 3.25-1.167-.627-.94-1.825-.827-2.45-1.924-.628-1.099.171-1.826 1.033-1.826.865 0 1.71-.135 2.26.727.548.863-.383 2.463.324 2.357.706-.106 1.477-.866 2.03-2.043.547-1.176 1.408-.47 1.723-1.176.313-.705 2.04-2.039 1.177-1.804-.864.236-1.726.392-1.96-.47-.237-.863.388-1.726-.237-1.647-.627.08-.86-.089-1.725-.004-.862.083-1.333.631-2.039-.545-.705-1.175-1.254-1.96-1.567-2.509-.315-.549-.785-.86-.55-1.96.235-1.099-.628-.785-.628.156 0 .94-.548 1.098-1.253.942-.706-.157-1.803-.313-1.724-1.098.077-.784-.315-1.725.313-2.352.627-.629 1.33.076 1.723-.158.393-.237 1.525-.023 1.133-.416-.393-.39-1.76-.88-.976-1.509a4.831 4.831 0 011.893-.907c.313-.08.062.774 1.083 1.166 1.017.392 2.608 1.29 3 .584.391-.705.338-.595 1.75-.75 1.41-.156 1.79-.585 2.417-1.917.626-1.333.446-1.192 1.462-1.58 1.021-.394 1.678-.223.737-1.087-.94-.86-1.65-.814-2.199-1.833-.55-1.017-.153-1.73-1.25-2.75A20.755 20.755 0 0024 4c-.618-.37-2.162-2.07-3.083-2.667-.834-.54-1.083 0-1.083 0s.256 1.667.964 2.372c.704.705 1.105 3.344.87 4.128-.235.783-1.36 1.02-1.75 1.333-.393.312-1.418 1.548-1.418 2.334 0 .784 1.71 2.81 1.71 2.81.218-1.089-1.039.328-1.627.523-.47.157-1.542 1.656-2.459 1.814-.916.16-1.363.7-2.068 1.25-.706.55-2.43 1.332-2.353 2.195.08.862-1.725 1.568-2.038 1.568-.314 0-1.019 0-1.647 1.098-.627 1.098-1.725 2.196-1.41 2.98.312.783.391 1.726.233 2.588-.156.862-1.332 1.176-1.567.941-.235-.236-1.489-1.335-1.647-.315"
fill="#5c913b"
/>
</g>
</svg>
<span
class="emotion-13 emotion-14"
>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="0 0 45 45"
>
<defs>
<clippath
id="prefix__a"
>
<path
d="M0 36h36V0H0v36z"
/>
</clippath>
</defs>
<g
clip-path="url(#prefix__a)"
transform="matrix(1.25 0 0 -1.25 0 45)"
>
<path
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
fill="#c60a1d"
/>
<path
d="M36 12H0v12h36V12z"
fill="#ffc400"
/>
<path
d="M9 19v-3a3 3 0 116 0v3H9z"
fill="#ea596e"
/>
<path
d="M12 17h3v3h-3v-3z"
fill="#f4a2b2"
/>
<path
d="M12 17H9v3h3v-3z"
fill="#dd2e44"
/>
<path
d="M15 21.5c0-.829-1.343-1.5-3-1.5s-3 .671-3 1.5 1.343 1.5 3 1.5 3-.671 3-1.5"
fill="#ea596e"
/>
<path
d="M15 22.25c0 .414-1.343.75-3 .75s-3-.336-3-.75 1.343-.75 3-.75 3 .336 3 .75"
fill="#ffac33"
/>
<path
d="M7 13h1v7H7v-7zm10 0h-1v7h1v-7z"
fill="#99aab5"
/>
<path
d="M9 13H6v1h3v-1zm9 0h-3v1h3v-1zM8 20H7v1h1v-1zm9 0h-1v1h1v-1z"
fill="#66757f"
/>
</g>
<title>
Spain
</title>
</svg>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="0 0 512 512"
>
<path
d="M512 384c0 31.418-25.473 56.889-56.889 56.889H56.89C25.472 440.889 0 415.417 0 384V128c0-31.418 25.472-56.889 56.889-56.889H455.11C486.53 71.111 512 96.584 512 128v256z"
fill="#265fb5"
/>
<path
d="M512 327.111H0V184.89h512v142.22z"
fill="#eee"
/>
<path
d="M320.811 256c0 35.797-29.014 64.811-64.811 64.811-35.783 0-64.811-29.014-64.811-64.811s29.027-64.811 64.811-64.811c35.797 0 64.811 29.013 64.811 64.811"
fill="#a9bf4c"
/>
<path
d="M312.889 256c0 31.418-25.473 56.889-56.889 56.889S199.111 287.416 199.111 256s25.473-56.889 56.889-56.889 56.889 25.471 56.889 56.889"
fill="#eee"
/>
<path
d="M209.891 286.649l45.909-79.517 45.909 79.517H209.89z"
fill="#265fb5"
/>
<path
d="M215.04 283.591l40.818-70.685 40.803 70.685H215.04z"
fill="#55acee"
/>
<path
d="M215.04 283.591l9.841-17.052 61.483-.783 10.297 17.835H215.04z"
fill="#bbddf5"
/>
<path
d="M222.891 272.441l15.331-12.445 6.67 7.553 5.774-6.215 4.893 4.892 4.665-5.12 5.561 5.12 5.106-5.334 4.665 5.334 4.451-4.892 7.325 9.102 1.338 2.674s-7.78 1.55-18.446.669c-10.667-.896-16.882 1.55-25.33 2.66-8.448 1.109-23.553-1.109-23.553-1.109l1.55-2.889z"
fill="#5c913b"
/>
<path
d="M237.995 262.67l10.226 11.107-5.12.442-5.774-8.434.668-3.115zm12.231.883l8.22 7.338-3.782.654-3.996-5.546-.442-2.446zm10.439-.442l7.111 6.67L266 270.89l-5.774-6.229.44-1.55zm9.33 0l-2.888 2.66 1.338 1.565 1.55-4.225zm8.889.228l-3.328 4.224 1.109 1.99 2.446-4.664-.227-1.55z"
fill="#e2f09f"
/>
<path
d="M256.426 233.671l.939 13.227 7.566-10.867-5.675 11.805 11.805-5.66-10.851 7.553 13.213.939-13.213.952 10.851 7.553-11.805-5.66 5.675 11.805-7.566-10.867-.939 13.226-.939-13.226-7.566 10.867 5.675-11.805-11.805 5.66 10.851-7.553-13.212-.953 13.212-.938-10.85-7.553 11.804 5.66-5.675-11.805 7.566 10.866.94-13.226z"
fill="#bbddf5"
/>
<path
d="M256 244.665l-2.66 2.66s.654 4.011.441 4.679C253.554 252.658 256 256 256 256l3.327-3.996-.88-5.334-2.447-2.005z"
fill="#dd2e44"
/>
<path
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-3.996-7.111s-6.443-4.893-13.995-4.893c-7.567 0-16 6.001-16 6.001l-3.783 7.553s9.771-6.884 20.665-6.884"
fill="#269"
/>
<path
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.888-5.106s-7.78-4.665-15.331-4.665-16.896 5.987-16.896 5.987l-2.66 5.334c.001 0 9.772-6.884 20.666-6.884"
fill="#ffcc4d"
/>
<path
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.005-3.327s-5.988-4.224-16.214-3.783c-7.553 0-18.005 5.561-18.005 5.561l-1.55 3.1c0-.001 9.771-6.885 20.665-6.885"
fill="#dd2e44"
/>
<path
d="M264.291 322.873h-14.165V309.29h14.165v13.582zm-16.426-118.898h-10.183l-5.106-12.459 10.198-1.137 5.091 13.596zm23.21 5.66l-6.784-2.261 3.385-16.426 13.028 1.137-9.629 17.55zm-61.141 69.646l-19.811 4.523-.57-13.583 19.812-2.83.569 11.89zm5.106 14.152l-16.426 6.812-3.954-10.766 15.289-5.106 5.091 9.06zm109.269-12.444l-18.105-5.091v-9.63l20.366-2.83-2.261 17.55zm-9.003 18.674l-16.981-7.937 5.646-10.182 18.703 5.66-7.368 12.459z"
fill="#eee"
/>
<title>
Nicaragua
</title>
</svg>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="0 0 512 512"
>
<path
d="M0 384c0 31.418 25.473 56.889 56.889 56.889H455.11c31.42 0 56.89-25.473 56.89-56.889v-56.889H0V384z"
fill="#138808"
/>
<path
d="M0 327.111h512V184.89H0v142.22z"
fill="#eee"
/>
<path
d="M512 184.889V128c0-31.417-25.473-56.889-56.889-56.889H56.89C25.472 71.111 0 96.583 0 128v56.889h512z"
fill="#f93"
/>
<path
d="M312.889 256c0-31.431-25.473-56.902-56.903-56.902-31.417 0-56.888 25.472-56.888 56.902 0 31.418 25.472 56.889 56.888 56.889 31.432 0 56.903-25.472 56.903-56.889"
fill="navy"
/>
<path
d="M298.666 256c0-23.566-19.115-42.681-42.681-42.681S213.319 232.434 213.319 256s19.1 42.666 42.666 42.666 42.681-19.1 42.681-42.666"
fill="#eee"
/>
<path
d="M256 213.334l2.076 32.199 14.251-28.943-10.396 30.535 24.235-21.305-21.291 24.249 30.535-10.396-28.942 14.25L298.666 256l-32.198 2.076 28.942 14.237-30.535-10.383 21.291 24.235-24.235-21.29 10.396 30.535-14.25-28.942L256 298.666l-2.076-32.198-14.252 28.942 10.397-30.535-24.249 21.291 21.305-24.235-30.535 10.383 28.942-14.236L213.334 256l32.199-2.076-28.943-14.251 30.535 10.396-21.305-24.249 24.249 21.305-10.396-30.535 14.25 28.943 2.077-32.2z"
fill="navy"
opacity="0.6"
/>
<path
d="M241.778 256c0-7.851 6.37-14.223 14.222-14.223s14.223 6.372 14.223 14.223-6.372 14.223-14.223 14.223-14.223-6.372-14.223-14.223"
fill="navy"
/>
<title>
India
</title>
</svg>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="0 0 45 45"
>
<defs>
<clippath
id="prefix__a"
>
<path
d="M0 36h36V0H0v36z"
/>
</clippath>
</defs>
<g
clip-path="url(#prefix__a)"
transform="matrix(1.25 0 0 -1.25 0 45)"
>
<path
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
fill="#009b3a"
/>
<path
d="M32.727 18L18 6.876 3.27 18 18 29.125 32.727 18z"
fill="#fedf01"
/>
<path
d="M24.434 18.076a6.458 6.458 0 11-12.917 0 6.458 6.458 0 0112.917 0"
fill="#002776"
/>
<path
d="M12.277 21.113a6.406 6.406 0 01-.672-2.023c3.994.29 9.417-1.892 11.744-4.596.402.604.7 1.28.882 2.004-2.871 2.809-7.916 4.63-11.954 4.615"
fill="#cbe9d4"
/>
<path
d="M13 16.767h-1v1h1v-1zm1-2h-1v1h1v-1z"
fill="#88c9f9"
/>
<path
d="M16 16.767h-1v1h1v-1zm2-1h-1v1h1v-1zm4-2h-1v1h1v-1zm-3-1h-1v1h1v-1zm3 6h-1v1h1v-1z"
fill="#55acee"
/>
<path
d="M20 14.767h-1v1h1v-1z"
fill="#3b88c3"
/>
</g>
<title>
Brazil
</title>
</svg>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="0 0 45 45"
>
<defs>
<clippath
id="prefix__a"
>
<path
d="M0 36h36V0H0v36z"
/>
</clippath>
</defs>
<g
clip-path="url(#prefix__a)"
transform="matrix(1.25 0 0 -1.25 0 45)"
>
<path
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
fill="#de2910"
/>
<path
d="M7 25.049l.929-2.67 2.826-.06-2.253-1.706.819-2.707L7 19.52l-2.321-1.615.819 2.707-2.253 1.707 2.826.059.929 2.67zm6 3.423l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm2-4l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm0-4l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm-2-3.999l.34-.69.759-.11-.549-.534.129-.757-.679.357-.679-.357.13.757-.55.535.76.11.339.689z"
fill="#ffde02"
/>
</g>
<title>
China
</title>
</svg>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="0 0 512 512"
>
<path
d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345zM0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z"
fill="#ff4b55"
/>
<path
d="M0 200.09h512V311.9H0z"
fill="#f5f5f5"
/>
<title>
Austria
</title>
</svg>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="0 0 512 512"
>
<path
d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345z"
fill="#464655"
/>
<path
d="M0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z"
fill="#ffe15a"
/>
<path
d="M0 200.09h512V311.9H0z"
fill="#ff4b55"
/>
<title>
Germany
</title>
</svg>
<svg
class="MuiSvgIcon-root emotion-15 emotion-12"
focusable="false"
role="img"
viewBox="-60 -40 240 160"
>
<rect
fill="#fe0000"
height="100%"
width="100%"
x="-60"
y="-40"
/>
<rect
fill="#000095"
height="50%"
width="50%"
x="-60"
y="-40"
/>
<path
d="M8 0L0 30-8 0l8-30M0 8l30-8L0-8l-30 8"
fill="#fff"
id="prefix__a"
/>
<use
transform="rotate(30)"
xlink:href="#prefix__a"
/>
<use
transform="rotate(60)"
xlink:href="#prefix__a"
/>
<circle
fill="#000095"
r="17"
/>
<circle
fill="#fff"
r="15"
/>
<title>
Taiwan
</title>
</svg>
</span>
</span>
</div>
<div
class="emotion-31 emotion-32"
>
Powered by
<div
class="emotion-33 emotion-34"
/>
/ v.1.0.0
</div>
</div>
</div>
`;

View File

@ -23,7 +23,7 @@ const props = {
/* eslint-disable react/jsx-no-bind*/ /* eslint-disable react/jsx-no-bind*/
describe('<Header /> component with logged in state', () => { describe('<Header /> component with logged in state', () => {
test('should load the component in logged out state', () => { test('should load the component in logged out state', () => {
const { queryByTestId, getByText } = render( const { container, queryByTestId, getByText } = render(
<Router> <Router>
<AppContextProvider> <AppContextProvider>
<Header /> <Header />
@ -31,12 +31,13 @@ describe('<Header /> component with logged in state', () => {
</Router> </Router>
); );
expect(container.firstChild).toMatchSnapshot();
expect(queryByTestId('header--menu-accountcircle')).toBeNull(); expect(queryByTestId('header--menu-accountcircle')).toBeNull();
expect(getByText('Login')).toBeTruthy(); expect(getByText('Login')).toBeTruthy();
}); });
test('should load the component in logged in state', () => { test('should load the component in logged in state', () => {
const { getByTestId, queryByText } = render( const { container, getByTestId, queryByText } = render(
<Router> <Router>
<AppContextProvider user={props.user}> <AppContextProvider user={props.user}>
<Header /> <Header />
@ -44,6 +45,7 @@ describe('<Header /> component with logged in state', () => {
</Router> </Router>
); );
expect(container.firstChild).toMatchSnapshot();
expect(getByTestId('header--menu-accountcircle')).toBeTruthy(); expect(getByTestId('header--menu-accountcircle')).toBeTruthy();
expect(queryByText('Login')).toBeNull(); expect(queryByText('Login')).toBeNull();
}); });

View File

@ -2,8 +2,8 @@ import React, { useState, useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Button from 'verdaccio-ui/components/Button'; import Button from 'verdaccio-ui/components/Button';
import { useConfig } from 'verdaccio-ui/providers/config';
import storage from 'verdaccio-ui/utils/storage'; import storage from 'verdaccio-ui/utils/storage';
import { getRegistryURL } from 'verdaccio-ui/utils/url';
import AppContext from '../../App/AppContext'; import AppContext from '../../App/AppContext';
@ -31,6 +31,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
} }
const { user, scope, setUser } = appContext; const { user, scope, setUser } = appContext;
const { configOptions } = useConfig();
/** /**
* Logouts user * Logouts user
@ -52,14 +53,14 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)} onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
onToggleLogin={() => setShowLoginModal(!showLoginModal)} onToggleLogin={() => setShowLoginModal(!showLoginModal)}
onToggleMobileNav={() => setShowMobileNavBar(!showMobileNavBar)} onToggleMobileNav={() => setShowMobileNavBar(!showMobileNavBar)}
username={user && user.username} username={user?.username}
withoutSearch={withoutSearch} withoutSearch={withoutSearch}
/> />
</InnerNavBar> </InnerNavBar>
<HeaderInfoDialog <HeaderInfoDialog
isOpen={isInfoDialogOpen} isOpen={isInfoDialogOpen}
onCloseDialog={() => setOpenInfoDialog(false)} onCloseDialog={() => setOpenInfoDialog(false)}
registryUrl={getRegistryURL()} registryUrl={configOptions.base}
scope={scope} scope={scope}
/> />
</NavBar> </NavBar>

View File

@ -14,7 +14,7 @@ const HeaderGreetings: React.FC<Props> = ({ username }) => {
return ( return (
<> <>
<Greetings>{t('header.greetings')}</Greetings> <Greetings>{t('header.greetings')}</Greetings>
<Label capitalize={true} data-testid="greetings-label" text={username} weight="bold" /> <Label data-testid="greetings-label" text={username} weight="bold" />
</> </>
); );
}; };

View File

@ -11,7 +11,7 @@ import { RightSide } from './styles';
interface Props { interface Props {
withoutSearch?: boolean; withoutSearch?: boolean;
username?: string; username?: string | null;
onToggleLogin: () => void; onToggleLogin: () => void;
onOpenRegistryInfoDialog: () => void; onOpenRegistryInfoDialog: () => void;
onToggleMobileNav: () => void; onToggleMobileNav: () => void;

View File

@ -25,7 +25,7 @@ import MenuItem from 'verdaccio-ui/components/MenuItem';
import { Theme } from 'verdaccio-ui/design-tokens/theme'; import { Theme } from 'verdaccio-ui/design-tokens/theme';
import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext'; import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext';
import { Language } from '../../../../i18n/config'; import { Language } from '../../../i18n/config';
const lngDetails: Record<Language, { translation: TFunctionKeys; icon: React.ReactElement }> = { const lngDetails: Record<Language, { translation: TFunctionKeys; icon: React.ReactElement }> = {
'fr-FR': { 'fr-FR': {
@ -166,7 +166,7 @@ const StyledLanguageIcon = styled(LanguageIcon)<{ theme?: Theme }>(({ theme }) =
const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({ const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({
width: 220, width: 220,
display: 'none', display: 'none',
[`@media screen and (min-width: ${theme && theme.breakPoints.medium}px)`]: { [`@media screen and (min-width: ${theme?.breakPoints.medium}px)`]: {
display: 'inline-block', display: 'inline-block',
}, },
})); }));

View File

@ -1 +0,0 @@
export { default } from './LanguageSwitch';

View File

@ -1,7 +1,13 @@
import React from 'react'; import React from 'react';
import api from 'verdaccio-ui/utils/api'; import api from 'verdaccio-ui/providers/API/api';
import { render, waitFor, fireEvent } from 'verdaccio-ui/utils/test-react-testing-library'; import {
render,
waitFor,
fireEvent,
cleanup,
act,
} from 'verdaccio-ui/utils/test-react-testing-library';
import AppContext, { AppContextProps } from '../../AppContext'; import AppContext, { AppContextProps } from '../../AppContext';
@ -16,6 +22,7 @@ describe('<LoginDialog /> component', () => {
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
jest.resetAllMocks(); jest.resetAllMocks();
cleanup();
}); });
test('should render the component in default state', () => { test('should render the component in default state', () => {
@ -61,12 +68,13 @@ describe('<LoginDialog /> component', () => {
const loginDialogButton = await waitFor(() => getByTestId('close-login-dialog-button')); const loginDialogButton = await waitFor(() => getByTestId('close-login-dialog-button'));
expect(loginDialogButton).toBeTruthy(); expect(loginDialogButton).toBeTruthy();
fireEvent.click(loginDialogButton, { open: false }); act(() => {
fireEvent.click(loginDialogButton, { open: false });
});
expect(props.onClose).toHaveBeenCalled(); expect(props.onClose).toHaveBeenCalled();
}); });
// TODO test('setCredentials - should set username and password in state', async () => {
test.skip('setCredentials - should set username and password in state', async () => {
const props = { const props = {
open: true, open: true,
onClose: jest.fn(), onClose: jest.fn(),
@ -79,7 +87,7 @@ describe('<LoginDialog /> component', () => {
}) })
); );
const { getByPlaceholderText, getByText } = render( const { getByPlaceholderText, getByTestId } = render(
<AppContext.Provider value={appContextValue}> <AppContext.Provider value={appContextValue}>
<LoginDialog onClose={props.onClose} open={props.open} /> <LoginDialog onClose={props.onClose} open={props.open} />
</AppContext.Provider> </AppContext.Provider>
@ -87,17 +95,23 @@ describe('<LoginDialog /> component', () => {
// TODO: the input's value is not being updated in the DOM // TODO: the input's value is not being updated in the DOM
const userNameInput = getByPlaceholderText('Your username'); const userNameInput = getByPlaceholderText('Your username');
fireEvent.focus(userNameInput); fireEvent.focus(userNameInput);
fireEvent.change(userNameInput, { target: { value: 'xyz' } }); fireEvent.change(userNameInput, { target: { value: 'xyz' } });
// TODO: the input's value is not being updated in the DOM // TODO: the input's value is not being updated in the DOM
const passwordInput = getByPlaceholderText('Your strong password'); const passwordInput = getByPlaceholderText('Your strong password');
fireEvent.focus(passwordInput); fireEvent.focus(passwordInput);
fireEvent.change(passwordInput, { target: { value: '1234' } }); fireEvent.change(passwordInput, { target: { value: '1234' } });
// TODO: submitting form does not work // TODO: submitting form does not work
const signInButton = getByText('Sign in'); const signInButton = getByTestId('login-dialog-form-login-button');
fireEvent.click(signInButton); expect(signInButton).not.toBeDisabled();
act(() => {
fireEvent.click(signInButton);
});
}); });
test.todo('validateCredentials: should validate credentials'); test.todo('validateCredentials: should validate credentials');

View File

@ -1,9 +1,12 @@
import i18next from 'i18next';
import isEmpty from 'lodash/isEmpty';
import React, { useState, useContext, useCallback } from 'react'; import React, { useState, useContext, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Dialog from 'verdaccio-ui/components/Dialog'; import Dialog from 'verdaccio-ui/components/Dialog';
import DialogContent from 'verdaccio-ui/components/DialogContent'; import DialogContent from 'verdaccio-ui/components/DialogContent';
import { makeLogin, LoginError } from 'verdaccio-ui/utils/login'; import { useAPI, LoginBody } from 'verdaccio-ui/providers/API/APIProvider';
import { LoginError } from 'verdaccio-ui/utils/login';
import storage from 'verdaccio-ui/utils/storage'; import storage from 'verdaccio-ui/utils/storage';
import AppContext from '../../../App/AppContext'; import AppContext from '../../../App/AppContext';
@ -20,6 +23,34 @@ interface Props {
const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => { const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const appContext = useContext(AppContext); const appContext = useContext(AppContext);
const { doLogin } = useAPI();
const makeLogin = useCallback(
async (username?: string, password?: string): Promise<LoginBody> => {
// checks isEmpty
if (isEmpty(username) || isEmpty(password)) {
const error = {
type: 'error',
description: i18next.t('form-validation.username-or-password-cant-be-empty'),
};
return { error };
}
try {
const response: LoginBody = await doLogin(username as string, password as string);
return response;
} catch (e) {
console.error('login error', e.message);
const error = {
type: 'error',
description: i18next.t('form-validation.unable-to-sign-in'),
};
return { error };
}
},
[doLogin]
);
if (!appContext) { if (!appContext) {
throw Error(t('app-context-not-correct-used')); throw Error(t('app-context-not-correct-used'));
@ -42,7 +73,7 @@ const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
onClose(); onClose();
} }
}, },
[appContext, onClose] [appContext, onClose, makeLogin]
); );
return ( return (

View File

@ -79,7 +79,7 @@ const RegistryInfoContent: React.FC<Props> = (props) => {
/* eslint react/prop-types:0 */ /* eslint react/prop-types:0 */
const TabContainer: React.FC = ({ children }): JSX.Element => { const TabContainer: React.FC = ({ children }): JSX.Element => {
return ( return (
<CommandContainer> <CommandContainer data-testid={'tab-content'}>
<Typography>{children}</Typography> <Typography>{children}</Typography>
</CommandContainer> </CommandContainer>
); );

View File

@ -2,9 +2,17 @@ import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom/extend-expect';
import api from 'verdaccio-ui/utils/api';
import api from 'verdaccio-ui/providers/API/api';
import { render, fireEvent, waitFor } from 'verdaccio-ui/utils/test-react-testing-library'; import { render, fireEvent, waitFor } from 'verdaccio-ui/utils/test-react-testing-library';
jest.mock('lodash/debounce', () =>
jest.fn((fn) => {
fn.cancel = jest.fn();
return fn;
})
);
import Search from './Search'; import Search from './Search';
/* eslint-disable verdaccio/jsx-spread */ /* eslint-disable verdaccio/jsx-spread */

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { RouteComponentProps, withRouter } from 'react-router'; import { RouteComponentProps, withRouter } from 'react-router';
import AutoComplete from 'verdaccio-ui/components/AutoComplete'; import AutoComplete from 'verdaccio-ui/components/AutoComplete';
import { callSearch } from 'verdaccio-ui/utils/calls'; import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
import SearchAdornment from './SearchAdornment'; import SearchAdornment from './SearchAdornment';
@ -23,6 +23,7 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const mountedRef = useRef(true); const mountedRef = useRef(true);
const [requestList, setRequestList] = useState<{ abort: () => void }[]>([]); const [requestList, setRequestList] = useState<{ abort: () => void }[]>([]);
const { callSearch } = useAPI();
/** /**
* Cancel all the requests which are in pending state. * Cancel all the requests which are in pending state.
@ -138,7 +139,7 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
} }
} }
}, },
[requestList, setRequestList, setSuggestions, setLoaded, setError, setLoading] [requestList, setRequestList, setSuggestions, setLoaded, setError, setLoading, callSearch]
); );
useEffect(() => { useEffect(() => {

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,17 @@ import styled from '@emotion/styled';
import BugReportIcon from '@material-ui/icons/BugReport'; import BugReportIcon from '@material-ui/icons/BugReport';
import DownloadIcon from '@material-ui/icons/CloudDownload'; import DownloadIcon from '@material-ui/icons/CloudDownload';
import HomeIcon from '@material-ui/icons/Home'; import HomeIcon from '@material-ui/icons/Home';
import React from 'react'; import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Theme } from 'verdaccio-ui/design-tokens/theme'; import { Theme } from 'verdaccio-ui/design-tokens/theme';
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
import { extractFileName, downloadFile } from 'verdaccio-ui/utils/url';
import FloatingActionButton from '../FloatingActionButton'; import FloatingActionButton from '../FloatingActionButton';
import Link from '../Link'; import Link from '../Link';
import Tooltip from '../Tooltip'; import Tooltip from '../Tooltip';
import downloadTarball from './download-tarball';
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(({ theme }) => ({ export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: backgroundColor:
theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue, theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
@ -34,6 +34,14 @@ export interface ActionBarActionProps {
/* eslint-disable react/jsx-no-bind */ /* eslint-disable react/jsx-no-bind */
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => { const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { getResource } = useAPI();
const handleDownload = useCallback(async () => {
const fileStream = await getResource(link);
const fileName = extractFileName(link);
downloadFile(fileStream, fileName);
}, [getResource, link]);
switch (type) { switch (type) {
case 'VISIT_HOMEPAGE': case 'VISIT_HOMEPAGE':
return ( return (
@ -58,7 +66,7 @@ const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
case 'DOWNLOAD_TARBALL': case 'DOWNLOAD_TARBALL':
return ( return (
<Tooltip title={t('action-bar-action.download-tarball')}> <Tooltip title={t('action-bar-action.download-tarball')}>
<Fab data-testid="download-tarball-btn" onClick={downloadTarball(link)} size="small"> <Fab data-testid="download-tarball-btn" onClick={handleDownload} size="small">
<DownloadIcon /> <DownloadIcon />
</Fab> </Fab>
</Tooltip> </Tooltip>

View File

@ -1,18 +0,0 @@
import api from 'verdaccio-ui/utils/api';
import { extractFileName, downloadFile } from 'verdaccio-ui/utils/url';
function downloadTarball(link: string) {
return async function downloadHandler(): Promise<void> {
const fileStream: Blob = await api.request(link, 'GET', {
headers: {
['accept']:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
},
credentials: 'include',
});
const fileName = extractFileName(link);
downloadFile(fileStream, fileName);
};
}
export default downloadTarball;

View File

@ -1,2 +1 @@
export { default } from './ActionBar'; export { default } from './ActionBar';
export { default as downloadTarball } from './download-tarball';

View File

@ -1 +0,0 @@
export { default } from './AppBar';

View File

@ -139,6 +139,7 @@ const AutoComplete = memo(
onBlur, onBlur,
}; };
// this format avoid arrow function eslint rule
const renderSuggestionsContainer: RenderSuggestionsContainer = function ({ const renderSuggestionsContainer: RenderSuggestionsContainer = function ({
containerProps, containerProps,
children, children,

View File

@ -1 +0,0 @@
export { default } from './Avatar';

View File

@ -1,6 +1,8 @@
import { default as MaterialUIBox, BoxProps } from '@material-ui/core/Box'; import { default as MaterialUIBox, BoxProps } from '@material-ui/core/Box';
import React from 'react'; import React from 'react';
const Box: React.FC<BoxProps> = (props) => <MaterialUIBox {...props} />; function Box(props: BoxProps) {
return <MaterialUIBox {...props} />;
}
export default Box; export default Box;

View File

@ -1 +0,0 @@
export { default } from './Box';

View File

@ -1 +0,0 @@
export { default } from './Button';

View File

@ -1 +0,0 @@
export { default } from './Card';

View File

@ -1 +0,0 @@
export { default } from './CardActions';

View File

@ -1 +0,0 @@
export { default } from './CardContent';

View File

@ -1 +0,0 @@
export { default } from './Chip';

View File

@ -1 +0,0 @@
export { default } from './CircularProgress';

View File

@ -0,0 +1,45 @@
import styled from '@emotion/styled';
import FileCopy from '@material-ui/icons/FileCopy';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { copyToClipBoardUtility } from 'verdaccio-ui/utils/cli-utils';
import IconButton from './IconButton';
import Tooltip from './Tooltip';
interface Props {
text: string;
children?: React.ReactNode;
}
function CopyToClipBoard({ text, children, ...props }: Props) {
const { t } = useTranslation();
return (
<Wrapper {...props}>
<Content>{children ?? text}</Content>
<Tooltip disableFocusListener={true} title={t('copy-to-clipboard')}>
<IconButton onClick={copyToClipBoardUtility(text)} data-testid="copy-icon">
<FileCopy />
</IconButton>
</Tooltip>
</Wrapper>
);
}
export default CopyToClipBoard;
const Wrapper = styled('div')({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
const Content = styled('span')({
display: 'inline-block',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
height: '21px',
fontSize: '1rem',
});

View File

@ -1,27 +0,0 @@
import React from 'react';
import { copyToClipBoardUtility } from 'verdaccio-ui/utils/cli-utils';
import { render, cleanup, fireEvent } from 'verdaccio-ui/utils/test-react-testing-library';
import CopyToClipBoard from './CopyToClipBoard';
jest.mock('verdaccio-ui/utils/cli-utils');
describe('<CopyToClipBoard /> component', () => {
let wrapper: any;
const copyText = 'copy text';
beforeEach(() => {
cleanup();
wrapper = render(<CopyToClipBoard text={copyText} />);
});
test('should load the component in default state', () => {
expect(wrapper).toMatchSnapshot();
});
test('should call the copyToClipBoardUtility for copy to clipboard utility', () => {
fireEvent.click(wrapper.getByTestId('copy-icon'));
expect(copyToClipBoardUtility).toHaveBeenCalledWith(copyText);
});
});

View File

@ -1,38 +0,0 @@
import FileCopy from '@material-ui/icons/FileCopy';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { copyToClipBoardUtility } from 'verdaccio-ui/utils/cli-utils';
import Tooltip from '../Tooltip';
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
interface Props {
text: string;
children?: React.ReactNode;
}
const renderText = (text: string, children: React.ReactNode): JSX.Element => {
if (children) {
return <ClipBoardCopyText>{children}</ClipBoardCopyText>;
}
return <ClipBoardCopyText>{text}</ClipBoardCopyText>;
};
const CopyToClipBoard: React.FC<Props> = ({ text, children }) => {
const { t } = useTranslation();
return (
<ClipBoardCopy>
{renderText(text, children)}
<Tooltip disableFocusListener={true} title={t('copy-to-clipboard')}>
<CopyIcon onClick={copyToClipBoardUtility(text)} data-testid="copy-icon">
<FileCopy />
</CopyIcon>
</Tooltip>
</ClipBoardCopy>
);
};
export default CopyToClipBoard;

View File

@ -1,180 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<CopyToClipBoard /> component should load the component in default state 1`] = `
Object {
"asFragment": [Function],
"baseElement": .emotion-0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.emotion-2 {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
height: 21px;
font-size: 1rem;
}
<body>
<div>
<div
class="emotion-0 emotion-1"
>
<span
class="emotion-2 emotion-3"
>
copy text
</span>
<button
class="MuiButtonBase-root MuiIconButton-root emotion-4 emotion-5"
data-testid="copy-icon"
tabindex="0"
title="Copy to clipboard"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
</body>,
"container": .emotion-0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.emotion-2 {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
height: 21px;
font-size: 1rem;
}
<div>
<div
class="emotion-0 emotion-1"
>
<span
class="emotion-2 emotion-3"
>
copy text
</span>
<button
class="MuiButtonBase-root MuiIconButton-root emotion-4 emotion-5"
data-testid="copy-icon"
tabindex="0"
title="Copy to clipboard"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>,
"debug": [Function],
"findAllByAltText": [Function],
"findAllByDisplayValue": [Function],
"findAllByLabelText": [Function],
"findAllByPlaceholderText": [Function],
"findAllByRole": [Function],
"findAllByTestId": [Function],
"findAllByText": [Function],
"findAllByTitle": [Function],
"findByAltText": [Function],
"findByDisplayValue": [Function],
"findByLabelText": [Function],
"findByPlaceholderText": [Function],
"findByRole": [Function],
"findByTestId": [Function],
"findByText": [Function],
"findByTitle": [Function],
"getAllByAltText": [Function],
"getAllByDisplayValue": [Function],
"getAllByLabelText": [Function],
"getAllByPlaceholderText": [Function],
"getAllByRole": [Function],
"getAllByTestId": [Function],
"getAllByText": [Function],
"getAllByTitle": [Function],
"getByAltText": [Function],
"getByDisplayValue": [Function],
"getByLabelText": [Function],
"getByPlaceholderText": [Function],
"getByRole": [Function],
"getByTestId": [Function],
"getByText": [Function],
"getByTitle": [Function],
"queryAllByAltText": [Function],
"queryAllByDisplayValue": [Function],
"queryAllByLabelText": [Function],
"queryAllByPlaceholderText": [Function],
"queryAllByRole": [Function],
"queryAllByTestId": [Function],
"queryAllByText": [Function],
"queryAllByTitle": [Function],
"queryByAltText": [Function],
"queryByDisplayValue": [Function],
"queryByLabelText": [Function],
"queryByPlaceholderText": [Function],
"queryByRole": [Function],
"queryByTestId": [Function],
"queryByText": [Function],
"queryByTitle": [Function],
"rerender": [Function],
"unmount": [Function],
}
`;

View File

@ -1 +0,0 @@
export { default } from './CopyToClipBoard';

View File

@ -1,20 +0,0 @@
import styled from '@emotion/styled';
import IconButton from '../IconButton';
export const ClipBoardCopy = styled('div')({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
export const ClipBoardCopyText = styled('span')({
display: 'inline-block',
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
height: '21px',
fontSize: '1rem',
});
export const CopyIcon = styled(IconButton)({});

View File

@ -1 +0,0 @@
export { default } from './Dialog';

View File

@ -1 +0,0 @@
export { default } from './DialogActions';

View File

@ -1 +0,0 @@
export { default } from './DialogContent';

View File

@ -1 +0,0 @@
export { default } from './DialogTitle';

View File

@ -1 +0,0 @@
export { default } from './Divider';

View File

@ -1 +0,0 @@
export { default } from './FloatingActionButton';

View File

@ -1 +0,0 @@
export { default } from './FormControl';

View File

@ -1 +0,0 @@
export { default } from './FormHelperText';

View File

@ -1 +0,0 @@
export { default } from './Grid';

View File

@ -1 +0,0 @@
export { default } from './Heading';

View File

@ -1 +0,0 @@
export { default } from './IconButton';

View File

@ -1 +0,0 @@
export { default } from './Input';

View File

@ -1 +0,0 @@
export { default } from './InputLabel';

View File

@ -1,7 +1,7 @@
import React, { MouseEvent } from 'react'; import React, { MouseEvent } from 'react';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
import Text, { TextProps } from '../Text'; import Text, { TextProps } from './Text';
interface Props extends Pick<TextProps, 'variant'> { interface Props extends Pick<TextProps, 'variant'> {
external?: boolean; external?: boolean;

View File

@ -1 +0,0 @@
export { default } from './Link';

View File

@ -1 +0,0 @@
export { default } from './List';

View File

@ -1 +0,0 @@
export { default } from './ListItem';

View File

@ -1 +0,0 @@
export { default } from './ListItemText';

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import React from 'react'; import React from 'react';
import { Theme } from 'verdaccio-ui/design-tokens/theme'; import { Theme } from 'verdaccio-ui/design-tokens/theme';
import { useConfig } from 'verdaccio-ui/providers/config';
import blackAndWithLogo from './img/logo-black-and-white.svg'; import blackAndWithLogo from './img/logo-black-and-white.svg';
import defaultLogo from './img/logo.svg'; import defaultLogo from './img/logo.svg';
@ -17,8 +18,6 @@ const logos = {
dark: blackAndWithLogo, dark: blackAndWithLogo,
}; };
const logo = window?.__VERDACCIO_BASENAME_UI_OPTIONS?.logoURI;
interface Props { interface Props {
size?: keyof typeof sizes; size?: keyof typeof sizes;
onClick?: () => void; onClick?: () => void;
@ -26,10 +25,11 @@ interface Props {
} }
const Logo: React.FC<Props> = ({ size, onClick, className }) => { const Logo: React.FC<Props> = ({ size, onClick, className }) => {
if (logo) { const { configOptions } = useConfig();
if (configOptions?.logo) {
return ( return (
<ImageLogo onClick={onClick} className={className}> <ImageLogo onClick={onClick} className={className}>
<img alt="logo" height="40px" src={logo} /> <img alt="logo" height="40px" src={configOptions.logo} />
</ImageLogo> </ImageLogo>
); );
} }

View File

@ -1 +0,0 @@
export { default } from './Menu';

View File

@ -1 +0,0 @@
export { default } from './MenuItem';

View File

@ -1,5 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { PRIMARY_COLOR } from 'verdaccio-ui/utils/colors';
import { default as MuiCard } from '../Card'; import { default as MuiCard } from '../Card';
import { default as Typography } from '../Heading'; import { default as Typography } from '../Heading';
import List from '../List'; import List from '../List';
@ -25,12 +27,12 @@ export const EmptyPackage = styled('img')({
}); });
export const Heading = styled(Typography)({ export const Heading = styled(Typography)({
color: '#4b5e40', color: PRIMARY_COLOR,
}); });
export const StyledList = styled(List)({ export const StyledList = styled(List)({
padding: 0, padding: 0,
color: '#4b5e40', color: PRIMARY_COLOR,
}); });
export const Card = styled(MuiCard)({ export const Card = styled(MuiCard)({

View File

@ -1 +0,0 @@
export { default } from './Paper';

View File

@ -1 +0,0 @@
export { default } from './SnackbarContent';

View File

@ -1,10 +0,0 @@
import { default as MaterialUISvgIcon, SvgIconProps } from '@material-ui/core/SvgIcon';
import React, { forwardRef } from 'react';
type SvgIconRef = SVGSVGElement;
const SvgIcon = forwardRef<SvgIconRef, SvgIconProps>(function SvgIcon(props, ref) {
return <MaterialUISvgIcon {...props} ref={ref} />;
});
export default SvgIcon;

View File

@ -1 +0,0 @@
export { default } from './SvgIcon';

View File

@ -1 +0,0 @@
export { default } from './Tab';

Some files were not shown because too many files have changed in this diff Show More