0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-03-25 02:32:52 -05:00

feat: ui set global package on sidebar setting (#3826)

This commit is contained in:
Juan Picado 2023-06-03 00:52:24 +02:00 committed by GitHub
parent 679c19c1b6
commit 7344a7fcf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 1075 additions and 403 deletions

View file

@ -0,0 +1,6 @@
---
'@verdaccio/ui-theme': minor
'@verdaccio/ui-components': minor
---
feat: ui bugfixes and improvements

View file

@ -8,7 +8,7 @@
"@verdaccio/config": "workspace:6.0.0-6-next.70",
"@verdaccio/test-helper": "workspace:2.0.0-6-next.8",
"debug": "4.3.4",
"cypress": "11.2.0",
"cypress": "^11.2.0",
"get-port": "5.1.1"
},
"scripts": {

View file

@ -1,4 +0,0 @@
require('@babel/register')({
extensions: ['.ts', '.js'],
});
module.exports = require('./setup');

View file

@ -1,26 +0,0 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const { green } = require('colorette');
const puppeteer = require('puppeteer');
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
module.exports = async function () {
// eslint-disable-next-line no-console
console.log(green('Setup Puppeteer'));
const browser = await puppeteer.launch({
isMobile: false,
ignoreHTTPSErrors: true,
// invert values for local testing
devtools: false,
headless: true,
// slowMo: 6000,
// invert values for local testing
args: ['--no-sandbox'],
});
global.__BROWSER__ = browser;
fs.mkdirSync(DIR, { recursive: true, force: true });
fs.writeFileSync(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint());
};

View file

@ -1,14 +0,0 @@
const os = require('os');
const path = require('path');
const { green } = require('kleur');
const rimraf = require('rimraf');
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
module.exports = async function () {
// eslint-disable-next-line no-console
console.log(green('Teardown Puppeteer'));
await global.__BROWSER__.close();
rimraf.sync(DIR);
};

View file

@ -106,7 +106,8 @@
"install-using-npm": "Install using npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Install using pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
"install-using-pnpm-command": "pnpm install {{packageName}}",
"global": "View as global"
},
"repository": {
"title": "Repository"

View file

@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
import {
AppConfigurationProvider,
PersistenceSettingProvider,
StyleBaseline,
ThemeProvider,
store,
@ -20,7 +21,9 @@ const AppContainer = () => (
<AppConfigurationProvider>
<ThemeProvider>
<StyleBaseline />
<App />
<PersistenceSettingProvider>
<App />
</PersistenceSettingProvider>
</ThemeProvider>
</AppConfigurationProvider>
</Provider>

View file

@ -1,16 +1,8 @@
{
"extends": "../../.babelrc",
"plugins": ["@emotion"],
"sourceMaps" : "inline",
"presets": [
[
"@babel/preset-env",
{
"targets": ["last 5 versions"],
"bugfixes": true,
"modules": "auto",
"debug": false
}
],
"@babel/preset-react"
]
}

View file

@ -4,6 +4,7 @@ import { Provider } from 'react-redux';
import config from '../../plugins/ui-theme/src/i18n/config';
import {
AppConfigurationProvider,
PersistenceSettingProvider,
StyleBaseline,
ThemeProvider,
TranslatorProvider,
@ -34,12 +35,14 @@ export const parameters = {
export const withMuiTheme = (Story) => (
<Provider store={store}>
<TranslatorProvider onMount={() => {}} i18n={config} listLanguages={listLanguages}>
<AppConfigurationProvider>
<ThemeProvider>
<StyleBaseline />
<Story />
</ThemeProvider>
</AppConfigurationProvider>
<PersistenceSettingProvider>
<AppConfigurationProvider>
<ThemeProvider>
<StyleBaseline />
<Story />
</ThemeProvider>
</AppConfigurationProvider>
</PersistenceSettingProvider>
</TranslatorProvider>
</Provider>
);

View file

@ -21,6 +21,8 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@fontsource/material-icons": "^4.5.4",
"@fontsource/roboto": "^4.5.8",
"@mui/icons-material": "5.11.16",
"@mui/material": "5.12.0",
"@mui/styles": "5.12.0",
@ -28,8 +30,6 @@
"@rematch/core": "2.2.0",
"@rematch/loading": "2.1.2",
"@rematch/persist": "2.1.2",
"@fontsource/material-icons": "^4.5.4",
"@fontsource/roboto": "^4.5.8",
"country-flag-icons": "1.5.5",
"dayjs": "1.11.7",
"dompurify": "2.4.5",
@ -56,7 +56,6 @@
"validator": "13.9.0"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.25",
"@babel/core": "^7.20.7",
"@emotion/babel-plugin": "11.10.6",
"@emotion/jest": "11.10.5",
@ -74,6 +73,7 @@
"@types/hast": "^2.0.0",
"@types/react-router": "^5.1.20",
"@types/unist": "^2.0.0",
"@verdaccio/types": "workspace:11.0.0-6-next.25",
"babel-loader": "^8.3.0",
"mockdate": "3.0.5",
"msw": "0.49.2"

View file

@ -1,50 +1,64 @@
import type { Meta, StoryObj } from '@storybook/react';
import { clone, merge } from 'lodash';
import React from 'react';
import { default as ActionBar } from '.';
export default {
title: 'ActionBar ',
const meta: Meta<typeof ActionBar> = {
title: 'Components/Sidebar/ActionBar',
component: ActionBar,
};
export const ActionBarAll: any = () => (
<ActionBar
packageMeta={{
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
dist: {
fileCount: 0,
unpackedSize: 0,
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
},
homepage: 'https://verdaccio.org',
bugs: {
url: 'https://github.com/verdaccio/monorepo/issues',
},
},
}}
/>
);
export default meta;
type Story = StoryObj<typeof ActionBar>;
export const RawViewer: any = () => (
<ActionBar
packageMeta={{
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
dist: {
fileCount: 0,
unpackedSize: 0,
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
},
homepage: 'https://verdaccio.org',
bugs: {
url: 'https://github.com/verdaccio/monorepo/issues',
},
},
}}
showRaw={true}
/>
);
const packageMeta = {
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
dist: {
fileCount: 0,
unpackedSize: 0,
tarball: 'https://registry.verdaccio.org/storybook/-/storybook-0.2.0.tgz',
},
homepage: 'https://verdaccio.org',
bugs: {
url: 'https://github.com/verdaccio/monorepo/issues',
},
},
};
export const Primary: Story = {
name: 'Default',
render: () => <ActionBar packageMeta={packageMeta} />,
};
export const Raw: Story = {
name: 'Raw viewer',
render: () => <ActionBar packageMeta={packageMeta} showRaw={true} />,
};
export const NoLatest: Story = {
name: 'No latest (empty)',
render: () => <ActionBar packageMeta={{}} showRaw={true} />,
};
export const Download: Story = {
name: 'No show download',
render: () => <ActionBar packageMeta={{ ...clone(packageMeta) }} showDownloadTarball={false} />,
};
export const Home: Story = {
name: 'No home',
render: () => (
<ActionBar packageMeta={{ ...merge(clone(packageMeta), { latest: { homepage: null } }) }} />
),
};
export const Bugs: Story = {
name: 'No bugs',
render: () => (
<ActionBar packageMeta={{ ...merge(clone(packageMeta), { latest: { bugs: null } }) }} />
),
};

View file

@ -3,7 +3,7 @@ import React from 'react';
import { default as Author } from '.';
export default {
title: 'Author ',
title: 'Components/Sidebar/Author',
};
export const AuthorAll: any = () => (

View file

@ -3,7 +3,7 @@ import React from 'react';
import CopyToClipBoard from './CopyToClipBoard';
export default {
title: 'CopyToClipBoard',
title: 'Components/Sidebar/CopyToClipBoard',
component: CopyToClipBoard,
argTypes: {
backgroundColor: { control: 'color' },

View file

@ -34,12 +34,12 @@ function CopyToClipBoard({ text, children, dataTestId, title, ...props }: Props)
<Content>{children ?? text}</Content>
{title ? (
<Tooltip disableFocusListener={true} title={title}>
<IconButton data-testid={dataTestId} onClick={copyToClipBoardUtility(text)} size="large">
<IconButton data-testid={dataTestId} onClick={copyToClipBoardUtility(text)} size="small">
<FileCopy fontSize="small" />
</IconButton>
</Tooltip>
) : (
<IconButton data-testid={dataTestId} onClick={copyToClipBoardUtility(text)} size="large">
<IconButton data-testid={dataTestId} onClick={copyToClipBoardUtility(text)} size="small">
<FileCopy fontSize="small" />
</IconButton>
)}

View file

@ -1,58 +1,158 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { default as Dependencies } from '.';
export default {
title: 'Dependencies ',
const meta: Meta<typeof Dependencies> = {
title: 'Components/Detail/Dependencies',
component: Dependencies,
};
export const DeprecatedAll: any = () => (
<Dependencies
packageMeta={{
latest: {
name: 'verdaccio',
version: '4.0.0',
author: {
name: 'verdaccio user',
email: 'verdaccio.user@verdaccio.org',
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
dependencies: {
react: '16.9.0',
'react-dom': '16.9.0',
},
devDependencies: {
'babel-core': '7.0.0-beta6',
},
peerDependencies: {
'styled-components': '5.0.0',
},
},
_uplinks: {},
}}
/>
);
export default meta;
type Story = StoryObj<typeof Dependencies>;
export const NoDependencies: any = () => (
<Dependencies
packageMeta={{
latest: {
name: 'verdaccio',
version: '4.0.0',
author: {
name: 'verdaccio user',
email: 'verdaccio.user@verdaccio.org',
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
dependencies: {},
devDependencies: {},
peerDependencies: {},
},
_uplinks: {},
}}
/>
);
const packageMeta = {
latest: {
name: 'verdaccio',
version: '4.0.0',
author: {
name: 'verdaccio user',
email: 'verdaccio.user@verdaccio.org',
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
dependencies: {
react: '16.9.0',
'react-dom': '16.9.0',
'@storybook/codemod': '^3.2.6',
chalk: '^2.1.0',
'child-process-promise': '^2.2.1',
commander: '^2.11.0',
'cross-spawn': '^5.0.1',
jscodeshift: '^0.3.30',
json5: '^0.5.1',
'latest-version': '^3.1.0',
'merge-dirs': '^0.2.1',
opencollective: '^1.0.3',
shelljs: '^0.7.8',
'update-notifier': '^2.1.0',
},
devDependencies: {
'babel-core': '7.0.0-beta6',
'cross-spawn': '^5.0.1',
jscodeshift: '^0.3.30',
json5: '^0.5.1',
'latest-version': '^3.1.0',
'merge-dirs': '^0.2.1',
opencollective: '^1.0.3',
shelljs: '^0.7.8',
'update-notifier': '^2.1.0',
},
peerDependencies: {
'styled-components': '5.0.0',
'cross-spawn': '^5.0.1',
jscodeshift: '^0.3.30',
json5: '^0.5.1',
'latest-version': '^3.1.0',
'merge-dirs': '^0.2.1',
opencollective: '^1.0.3',
shelljs: '^0.7.8',
'update-notifier': '^2.1.0',
},
optionalDependencies: {
'styled-components': '5.0.0',
'cross-spawn': '^5.0.1',
jscodeshift: '^0.3.30',
json5: '^0.5.1',
'latest-version': '^3.1.0',
'merge-dirs': '^0.2.1',
opencollective: '^1.0.3',
shelljs: '^0.7.8',
'update-notifier': '^2.1.0',
},
bundleDependencies: {
'styled-components': '5.0.0',
'cross-spawn': '^5.0.1',
jscodeshift: '^0.3.30',
json5: '^0.5.1',
'latest-version': '^3.1.0',
'merge-dirs': '^0.2.1',
opencollective: '^1.0.3',
shelljs: '^0.7.8',
'update-notifier': '^2.1.0',
},
},
_uplinks: {},
};
export const Primary: Story = {
name: 'Default',
render: () => {
return <Dependencies packageMeta={packageMeta} />;
},
};
export const OnlyDeps: Story = {
name: 'Only dependencies',
render: () => {
return (
<Dependencies
packageMeta={{
latest: {
name: 'verdaccio',
version: '4.0.0',
author: {
name: 'verdaccio user',
email: 'verdaccio.user@verdaccio.org',
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
dependencies: {
'styled-components': '5.0.0',
'cross-spawn': '^5.0.1',
jscodeshift: '^0.3.30',
json5: '^0.5.1',
'latest-version': '^3.1.0',
'merge-dirs': '^0.2.1',
opencollective: '^1.0.3',
shelljs: '^0.7.8',
'update-notifier': '^2.1.0',
},
devDependencies: { jscodeshift: '^0.3.30', json5: '^0.5.1' },
peerDependencies: {},
},
_uplinks: {},
}}
/>
);
},
};
export const NoDependencies: Story = {
name: 'No dependencies',
render: () => {
return (
<Dependencies
packageMeta={{
latest: {
name: 'verdaccio',
version: '4.0.0',
author: {
name: 'verdaccio user',
email: 'verdaccio.user@verdaccio.org',
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
dependencies: {},
devDependencies: {},
peerDependencies: {},
},
_uplinks: {},
}}
/>
);
},
};

View file

@ -1,4 +1,6 @@
import Box from '@mui/material/Box';
import CardContent from '@mui/material/CardContent';
import { useTheme } from '@mui/styles';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
@ -10,44 +12,38 @@ import { CardWrap, StyledText, Tag, Tags } from './styles';
interface DependencyBlockProps {
title: string;
dependencies: PackageDependencies;
enableLoading?: () => void;
}
const DependencyBlock: React.FC<DependencyBlockProps> = ({
title,
dependencies,
enableLoading,
}) => {
const DependencyBlock: React.FC<DependencyBlockProps> = ({ title, dependencies }) => {
const history = useHistory();
const { t } = useTranslation();
const theme = useTheme();
const deps = Object.entries(dependencies);
function handleClick(name: string): void {
enableLoading?.();
history.push(`/-/web/detail/${name}`);
}
return (
<CardWrap data-testid={title}>
<CardContent>
<StyledText variant="subtitle1">{`${title} (${deps.length})`}</StyledText>
<Tags>
{deps.map(([name, version]) => (
<Tag
className={'dep-tag'}
clickable={true}
data-testid={name}
key={name}
label={t('dependencies.dependency-block', { package: name, version })}
// eslint-disable-next-line
onClick={() => handleClick(name)}
/>
))}
</Tags>
</CardContent>
</CardWrap>
<Box data-testid={title} sx={{ margin: theme.spacing(2) }}>
<StyledText sx={{ marginBottom: theme.spacing(1) }} variant="subtitle1">
{`${title} (${deps.length})`}
</StyledText>
<Tags>
{deps.map(([name, version]) => (
<Tag
className={'dep-tag'}
clickable={true}
data-testid={name}
key={name}
label={t('dependencies.dependency-block', { package: name, version })}
// eslint-disable-next-line
onClick={() => handleClick(name)}
/>
))}
</Tags>
</Box>
);
};
@ -65,37 +61,51 @@ const Dependencies: React.FC<{ packageMeta: any }> = ({ packageMeta }) => {
const { latest } = packageMeta;
// FIXME: add dependencies to package meta type
// @ts-ignore
const { dependencies, devDependencies, peerDependencies, name } = latest;
const dependencyMap = { dependencies, devDependencies, peerDependencies };
const {
dependencies,
devDependencies,
peerDependencies,
optionalDependencies,
bundleDependencies,
name,
} = latest;
const dependencyMap = {
dependencies,
devDependencies,
peerDependencies,
bundleDependencies,
optionalDependencies,
};
const hasDependencies =
hasKeys(dependencies) || hasKeys(devDependencies) || hasKeys(peerDependencies);
hasKeys(dependencies) ||
hasKeys(bundleDependencies) ||
hasKeys(optionalDependencies) ||
hasKeys(devDependencies) ||
hasKeys(peerDependencies);
if (hasDependencies) {
return (
<>
{Object.entries(dependencyMap).map(([dependencyType, dependencies]) => {
if (!dependencies || Object.keys(dependencies).length === 0) {
return null;
}
return (
<DependencyBlock
dependencies={dependencies}
key={dependencyType}
title={dependencyType}
/>
);
})}
</>
<CardWrap>
<CardContent>
{Object.entries(dependencyMap).map(([dependencyType, dependencies]) => {
if (!dependencies || Object.keys(dependencies).length === 0) {
return null;
}
return (
<>
<DependencyBlock
dependencies={dependencies}
key={dependencyType}
title={dependencyType}
/>
</>
);
})}
</CardContent>
</CardWrap>
);
}
return (
<NoItems
className="no-dependencies"
text={t('dependencies.has-no-dependencies', { package: name })}
/>
);
return <NoItems text={t('dependencies.has-no-dependencies', { package: name })} />;
};
export default Dependencies;

View file

@ -3,7 +3,7 @@ import React from 'react';
import { default as Deprecated } from '.';
export default {
title: 'Deprecated',
title: 'Components/Detail/Deprecated',
};
export const DeprecatedAll: any = () => <Deprecated message="this is deprecated" />;

View file

@ -3,7 +3,7 @@ import React from 'react';
import { DeveloperType, default as Developers } from '.';
export default {
title: 'Developers',
title: 'Components/Sidebar/Developers',
};
export const DevelopersAll: any = () => (

View file

@ -3,7 +3,7 @@ import React from 'react';
import { default as Dist } from '.';
export default {
title: 'Dist',
title: 'Components/Sidebar/Dist',
};
export const AllProperties: any = () => (

View file

@ -3,7 +3,7 @@ import React from 'react';
import { default as Engines } from '.';
export default {
title: 'Engines',
title: 'Components/Sidebar/Engines',
};
export const EnginesNpmNode: any = () => (

View file

@ -1,25 +1,56 @@
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { default as FundButton } from '.';
export default {
title: 'FundButton',
const meta: Meta<typeof FundButton> = {
title: 'Components/Sidebar/FundButton',
component: FundButton,
};
export const FundButtonUrl: any = () => (
<Box sx={{ width: '100%' }}>
<Stack spacing={2}>
<FundButton
packageMeta={{
latest: {
funding: {
url: 'https://opencollective.com/verdaccio',
},
},
}}
/>
</Stack>
</Box>
);
export default meta;
type Story = StoryObj<typeof FundButton>;
export const Primary: Story = {
name: 'Default',
render: () => {
return (
<Box sx={{ width: '100%' }}>
<Stack spacing={2}>
<FundButton
packageMeta={{
latest: {
funding: {
url: 'https://opencollective.com/verdaccio',
},
},
}}
/>
</Stack>
</Box>
);
},
};
export const Bad: Story = {
name: 'Bad Link (empty)',
render: () => {
return (
<Box sx={{ width: '100%' }}>
<Stack spacing={2}>
<FundButton
packageMeta={{
latest: {
funding: {
url: 'bad_link',
},
},
}}
/>
</Stack>
</Box>
);
},
};

View file

@ -3,7 +3,7 @@ import React from 'react';
import { default as Help } from '.';
export default {
title: 'Help',
title: 'Components/Home/Help',
};
export const HelpMessage: any = () => <Help />;

View file

@ -116,8 +116,8 @@ exports[`<Help /> component should load the component in default state 1`] = `
color: rgba(0, 0, 0, 0.54);
-webkit-transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
padding: 12px;
font-size: 1.75rem;
padding: 5px;
font-size: 1.125rem;
}
.emotion-10::-moz-focus-inner {
@ -315,7 +315,7 @@ exports[`<Help /> component should load the component in default state 1`] = `
help.first-step-command-line
</span>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeLarge emotion-10"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall emotion-10"
data-testid="segments"
tabindex="0"
type="button"
@ -350,7 +350,7 @@ exports[`<Help /> component should load the component in default state 1`] = `
help.second-step-command-line
</span>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeLarge emotion-10"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall emotion-10"
data-testid="segments"
tabindex="0"
type="button"

View file

@ -16,7 +16,7 @@ import {
} from '.';
export default {
title: 'Icons',
title: 'Components/Icons/Overview',
};
export const Icons: any = () => (

View file

@ -1,34 +1,87 @@
/* eslint-disable react-hooks/rules-of-hooks */
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { default as Install } from '.';
import { useConfig } from '../../';
export default {
title: 'Install ',
const meta: Meta<typeof Install> = {
title: 'Components/Sidebar/Install',
component: Install,
};
export const ActionBarAll: any = () => {
const { configOptions } = useConfig();
return (
<Install
configOptions={{ ...configOptions, pkgManagers: ['npm', 'yarn', 'pnpm'] }}
packageMeta={{
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
dist: {
fileCount: 0,
unpackedSize: 0,
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
},
homepage: 'https://verdaccio.org',
bugs: {
url: 'https://github.com/verdaccio/monorepo/issues',
},
},
}}
packageName="jquery"
/>
);
export default meta;
type Story = StoryObj<typeof Install>;
const packageMeta = {
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
dist: {
fileCount: 0,
unpackedSize: 0,
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
},
homepage: 'https://verdaccio.org',
bugs: {
url: 'https://github.com/verdaccio/monorepo/issues',
},
},
};
export const Primary: Story = {
name: 'Default',
render: () => {
const { configOptions } = useConfig();
return (
<Install
configOptions={{ ...configOptions, pkgManagers: ['npm', 'yarn', 'pnpm'] }}
packageMeta={packageMeta}
packageName="jquery"
/>
);
},
};
export const npmOnly: Story = {
name: 'Only NPM',
render: () => {
const { configOptions } = useConfig();
return (
<Install
configOptions={{ ...configOptions, pkgManagers: ['npm'] }}
packageMeta={packageMeta}
packageName="jquery"
/>
);
},
};
export const yarnOnly: Story = {
name: 'Only Yarn',
render: () => {
const { configOptions } = useConfig();
return (
<Install
configOptions={{ ...configOptions, pkgManagers: ['yarn'] }}
packageMeta={packageMeta}
packageName="jquery"
/>
);
},
};
export const pnpmOnly: Story = {
name: 'Only Pnpm',
render: () => {
const { configOptions } = useConfig();
return (
<Install
configOptions={{ ...configOptions, pkgManagers: ['pnpm'] }}
packageMeta={packageMeta}
packageName="jquery"
/>
);
},
};

View file

@ -1,6 +1,8 @@
import styled from '@emotion/styled';
import { Typography } from '@mui/material';
import Grid from '@mui/material/Grid';
import List from '@mui/material/List';
import { useTheme } from '@mui/styles';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -8,6 +10,7 @@ import { TemplateUIOptions } from '@verdaccio/types';
import { Theme } from '../../Theme';
import { PackageMetaInterface } from '../../types/packageMeta';
import { SettingsMenu } from '../SettingsMenu';
import InstallListItem, { DependencyManager } from './InstallListItem';
const StyledText = styled(Typography)<{ theme?: Theme }>((props) => ({
@ -23,30 +26,41 @@ export type Props = {
const Install: React.FC<Props> = ({ packageMeta, packageName, configOptions }) => {
const { t } = useTranslation();
const theme = useTheme();
if (!packageMeta || !packageName) {
return null;
}
const hasNpm = configOptions?.pkgManagers?.includes('npm');
const hasYarn = configOptions?.pkgManagers?.includes('yarn');
const hasPnpm = configOptions?.pkgManagers?.includes('pnpm') ?? true;
const hasPkgManagers = hasNpm || hasPnpm || hasYarn;
return hasPkgManagers ? (
<List
data-testid={'installList'}
subheader={<StyledText variant={'subtitle1'}>{t('sidebar.installation.title')}</StyledText>}
>
{hasNpm && (
<InstallListItem dependencyManager={DependencyManager.NPM} packageName={packageName} />
)}
{hasYarn && (
<InstallListItem dependencyManager={DependencyManager.YARN} packageName={packageName} />
)}
{hasPnpm && (
<InstallListItem dependencyManager={DependencyManager.PNPM} packageName={packageName} />
)}
</List>
<>
<Grid
container={true}
justifyContent="flex-end"
sx={{ marginRight: theme.spacing(10), alingText: 'right' }}
>
<SettingsMenu packageName={packageName} />
</Grid>
<List
data-testid={'installList'}
subheader={<StyledText variant={'subtitle1'}>{t('sidebar.installation.title')}</StyledText>}
>
{hasNpm && (
<InstallListItem dependencyManager={DependencyManager.NPM} packageName={packageName} />
)}
{hasYarn && (
<InstallListItem dependencyManager={DependencyManager.YARN} packageName={packageName} />
)}
{hasPnpm && (
<InstallListItem dependencyManager={DependencyManager.PNPM} packageName={packageName} />
)}
</List>
</>
) : null;
};

View file

@ -5,6 +5,7 @@ import ListItemText from '@mui/material/ListItemText';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSettings } from '../../providers/PersistenceSettingProvider';
import CopyToClipBoard from '../CopyClipboard';
import { Npm, Pnpm, Yarn } from '../Icons';
@ -16,13 +17,13 @@ const InstallItem = styled(ListItem)({
});
const InstallListItemText = styled(ListItemText)({
padding: '0 10px',
padding: '0 0 0 10px',
margin: 0,
});
const PackageMangerAvatar = styled(Avatar)({
borderRadius: '0px',
padding: '0',
padding: 0,
});
export enum DependencyManager {
@ -38,6 +39,9 @@ interface Interface {
const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }) => {
const { t } = useTranslation();
const { localSettings } = useSettings();
const isGlobal = localSettings[packageName]?.global ?? false;
const pkgName = isGlobal ? `-g ${packageName}` : packageName;
switch (dependencyManager) {
case DependencyManager.NPM:
@ -50,8 +54,12 @@ const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }
primary={
<CopyToClipBoard
dataTestId="installYarn"
text={t('sidebar.installation.install-using-npm-command', { packageName })}
title={t('sidebar.installation.install-using-npm-command', { packageName })}
text={t('sidebar.installation.install-using-npm-command', {
packageName: pkgName,
})}
title={t('sidebar.installation.install-using-npm-command', {
packageName: pkgName,
})}
/>
}
secondary={t('sidebar.installation.install-using-npm')}
@ -68,8 +76,12 @@ const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }
primary={
<CopyToClipBoard
dataTestId="installYarn"
text={t('sidebar.installation.install-using-yarn-command', { packageName })}
title={t('sidebar.installation.install-using-yarn-command', { packageName })}
text={t('sidebar.installation.install-using-yarn-command', {
packageName: pkgName,
})}
title={t('sidebar.installation.install-using-yarn-command', {
packageName: pkgName,
})}
/>
}
secondary={t('sidebar.installation.install-using-yarn')}
@ -86,8 +98,12 @@ const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }
primary={
<CopyToClipBoard
dataTestId="installPnpm"
text={t('sidebar.installation.install-using-pnpm-command', { packageName })}
title={t('sidebar.installation.install-using-pnpm-command', { packageName })}
text={t('sidebar.installation.install-using-pnpm-command', {
packageName: pkgName,
})}
title={t('sidebar.installation.install-using-pnpm-command', {
packageName: pkgName,
})}
/>
}
secondary={t('sidebar.installation.install-using-pnpm')}

View file

@ -4,7 +4,7 @@ import React from 'react';
import Loading from './Loading';
export default {
title: 'Loading',
title: 'Components/Detail/Loading',
};
export const Icons: any = () => (

View file

@ -5,7 +5,7 @@ import React from 'react';
import LoginDialog from './LoginDialog';
export default {
title: 'LoginDialog',
title: 'Components/Dialog/LoginDialog',
};
export const OpenLoginDialog = {

View file

@ -3,7 +3,7 @@ import React from 'react';
import { default as NoItems } from '.';
export default {
title: 'Install ',
title: 'Components/Detail/No Items',
};
export const NoItemsText: any = () => {

View file

@ -1,16 +1,15 @@
import Alert from '@mui/material/Alert';
import Typography from '@mui/material/Typography';
import React from 'react';
interface Props {
text: string;
className?: string;
}
const NoItems: React.FC<Props> = ({ className, text, ...props }) => (
// eslint-disable-next-line verdaccio/jsx-spread
<Typography {...props} className={className} gutterBottom={true} variant="subtitle1">
{text}
</Typography>
const NoItems: React.FC<Props> = ({ text, ...props }) => (
<Alert severity="info">
<Typography {...props}>{text}</Typography>
</Alert>
);
export default NoItems;

View file

@ -2,17 +2,98 @@
exports[`<NoItem /> component should load the component in default state 1`] = `
.emotion-0 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
-webkit-transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
border-radius: 4px;
box-shadow: none;
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
font-weight: 400;
font-size: 0.875rem;
line-height: 1.43;
background-color: rgb(229, 246, 253);
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 6px 16px;
color: rgb(1, 67, 97);
}
.emotion-0 .MuiAlert-icon {
color: #0288d1;
}
.emotion-1 {
margin-right: 12px;
padding: 7px 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
font-size: 22px;
opacity: 0.9;
}
.emotion-2 {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width: 1em;
height: 1em;
display: inline-block;
fill: currentColor;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
-webkit-transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
font-size: inherit;
}
.emotion-3 {
padding: 8px 0;
min-width: 0;
overflow: auto;
}
.emotion-4 {
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
font-weight: 400;
font-size: 1rem;
line-height: 1.75;
margin-bottom: 0.35em;
line-height: 1.5;
}
<h6
class="MuiTypography-root MuiTypography-subtitle1 MuiTypography-gutterBottom emotion-0"
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiAlert-root MuiAlert-standardInfo MuiAlert-standard emotion-0"
role="alert"
>
test
</h6>
<div
class="MuiAlert-icon emotion-1"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeInherit emotion-2"
data-testid="InfoOutlinedIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20, 12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10, 10 0 0,0 12,2M11,17H13V11H11V17Z"
/>
</svg>
</div>
<div
class="MuiAlert-message emotion-3"
>
<p
class="MuiTypography-root MuiTypography-body1 emotion-4"
>
test
</p>
</div>
</div>
`;

View file

@ -1,31 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { default as Repository } from '.';
export default {
title: 'Repository',
const meta: Meta<typeof Repository> = {
title: 'Components/Sidebar/Repository',
component: Repository,
};
export default meta;
type Story = StoryObj<typeof Repository>;
export const RepositoryGit: any = () => {
return (
<Repository
packageMeta={{
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
repository: {
type: 'git',
url: 'git://github.com/verdaccio/ui.git',
export const Primary: Story = {
name: 'Git Repository',
render: () => {
return (
<Repository
packageMeta={{
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
repository: {
type: 'git',
url: 'git://github.com/verdaccio/ui.git',
},
},
},
}}
/>
);
}}
/>
);
},
};
export const RepositoryHTTPS: any = () => {
return (
export const HTTPS: Story = {
name: 'Https repo',
render: () => (
<Repository
packageMeta={{
_uplinks: {},
@ -39,5 +47,24 @@ export const RepositoryHTTPS: any = () => {
},
}}
/>
);
),
};
export const HTTP: Story = {
name: 'Http repo',
render: () => (
<Repository
packageMeta={{
_uplinks: {},
latest: {
name: 'verdaccio-ui/local-storage',
version: '8.0.1-next.1',
repository: {
type: 'http',
url: 'http://github.com/verdaccio/ui.git',
},
},
}}
/>
),
};

View file

@ -47,13 +47,11 @@ const RepositoryAvatar = styled(Avatar)({
const Repository: React.FC<{ packageMeta: any }> = ({ packageMeta }) => {
const { t } = useTranslation();
if (!packageMeta?.latest?.repository?.url || !urlUtils.isURL(packageMeta.latest.repository.url)) {
const url = packageMeta?.latest?.repository?.url;
if (!url || !urlUtils.isURL(url)) {
return null;
}
const { url } = packageMeta.latest.repository;
const getCorrectRepositoryURL = (): string => {
if (!url.includes('git+')) {
return url;

View file

@ -6,7 +6,7 @@ import { MemoryRouter } from 'react-router';
import Search from './Search';
export default {
title: 'Search',
title: 'Components/Header/Search',
};
export const SearchByQuery = {

View file

@ -0,0 +1,67 @@
import Check from '@mui/icons-material/Check';
import Settings from '@mui/icons-material/Settings';
import IconButton from '@mui/material/IconButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSettings } from '../../providers/PersistenceSettingProvider';
interface Props {
packageName: string;
}
const InstallListItem: React.FC<Props> = ({ packageName }) => {
const { t } = useTranslation();
const { localSettings, updateSettings } = useSettings();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleOpenMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClick = () => {
const statusGlobal = !localSettings[packageName]?.global;
updateSettings({ [packageName]: { global: statusGlobal } });
setAnchorEl(null);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<IconButton
aria-controls={open ? 'basic-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
id="basic-button"
onClick={handleOpenMenu}
size="small"
>
<Settings fontSize="small" />
</IconButton>
<Menu
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
anchorEl={anchorEl}
id="basic-menu"
onClose={handleClose}
open={open}
>
<MenuItem onClick={handleClick}>
{' '}
{localSettings?.global ? (
<ListItemIcon>
<Check />
</ListItemIcon>
) : null}
{t('sidebar.installation.global')}
</MenuItem>
</Menu>
</>
);
};
export default InstallListItem;

View file

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

View file

@ -0,0 +1,68 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { default as SideBarTittle } from '.';
const meta: Meta<typeof SideBarTittle> = {
title: 'Components/Sidebar/Title',
component: SideBarTittle,
};
export default meta;
type Story = StoryObj<typeof SideBarTittle>;
export const Commonjs: Story = {
name: 'CommonJS package',
render: () => (
<SideBarTittle
isLatest={false}
moduleType="commonjs"
packageName="jquery"
time="2012-12-31T06:54:14.275Z"
version="1.0.0"
/>
),
};
export const ES6: Story = {
name: 'ES6 package',
render: () => (
<SideBarTittle
isLatest={true}
moduleType="module"
packageName="react"
time="2012-12-31T06:54:14.275Z"
version="14.0.0"
/>
),
};
export const Description: Story = {
name: 'With description',
render: () => (
<SideBarTittle
description="Storybook's CLI - easiest method of adding storybook to your projects"
isLatest={true}
moduleType="module"
packageName="storybook"
time="2012-12-31T06:54:14.275Z"
version="14.0.0"
/>
),
};
export const WithTypes: Story = {
name: 'With types declaration',
render: () => (
<SideBarTittle
description="Storybook's CLI - easiest method of adding storybook to your projects"
hasTypes={true}
isLatest={true}
moduleType="module"
packageName="storybook"
time="2012-12-31T06:54:14.275Z"
version="14.0.0"
/>
),
};

View file

@ -3,7 +3,7 @@ import React from 'react';
import { default as Uplinks } from '.';
export default {
title: 'Uplinks',
title: 'Components/Detail/Uplinks',
};
export const UplinksAll: any = () => {

View file

@ -4,40 +4,202 @@ exports[`<UpLinks /> component should render the component when there is no upli
{
"asFragment": [Function],
"baseElement": .emotion-0 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
-webkit-transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
border-radius: 4px;
box-shadow: none;
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
font-weight: 400;
font-size: 0.875rem;
line-height: 1.43;
background-color: rgb(229, 246, 253);
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 6px 16px;
color: rgb(1, 67, 97);
}
.emotion-0 .MuiAlert-icon {
color: #0288d1;
}
.emotion-1 {
margin-right: 12px;
padding: 7px 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
font-size: 22px;
opacity: 0.9;
}
.emotion-2 {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width: 1em;
height: 1em;
display: inline-block;
fill: currentColor;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
-webkit-transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
font-size: inherit;
}
.emotion-3 {
padding: 8px 0;
min-width: 0;
overflow: auto;
}
.emotion-4 {
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
font-weight: 400;
font-size: 1rem;
line-height: 1.75;
margin-bottom: 0.35em;
line-height: 1.5;
}
<body>
<div>
<h6
class="MuiTypography-root MuiTypography-subtitle1 MuiTypography-gutterBottom emotion-0"
data-testid="no-uplinks"
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiAlert-root MuiAlert-standardInfo MuiAlert-standard emotion-0"
role="alert"
>
uplinks.no-items
</h6>
<div
class="MuiAlert-icon emotion-1"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeInherit emotion-2"
data-testid="InfoOutlinedIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20, 12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10, 10 0 0,0 12,2M11,17H13V11H11V17Z"
/>
</svg>
</div>
<div
class="MuiAlert-message emotion-3"
>
<p
class="MuiTypography-root MuiTypography-body1 emotion-4"
data-testid="no-uplinks"
>
uplinks.no-items
</p>
</div>
</div>
</div>
</body>,
"container": .emotion-0 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
-webkit-transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
border-radius: 4px;
box-shadow: none;
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
font-weight: 400;
font-size: 0.875rem;
line-height: 1.43;
background-color: rgb(229, 246, 253);
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 6px 16px;
color: rgb(1, 67, 97);
}
.emotion-0 .MuiAlert-icon {
color: #0288d1;
}
.emotion-1 {
margin-right: 12px;
padding: 7px 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
font-size: 22px;
opacity: 0.9;
}
.emotion-2 {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width: 1em;
height: 1em;
display: inline-block;
fill: currentColor;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
-webkit-transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
font-size: inherit;
}
.emotion-3 {
padding: 8px 0;
min-width: 0;
overflow: auto;
}
.emotion-4 {
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
font-weight: 400;
font-size: 1rem;
line-height: 1.75;
margin-bottom: 0.35em;
line-height: 1.5;
}
<div>
<h6
class="MuiTypography-root MuiTypography-subtitle1 MuiTypography-gutterBottom emotion-0"
data-testid="no-uplinks"
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiAlert-root MuiAlert-standardInfo MuiAlert-standard emotion-0"
role="alert"
>
uplinks.no-items
</h6>
<div
class="MuiAlert-icon emotion-1"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeInherit emotion-2"
data-testid="InfoOutlinedIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20, 12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10, 10 0 0,0 12,2M11,17H13V11H11V17Z"
/>
</svg>
</div>
<div
class="MuiAlert-message emotion-3"
>
<p
class="MuiTypography-root MuiTypography-body1 emotion-4"
data-testid="no-uplinks"
>
uplinks.no-items
</p>
</div>
</div>
</div>,
"debug": [Function],
"findAllByAltText": [Function],

View file

@ -1,7 +1,6 @@
import { PackageMetaInterface } from '../../types/packageMeta';
export interface DetailContextProps {
enableLoading: () => void;
hasNotBeenFound: boolean;
isLoading: boolean;
packageMeta: PackageMetaInterface;
@ -11,7 +10,6 @@ export interface DetailContextProps {
}
export interface VersionPageConsumerProps {
enableLoading: () => void;
packageMeta: PackageMetaInterface;
packageName: string;
packageVersion?: string;

View file

@ -42,6 +42,8 @@ export { VersionLayout } from './layouts/Version';
// providers
export { default as AppConfigurationProvider } from './providers/AppConfigurationProvider';
export { default as PersistenceSettingProvider } from './providers/PersistenceSettingProvider';
export * from './providers/AppConfigurationProvider';
export { TranslatorProvider, useLanguage, LanguageItem } from './providers/TranslatorProvider';
export * from './providers/TranslatorProvider';

View file

@ -1,29 +1,44 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { MemoryRouter, Route } from 'react-router';
import { VersionProvider } from '../../providers';
import VersionLayout from './Version';
export default {
title: 'VersionLayout',
const meta: Meta<typeof VersionLayout> = {
title: 'Layout/Version',
component: VersionLayout,
};
export const VersionLayoutStorybook: any = () => (
<MemoryRouter initialEntries={[`/-/web/detail/storybook`]}>
<Route exact={true} path="/-/web/detail/:package">
<VersionProvider>
<VersionLayout />
</VersionProvider>
</Route>
</MemoryRouter>
);
export default meta;
type Story = StoryObj<typeof VersionLayout>;
export const VersionLayoutJquery: any = () => (
<MemoryRouter initialEntries={[`/-/web/detail/jquery`]}>
<Route exact={true} path="/-/web/detail/:package">
<VersionProvider>
<VersionLayout />
</VersionProvider>
</Route>
</MemoryRouter>
);
export const Primary: Story = {
name: 'Storybook',
render: () => {
return (
<MemoryRouter initialEntries={[`/-/web/detail/storybook`]}>
<Route exact={true} path="/-/web/detail/:package">
<VersionProvider>
<VersionLayout />
</VersionProvider>
</Route>
</MemoryRouter>
);
},
};
export const jQuery: Story = {
name: 'jQuery',
render: () => {
return (
<MemoryRouter initialEntries={[`/-/web/detail/jquery`]}>
<Route exact={true} path="/-/web/detail/:package">
<VersionProvider>
<VersionLayout />
</VersionProvider>
</Route>
</MemoryRouter>
);
},
};

View file

@ -54,7 +54,6 @@ const AppConfigurationContext = createContext<ConfigProviderProps>(defaultValues
const AppConfigurationProvider: FunctionComponent<{ children: React.ReactElement<any> }> = ({
children,
}) => {
// Read only
const [configOptions] = useState<TemplateUIOptions>(getConfiguration());
const value = useMemo(

View file

@ -0,0 +1,57 @@
import React, {
FunctionComponent,
createContext,
useCallback,
useContext,
useMemo,
useState,
} from 'react';
import useLocalStorage from '../../hooks/useLocalStorage';
type PersistenceSettingsProps = {
isGlobal?: boolean;
};
const defaultValues: PersistenceSettingsProps = {
isGlobal: false,
};
const PersistenceSettingContext = createContext<any>(defaultValues);
const PersistenceSettingProvider: FunctionComponent<{ children: React.ReactElement<any> }> = ({
children,
}) => {
// get the initial state from the local storage
const [settings, setSettings] = useLocalStorage(`settings-ui-verdaccio`, {});
const [localSettings, setLocalSettings] = useState<PersistenceSettingsProps>(
settings ? settings : {}
);
const updateSettings = useCallback(
(newSettings: React.SetStateAction<PersistenceSettingsProps>) => {
setLocalSettings(newSettings);
setSettings(newSettings);
},
[setSettings]
);
const value = useMemo(
() => ({
localSettings: localSettings ? localSettings : {},
updateSettings,
}),
[localSettings, updateSettings]
);
return (
<PersistenceSettingContext.Provider value={value}>
{children}
</PersistenceSettingContext.Provider>
);
};
export default PersistenceSettingProvider;
export const useSettings = () => useContext(PersistenceSettingContext);

View file

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

View file

@ -15,7 +15,6 @@ function getRouterPackageName(packageName: string, scope?: string): string {
}
export interface DetailContextProps {
enableLoading: () => void;
hasNotBeenFound: boolean;
isLoading: boolean;
packageMeta: PackageMetaInterface;
@ -25,7 +24,6 @@ export interface DetailContextProps {
}
export interface VersionPageConsumerProps {
enableLoading: () => void;
packageMeta: PackageMetaInterface;
packageName: string;
packageVersion?: string;

View file

@ -5,7 +5,7 @@ import { VersionProvider } from '../../providers';
import Detail from './Detail';
export default {
title: 'Detail',
title: 'Sections/Detail',
};
export const DetailStorybook: any = () => (

View file

@ -23,7 +23,7 @@ exports[`DetailContainer renders correctly 1`] = `
margin-bottom: 16px;
}
@media (max-width:599.95px) {
@media (max-width:399.95px) {
.emotion-2 .MuiTabs-scrollButtons {
display: none;
}

View file

@ -6,7 +6,7 @@ import { VersionProvider } from '../../providers';
import Header from './Header';
export default {
title: 'Header',
title: 'Sections/Header',
};
function CustomInfoDialog({ onCloseDialog, title, isOpen }) {

View file

@ -4,7 +4,7 @@ import { MemoryRouter, Route } from 'react-router';
import Home from './Home';
export default {
title: 'Home',
title: 'Sections/Home',
};
export const HomeDefault: any = () => (

View file

@ -5,7 +5,7 @@ import { VersionProvider } from '../../providers';
import DetailSidebar from './Sidebar';
export default {
title: 'Sidebar',
title: 'Sections/Sidebar',
};
export const SidebarLatestPackage: any = () => (

View file

@ -43,7 +43,6 @@ const packageMeta = {
// const detailContextValue = {
// packageName: 'foo',
// readMe: 'test',
// enableLoading: () => {},
// isLoading: false,
// hasNotBeenFound: false,
// packageMeta: ,

View file

@ -6,6 +6,7 @@ import { Provider } from 'react-redux';
import { ThemeProvider } from '../Theme';
import AppConfigurationProvider from '../providers/AppConfigurationProvider';
import PersistenceSettingProvider from '../providers/PersistenceSettingProvider';
import { Store } from '../store/store';
import i18nConfig from './i18n-config';
@ -13,13 +14,15 @@ const renderWithStore = (ui: React.ReactElement<any>, store: Store) =>
render(ui, {
wrapper: ({ children }) => (
<Provider store={store}>
<AppConfigurationProvider>
<StyledEngineProvider injectFirst={true}>
<ThemeProvider>
<I18nextProvider i18n={i18nConfig}>{children}</I18nextProvider>
</ThemeProvider>
</StyledEngineProvider>
</AppConfigurationProvider>
<PersistenceSettingProvider>
<AppConfigurationProvider>
<StyledEngineProvider injectFirst={true}>
<ThemeProvider>
<I18nextProvider i18n={i18nConfig}>{children}</I18nextProvider>
</ThemeProvider>
</StyledEngineProvider>
</AppConfigurationProvider>
</PersistenceSettingProvider>
</Provider>
),
});
@ -27,11 +30,13 @@ const renderWithStore = (ui: React.ReactElement<any>, store: Store) =>
const customRender = (node: React.ReactElement, ...options: any) => {
return render(
<AppConfigurationProvider>
<StyledEngineProvider injectFirst={true}>
<ThemeProvider>
<I18nextProvider i18n={i18nConfig}>{node}</I18nextProvider>
</ThemeProvider>
</StyledEngineProvider>
<PersistenceSettingProvider>
<StyledEngineProvider injectFirst={true}>
<ThemeProvider>
<I18nextProvider i18n={i18nConfig}>{node}</I18nextProvider>
</ThemeProvider>
</StyledEngineProvider>
</PersistenceSettingProvider>
</AppConfigurationProvider>,
...options
);

View file

@ -16,6 +16,10 @@ describe('utils', () => {
test('isEmail() - should return false if invalid', () => {
expect(isEmail('')).toBeFalsy();
});
test('git repo is valid', () => {
expect(isURL('git://github.com/verdaccio/ui.git')).toBeTruthy();
});
});
describe('extractFileName', () => {

View file

@ -3,7 +3,7 @@ import isURLValidator from 'validator/lib/isURL';
export function isURL(url: string): boolean {
return isURLValidator(url || '', {
protocols: ['http', 'https', 'git+https'],
protocols: ['http', 'https', 'git+https', 'git'],
require_protocol: true,
require_tld: false,
});

22
pnpm-lock.yaml generated
View file

@ -484,7 +484,7 @@ importers:
specifier: workspace:2.0.0-6-next.8
version: link:../../packages/tools/helpers
cypress:
specifier: 11.2.0
specifier: ^11.2.0
version: 11.2.0
debug:
specifier: 4.3.4
@ -3840,7 +3840,7 @@ packages:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.20.7
'@babel/helper-plugin-utils': 7.21.5
'@babel/helper-plugin-utils': 7.20.2
/@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.21.4):
resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
@ -3848,7 +3848,7 @@ packages:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.21.4
'@babel/helper-plugin-utils': 7.21.5
'@babel/helper-plugin-utils': 7.20.2
dev: true
/@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.21.8):
@ -5811,7 +5811,7 @@ packages:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.20.7
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-plugin-utils': 7.21.5
'@babel/helper-validator-option': 7.21.0
'@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.20.7)
'@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.20.7)
@ -5825,7 +5825,7 @@ packages:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.21.4
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-plugin-utils': 7.21.5
'@babel/helper-validator-option': 7.21.0
'@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.21.4)
'@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.4)
@ -15034,7 +15034,7 @@ packages:
pretty-bytes: 5.6.0
proxy-from-env: 1.0.0
request-progress: 3.0.0
semver: 7.4.0
semver: 7.5.0
supports-color: 8.1.1
tmp: 0.2.1
untildify: 4.0.0
@ -24785,7 +24785,7 @@ packages:
peerDependencies:
react: '>=15'
dependencies:
'@babel/runtime': 7.21.5
'@babel/runtime': 7.21.0
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
@ -25720,14 +25720,6 @@ packages:
hasBin: true
dev: true
/semver@7.4.0:
resolution: {integrity: sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/semver@7.5.0:
resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==}
engines: {node: '>=10'}