Merge branch 'master' into chore-html-linter

This commit is contained in:
Juan Picado 2020-05-04 07:43:04 +02:00 committed by GitHub
commit 294c9f8126
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
124 changed files with 3440 additions and 1108 deletions

View File

@ -2,6 +2,94 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.7.1](https://github.com/verdaccio/ui/compare/v1.7.0...v1.7.1) (2020-04-27)
### Bug Fixes
* **i18n:** fixed current locale ([#462](https://github.com/verdaccio/ui/issues/462)) ([f44abd7](https://github.com/verdaccio/ui/commit/f44abd7dd08a8d68b1bfc2bf0c053f3e80a343d0))
## [1.7.0](https://github.com/verdaccio/ui/compare/v1.6.0...v1.7.0) (2020-04-24)
### Features
* add japanese translations ([#460](https://github.com/verdaccio/ui/issues/460)) ([25def6c](https://github.com/verdaccio/ui/commit/25def6ccd5a42d43af1c33e7ace4bd7fdbec0e64))
## [1.6.0](https://github.com/verdaccio/ui/compare/v1.5.0...v1.6.0) (2020-04-23)
### Features
* **lng:** Added change language on the fly ([#456](https://github.com/verdaccio/ui/issues/456)) ([675ee98](https://github.com/verdaccio/ui/commit/675ee980ee2c4c789e52d38553f751bb219d1270))
* Add french language + minor english language fix ([#459](https://github.com/verdaccio/ui/issues/459)) ([b173684](https://github.com/verdaccio/ui/commit/b17368470d63878292aca3e6d2f9adc97748ebac))
## [1.5.0](https://github.com/verdaccio/ui/compare/v1.4.0...v1.5.0) (2020-04-08)
### Features
* **flag:** Added Germany flag ([#454](https://github.com/verdaccio/ui/issues/454)) ([07620e5](https://github.com/verdaccio/ui/commit/07620e5d4b1ed54bae2266d936af5306bfbe2d8b))
### Bug Fixes
* **styles:** Updated dark colors ([#455](https://github.com/verdaccio/ui/issues/455)) ([d29aa05](https://github.com/verdaccio/ui/commit/d29aa05cc6ef31cb871e79de10c1b1ddd74f023e))
## [1.4.0](https://github.com/verdaccio/ui/compare/v1.3.0...v1.4.0) (2020-04-02)
### Features
* added zh-CN translations to the UI ([#448](https://github.com/verdaccio/ui/issues/448)) ([03114a8](https://github.com/verdaccio/ui/commit/03114a842b88ae0f482f389e7ae91af62e00bca4))
## [1.3.0](https://github.com/verdaccio/ui/compare/v1.0.4...v1.3.0) (2020-04-01)
### Features
* **style:** added dark mode ([#446](https://github.com/verdaccio/ui/issues/446)) ([cdad5cf](https://github.com/verdaccio/ui/commit/cdad5cf70d69b7bb045fce461a32108def81721d))
### Bug Fixes
* **chore:** droped release ([#449](https://github.com/verdaccio/ui/issues/449)) ([f321f7b](https://github.com/verdaccio/ui/commit/f321f7b6fe1ac44897753f0bfdbbaa6ca7eca515))
## [1.2.0](https://github.com/verdaccio/ui/compare/v1.0.4...v1.2.0) (2020-04-01)
### Features
* **style:** added dark mode ([#446](https://github.com/verdaccio/ui/issues/446)) ([cdad5cf](https://github.com/verdaccio/ui/commit/cdad5cf70d69b7bb045fce461a32108def81721d))
### Bug Fixes
* **chore:** droped release ([#449](https://github.com/verdaccio/ui/issues/449)) ([f321f7b](https://github.com/verdaccio/ui/commit/f321f7b6fe1ac44897753f0bfdbbaa6ca7eca515))
## [1.1.0](https://github.com/verdaccio/ui/compare/v1.0.4...v1.1.0) (2020-04-01)
### Features
* **style:** added dark mode ([#446](https://github.com/verdaccio/ui/issues/446)) ([cdad5cf](https://github.com/verdaccio/ui/commit/cdad5cf70d69b7bb045fce461a32108def81721d))
### Bug Fixes
* **chore:** droped release ([#449](https://github.com/verdaccio/ui/issues/449)) ([f321f7b](https://github.com/verdaccio/ui/commit/f321f7b6fe1ac44897753f0bfdbbaa6ca7eca515))
### [1.0.4](https://github.com/verdaccio/ui/compare/v1.0.0...v1.0.4) (2020-03-17)
## [1.0.0](https://github.com/verdaccio/ui/compare/v0.3.13...v1.0.0) (2020-03-13)
### Features
* **de-translations:** added de-DE translations to the UI ([#441](https://github.com/verdaccio/ui/issues/441)) ([e27d59b](https://github.com/verdaccio/ui/commit/e27d59bff7039473e566090fa0f825f7e462aa4e))
* spanish translations to UI ([#440](https://github.com/verdaccio/ui/issues/440)) ([0abe1ef](https://github.com/verdaccio/ui/commit/0abe1ef41ca93b900ddda72e2d873ee52078221c))
* **i18n:** added i18next for user interface translations ([#432](https://github.com/verdaccio/ui/issues/432)) ([7428384](https://github.com/verdaccio/ui/commit/7428384b55e6089dbe45e6b216eee0b670dff576))
### [0.3.13](https://github.com/verdaccio/ui/compare/v0.3.12...v0.3.13) (2020-02-02)
@ -358,4 +446,4 @@ All notable changes to this project will be documented in this file. See [standa
<a name="0.0.3"></a>
## 0.0.3 (2019-04-04)
## 0.0.3 (2019-04-04)

View File

@ -93,7 +93,7 @@ If you have any issue you can try the following options, do no desist to ask or
* [Blog](https://medium.com/verdaccio)
* [Donations](https://opencollective.com/verdaccio)
* [Roadmaps](https://github.com/verdaccio/verdaccio/projects)
* [Roadmaps](https://github.com/verdaccio/ui/projects)
* [Reporting an issue](https://github.com/verdaccio/verdaccio/blob/master/CONTRIBUTING.md#reporting-a-bug)
* [Running discussions](https://github.com/verdaccio/verdaccio/issues?q=is%3Aissue+is%3Aopen+label%3Adiscuss)
* [Chat](http://chat.verdaccio.org/)
@ -101,6 +101,32 @@ If you have any issue you can try the following options, do no desist to ask or
* [FAQ](https://github.com/verdaccio/verdaccio/issues?utf8=%E2%9C%93&q=is%3Aissue%20label%3Aquestion%20)
* [Docker Examples](https://github.com/verdaccio/docker-examples)
### Translations
Translations are handled locally. I18n files can be found in the folder ```i18n/translations/*``` of this repository. We would love to provide translations from other languages, embracing all our users, but unfortunately we cannot do this without your help. Would you like to help us? Please feel **super welcome** to add a locale by opening a pull request.
Your PR should contain:
1 - A json file in the folder ```i18n/translations/*``` with the translations. The file must be named according to the new added language
2 - The files ```i18n/config.ts``` and ```LanguageSwitch.tsx``` updated with the new language. Please see the current structure
3 - The other translations containing the new language in the language of the file. Example:
New language: ```cs_CZ ```
The file ```pt-BR ``` should contain:
```
"lng": {
...,
"czech": "Tcheco"
}
```
4 - A SVG flag of the new translated language in the the folder ```src/components/Icon/img/*```. You maybe want to compress the svg file using https://jakearchibald.github.io/svgomg/
### License
Verdaccio is [MIT licensed](https://github.com/verdaccio/verdaccio/blob/master/LICENSE)

61
i18n/config.ts Normal file
View File

@ -0,0 +1,61 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import translationEN from './translations/en-US.json';
import translationPT from './translations/pt-BR.json';
import translationES from './translations/es-ES.json';
import translationDE from './translations/de-DE.json';
import translationFR from './translations/fr-FR.json';
import translationCN from './translations/zh-CN.json';
import translationJP from './translations/ja-JP.json';
import translationUA from './translations/uk-UA.json';
const languages = {
'en-US': {
translation: translationEN,
},
'pt-BR': {
translation: translationPT,
},
'es-ES': {
translation: translationES,
},
'de-DE': {
translation: translationDE,
},
'fr-FR': {
translation: translationFR,
},
'zh-CN': {
translation: translationCN,
},
'ja-JP': {
translation: translationJP,
},
'uk-UA': {
translation: translationUA,
},
};
type Language = keyof typeof languages;
i18n
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
// in case window.VEDACCIO_LANGUAGE is undefined,it will fall back to 'en-US'
lng: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.language,
fallbackLng: 'en-US',
whitelist: ['en-US', 'pt-BR', 'es-ES', 'de-DE', 'fr-FR', 'zh-CN', 'ja-JP', 'uk-UA'],
load: 'currentOnly',
resources: languages,
debug: false,
interpolation: {
escapeValue: false, // react already safes from xss
},
});
export default i18n;
export { Language };

View File

@ -0,0 +1,151 @@
{
"copy-to-clipboard": "In die Zwischenablage kopieren",
"author-anonymous": "Anonymus",
"action-bar-action": {
"visit-home-page": "Zur Homepage",
"open-an-issue": "Einen Fehler melden",
"download-tarball": "Archiv (Tarball) herunterladen"
},
"dialog": {
"registry-info": {
"title": "Registrierungsinformationen"
}
},
"header": {
"documentation": "Dokumentation",
"registry-info": "Registrierungsinformationen",
"greetings": "Hallo "
},
"search": {
"packages": "Pakete suchen"
},
"auto-complete": {
"loading": "wird geladen...",
"no-results-found": "Kein Ergebnis gefunden"
},
"tab": {
"uplinks": "Uplinks",
"versions": "Versionen",
"dependencies": "Abhängigkeiten",
"readme": "Liesmich"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} hat keine Uplinks."
},
"versions": {
"current-tags": "Aktuelle Tags",
"version-history": "Versionsgeschichte",
"not-available": "Nicht verfügbar"
},
"package": {
"published-on": "Veröffentlicht am {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Zur Homepage",
"homepage": "Homepage",
"open-an-issue": "Einen Fehler melden",
"bugs": "Fehler",
"download": "{{what}} herunterladen",
"the-tar-file": "die tar-Datei",
"tarball": "Archiv (Tarball)"
},
"dependencies": {
"has-no-dependencies": "{{package}} hat keine Abhängigkeiten",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Benutzername",
"password": "Passwort"
},
"form-placeholder": {
"username": "Dein Benutzername",
"password": "Dein sicheres Passwort"
},
"form-validation": {
"required-field": "Dieses Feld ist erforderlich",
"required-min-length": "Dieses Feld erfordert eine Mindestlänge von {{length}}",
"unable-to-sign-in": "Anmeldung nicht möglich",
"username-or-password-cant-be-empty": "Benutzername und Passwort dürfen nicht leer sein!"
},
"help": {
"title": "Noch kein Paket publiziert.",
"sub-title": "Um dein erstes Paket einfach zu publizieren:",
"first-step": "1. Einloggen",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publizieren",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Diese Seite aktualisieren."
},
"sidebar": {
"detail": {
"latest-version": "Letzte v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Installierung",
"install-using-yarn": "Mit yarn installieren",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Mit npm installieren",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Mit pnpm installieren",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Repository"
},
"author": {
"title": "Autor(in)"
},
"distribution": {
"title": "Neueste Distribution",
"license": "Lizenz",
"size": "Größe",
"file-count": "Anzahl der Dateien"
},
"maintainers": {
"title": "Maintainer"
},
"contributors": {
"title": "Contributor"
},
"engines": {
"npm-version": "NPM Version",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Unterstützt von",
"made-with-love-on": "Gemacht mit <0>♥</0> in"
},
"button": {
"close": "Schließen",
"cancel": "Abbrechen",
"login": "Einloggen",
"logout": "Ausloggen",
"go-to-the-home-page": "Zur Homepage",
"learn-more": "Mehr erfahren",
"fund-this-package": "Dieses Paket <0>finanzieren</0>"
},
"error": {
"unspecific": "Etwas ist schief gelaufen.",
"404": {
"page-not-found": "404 - Seite nicht gefunden",
"sorry-we-could-not-find-it": "Entschuldigung, wir konnten es nicht finden..."
},
"app-context-not-correct-used": "Der App-Kontext wurde nicht korrekt verwendet",
"theme-context-not-correct-used": "Der Theme-Kontext wurde nicht korrekt verwendet",
"package-meta-is-required-at-detail-context": "packageMeta wird bei DetailContext benötigt"
},
"lng": {
"english": "Englisch",
"japanese": "Japanisch",
"portuguese": "Portugiesisch",
"spanish": "Spanisch",
"german": "Deutsch",
"chinese": "Chinesisch",
"french": "Französisch",
"ukraine": "Ukrainisch"
},
"help-to-translate": "Hilfe beim Übersetzen",
"change-language": "Sprache ändern"
}

View File

@ -0,0 +1,151 @@
{
"copy-to-clipboard": "Copy to clipboard",
"author-anonymous": "Anonymous",
"action-bar-action": {
"visit-home-page": "Visit homepage",
"open-an-issue": "Open an issue",
"download-tarball": "Download tarball"
},
"dialog": {
"registry-info": {
"title": "Registry Info"
}
},
"header": {
"documentation": "Documentation",
"registry-info": "Registry Information",
"greetings": "Hi "
},
"search": {
"packages": "Search Packages"
},
"auto-complete": {
"loading": "Loading...",
"no-results-found": "No results found"
},
"tab": {
"uplinks": "Uplinks",
"versions": "Versions",
"dependencies": "Dependencies",
"readme": "Readme"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} has no uplinks."
},
"versions": {
"current-tags": "Current Tags",
"version-history": "Version history",
"not-available": "Not available"
},
"package": {
"published-on": "Published on {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Visit homepage",
"homepage": "Homepage",
"open-an-issue": "Open an issue",
"bugs": "Bugs",
"download": "Download {{what}}",
"the-tar-file": "the tar file",
"tarball": "Tarball"
},
"dependencies": {
"has-no-dependencies": "{{package}} has no dependencies.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Username",
"password": "Password"
},
"form-placeholder": {
"username": "Your username",
"password": "Your strong password"
},
"form-validation": {
"required-field": "This field is required",
"required-min-length": "This field required the min length of {{length}}",
"unable-to-sign-in": "Unable to sign in",
"username-or-password-cant-be-empty": "Username or password can't be empty!"
},
"help": {
"title": "No Package Published Yet.",
"sub-title": "To publish your first package just:",
"first-step": "1. Login",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publish",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Refresh this page."
},
"sidebar": {
"detail": {
"latest-version": "Latest v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Installation",
"install-using-yarn": "Install using yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"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}}"
},
"repository": {
"title": "Repository"
},
"author": {
"title": "Author"
},
"distribution": {
"title": "Latest Distribution",
"license": "License",
"size": "Size",
"file-count": "file count"
},
"maintainers": {
"title": "Maintainers"
},
"contributors": {
"title": "Contributors"
},
"engines": {
"npm-version": "NPM Version",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Powered by",
"made-with-love-on": "Made with <0>♥</0> on"
},
"button": {
"close": "Close",
"cancel": "Cancel",
"login": "Login",
"logout": "Logout",
"go-to-the-home-page": "Go to the home page",
"learn-more": "Learn More",
"fund-this-package": "<0>Fund</0> this package"
},
"error": {
"unspecific": "Something went wrong.",
"404": {
"page-not-found": "404 - Page not found",
"sorry-we-could-not-find-it": "Sorry, we couldn't find it..."
},
"app-context-not-correct-used": "The app context was not used correctly",
"theme-context-not-correct-used": "The theme context was not used correctly",
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
},
"lng": {
"english": "English",
"japanese": "Japanese",
"portuguese": "Portuguese",
"spanish": "Spanish",
"german": "German",
"chinese": "Chinese",
"french": "French",
"ukraine": "Ukraine"
},
"help-to-translate": "Help to translate",
"change-language": "Change language"
}

View File

@ -0,0 +1,151 @@
{
"copy-to-clipboard": "Copiar al portapapeles",
"author-anonymous": "Anónimo",
"action-bar-action": {
"visit-home-page": "Visitar página principal",
"open-an-issue": "Reportar un error",
"download-tarball": "Descargar libreria"
},
"dialog": {
"registry-info": {
"title": "Información del Registro"
}
},
"header": {
"documentation": "Documentación",
"registry-info": "Información del Registro",
"greetings": "Hola "
},
"search": {
"packages": "Buscar paquetes"
},
"auto-complete": {
"loading": "Cargando...",
"no-results-found": "Sin resultados encontrados"
},
"tab": {
"uplinks": "Remoto",
"versions": "Versiones",
"dependencies": "Dependencias",
"readme": "Léeme"
},
"uplinks": {
"title": "Remoto",
"no-items": "{{name}} not tiene remotos."
},
"versions": {
"current-tags": "Etiquetas actuales",
"version-history": "Historial de versiones",
"not-available": "No disponible"
},
"package": {
"published-on": "Publicado en {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Ir a la página principal",
"homepage": "Página pricinpal",
"open-an-issue": "Reportar un problema",
"bugs": "Errores",
"download": "Descargar {{what}}",
"the-tar-file": "el archivo tar",
"tarball": "Libreria"
},
"dependencies": {
"has-no-dependencies": "{{package}} no tiene dependencias.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Usuario",
"password": "Contraseña"
},
"form-placeholder": {
"username": "Tu usuario",
"password": "Tu fuerte conntraseña"
},
"form-validation": {
"required-field": "Este campo es requerido",
"required-min-length": "Este campo es requerido y la mínima longitud es {{length}}",
"unable-to-sign-in": "No se ha podido iniciar sesión",
"username-or-password-cant-be-empty": "Nombre de usuario o contraseña no puede estar vacio!"
},
"help": {
"title": "Ningún paquete ha sido publicado aun.",
"sub-title": "Para publicar tu primer paquete:",
"first-step": "1. Inicia sesión",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publica",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Refresca la página."
},
"sidebar": {
"detail": {
"latest-version": "Última v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Instalación",
"install-using-yarn": "Instala usando yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Instala usando npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Instala usando pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Repositorio"
},
"author": {
"title": "Autor"
},
"distribution": {
"title": "Última distribución",
"license": "Licencia",
"size": "Tamaño",
"file-count": "archivo cuenta"
},
"maintainers": {
"title": "Mantenedores"
},
"contributors": {
"title": "Colaboradores"
},
"engines": {
"npm-version": "Version NPM",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Hecho con",
"made-with-love-on": "Hecho con <0>♥</0> on"
},
"button": {
"close": "Cerrar",
"cancel": "Cancelar",
"login": "Iniciar sesión",
"logout": "Cerrar sesión",
"go-to-the-home-page": "Ir a la página principal",
"learn-more": "Aprender más",
"fund-this-package": "<0>Donar</0> a este paquete"
},
"error": {
"unspecific": "Algo ha salido mal.",
"404": {
"page-not-found": "404 - Paquete no encontrado",
"sorry-we-could-not-find-it": "Lo siento, no hemos podido encontrarlo..."
},
"app-context-not-correct-used": "El contexto de la aplicación no fue correctamente usado",
"theme-context-not-correct-used": "El contexto del tema no fue correctamente usado",
"package-meta-is-required-at-detail-context": "packageMeta es requerido en DetailContext"
},
"lng": {
"english": "Inglés",
"japanese": "Japonés",
"portuguese": "Portugués",
"spanish": "Español",
"german": "Alemán",
"chinese": "Chino",
"french": "Francés",
"ukraine": "Ucraniano"
},
"help-to-translate": "Ayuda a traducir",
"change-language": "Cambiar idioma"
}

View File

@ -0,0 +1,151 @@
{
"copy-to-clipboard": "Copier dans le presse-papier",
"author-anonymous": "Anonyme",
"action-bar-action": {
"visit-home-page": "Visiter la page d'accueil",
"open-an-issue": "Ouvrir un ticket",
"download-tarball": "Télécharger l'archive"
},
"dialog": {
"registry-info": {
"title": "Informations du Registry"
}
},
"header": {
"documentation": "Documentation",
"registry-info": "Informations du Registry",
"greetings": "Bonjour "
},
"search": {
"packages": "Rechercher des paquets"
},
"auto-complete": {
"loading": "En cours de chargement...",
"no-results-found": "Aucun resultat trouvé"
},
"tab": {
"uplinks": "Uplinks",
"versions": "Versions",
"dependencies": "Dépendances",
"readme": "Readme"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} n'a pas de uplink."
},
"versions": {
"current-tags": "Tags courants",
"version-history": "Historique de version",
"not-available": "Non disponible"
},
"package": {
"published-on": "Publié le {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Visiter la page d'accueil",
"homepage": "Page d'accueil",
"open-an-issue": "Ouvrir un ticket",
"bugs": "Bugs",
"download": "Télécharger {{what}}",
"the-tar-file": "le fichier tar",
"tarball": "Archive"
},
"dependencies": {
"has-no-dependencies": "{{package}} n'a aucune dépendance.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Nom d'utilisateur",
"password": "Mot de passe"
},
"form-placeholder": {
"username": "Votre nom d'utilisateur",
"password": "Votre mot de passe"
},
"form-validation": {
"required-field": "Ce champ est obligatoire",
"required-min-length": "Ce champ doit faire au moins {{length}} caractères",
"unable-to-sign-in": "Connexion impossible",
"username-or-password-cant-be-empty": "Le nom d'utilisateur ou mot de passe ne peut pas être vide!"
},
"help": {
"title": "Aucun paquet publié pour l'instant.",
"sub-title": "Pour publier votre premier paquet:",
"first-step": "1. Se connecter",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publier",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Recharger cette page."
},
"sidebar": {
"detail": {
"latest-version": "Dernière v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Installation",
"install-using-yarn": "Installer avec yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Installer avec npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Installer avec pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Dépôt"
},
"author": {
"title": "Auteur"
},
"distribution": {
"title": "Dernière distribution",
"license": "Licence",
"size": "Taille",
"file-count": "nombre de fichiers"
},
"maintainers": {
"title": "Mainteneurs"
},
"contributors": {
"title": "Contributeurs"
},
"engines": {
"npm-version": "Version NPM",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Propulsé par",
"made-with-love-on": "Fait avec <0>♥</0> sur"
},
"button": {
"close": "Fermer",
"cancel": "Annuler",
"login": "Se connecter",
"logout": "Se déconnecter",
"go-to-the-home-page": "Aller à la page d'accueil",
"learn-more": "En savoir plus",
"fund-this-package": "<0>Financer</0> ce paquet"
},
"error": {
"unspecific": "Quelque chose a mal tourné.",
"404": {
"page-not-found": "404 - Page non trouvée",
"sorry-we-could-not-find-it": "Desolé, nous n'avons rien retrouvé..."
},
"app-context-not-correct-used": "Le contexte de l'application n'a pas été utilisé correctement",
"theme-context-not-correct-used": "Le contexte du thème n'a pas été utilisé correctement",
"package-meta-is-required-at-detail-context": "packageMeta est obligatoire à DetailContext"
},
"lng": {
"english": "Anglais",
"japanese": "Japonais",
"portuguese": "Portugais",
"spanish": "Espagnol",
"german": "Allemand",
"chinese": "Chinois",
"french": "Français",
"ukraine": "Ukrainien"
},
"help-to-translate": "Aide à la traduction",
"change-language": "Changer la langue"
}

View File

@ -0,0 +1,151 @@
{
"copy-to-clipboard": "クリップボードにコピー",
"author-anonymous": "匿名",
"action-bar-action": {
"visit-home-page": "ホームページへ移動",
"open-an-issue": "課題を開く",
"download-tarball": "tar形式でダウンロード"
},
"dialog": {
"registry-info": {
"title": "レジストリの設定方法"
}
},
"header": {
"documentation": "ドキュメント",
"registry-info": "レジストリ情報",
"greetings": "こんにちは、"
},
"search": {
"packages": "パッケージを検索"
},
"auto-complete": {
"loading": "ロード中...",
"no-results-found": "パッケージが見つかりませんでした"
},
"tab": {
"uplinks": "アップリンク",
"versions": "バージョン情報",
"dependencies": "依存パッケージ",
"readme": "Readme"
},
"uplinks": {
"title": "アップリンク",
"no-items": "{{name}}にアップリンクはありません"
},
"versions": {
"current-tags": "タグ一覧",
"version-history": "バージョン履歴",
"not-available": "利用できません"
},
"package": {
"published-on": "{{time}}に更新されました •",
"version": "v{{version}}",
"visit-home-page": "ホームページへ移動",
"homepage": "ホームページ",
"open-an-issue": "課題を開く",
"bugs": "バグ",
"download": "{{what}}ダウンロード",
"the-tar-file": "tar形式のファイル",
"tarball": "tar形式でダウンロード"
},
"dependencies": {
"has-no-dependencies": "{{package}}に依存パッケージはありません",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "ユーザ名",
"password": "パスワード"
},
"form-placeholder": {
"username": "あなたのユーザ名",
"password": "あなたのパスワード"
},
"form-validation": {
"required-field": "この項目は必ず入力して下さい",
"required-min-length": "この項目は{{length}}文字以上入力して下さい",
"unable-to-sign-in": "サインインできません",
"username-or-password-cant-be-empty": "ユーザ名とパスワードは空にできません"
},
"help": {
"title": "まだパッケージが登録されていません",
"sub-title": "以下の手順で最初のパッケージを登録しましょう",
"first-step": "1. ログイン",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. 登録",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. このページを再読み込みして下さい"
},
"sidebar": {
"detail": {
"latest-version": "最新バージョンは{{version}}です",
"version": "v{{version}}"
},
"installation": {
"title": "インストール方法",
"install-using-yarn": "yarnでインストール",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "npmでインストール",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "pnpmでインストール",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "リポジトリ"
},
"author": {
"title": "作者"
},
"distribution": {
"title": "最新の配信内容",
"license": "ライセンス",
"size": "サイズ",
"file-count": "ファイル数"
},
"maintainers": {
"title": "パッケージメンテナ"
},
"contributors": {
"title": "コントリビューター"
},
"engines": {
"npm-version": "NPM Version",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Powered by",
"made-with-love-on": "Made with <0>♥</0> on"
},
"button": {
"close": "閉じる",
"cancel": "キャンセル",
"login": "ログイン",
"logout": "ログアウト",
"go-to-the-home-page": "トップページに戻る",
"learn-more": "もっと知る",
"fund-this-package": "このパッケージに<0>投資</0>"
},
"error": {
"unspecific": "何か問題が発生したようです。",
"404": {
"page-not-found": "404 - Page not found",
"sorry-we-could-not-find-it": "残念ながら、ご指定のページはありませんでした…。"
},
"app-context-not-correct-used": "AppContextが正しく使用されませんでした",
"theme-context-not-correct-used": "ThemeContextが正しく使用されませんでした",
"package-meta-is-required-at-detail-context": "DetailContextではpackageMetaが必要です"
},
"lng": {
"english": "英語",
"japanese": "日本語",
"portuguese": "ポルトガル語",
"spanish": "スペイン語",
"german": "ドイツ語",
"chinese": "中国語",
"french": "フランス語",
"ukraine": "ウクライナ"
},
"help-to-translate": "翻訳を助ける",
"change-language": "言語を変更"
}

View File

@ -0,0 +1,151 @@
{
"copy-to-clipboard": "Copiar para área de transferência",
"author-anonymous": "Anônimo(a)",
"action-bar-action": {
"visit-home-page": "Visitar a página inicial",
"open-an-issue": "Criar um incidente",
"download-tarball": "Baixar Tarball"
},
"dialog": {
"registry-info": {
"title": "Informações do Registro"
}
},
"header": {
"documentation": "Documentação",
"registry-info": "Informações do Registro",
"greetings": "Oi "
},
"search": {
"packages": "Pesquisar Pacotes"
},
"auto-complete": {
"loading": "Carregando...",
"no-results-found": "Nenhum resultado encontrado"
},
"tab": {
"uplinks": "Uplinks",
"versions": "Versões",
"dependencies": "Dependências",
"readme": "Leia-me"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} não tem uplinks."
},
"versions": {
"current-tags": "Tags atuais",
"version-history": "Histórico de versões",
"not-available": "Não disponível"
},
"package": {
"published-on": "Publicado em {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Visitar a página inicial",
"homepage": "Página inicial",
"open-an-issue": "Criar um incidente",
"bugs": "Erros",
"download": "Baixar {{what}}",
"the-tar-file": "o arquivo tar",
"tarball": "Tarball"
},
"dependencies": {
"has-no-dependencies": "{{package}} não tem dependências.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Nome do usuário",
"password": "Senha"
},
"form-placeholder": {
"username": "O seu nome",
"password": "A sua senha forte"
},
"form-validation": {
"required-field": "Este campo é obrigatório",
"required-min-length": "Este campo requer o mínimo de {{length}} caracteres",
"unable-to-sign-in": "Não foi possível fazer login",
"username-or-password-cant-be-empty": "Nome de usuário ou senha não podem estar vazios!"
},
"help": {
"title": "Nenhum pacote publicado ainda.",
"sub-title": "Para publicar seu primeiro pacote apenas:",
"first-step": "1. Faça login",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publique",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Atualize esta página."
},
"sidebar": {
"detail": {
"latest-version": "Última versão: v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Instalação",
"install-using-yarn": "Instale usando yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Instale usando npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Instale usando pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Repositório"
},
"author": {
"title": "Autor(a)"
},
"distribution": {
"title": "Distribuição mais recente",
"license": "Licença",
"size": "Tamanho",
"file-count": "Contagem de arquivos"
},
"maintainers": {
"title": "Mantenedores(as)"
},
"contributors": {
"title": "Contribuidores(as)"
},
"engines": {
"npm-version": "Versão NPM",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Feito por",
"made-with-love-on": "Feito com amor <0>♥</0> no(a)"
},
"button": {
"close": "Fechar",
"cancel": "Cancelar",
"login": "Conectar",
"logout": "Desconectar",
"go-to-the-home-page": "Ir para a página inicial",
"learn-more": "Leia mais",
"fund-this-package": "<0>Financie</0> este pacote"
},
"error": {
"unspecific": "Algo deu errado.",
"404": {
"page-not-found": "404 - Página não encontrada",
"sorry-we-could-not-find-it": "Desculpe, não conseguimos encontrar..."
},
"app-context-not-correct-used": "O contexto do aplicativo não foi usado corretamente",
"theme-context-not-correct-used": "O contexto do tema não foi usado corretamente",
"package-meta-is-required-at-detail-context": "packageMeta é requerido em DetailContext"
},
"lng": {
"english": "Inglês",
"japanese": "Japonês",
"portuguese": "Português",
"spanish": "Espanhol",
"german": "Alemão",
"chinese": "Chinês",
"french": "Francês",
"ukraine": "Ucraniano"
},
"help-to-translate": "Ajude a traduzir",
"change-language": "Mudar idioma"
}

View File

@ -0,0 +1,151 @@
{
"copy-to-clipboard": "Копіювати в буфер обміну",
"author-anonymous": "Анонімний",
"action-bar-action": {
"visit-home-page": "Відвідати домашню сторінку",
"open-an-issue": "Відкрити питання, проблему, ...",
"download-tarball": "Завантажити архів"
},
"dialog": {
"registry-info": {
"title": "Інформація про реєстр"
}
},
"header": {
"documentation": "Документація",
"registry-info": "Інформація про реєстр",
"greetings": "Привіт "
},
"search": {
"packages": "Пошук пакетів"
},
"auto-complete": {
"loading": "Завантаження...",
"no-results-found": "Нічого не знайдено"
},
"tab": {
"uplinks": "Публікації",
"versions": "Версії",
"dependencies": "Залежності",
"readme": "Опис"
},
"uplinks": {
"title": "Публікації",
"no-items": "{{name}} не опубліковано."
},
"versions": {
"current-tags": "Поточні теги",
"version-history": "Історія версій",
"not-available": "Недоступний"
},
"package": {
"published-on": "Опубліковано {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Відвідати домашню сторінку",
"homepage": "Домашня сторінка",
"open-an-issue": "Відкрити питання, проблему, ...",
"bugs": "Помилки",
"download": "Завантажити {{what}}",
"the-tar-file": "файл tar",
"tarball": "Завантажити"
},
"dependencies": {
"has-no-dependencies": "{{package}} не має залежностей.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Ім'я користувача",
"password": "Пароль"
},
"form-placeholder": {
"username": "Ваше ім'я користувача",
"password": "Ваш надійний пароль"
},
"form-validation": {
"required-field": "Це поле є обов'язковим",
"required-min-length": "Для цього поля потрібна мінімальна довжина {{length}}",
"unable-to-sign-in": "Неможливо ввійти",
"username-or-password-cant-be-empty": "Ім'я користувача або пароль не можуть бути порожніми !"
},
"help": {
"title": "Пакети ще не опубліковані",
"sub-title": "Опублікувати свій перший пакет просто:",
"first-step": "1. Підключіться",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Опублікуйте",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Оновіть цю сторінку."
},
"sidebar": {
"detail": {
"latest-version": "Остання версія v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Встановлення",
"install-using-yarn": "Встановлення за допомогою yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Встановлення за допомогою npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Встановлення за допомогою pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Сховище"
},
"author": {
"title": "Автор"
},
"distribution": {
"title": "Умови поширення",
"license": "Ліцензія",
"size": "Розмір",
"file-count": "кількість файлів"
},
"maintainers": {
"title": "Технічне супроводження"
},
"contributors": {
"title": "Автори"
},
"engines": {
"npm-version": "NPM версія",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Працює на",
"made-with-love-on": "Зроблено з <0> ♥ </0> на"
},
"button": {
"close": "Закрити",
"cancel": "Скасувати",
"login": "Вхід",
"logout": "Вийти",
"go-to-the-home-page": "Перейдіть на головну сторінку",
"learn-more": "Вивчайте більше",
"fund-this-package": "<0>Фінансувати</0> цей пакет"
},
"error": {
"unspecific": "Щось пішло не так.",
"404": {
"page-not-found": "404 - Сторінку не знайдено",
"sorry-we-could-not-find-it": "На жаль, ми не змогли його знайти ..."
},
"app-context-not-correct-used": "Контекст програми не використовувався правильно",
"theme-context-not-correct-used": "Контекст теми використано неправильно",
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
},
"lng": {
"english": "Англійська",
"japanese": "Японська",
"portuguese": "Португальська",
"spanish": "Іспанська",
"german": "Німецька",
"chinese": "Китайська",
"french": "Французька",
"ukraine": "Українська"
},
"help-to-translate": "Допоможіть перекласти",
"change-language": "Змінити мову"
}

View File

@ -0,0 +1,150 @@
{
"copy-to-clipboard": "复制到粘贴板",
"author-anonymous": "匿名",
"action-bar-action": {
"visit-home-page": "访问主页",
"open-an-issue": "提交问题",
"download-tarball": "下载 tarball"
},
"dialog": {
"registry-info": {
"title": "注册信息"
}
},
"header": {
"documentation": "文档",
"registry-info": "登记信息",
"greetings": "您好 "
},
"search": {
"packages": "查找Packages"
},
"auto-complete": {
"loading": "加载中...",
"no-results-found": "没有找到结果"
},
"tab": {
"uplinks": "Uplinks",
"versions": "版本",
"dependencies": "依赖",
"readme": "Readme"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} has no uplinks."
},
"versions": {
"current-tags": "当前标签",
"version-history": "历史版本",
"not-available": "不可用"
},
"package": {
"published-on": "发表于 {{time}} •",
"version": "v{{version}}",
"visit-home-page": "访问主页",
"homepage": "主页",
"open-an-issue": "提交问题",
"bugs": "Bugs",
"download": "下载 {{what}}",
"the-tar-file": "tar 文件",
"tarball": "Tarball"
},
"dependencies": {
"has-no-dependencies": "{{package}} 没有依赖包.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "用户名",
"password": "密码"
},
"form-placeholder": {
"username": "您的用户名",
"password": "您的密码"
},
"form-validation": {
"required-field": "必填项",
"required-min-length": "长度不能小于{{length}}",
"unable-to-sign-in": "登录失败",
"username-or-password-cant-be-empty": "用户名或密码不能为空!"
},
"help": {
"title": "还没有发布任何NPM包.",
"sub-title": "发布第一个NPM包:",
"first-step": "1. 登录",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. 发布",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. 刷新当前页面."
},
"sidebar": {
"detail": {
"latest-version": "最新版本 v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "安装",
"install-using-yarn": "使用yarn安装",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "使用npm安装",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "使用pnpm安装",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "库"
},
"author": {
"title": "作者"
},
"distribution": {
"title": "最后发布",
"license": "License",
"size": "大小",
"file-count": "文件数量"
},
"maintainers": {
"title": "维护人"
},
"contributors": {
"title": "贡献者"
},
"engines": {
"npm-version": "NPM版本",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Powered by",
"made-with-love-on": "Made with <0>♥</0> on"
},
"button": {
"close": "关闭",
"cancel": "取消",
"login": "登录",
"logout": "注销",
"go-to-the-home-page": "跳转到主页",
"learn-more": "了解更多",
"fund-this-package": "<0>Fund</0> this package"
},
"error": {
"unspecific": "发生错误.",
"404": {
"page-not-found": "404 - 页面不存在",
"sorry-we-could-not-find-it": "对不起, 我们没找到..."
},
"app-context-not-correct-used": "The app context was not correct used",
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
},
"lng": {
"english": "英语",
"japanese": "日语",
"portuguese": "葡萄牙语",
"spanish": "西班牙语",
"german": "德语",
"chinese": "中文",
"french": "法语",
"ukraine": "烏克蘭"
},
"help-to-translate": "幫助翻译",
"change-language": "改变语言"
}

View File

@ -1,6 +1,6 @@
{
"name": "@verdaccio/ui-theme",
"version": "0.3.13",
"version": "1.7.1",
"description": "Verdaccio User Interface",
"author": {
"name": "Verdaccio Core Team",
@ -15,7 +15,7 @@
"devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "7.8.0",
"@babel/plugin-proposal-optional-chaining": "7.8.0",
"@commitlint/cli": "8.3.4",
"@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.3.4",
"@emotion/core": "10.0.22",
"@emotion/styled": "10.0.23",
@ -28,7 +28,7 @@
"@testing-library/react": "9.4.0",
"@types/autosuggest-highlight": "3.1.0",
"@types/enzyme": "3.10.4",
"@types/jest": "24.0.24",
"@types/jest": "25.1.4",
"@types/js-base64": "2.3.1",
"@types/lodash": "4.14.149",
"@types/node": "13.1.6",
@ -73,8 +73,9 @@
"github-markdown-css": "3.0.1",
"html-webpack-plugin": "3.2.0",
"husky": "3.1.0",
"i18next": "19.1.0",
"identity-obj-proxy": "3.0.0",
"in-publish": "2.0.0",
"in-publish": "2.0.1",
"jest": "24.9.0",
"jest-emotion": "10.0.27",
"jest-environment-jsdom": "24.9.0",
@ -98,9 +99,10 @@
"puppeteer": "2.0.0",
"react": "16.12.0",
"react-autosuggest": "9.4.3",
"react-dom": "16.12.0",
"react-dom": "16.13.0",
"react-hook-form": "3.29.4",
"react-hot-loader": "4.12.18",
"react-i18next": "11.3.1",
"react-router-dom": "5.1.2",
"request": "2.88.0",
"resolve-url-loader": "3.1.1",
@ -140,11 +142,11 @@
"bundlesize": [
{
"path": "./static/vendors.*.js",
"maxSize": "185 kB"
"maxSize": "210 kB"
},
{
"path": "./static/main.*.js",
"maxSize": "30 kB"
"maxSize": "35 kB"
},
{
"path": "./static/[0-9].*.{js,css}",

View File

@ -4976,8 +4976,12 @@
"_attachments": {
"jquery-1.5.1.tgz": {
"shasum": "2ae2d661e906c1a01e044a71bb5b2743942183e5"
},
"jquery-3.3.1.tgz": {
"shasum": "958ce29e81c9790f31be7792df5d4d95fc57fbca"
}
},
"_rev": "60-fed4915c27b9c1e6",
"readme": "# jQuery\n\n> jQuery is a fast, small, and feature-rich JavaScript library.\n\nFor information on how to get started and how to use jQuery, please see [jQuery's documentation](http://api.jquery.com/).\nFor source files and issues, please visit the [jQuery repo](https://github.com/jquery/jquery).\n\nIf upgrading, please see the [blog post for 3.3.1](https://blog.jquery.com/2017/03/20/jquery-3.3.1-now-available/). This includes notable differences from the previous version and a more readable changelog.\n\n## Including jQuery\n\nBelow are some of the most common ways to include jQuery.\n\n### Browser\n\n#### Script tag\n\n```html\n<script src=\"https://code.jquery.com/jquery-3.3.1.min.js\"></script>\n```\n\n#### Babel\n\n[Babel](http://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively.\n\n```js\nimport $ from \"jquery\";\n```\n\n#### Browserify/Webpack\n\nThere are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documention. In the script, including jQuery will usually look like this...\n\n```js\nvar $ = require(\"jquery\");\n```\n\n#### AMD (Asynchronous Module Definition)\n\nAMD is a module format built for the browser. For more information, we recommend [require.js' documentation](http://requirejs.org/docs/whyamd.html).\n\n```js\ndefine([\"jquery\"], function($) {\n\n});\n```\n\n### Node\n\nTo include jQuery in [Node](nodejs.org), first install with npm.\n\n```sh\nnpm install jquery\n```\n\nFor jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/tmpvar/jsdom). This can be useful for testing purposes.\n\n```js\nrequire(\"jsdom\").env(\"\", function(err, window) {\n\tif (err) {\n\t\tconsole.error(err);\n\t\treturn;\n\t}\n\n\tvar $ = require(\"jquery\")(window);\n});\n```"
"_rev": "61-e6be890a78963127",
"readme": "# jQuery\n\n> jQuery is a fast, small, and feature-rich JavaScript library.\n\nFor information on how to get started and how to use jQuery, please see [jQuery's documentation](http://api.jquery.com/).\nFor source files and issues, please visit the [jQuery repo](https://github.com/jquery/jquery).\n\nIf upgrading, please see the [blog post for 3.3.1](https://blog.jquery.com/2017/03/20/jquery-3.3.1-now-available/). This includes notable differences from the previous version and a more readable changelog.\n\n## Including jQuery\n\nBelow are some of the most common ways to include jQuery.\n\n### Browser\n\n#### Script tag\n\n```html\n<script src=\"https://code.jquery.com/jquery-3.3.1.min.js\"></script>\n```\n\n#### Babel\n\n[Babel](http://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively.\n\n```js\nimport $ from \"jquery\";\n```\n\n#### Browserify/Webpack\n\nThere are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documention. In the script, including jQuery will usually look like this...\n\n```js\nvar $ = require(\"jquery\");\n```\n\n#### AMD (Asynchronous Module Definition)\n\nAMD is a module format built for the browser. For more information, we recommend [require.js' documentation](http://requirejs.org/docs/whyamd.html).\n\n```js\ndefine([\"jquery\"], function($) {\n\n});\n```\n\n### Node\n\nTo include jQuery in [Node](nodejs.org), first install with npm.\n\n```sh\nnpm install jquery\n```\n\nFor jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/tmpvar/jsdom). This can be useful for testing purposes.\n\n```js\nrequire(\"jsdom\").env(\"\", function(err, window) {\n\tif (err) {\n\t\tconsole.error(err);\n\t\treturn;\n\t}\n\n\tvar $ = require(\"jquery\")(window);\n});\n```",
"_id": "jquery"
}

View File

@ -17,7 +17,7 @@ getStdin()
repo: repoName,
tag_name: tag,
body: changelog,
draft: true,
draft: false,
})
)
.catch(err => {

View File

@ -1,21 +1,25 @@
import React, { useState, useEffect } from 'react';
/* eslint-disable react/jsx-max-depth */
import React, { useState, useEffect, Suspense } from 'react';
import styled from '@emotion/styled';
import isNil from 'lodash/isNil';
import { Router } from 'react-router-dom';
import '../../i18n/config';
import storage from '../utils/storage';
import { isTokenExpire } from '../utils/login';
import Header from '../components/Header';
import Footer from '../components/Footer';
import Loading from '../components/Loading';
import Box from '../muiComponents/Box';
import StyleBaseline from '../design-tokens/StyleBaseline';
import { Theme } from '../design-tokens/theme';
import loadDayJSLocale from '../design-tokens/load-dayjs-locale';
import AppContextProvider from './AppContextProvider';
import AppRoute, { history } from './AppRoute';
const StyledBox = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: theme && theme.palette.white,
backgroundColor: theme?.palette.background.default,
}));
const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
@ -31,7 +35,6 @@ const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
/* eslint-disable react-hooks/exhaustive-deps */
const App: React.FC = () => {
const [user, setUser] = useState();
/**
* Logout user
* Required by: <Header />
@ -57,10 +60,11 @@ const App: React.FC = () => {
useEffect(() => {
checkUserAlreadyLoggedIn();
loadDayJSLocale();
}, []);
return (
<>
<Suspense fallback={<Loading />}>
<StyleBaseline />
<StyledBox display="flex" flexDirection="column" height="100%">
<>
@ -68,7 +72,6 @@ const App: React.FC = () => {
<AppContextProvider user={user}>
<Header />
<StyledBoxContent flexGrow={1}>
{/* eslint-disable-next-line react/jsx-max-depth */}
<AppRoute />
</StyledBoxContent>
</AppContextProvider>
@ -76,7 +79,7 @@ const App: React.FC = () => {
<Footer />
</>
</StyledBox>
</>
</Suspense>
);
};

View File

@ -1,8 +1,7 @@
import React, { lazy, useContext, Suspense } from 'react';
import React, { lazy, useContext } from 'react';
import { Route as ReactRouterDomRoute, Switch, Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import Loading from '../components/Loading';
import { useTranslation } from 'react-i18next';
import AppContext from './AppContext';
@ -25,9 +24,10 @@ export const history = createBrowserHistory({
const AppRoute: React.FC = () => {
const appContext = useContext(AppContext);
const { t } = useTranslation();
if (!appContext) {
throw Error('The app Context was not correct used');
throw Error(t('app-context-not-correct-used'));
}
const { user } = appContext;
@ -36,36 +36,34 @@ const AppRoute: React.FC = () => {
return (
<Router history={history}>
<Suspense fallback={<Loading />}>
<Switch>
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
<HomePage isUserLoggedIn={!!isUserLoggedIn} />
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute>
<NotFound />
</ReactRouterDomRoute>
</Switch>
</Suspense>
<Switch>
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
<HomePage isUserLoggedIn={!!isUserLoggedIn} />
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute>
<NotFound />
</ReactRouterDomRoute>
</Switch>
</Router>
);
};

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import BugReportIcon from '@material-ui/icons/BugReport';
import DownloadIcon from '@material-ui/icons/CloudDownload';
import HomeIcon from '@material-ui/icons/Home';
import { useTranslation } from 'react-i18next';
import Tooltip from '../../muiComponents/Tooltip';
import Link from '../Link';
@ -11,10 +12,14 @@ import { Theme } from '../../design-tokens/theme';
import downloadTarball from './download-tarball';
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
backgroundColor: props.theme && props.theme.palette.primary.main,
color: props.theme && props.theme.palette.white,
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
color: theme?.palette.white,
marginRight: 10,
':hover': {
color: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
background: theme?.palette.white,
},
}));
type ActionType = 'VISIT_HOMEPAGE' | 'OPEN_AN_ISSUE' | 'DOWNLOAD_TARBALL';
@ -26,10 +31,11 @@ export interface ActionBarActionProps {
/* eslint-disable react/jsx-no-bind */
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
const { t } = useTranslation();
switch (type) {
case 'VISIT_HOMEPAGE':
return (
<Tooltip title="Visit homepage">
<Tooltip title={t('action-bar-action.visit-home-page')}>
<Link external={true} to={link}>
<Fab size="small">
<HomeIcon />
@ -39,7 +45,7 @@ const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
);
case 'OPEN_AN_ISSUE':
return (
<Tooltip title="Open an issue">
<Tooltip title={t('action-bar-action.open-an-issue')}>
<Link external={true} to={link}>
<Fab size="small">
<BugReportIcon />
@ -49,7 +55,7 @@ const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
);
case 'DOWNLOAD_TARBALL':
return (
<Tooltip title="Download tarball">
<Tooltip title={t('action-bar-action.download-tarball')}>
<Fab data-testid="download-tarball-btn" onClick={downloadTarball(link)} size="small">
<DownloadIcon />
</Fab>

View File

@ -7,6 +7,11 @@ exports[`<ActionBar /> component should render the component in default state 1`
margin-right: 10px;
}
.emotion-0:hover {
color: #4b5e40;
background: #fff;
}
<div
class="MuiBox-root MuiBox-root-2"
>

View File

@ -1,14 +1,17 @@
import React, { FC, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import { isEmail } from '../../utils/url';
import Avatar from '../../muiComponents/Avatar';
import List from '../../muiComponents/List';
import { getAuthorName } from '../../utils/package';
import { StyledText, AuthorListItem, AuthorListItemText } from './styles';
const Author: FC = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
if (!packageMeta) {
return null;
@ -25,7 +28,7 @@ const Author: FC = () => {
const avatarComponent = <Avatar alt={author.name} src={author.avatar} />;
return (
<List subheader={<StyledText variant={'subtitle1'}>{'Author'}</StyledText>}>
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.author.title')}</StyledText>}>
<AuthorListItem button={true}>
{!email || !isEmail(email) ? (
avatarComponent
@ -34,8 +37,7 @@ const Author: FC = () => {
{avatarComponent}
</a>
)}
<AuthorListItemText primary={name} />
{name && <AuthorListItemText primary={getAuthorName(name)} />}
</AuthorListItem>
</List>
);

View File

@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Author /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText e1xuehjw0 MuiTypography-subtitle1\\">Author</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1k45khb-AuthorListItem e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><a href=\\"mailto:verdaccio.user@verdaccio.org?subject=verdaccio@4.0.0\\" target=\\"_top\\"><div class=\\"MuiAvatar-root MuiAvatar-circle\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div></a><div class=\\"MuiListItemText-root css-1cnlq5d-AuthorListItemText e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Author /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-5wp24z-StyledText e1xuehjw0 MuiTypography-subtitle1\\">sidebar.author.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1k45khb-AuthorListItem e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><a href=\\"mailto:verdaccio.user@verdaccio.org?subject=verdaccio@4.0.0\\" target=\\"_top\\"><div class=\\"MuiAvatar-root MuiAvatar-circle\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div></a><div class=\\"MuiListItemText-root css-1cnlq5d-AuthorListItemText e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Author /> component should render the component when there is no author email 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText e1xuehjw0 MuiTypography-subtitle1\\">Author</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1k45khb-AuthorListItem e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div><div class=\\"MuiListItemText-root css-1cnlq5d-AuthorListItemText e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Author /> component should render the component when there is no author email 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-5wp24z-StyledText e1xuehjw0 MuiTypography-subtitle1\\">sidebar.author.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1k45khb-AuthorListItem e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div><div class=\\"MuiListItemText-root css-1cnlq5d-AuthorListItemText e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;

View File

@ -7,7 +7,6 @@ import { Theme } from '../../design-tokens/theme';
export const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
textTransform: 'capitalize',
}));
export const AuthorListItem = styled(ListItem)({

View File

@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import Autosuggest, { SuggestionSelectedEventData, InputProps, ChangeEvent } from 'react-autosuggest';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';
import { useTranslation } from 'react-i18next';
import MenuItem from '../../muiComponents/MenuItem';
import { Theme } from '../../design-tokens/theme';
@ -83,12 +84,6 @@ const renderMessage = (message): JSX.Element => {
);
};
const SUGGESTIONS_RESPONSE = {
LOADING: 'Loading...',
FAILURE: 'Something went wrong.',
NO_RESULT: 'No results found.',
};
const AutoComplete = memo(
({
suggestions,
@ -106,6 +101,8 @@ const AutoComplete = memo(
suggestionsLoaded = false,
suggestionsError = false,
}: Props) => {
const { t } = useTranslation();
const autosuggestProps = {
renderInputComponent,
suggestions,
@ -130,9 +127,9 @@ const AutoComplete = memo(
function renderSuggestionsContainer({ containerProps, children, query }): JSX.Element {
return (
<SuggestionContainer {...containerProps} square={true}>
{suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)}
{suggestionsLoading && query && renderMessage(SUGGESTIONS_RESPONSE.LOADING)}
{suggestionsError && renderMessage(SUGGESTIONS_RESPONSE.FAILURE)}
{suggestionsLoaded && children === null && query && renderMessage(t('auto-complete.no-results-found'))}
{suggestionsLoading && query && renderMessage(t('auto-complete.loading'))}
{suggestionsError && renderMessage(t('error.unspecific'))}
{children}
</SuggestionContainer>
);

View File

@ -1,8 +1,8 @@
import FileCopy from '@material-ui/icons/FileCopy';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { copyToClipBoardUtility } from '../../utils/cli-utils';
import { TEXT } from '../../utils/constants';
import Tooltip from '../../muiComponents/Tooltip';
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
@ -20,19 +20,16 @@ const renderText = (text: string, children: React.ReactNode): JSX.Element => {
return <ClipBoardCopyText>{text}</ClipBoardCopyText>;
};
const renderToolTipFileCopy = (text: string): React.ReactElement<HTMLElement> => (
<Tooltip disableFocusListener={true} title={TEXT.CLIPBOARD_COPY}>
<CopyIcon onClick={copyToClipBoardUtility(text)}>
<FileCopy />
</CopyIcon>
</Tooltip>
);
const CopyToClipBoard: React.FC<Props> = ({ text, children }) => {
const { t } = useTranslation();
return (
<ClipBoardCopy>
{renderText(text, children)}
{renderToolTipFileCopy(text)}
<Tooltip disableFocusListener={true} title={t('copy-to-clipboard')}>
<CopyIcon onClick={copyToClipBoardUtility(text)}>
<FileCopy />
</CopyIcon>
</Tooltip>
</ClipBoardCopy>
);
};

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<CopyToClipBoard /> component should load the component in default state 1`] = `"<div class=\\"css-1in239f-ClipBoardCopy eb8w2fo0\\"><span class=\\"css-7gar9h-ClipBoardCopyText eb8w2fo1\\">copy text</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-1fs86cq-CopyIcon eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><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\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div>"`;
exports[`<CopyToClipBoard /> component should load the component in default state 1`] = `"<div class=\\"css-1in239f-ClipBoardCopy eb8w2fo0\\"><span class=\\"css-7gar9h-ClipBoardCopyText eb8w2fo1\\">copy text</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-1fs86cq-CopyIcon eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"copy-to-clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><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\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div>"`;

View File

@ -1,5 +1,6 @@
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import CardContent from '../../muiComponents/CardContent';
import { PackageDependencies } from '../../../types/packageMeta';
@ -16,6 +17,7 @@ interface DependencyBlockProps {
const DependencyBlock: React.FC<DependencyBlockProps> = ({ title, dependencies }) => {
const { enableLoading } = useContext(DetailContext);
const history = useHistory();
const { t } = useTranslation();
const deps = Object.entries(dependencies);
@ -31,8 +33,14 @@ const DependencyBlock: React.FC<DependencyBlockProps> = ({ title, dependencies }
<StyledText variant="subtitle1">{`${title} (${deps.length})`}</StyledText>
<Tags>
{deps.map(([name, version]) => (
// eslint-disable-next-line
<Tag className={'dep-tag'} clickable={true} key={name} label={`${name}@${version}`} onClick={() => handleClick(name)} />
<Tag
className={'dep-tag'}
clickable={true}
key={name}
label={t('dependencies.dependency-block', { package: name, version })}
// eslint-disable-next-line
onClick={() => handleClick(name)}
/>
))}
</Tags>
</CardContent>
@ -46,9 +54,10 @@ function hasKeys(object?: { [key: string]: any }): boolean {
const Dependencies: React.FC<{}> = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
if (!packageMeta) {
throw new Error('packageMeta is required at DetailContext');
throw new Error(t('error.package-meta-is-required-at-detail-context'));
}
const { latest } = packageMeta;
@ -72,7 +81,7 @@ const Dependencies: React.FC<{}> = () => {
);
}
return <NoItems className="no-dependencies" text={`${name} has no dependencies.`} />;
return <NoItems className="no-dependencies" text={t('dependencies.has-no-dependencies', { package: name })} />;
};
export default Dependencies;

View File

@ -1,4 +1,4 @@
import React, { useCallback, useState, ChangeEvent, useContext } from 'react';
import React, { useState, useContext } from 'react';
import { DetailContext } from '../../pages/Version';
import Box from '../../muiComponents/Box';
@ -8,24 +8,19 @@ import DetailContainerContent from './DetailContainerContent';
import { TabPosition } from './tabs';
const DetailContainer: React.FC = () => {
const [tabPosition, setTabPosition] = useState(TabPosition.README);
const tabs = Object.values(TabPosition);
const [tabPosition, setTabPosition] = useState(0);
const detailContext = useContext(DetailContext);
const { readMe } = detailContext;
const handleChangeTabPosition = useCallback(
(event: ChangeEvent<{}>) => {
event.preventDefault();
const eventTarget = event.target as HTMLSpanElement;
const chosentab = eventTarget.innerText as TabPosition;
setTabPosition(TabPosition[chosentab]);
},
[setTabPosition]
);
const handleChange = (event, newValue) => {
setTabPosition(newValue);
};
return (
<Box component="div" display="flex" flexDirection="column" padding={2}>
<DetailContainerTabs onChangeTabPosition={handleChangeTabPosition} tabPosition={tabPosition} />
<DetailContainerContent readDescription={readMe} tabPosition={tabPosition} />
<DetailContainerTabs onChange={handleChange} tabPosition={tabPosition} />
<DetailContainerContent readDescription={readMe} tabPosition={tabs[tabPosition]} />
</Box>
);
};

View File

@ -1,44 +1,31 @@
import React, { ChangeEvent, useState, useEffect } from 'react';
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import { default as MuiTabs } from '../../muiComponents/Tabs';
import Tab from '../../muiComponents/Tab';
import { TabPosition } from './tabs';
import { Theme } from '../../design-tokens/theme';
interface Props {
tabPosition: TabPosition;
onChangeTabPosition: (event: ChangeEvent<{}>) => void;
onChange: (event, newValue) => void;
tabPosition: number;
}
const Tabs = styled(MuiTabs)({
marginBottom: 16,
});
const getTabIndex = (tabPosition: TabPosition): number =>
Object.keys(TabPosition).findIndex(position => position === String(tabPosition).toUpperCase());
const DetailContainerTabs: React.FC<Props> = ({ tabPosition, onChangeTabPosition }) => {
const [tabPositionIndex, setTabPositionIndex] = useState(0);
useEffect(() => {
const tabIndex = getTabIndex(tabPosition);
setTabPositionIndex(tabIndex);
}, [tabPosition]);
const DetailContainerTabs: React.FC<Props> = ({ tabPosition, onChange }) => {
const { t } = useTranslation();
return (
<Tabs
indicatorColor={'primary'}
onChange={onChangeTabPosition}
textColor={'primary'}
value={tabPositionIndex}
variant={'fullWidth'}>
<Tab data-testid={'readme-tab'} id={'readme-tab'} label={TabPosition.README} />
<Tab data-testid={'dependencies-tab'} id={'dependencies-tab'} label={TabPosition.DEPENDENCIES} />
<Tab data-testid={'versions-tab'} id={'versions-tab'} label={TabPosition.VERSIONS} />
<Tab data-testid={'uplinks-tab'} id={'uplinks-tab'} label={TabPosition.UPLINKS} />
<Tabs color={'primary'} indicatorColor={'primary'} onChange={onChange} value={tabPosition} variant={'fullWidth'}>
<Tab data-testid={'readme-tab'} id={'readme-tab'} label={t('tab.readme')} />
<Tab data-testid={'dependencies-tab'} id={'dependencies-tab'} label={t('tab.dependencies')} />
<Tab data-testid={'versions-tab'} id={'versions-tab'} label={t('tab.versions')} />
<Tab data-testid={'uplinks-tab'} id={'uplinks-tab'} label={t('tab.uplinks')} />
</Tabs>
);
};
export default DetailContainerTabs;
const Tabs = styled(MuiTabs)<{ theme?: Theme }>({
marginBottom: 16,
});

View File

@ -10,6 +10,7 @@ exports[`DetailContainer renders correctly 1`] = `
>
<div
class="MuiTabs-root emotion-0 emotion-1"
color="primary"
>
<div
class="MuiTabs-scroller MuiTabs-fixed"
@ -21,7 +22,7 @@ exports[`DetailContainer renders correctly 1`] = `
>
<button
aria-selected="true"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit Mui-selected MuiTab-fullWidth"
data-testid="readme-tab"
id="readme-tab"
role="tab"
@ -39,7 +40,7 @@ exports[`DetailContainer renders correctly 1`] = `
</button>
<button
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit MuiTab-fullWidth"
data-testid="dependencies-tab"
id="dependencies-tab"
role="tab"
@ -57,7 +58,7 @@ exports[`DetailContainer renders correctly 1`] = `
</button>
<button
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit MuiTab-fullWidth"
data-testid="versions-tab"
id="versions-tab"
role="tab"
@ -75,7 +76,7 @@ exports[`DetailContainer renders correctly 1`] = `
</button>
<button
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit MuiTab-fullWidth"
data-testid="uplinks-tab"
id="uplinks-tab"
role="tab"

View File

@ -1,6 +1,6 @@
export enum TabPosition {
README = 'Readme',
DEPENDENCIES = 'Dependencies',
VERSIONS = 'Versions',
UPLINKS = 'Uplinks',
README = 'readme',
DEPENDENCIES = 'dependencies',
VERSIONS = 'versions',
UPLINKS = 'uplinks',
}

View File

@ -15,10 +15,6 @@ import { Theme } from '../../design-tokens/theme';
import DetailSidebarTitle from './DetailSidebarTitle';
import DetailSidebarFundButton from './DetailSidebarFundButton';
const StyledPaper = styled(Paper)<{ theme?: Theme }>(({ theme }) => ({
padding: theme.spacing(3, 2),
}));
const DetailSidebar: React.FC = () => {
const detailContext = useContext(DetailContext);
@ -50,3 +46,7 @@ const DetailSidebar: React.FC = () => {
};
export default DetailSidebar;
const StyledPaper = styled(Paper)<{ theme?: Theme }>(({ theme }) => ({
padding: theme?.spacing(3, 2),
}));

View File

@ -1,6 +1,7 @@
import React, { useContext } from 'react';
import styled from '@emotion/styled';
import Favorite from '@material-ui/icons/Favorite';
import { Trans } from 'react-i18next';
import Button from '../../muiComponents/Button';
import Link from '../Link';
@ -8,21 +9,6 @@ import { isURL } from '../../utils/url';
import { Theme } from '../../design-tokens/theme';
import { DetailContext } from '../../pages/Version';
const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
marginTop: theme && theme.spacing(1),
marginBottom: theme && theme.spacing(1),
textDecoration: 'none',
display: 'block',
}));
const StyledFavoriteIcon = styled(Favorite)<{ theme?: Theme }>(({ theme }) => ({
color: theme && theme.palette.orange,
}));
const StyledFundStrong = styled('strong')({
marginRight: 3,
});
/* eslint-disable react/jsx-no-bind */
const DetailSidebarFundButton: React.FC = () => {
const detailContext = useContext(DetailContext);
@ -38,11 +24,25 @@ const DetailSidebarFundButton: React.FC = () => {
return (
<StyledLink external={true} to={fundingUrl}>
<Button color="primary" fullWidth={true} startIcon={<StyledFavoriteIcon />} variant="outlined">
<StyledFundStrong>{'Fund'}</StyledFundStrong>
{'this package'}
<Trans components={[<StyledFundStrong key="fund" />]} i18nKey="button.fund-this-package" />
</Button>
</StyledLink>
);
};
export default DetailSidebarFundButton;
const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
marginTop: theme?.spacing(1),
marginBottom: theme?.spacing(1),
textDecoration: 'none',
display: 'block',
}));
const StyledFavoriteIcon = styled(Favorite)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.orange,
}));
const StyledFundStrong = styled('strong')({
marginRight: 3,
});

View File

@ -1,5 +1,6 @@
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import Box from '../../muiComponents/Box';
import Heading from '../../muiComponents/Heading';
@ -12,6 +13,21 @@ interface Props {
isLatest: boolean;
}
const DetailSidebarTitle: React.FC<Props> = ({ description, packageName, version, isLatest }) => {
const { t } = useTranslation();
return (
<Box className={'detail-info'} display="flex" flexDirection="column" marginBottom="8px">
<StyledHeading>{packageName}</StyledHeading>
{description && <div>{description}</div>}
<StyledBoxVersion>
{isLatest ? t('sidebar.detail.latest-version', { version }) : t('sidebar.detail.version', { version })}
</StyledBoxVersion>
</Box>
);
};
export default DetailSidebarTitle;
const StyledHeading = styled(Heading)({
fontSize: '1rem',
fontWeight: 700,
@ -20,13 +36,3 @@ const StyledHeading = styled(Heading)({
const StyledBoxVersion = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
color: theme && theme.palette.text.secondary,
}));
const DetailSidebarTitle: React.FC<Props> = ({ description, packageName, version, isLatest }) => (
<Box className={'detail-info'} display="flex" flexDirection="column" marginBottom="8px">
<StyledHeading>{packageName}</StyledHeading>
{description && <div>{description}</div>}
<StyledBoxVersion>{`${isLatest ? 'Latest v' : 'v'}${version}`}</StyledBoxVersion>
</Box>
);
export default DetailSidebarTitle;

View File

@ -3,7 +3,8 @@ import React from 'react';
import { mount } from '../../utils/test-enzyme';
import { DetailContextProvider } from '../../pages/Version';
import Developers, { DeveloperType, Fab } from './Developers';
import Developers, { Fab } from './Developers';
import { DeveloperType } from './types';
describe('test Developers', () => {
const packageMeta = {

View File

@ -1,38 +1,29 @@
import React, { useState, useCallback, useContext, useEffect, useMemo } from 'react';
import Add from '@material-ui/icons/Add';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import Tooltip from '../../muiComponents/Tooltip';
import Avatar from '../../muiComponents/Avatar';
import Box from '../../muiComponents/Box';
import Text from '../../muiComponents/Text';
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
import { Theme } from '../../design-tokens/theme';
import getUniqueDeveloperValues from './get-unique-developer-values';
import DevelopersTitle from './DevelopersTitle';
import { DeveloperType } from './types';
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
backgroundColor: props.theme && props.theme.palette.primary.main,
color: props.theme && props.theme.palette.white,
}));
export enum DeveloperType {
CONTRIBUTORS = 'contributors',
MAINTAINERS = 'maintainers',
}
interface Props {
type: DeveloperType;
visibleMax?: number;
}
export const StyledText = styled(Text)<{ theme?: Theme }>(({ theme }) => ({
fontWeight: theme && theme.fontWeight.bold,
marginBottom: '10px',
textTransform: 'capitalize',
}));
const StyledBox = styled(Box)({
'> *': {
margin: 5,
@ -43,9 +34,10 @@ export const VISIBLE_MAX = 6;
const Developers: React.FC<Props> = ({ type, visibleMax = VISIBLE_MAX }) => {
const detailContext = useContext(DetailContext);
const { t } = useTranslation();
if (!detailContext) {
throw Error("The app's detail Context was not correct used");
throw Error(t('app-context-not-correct-used'));
}
const developers = useMemo(() => getUniqueDeveloperValues(detailContext.packageMeta?.latest[type]), [
@ -69,7 +61,7 @@ const Developers: React.FC<Props> = ({ type, visibleMax = VISIBLE_MAX }) => {
return (
<>
<StyledText variant={'subtitle1'}>{type}</StyledText>
<DevelopersTitle type={type} />
<StyledBox display="flex" flexWrap="wrap" margin="10px 0 10px 0">
{visibleDevelopers.map(visibleDeveloper => (
<Tooltip key={visibleDeveloper.email} title={visibleDeveloper.name}>

View File

@ -0,0 +1,30 @@
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import Text from '../../muiComponents/Text';
import { Theme } from '../../design-tokens/theme';
import { DeveloperType } from './types';
interface Props {
type: DeveloperType;
}
const DevelopersTitle: React.FC<Props> = ({ type }) => {
const { t } = useTranslation();
switch (type) {
case DeveloperType.CONTRIBUTORS:
return <StyledText variant={'subtitle1'}>{t('sidebar.contributors.title')}</StyledText>;
case DeveloperType.MAINTAINERS:
return <StyledText variant={'subtitle1'}>{t('sidebar.maintainers.title')}</StyledText>;
return null;
}
};
export default DevelopersTitle;
const StyledText = styled(Text)<{ theme?: Theme }>(({ theme }) => ({
fontWeight: theme && theme.fontWeight.bold,
marginBottom: '10px',
}));

View File

@ -4,7 +4,6 @@ exports[`test Developers should render the component for contributors with items
.emotion-0 {
font-weight: 700;
margin-bottom: 10px;
text-transform: capitalize;
}
.emotion-8 > * {
@ -14,64 +13,68 @@ exports[`test Developers should render the component for contributors with items
<Developers
type="contributors"
>
<StyledText
variant="subtitle1"
<DevelopersTitle
type="contributors"
>
<ForwardRef(Text)
className="emotion-0 emotion-1"
<StyledText
variant="subtitle1"
>
<WithStyles(ForwardRef(Typography))
<ForwardRef(Text)
className="emotion-0 emotion-1"
variant="subtitle1"
>
<ForwardRef(Typography)
<WithStyles(ForwardRef(Typography))
className="emotion-0 emotion-1"
classes={
Object {
"alignCenter": "MuiTypography-alignCenter",
"alignJustify": "MuiTypography-alignJustify",
"alignLeft": "MuiTypography-alignLeft",
"alignRight": "MuiTypography-alignRight",
"body1": "MuiTypography-body1",
"body2": "MuiTypography-body2",
"button": "MuiTypography-button",
"caption": "MuiTypography-caption",
"colorError": "MuiTypography-colorError",
"colorInherit": "MuiTypography-colorInherit",
"colorPrimary": "MuiTypography-colorPrimary",
"colorSecondary": "MuiTypography-colorSecondary",
"colorTextPrimary": "MuiTypography-colorTextPrimary",
"colorTextSecondary": "MuiTypography-colorTextSecondary",
"displayBlock": "MuiTypography-displayBlock",
"displayInline": "MuiTypography-displayInline",
"gutterBottom": "MuiTypography-gutterBottom",
"h1": "MuiTypography-h1",
"h2": "MuiTypography-h2",
"h3": "MuiTypography-h3",
"h4": "MuiTypography-h4",
"h5": "MuiTypography-h5",
"h6": "MuiTypography-h6",
"noWrap": "MuiTypography-noWrap",
"overline": "MuiTypography-overline",
"paragraph": "MuiTypography-paragraph",
"root": "MuiTypography-root",
"srOnly": "MuiTypography-srOnly",
"subtitle1": "MuiTypography-subtitle1",
"subtitle2": "MuiTypography-subtitle2",
}
}
variant="subtitle1"
>
<h6
className="MuiTypography-root emotion-0 emotion-1 MuiTypography-subtitle1"
<ForwardRef(Typography)
className="emotion-0 emotion-1"
classes={
Object {
"alignCenter": "MuiTypography-alignCenter",
"alignJustify": "MuiTypography-alignJustify",
"alignLeft": "MuiTypography-alignLeft",
"alignRight": "MuiTypography-alignRight",
"body1": "MuiTypography-body1",
"body2": "MuiTypography-body2",
"button": "MuiTypography-button",
"caption": "MuiTypography-caption",
"colorError": "MuiTypography-colorError",
"colorInherit": "MuiTypography-colorInherit",
"colorPrimary": "MuiTypography-colorPrimary",
"colorSecondary": "MuiTypography-colorSecondary",
"colorTextPrimary": "MuiTypography-colorTextPrimary",
"colorTextSecondary": "MuiTypography-colorTextSecondary",
"displayBlock": "MuiTypography-displayBlock",
"displayInline": "MuiTypography-displayInline",
"gutterBottom": "MuiTypography-gutterBottom",
"h1": "MuiTypography-h1",
"h2": "MuiTypography-h2",
"h3": "MuiTypography-h3",
"h4": "MuiTypography-h4",
"h5": "MuiTypography-h5",
"h6": "MuiTypography-h6",
"noWrap": "MuiTypography-noWrap",
"overline": "MuiTypography-overline",
"paragraph": "MuiTypography-paragraph",
"root": "MuiTypography-root",
"srOnly": "MuiTypography-srOnly",
"subtitle1": "MuiTypography-subtitle1",
"subtitle2": "MuiTypography-subtitle2",
}
}
variant="subtitle1"
>
contributors
</h6>
</ForwardRef(Typography)>
</WithStyles(ForwardRef(Typography))>
</ForwardRef(Text)>
</StyledText>
<h6
className="MuiTypography-root emotion-0 emotion-1 MuiTypography-subtitle1"
>
sidebar.contributors.title
</h6>
</ForwardRef(Typography)>
</WithStyles(ForwardRef(Typography))>
</ForwardRef(Text)>
</StyledText>
</DevelopersTitle>
<StyledBox
display="flex"
flexWrap="wrap"
@ -90,7 +93,7 @@ exports[`test Developers should render the component for contributors with items
margin="10px 0 10px 0"
>
<div
className="MuiBox-root MuiBox-root-60 emotion-8 emotion-9"
className="MuiBox-root MuiBox-root-91 emotion-8 emotion-9"
>
<ForwardRef(ToolTip)
key="dave.methvin@gmail.com"
@ -427,7 +430,6 @@ exports[`test Developers should render the component for maintainers with items
.emotion-0 {
font-weight: 700;
margin-bottom: 10px;
text-transform: capitalize;
}
.emotion-8 > * {
@ -437,64 +439,68 @@ exports[`test Developers should render the component for maintainers with items
<Developers
type="maintainers"
>
<StyledText
variant="subtitle1"
<DevelopersTitle
type="maintainers"
>
<ForwardRef(Text)
className="emotion-0 emotion-1"
<StyledText
variant="subtitle1"
>
<WithStyles(ForwardRef(Typography))
<ForwardRef(Text)
className="emotion-0 emotion-1"
variant="subtitle1"
>
<ForwardRef(Typography)
<WithStyles(ForwardRef(Typography))
className="emotion-0 emotion-1"
classes={
Object {
"alignCenter": "MuiTypography-alignCenter",
"alignJustify": "MuiTypography-alignJustify",
"alignLeft": "MuiTypography-alignLeft",
"alignRight": "MuiTypography-alignRight",
"body1": "MuiTypography-body1",
"body2": "MuiTypography-body2",
"button": "MuiTypography-button",
"caption": "MuiTypography-caption",
"colorError": "MuiTypography-colorError",
"colorInherit": "MuiTypography-colorInherit",
"colorPrimary": "MuiTypography-colorPrimary",
"colorSecondary": "MuiTypography-colorSecondary",
"colorTextPrimary": "MuiTypography-colorTextPrimary",
"colorTextSecondary": "MuiTypography-colorTextSecondary",
"displayBlock": "MuiTypography-displayBlock",
"displayInline": "MuiTypography-displayInline",
"gutterBottom": "MuiTypography-gutterBottom",
"h1": "MuiTypography-h1",
"h2": "MuiTypography-h2",
"h3": "MuiTypography-h3",
"h4": "MuiTypography-h4",
"h5": "MuiTypography-h5",
"h6": "MuiTypography-h6",
"noWrap": "MuiTypography-noWrap",
"overline": "MuiTypography-overline",
"paragraph": "MuiTypography-paragraph",
"root": "MuiTypography-root",
"srOnly": "MuiTypography-srOnly",
"subtitle1": "MuiTypography-subtitle1",
"subtitle2": "MuiTypography-subtitle2",
}
}
variant="subtitle1"
>
<h6
className="MuiTypography-root emotion-0 emotion-1 MuiTypography-subtitle1"
<ForwardRef(Typography)
className="emotion-0 emotion-1"
classes={
Object {
"alignCenter": "MuiTypography-alignCenter",
"alignJustify": "MuiTypography-alignJustify",
"alignLeft": "MuiTypography-alignLeft",
"alignRight": "MuiTypography-alignRight",
"body1": "MuiTypography-body1",
"body2": "MuiTypography-body2",
"button": "MuiTypography-button",
"caption": "MuiTypography-caption",
"colorError": "MuiTypography-colorError",
"colorInherit": "MuiTypography-colorInherit",
"colorPrimary": "MuiTypography-colorPrimary",
"colorSecondary": "MuiTypography-colorSecondary",
"colorTextPrimary": "MuiTypography-colorTextPrimary",
"colorTextSecondary": "MuiTypography-colorTextSecondary",
"displayBlock": "MuiTypography-displayBlock",
"displayInline": "MuiTypography-displayInline",
"gutterBottom": "MuiTypography-gutterBottom",
"h1": "MuiTypography-h1",
"h2": "MuiTypography-h2",
"h3": "MuiTypography-h3",
"h4": "MuiTypography-h4",
"h5": "MuiTypography-h5",
"h6": "MuiTypography-h6",
"noWrap": "MuiTypography-noWrap",
"overline": "MuiTypography-overline",
"paragraph": "MuiTypography-paragraph",
"root": "MuiTypography-root",
"srOnly": "MuiTypography-srOnly",
"subtitle1": "MuiTypography-subtitle1",
"subtitle2": "MuiTypography-subtitle2",
}
}
variant="subtitle1"
>
maintainers
</h6>
</ForwardRef(Typography)>
</WithStyles(ForwardRef(Typography))>
</ForwardRef(Text)>
</StyledText>
<h6
className="MuiTypography-root emotion-0 emotion-1 MuiTypography-subtitle1"
>
sidebar.maintainers.title
</h6>
</ForwardRef(Typography)>
</WithStyles(ForwardRef(Typography))>
</ForwardRef(Text)>
</StyledText>
</DevelopersTitle>
<StyledBox
display="flex"
flexWrap="wrap"

View File

@ -1 +1,2 @@
export { default, DeveloperType } from './Developers';
export { default } from './Developers';
export { DeveloperType } from './types';

View File

@ -0,0 +1,4 @@
export enum DeveloperType {
CONTRIBUTORS = 'contributors',
MAINTAINERS = 'maintainers',
}

View File

@ -1,4 +1,5 @@
import React, { FC, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import fileSizeSI from '../../utils/file-size';
@ -22,6 +23,7 @@ const DistChip: FC<{ name: string }> = ({ name, children }) =>
const Dist: FC = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
if (!packageMeta) {
return null;
@ -30,11 +32,11 @@ const Dist: FC = () => {
const { dist, license } = packageMeta && packageMeta.latest;
return (
<List subheader={<StyledText variant="subtitle1">{'Latest Distribution'}</StyledText>}>
<List subheader={<StyledText variant="subtitle1">{t('sidebar.distribution.title')}</StyledText>}>
<DistListItem button={true}>
<DistChip name="file count">{dist.fileCount}</DistChip>
<DistChip name="size">{dist.unpackedSize && fileSizeSI(dist.unpackedSize)}</DistChip>
<DistChip name="license">{formatLicense(license)}</DistChip>
<DistChip name={t('sidebar.distribution.file-count')}>{dist.fileCount}</DistChip>
<DistChip name={t('sidebar.distribution.size')}>{dist.unpackedSize && fileSizeSI(dist.unpackedSize)}</DistChip>
<DistChip name={t('sidebar.distribution.license')}>{formatLicense(license)}</DistChip>
</DistListItem>
</List>
);

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Dist /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">Latest Distribution</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">sidebar.distribution.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.file-count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.size</b>: 10.00 Bytes</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as object 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">Latest Distribution</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as object 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">sidebar.distribution.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.file-count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as string 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">Latest Distribution</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as string 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">sidebar.distribution.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.file-count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;

View File

@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import Avatar from '../../muiComponents/Avatar';
@ -12,6 +13,7 @@ import node from './img/node.png';
const Engine: React.FC = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
const engines = packageMeta?.latest?.engines;
@ -23,7 +25,7 @@ const Engine: React.FC = () => {
<Grid container={true}>
{engines.node && (
<Grid item={true} xs={6}>
<List subheader={<StyledText variant={'subtitle1'}>{'node JS'}</StyledText>}>
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.engines.node-js')}</StyledText>}>
<EngineListItem button={true}>
<Avatar src={node} />
<ListItemText primary={engines.node} />
@ -34,7 +36,7 @@ const Engine: React.FC = () => {
{engines.npm && (
<Grid item={true} xs={6}>
<List subheader={<StyledText variant={'subtitle1'}>{'NPM version'}</StyledText>}>
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.engines.npm-version')}</StyledText>}>
<EngineListItem button={true}>
<Avatar src={npm} />
<ListItemText primary={engines.npm} />

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Engines /> component should render the component in default state 1`] = `"<div class=\\"MuiGrid-root MuiGrid-container\\"><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText et66bt70 MuiTypography-subtitle1\\">node JS</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-18b06t0-EngineListItem et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle MuiAvatar-colorDefault\\"><svg class=\\"MuiSvgIcon-root MuiAvatar-fallback\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\\"></path></svg></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;= 0.1.98</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText et66bt70 MuiTypography-subtitle1\\">NPM version</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-18b06t0-EngineListItem et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle MuiAvatar-colorDefault\\"><svg class=\\"MuiSvgIcon-root MuiAvatar-fallback\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\\"></path></svg></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;3</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div></div>"`;
exports[`<Engines /> component should render the component in default state 1`] = `"<div class=\\"MuiGrid-root MuiGrid-container\\"><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText et66bt70 MuiTypography-subtitle1\\">sidebar.engines.node-js</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-18b06t0-EngineListItem et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle MuiAvatar-colorDefault\\"><svg class=\\"MuiSvgIcon-root MuiAvatar-fallback\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\\"></path></svg></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;= 0.1.98</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText et66bt70 MuiTypography-subtitle1\\">sidebar.engines.npm-version</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-18b06t0-EngineListItem et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle MuiAvatar-colorDefault\\"><svg class=\\"MuiSvgIcon-root MuiAvatar-fallback\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\\"></path></svg></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;3</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div></div>"`;

View File

@ -1,53 +1,39 @@
import React from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { goToVerdaccioWebsite } from '../../utils/windows';
import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles';
const renderTooltip = (): JSX.Element => (
<ToolTip>
<Earth name="earth" size="md" />
<Flags>
<Flag name="spain" size="md" />
<Flag name="nicaragua" size="md" />
<Flag name="india" size="md" />
<Flag name="brazil" size="md" />
<Flag name="china" size="md" />
<Flag name="austria" size="md" />
</Flags>
</ToolTip>
);
const POWERED_LABEL = 'Powered by';
const MADEWITH_LABEL = ' Made with';
const ON_LABEL = 'on';
const HEARTH_EMOJI = '♥';
const renderRight = (version = window.VERDACCIO_VERSION): JSX.Element => {
/* eslint-disable react/jsx-key */
const Footer: React.FC = () => {
const { t } = useTranslation();
return (
<Right>
{POWERED_LABEL}
<Logo img={true} name="verdaccio" onClick={goToVerdaccioWebsite} pointer={true} size="md" />
{`/ ${version}`}
</Right>
<Wrapper>
<Inner>
<Left>
<Trans components={[<Love />]} i18nKey="footer.made-with-love-on" />
<ToolTip>
<Earth name="earth" size="md" />
<Flags>
<Flag name="spain" size="md" />
<Flag name="nicaragua" size="md" />
<Flag name="india" size="md" />
<Flag name="brazil" size="md" />
<Flag name="china" size="md" />
<Flag name="austria" size="md" />
<Flag name="germany" size="md" />
</Flags>
</ToolTip>
</Left>
<Right>
{t('footer.powered-by')}
<Logo img={true} name="verdaccio" onClick={goToVerdaccioWebsite} pointer={true} size="md" />
{`/ ${window.VERDACCIO_VERSION}`}
</Right>
</Inner>
</Wrapper>
);
};
const renderLeft = (): JSX.Element => (
<Left>
{MADEWITH_LABEL}
<Love>{HEARTH_EMOJI}</Love>
{ON_LABEL}
{renderTooltip()}
</Left>
);
const Footer: React.FC = () => (
<Wrapper>
<Inner>
{renderLeft()}
{renderRight()}
</Inner>
</Wrapper>
);
export default Footer;

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Footer /> component should load the initial state of Footer component 1`] = `
.emotion-38 {
.emotion-41 {
background: #f9f9f9;
border-top: 1px solid #e3e3e3;
color: #999999;
@ -9,7 +9,7 @@ exports[`<Footer /> component should load the initial state of Footer component
padding: 20px;
}
.emotion-36 {
.emotion-39 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -26,7 +26,7 @@ exports[`<Footer /> component should load the initial state of Footer component
}
@media (min-width:768px) {
.emotion-36 {
.emotion-39 {
min-width: 400px;
max-width: 800px;
margin: auto;
@ -38,12 +38,12 @@ exports[`<Footer /> component should load the initial state of Footer component
}
@media (min-width:1024px) {
.emotion-36 {
.emotion-39 {
max-width: 1240px;
}
}
.emotion-27 {
.emotion-30 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@ -52,7 +52,7 @@ exports[`<Footer /> component should load the initial state of Footer component
}
@media (min-width:768px) {
.emotion-27 {
.emotion-30 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -65,12 +65,12 @@ exports[`<Footer /> component should load the initial state of Footer component
padding: 0 5px;
}
.emotion-25 {
.emotion-28 {
position: relative;
height: 18px;
}
.emotion-25:hover .emotion-24 {
.emotion-28:hover .emotion-27 {
visibility: visible;
}
@ -83,7 +83,7 @@ exports[`<Footer /> component should load the initial state of Footer component
padding: 0 10px;
}
.emotion-23 {
.emotion-26 {
position: absolute;
background: #d3dddd;
padding: 1px 4px;
@ -101,7 +101,7 @@ exports[`<Footer /> component should load the initial state of Footer component
top: -2px;
}
.emotion-23:before {
.emotion-26:before {
content: '';
position: absolute;
top: 29%;
@ -123,7 +123,7 @@ exports[`<Footer /> component should load the initial state of Footer component
padding: 0 5px;
}
.emotion-34 {
.emotion-37 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@ -136,7 +136,7 @@ exports[`<Footer /> component should load the initial state of Footer component
}
@media (min-width:768px) {
.emotion-34 {
.emotion-37 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -144,7 +144,7 @@ exports[`<Footer /> component should load the initial state of Footer component
}
}
.emotion-32 {
.emotion-35 {
box-sizing: initial;
display: inline-block;
cursor: pointer;
@ -153,29 +153,29 @@ exports[`<Footer /> component should load the initial state of Footer component
padding: 0 5px;
}
.emotion-29 {
.emotion-32 {
width: 100%;
height: auto;
}
<div
class="emotion-38 emotion-39"
class="emotion-41 emotion-42"
>
<div
class="emotion-36 emotion-37"
class="emotion-39 emotion-40"
>
<div
class="emotion-27 emotion-28"
class="emotion-30 emotion-31"
>
Made with
Made with
<span
class="emotion-0 emotion-1"
>
</span>
on
on
<span
class="emotion-25 emotion-26"
class="emotion-28 emotion-29"
>
<svg
class="emotion-2 emotion-3 emotion-4"
@ -188,7 +188,7 @@ exports[`<Footer /> component should load the initial state of Footer component
/>
</svg>
<span
class="emotion-23 emotion-24"
class="emotion-26 emotion-27"
>
<svg
class="emotion-5 emotion-6 emotion-4"
@ -250,20 +250,30 @@ exports[`<Footer /> component should load the initial state of Footer component
xlink:href="[object Object]#austria"
/>
</svg>
<svg
class="emotion-5 emotion-6 emotion-4"
>
<title>
Germany
</title>
<use
xlink:href="[object Object]#germany"
/>
</svg>
</span>
</span>
</div>
<div
class="emotion-34 emotion-35"
class="emotion-37 emotion-38"
>
Powered by
<span
class="emotion-5 emotion-32 emotion-33"
class="emotion-5 emotion-35 emotion-36"
title="Verdaccio"
>
<img
alt="Verdaccio"
class="emotion-29 emotion-30"
class="emotion-32 emotion-33"
src="[object Object]"
/>
</span>

View File

@ -3,10 +3,10 @@ import styled from '@emotion/styled';
import Icon from '../Icon/Icon';
import { Theme } from '../../design-tokens/theme';
export const Wrapper = styled('div')<{ theme?: Theme }>(props => ({
background: props.theme && props.theme.palette.snow,
borderTop: `1px solid ${props.theme && props.theme.palette.greyGainsboro}`,
color: props.theme && props.theme.palette.nobel01,
export const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({
background: theme?.palette.type === 'light' ? theme?.palette.snow : theme?.palette.cyanBlue,
borderTop: `1px solid ${theme?.palette.greyGainsboro}`,
color: theme?.palette.type === 'dark' ? theme?.palette.white : theme?.palette.nobel01,
fontSize: '14px',
padding: '20px',
}));
@ -16,13 +16,13 @@ export const Inner = styled('div')<{ theme?: Theme }>(({ theme }) => ({
alignItems: 'center',
justifyContent: 'flex-end',
width: '100%',
[`@media (min-width: ${theme && theme.breakPoints.medium}px)`]: {
[`@media (min-width: ${theme?.breakPoints.medium}px)`]: {
minWidth: 400,
maxWidth: 800,
margin: 'auto',
justifyContent: 'space-between',
},
[`@media (min-width: ${theme && theme.breakPoints.large}px)`]: {
[`@media (min-width: ${theme?.breakPoints.large}px)`]: {
maxWidth: 1240,
},
}));
@ -30,7 +30,7 @@ export const Inner = styled('div')<{ theme?: Theme }>(({ theme }) => ({
export const Left = styled('div')<{ theme?: Theme }>(({ theme }) => ({
alignItems: 'center',
display: 'none',
[`@media (min-width: ${theme && theme.breakPoints.medium}px)`]: {
[`@media (min-width: ${theme?.breakPoints.medium}px)`]: {
display: 'flex',
},
}));
@ -43,9 +43,9 @@ export const Earth = styled(Icon)({
padding: '0 10px',
});
export const Flags = styled('span')<{ theme?: Theme }>(props => ({
export const Flags = styled('span')<{ theme?: Theme }>(({ theme }) => ({
position: 'absolute',
background: props.theme && props.theme.palette.greyAthens,
background: theme?.palette.greyAthens,
padding: '1px 4px',
borderRadius: 3,
height: 20,
@ -60,7 +60,7 @@ export const Flags = styled('span')<{ theme?: Theme }>(props => ({
left: -4,
marginLeft: -5,
border: '5px solid',
borderColor: `${props.theme && props.theme.palette.greyAthens} transparent transparent transparent`,
borderColor: `${theme?.palette.greyAthens} transparent transparent transparent`,
transform: 'rotate(90deg)',
},
}));
@ -75,8 +75,8 @@ export const ToolTip = styled('span')({
},
});
export const Love = styled('span')<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.love,
export const Love = styled('span')<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.love,
padding: '0 5px',
}));

View File

@ -3,6 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
import { render, fireEvent, waitForElement, waitForElementToBeRemoved } from '../../utils/test-react-testing-library';
import { AppContextProvider } from '../../App';
import translationEN from '../../../i18n/translations/en-US.json';
import Header from './Header';
@ -44,7 +45,7 @@ describe('<Header /> component with logged in state', () => {
});
test('should open login dialog', async () => {
const { getByText } = render(
const { getByTestId } = render(
<Router>
<AppContextProvider>
<Header />
@ -52,9 +53,9 @@ describe('<Header /> component with logged in state', () => {
</Router>
);
const loginBtn = getByText('Login');
const loginBtn = getByTestId('header--button-login');
fireEvent.click(loginBtn);
const loginDialog = await waitForElement(() => getByText('Sign in'));
const loginDialog = await waitForElement(() => getByTestId('login--dialog'));
expect(loginDialog).toBeTruthy();
});
@ -119,7 +120,7 @@ describe('<Header /> component with logged in state', () => {
fireEvent.click(infoBtn);
// wait for Close's button of registrationInfo modal appearance and return the element
const closeBtn = await waitForElement(() => getByText('CLOSE'));
const closeBtn = await waitForElement(() => getByText(translationEN.button.close));
fireEvent.click(closeBtn);
const hasRegistrationInfoModalBeenRemoved = await waitForElementToBeRemoved(() =>

View File

@ -1,4 +1,5 @@
import React, { useState, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import storage from '../../utils/storage';
import { getRegistryURL } from '../../utils/url';
@ -18,17 +19,17 @@ interface Props {
/* eslint-disable react/jsx-no-bind*/
const Header: React.FC<Props> = ({ withoutSearch }) => {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [isInfoDialogOpen, setOpenInfoDialog] = useState();
const [showMobileNavBar, setShowMobileNavBar] = useState();
const [showLoginModal, setShowLoginModal] = useState(false);
if (!appContext) {
throw Error('The app Context was not correct used');
throw Error(t('app-context-not-correct-used'));
}
const { user, scope, setUser } = appContext;
const logo = window.VERDACCIO_LOGO;
/**
* Logouts user
@ -44,7 +45,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
<>
<NavBar data-testid="header" position="static">
<InnerNavBar>
<HeaderLeft logo={logo} />
<HeaderLeft />
<HeaderRight
onLogout={handleLogout}
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
@ -67,7 +68,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
<Search />
</InnerMobileNavBar>
<Button color="inherit" onClick={() => setShowMobileNavBar(false)}>
{'Cancel'}
{t('button.cancel')}
</Button>
</MobileNavBar>
)}

View File

@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import Label from '../Label';
@ -8,11 +9,14 @@ interface Props {
username: string;
}
const HeaderGreetings: React.FC<Props> = ({ username }) => (
<>
<Greetings>{'Hi,'}</Greetings>
<Label capitalize={true} data-testid="greetings-label" text={username} weight="bold" />
</>
);
const HeaderGreetings: React.FC<Props> = ({ username }) => {
const { t } = useTranslation();
return (
<>
<Greetings>{t('header.greetings')}</Greetings>
<Label capitalize={true} data-testid="greetings-label" text={username} weight="bold" />
</>
);
};
export default HeaderGreetings;

View File

@ -3,23 +3,22 @@ import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import Search from '../Search/';
import Logo from '../Logo';
import HeaderLogo from './HeaderLogo';
import { LeftSide, SearchWrapper } from './styles';
interface Props {
withoutSearch?: boolean;
logo?: string;
}
const StyledLink = styled(Link)({
marginRight: '1em',
});
const HeaderLeft: React.FC<Props> = ({ withoutSearch = false, logo }) => (
const HeaderLeft: React.FC<Props> = ({ withoutSearch = false }) => (
<LeftSide>
<StyledLink to={'/'}>
<HeaderLogo logo={logo} />
<Logo />
</StyledLink>
{!withoutSearch && (
<SearchWrapper>

View File

@ -1,25 +0,0 @@
import React from 'react';
import styled from '@emotion/styled';
import Logo from '../Logo';
interface Props {
logo?: string;
}
const HeaderLogo: React.FC<Props> = ({ logo }) => {
if (logo) {
const Wrapper = styled('div')({
fontSize: 0,
});
return (
<Wrapper>
<img alt="logo" height="40px" src={logo} />
</Wrapper>
);
}
return <Logo />;
};
export default HeaderLogo;

View File

@ -1,4 +1,5 @@
import React, { MouseEvent } from 'react';
import { useTranslation } from 'react-i18next';
import AccountCircle from '@material-ui/icons/AccountCircle';
import IconButton from '../../muiComponents/IconButton';
@ -23,35 +24,38 @@ const HeaderMenu: React.FC<Props> = ({
anchorEl,
onLoggedInMenu,
onLoggedInMenuClose,
}) => (
<>
<IconButton
color="inherit"
data-testid="header--menu-accountcircle"
id="header--button-account"
onClick={onLoggedInMenu}>
<AccountCircle />
</IconButton>
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
onClose={onLoggedInMenuClose}
open={isMenuOpen}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}>
<MenuItem disabled={true}>
<HeaderGreetings username={username} />
</MenuItem>
<MenuItem button={true} data-testid="header--button-logout" id="header--button-logout" onClick={onLogout}>
{'Logout'}
</MenuItem>
</Menu>
</>
);
}) => {
const { t } = useTranslation();
return (
<>
<IconButton
color="inherit"
data-testid="header--menu-accountcircle"
id="header--button-account"
onClick={onLoggedInMenu}>
<AccountCircle />
</IconButton>
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
onClose={onLoggedInMenuClose}
open={isMenuOpen}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}>
<MenuItem>
<HeaderGreetings username={username} />
</MenuItem>
<MenuItem button={true} data-testid="header--button-logout" id="header--button-logout" onClick={onLogout}>
{t('button.logout')}
</MenuItem>
</Menu>
</>
);
};
export default HeaderMenu;

View File

@ -1,6 +1,9 @@
import React, { useState, useEffect, MouseEvent } from 'react';
import React, { useState, useEffect, useContext, MouseEvent } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../../muiComponents/Button';
import ThemeContext from '../../design-tokens/ThemeContext';
import LanguageSwitch from '../LanguageSwitch';
import { RightSide } from './styles';
import HeaderToolTip from './HeaderToolTip';
@ -23,9 +26,16 @@ const HeaderRight: React.FC<Props> = ({
onToggleMobileNav,
onOpenRegistryInfoDialog,
}) => {
const themeContext = useContext(ThemeContext);
const [anchorEl, setAnchorEl] = useState();
const [isMenuOpen, setIsMenuOpen] = useState();
const { t } = useTranslation();
if (!themeContext) {
throw Error(t('theme-context-not-correct-used'));
}
useEffect(() => {
setIsMenuOpen(Boolean(anchorEl));
}, [anchorEl]);
@ -52,13 +62,26 @@ const HeaderRight: React.FC<Props> = ({
onToggleLogin();
};
const handleToggleDarkLightMode = () => {
setTimeout(() => {
themeContext.setIsDarkMode(!themeContext.isDarkMode);
}, 300);
};
return (
<RightSide data-testid="header-right">
{!withoutSearch && (
<HeaderToolTip onClick={onToggleMobileNav} title={'Search packages'} tooltipIconType={'search'} />
<HeaderToolTip onClick={onToggleMobileNav} title={t('search.packages')} tooltipIconType={'search'} />
)}
<HeaderToolTip title={'Documentation'} tooltipIconType={'help'} />
<HeaderToolTip onClick={onOpenRegistryInfoDialog} title={'Registry Information'} tooltipIconType={'info'} />
<LanguageSwitch />
<HeaderToolTip title={t('header.documentation')} tooltipIconType={'help'} />
<HeaderToolTip onClick={onOpenRegistryInfoDialog} title={t('header.registry-info')} tooltipIconType={'info'} />
<HeaderToolTip
onClick={handleToggleDarkLightMode}
title={t('header.documentation')}
tooltipIconType={themeContext.isDarkMode ? 'dark-mode' : 'light-mode'}
/>
{username ? (
<HeaderMenu
anchorEl={anchorEl}
@ -70,7 +93,7 @@ const HeaderRight: React.FC<Props> = ({
/>
) : (
<Button color="inherit" data-testid="header--button-login" onClick={handleToggleLogin}>
{'Login'}
{t('button.login')}
</Button>
)}
</RightSide>

View File

@ -2,12 +2,14 @@ import React, { forwardRef } from 'react';
import Info from '@material-ui/icons/Info';
import Help from '@material-ui/icons/Help';
import Search from '@material-ui/icons/Search';
import NightsStay from '@material-ui/icons/NightsStay';
import WbSunny from '@material-ui/icons/WbSunny';
import IconButton from '../../muiComponents/IconButton';
import { IconSearchButton, StyledLink } from './styles';
export type TooltipIconType = 'search' | 'help' | 'info';
export type TooltipIconType = 'search' | 'help' | 'info' | 'dark-mode' | 'light-mode';
interface Props {
tooltipIconType: TooltipIconType;
onClick?: () => void;
@ -50,6 +52,21 @@ const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function Heade
<Search />
</IconSearchButton>
);
case 'dark-mode':
// todo(Priscila): Add Zoom transition effect
return (
<IconButton color="inherit" onClick={onClick} ref={ref}>
<NightsStay />
</IconButton>
);
case 'light-mode':
// todo(Priscila): Add Zoom transition effect
return (
<IconButton color="inherit" onClick={onClick} ref={ref}>
<WbSunny />
</IconButton>
);
default:
return null;
}

View File

@ -1,8 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Header /> component with logged in state should load the component in logged in state 1`] = `
.emotion-24 {
.emotion-28 {
background-color: #4b5e40;
color: #fff;
min-height: 60px;
display: -webkit-box;
display: -webkit-flex;
@ -15,29 +16,29 @@ exports[`<Header /> component with logged in state should load the component in
}
@media (min-width:768px) {
.emotion-24 .emotion-13 {
.emotion-28 .emotion-13 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.emotion-24 .emotion-17 {
.emotion-28 .emotion-17 {
display: none;
}
.emotion-24 .e1jf5lit4 {
.emotion-28 .e1jf5lit4 {
display: none;
}
}
@media (min-width:1024px) {
.emotion-24 .emotion-23 {
.emotion-28 .emotion-27 {
padding: 0 20px;
}
@media (min-width:1275px) {
.emotion-24 .emotion-23 {
.emotion-28 .emotion-27 {
max-width: 1240px;
width: 100%;
margin: 0 auto;
@ -45,7 +46,7 @@ exports[`<Header /> component with logged in state should load the component in
}
}
.emotion-22 {
.emotion-26 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
@ -123,7 +124,7 @@ exports[`<Header /> component with logged in state should load the component in
overflow-y: auto;
}
.emotion-20 {
.emotion-24 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -135,16 +136,36 @@ exports[`<Header /> component with logged in state should load the component in
display: block;
}
.emotion-20 {
display: none;
}
@media screen and (min-width:768px) {
.emotion-20 {
display: inline-block;
}
}
.emotion-18 {
display: none;
}
@media screen and (min-width:768px) {
.emotion-18 {
display: inline-block;
}
}
.emotion-22 {
color: #fff;
}
<header
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic emotion-24 emotion-25 MuiAppBar-colorPrimary"
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic emotion-28 emotion-29 MuiAppBar-colorPrimary"
data-testid="header"
>
<div
class="MuiToolbar-root MuiToolbar-regular emotion-22 emotion-23 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-26 emotion-27 MuiToolbar-gutters"
>
<div
class="MuiToolbar-root MuiToolbar-regular emotion-14 emotion-15 MuiToolbar-gutters"
@ -213,7 +234,7 @@ exports[`<Header /> component with logged in state should load the component in
</div>
</div>
<div
class="MuiToolbar-root MuiToolbar-regular emotion-20 emotion-21 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-24 emotion-25 MuiToolbar-gutters"
data-testid="header-right"
>
<button
@ -240,8 +261,53 @@ exports[`<Header /> component with logged in state should load the component in
class="MuiTouchRipple-root"
/>
</button>
<div
class="emotion-20 emotion-21"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
tabindex="0"
title="Change language"
type="button"
>
<span
class="MuiButton-label ForwardRef(Button)-label-322"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"
/>
</svg>
<span
class="emotion-18 emotion-19"
>
English
</span>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<a
class="emotion-18 emotion-19"
class="emotion-22 emotion-23"
data-testid="header--tooltip-documentation"
href="https://verdaccio.org/docs/en/installation"
rel="noopener noreferrer"
@ -302,6 +368,30 @@ exports[`<Header /> component with logged in state should load the component in
class="MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
data-testid="header--menu-accountcircle"
@ -334,8 +424,9 @@ exports[`<Header /> component with logged in state should load the component in
`;
exports[`<Header /> component with logged in state should load the component in logged out state 1`] = `
.emotion-24 {
.emotion-28 {
background-color: #4b5e40;
color: #fff;
min-height: 60px;
display: -webkit-box;
display: -webkit-flex;
@ -348,29 +439,29 @@ exports[`<Header /> component with logged in state should load the component in
}
@media (min-width:768px) {
.emotion-24 .emotion-13 {
.emotion-28 .emotion-13 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.emotion-24 .emotion-17 {
.emotion-28 .emotion-17 {
display: none;
}
.emotion-24 .e1jf5lit4 {
.emotion-28 .e1jf5lit4 {
display: none;
}
}
@media (min-width:1024px) {
.emotion-24 .emotion-23 {
.emotion-28 .emotion-27 {
padding: 0 20px;
}
@media (min-width:1275px) {
.emotion-24 .emotion-23 {
.emotion-28 .emotion-27 {
max-width: 1240px;
width: 100%;
margin: 0 auto;
@ -378,7 +469,7 @@ exports[`<Header /> component with logged in state should load the component in
}
}
.emotion-22 {
.emotion-26 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
@ -456,7 +547,7 @@ exports[`<Header /> component with logged in state should load the component in
overflow-y: auto;
}
.emotion-20 {
.emotion-24 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -468,16 +559,36 @@ exports[`<Header /> component with logged in state should load the component in
display: block;
}
.emotion-20 {
display: none;
}
@media screen and (min-width:768px) {
.emotion-20 {
display: inline-block;
}
}
.emotion-18 {
display: none;
}
@media screen and (min-width:768px) {
.emotion-18 {
display: inline-block;
}
}
.emotion-22 {
color: #fff;
}
<header
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic emotion-24 emotion-25 MuiAppBar-colorPrimary"
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic emotion-28 emotion-29 MuiAppBar-colorPrimary"
data-testid="header"
>
<div
class="MuiToolbar-root MuiToolbar-regular emotion-22 emotion-23 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-26 emotion-27 MuiToolbar-gutters"
>
<div
class="MuiToolbar-root MuiToolbar-regular emotion-14 emotion-15 MuiToolbar-gutters"
@ -546,7 +657,7 @@ exports[`<Header /> component with logged in state should load the component in
</div>
</div>
<div
class="MuiToolbar-root MuiToolbar-regular emotion-20 emotion-21 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-24 emotion-25 MuiToolbar-gutters"
data-testid="header-right"
>
<button
@ -573,8 +684,53 @@ exports[`<Header /> component with logged in state should load the component in
class="MuiTouchRipple-root"
/>
</button>
<div
class="emotion-20 emotion-21"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
tabindex="0"
title="Change language"
type="button"
>
<span
class="MuiButton-label ForwardRef(Button)-label-119"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"
/>
</svg>
<span
class="emotion-18 emotion-19"
>
English
</span>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<a
class="emotion-18 emotion-19"
class="emotion-22 emotion-23"
data-testid="header--tooltip-documentation"
href="https://verdaccio.org/docs/en/installation"
rel="noopener noreferrer"
@ -635,6 +791,30 @@ exports[`<Header /> component with logged in state should load the component in
class="MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
data-testid="header--button-login"

View File

@ -54,11 +54,12 @@ export const SearchWrapper = styled('div')({
});
export const NavBar = styled(AppBar)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: theme && theme.palette.primary.main,
backgroundColor: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
color: theme?.palette.white,
minHeight: 60,
display: 'flex',
justifyContent: 'center',
[`@media (min-width: ${theme && theme.breakPoints.medium}px)`]: css`
[`@media (min-width: ${theme?.breakPoints.medium}px)`]: css`
${SearchWrapper} {
display: flex;
}
@ -69,12 +70,12 @@ export const NavBar = styled(AppBar)<{ theme?: Theme }>(({ theme }) => ({
display: none;
}
`,
[`@media (min-width: ${theme && theme.breakPoints.large}px)`]: css`
[`@media (min-width: ${theme?.breakPoints.large}px)`]: css`
${InnerNavBar} {
padding: 0 20px;
}
`,
[`@media (min-width: ${theme && theme.breakPoints.xlarge}px)`]: css`
[`@media (min-width: ${theme?.breakPoints.xlarge}px)`]: css`
${InnerNavBar} {
max-width: 1240px;
width: 100%;
@ -83,6 +84,6 @@ export const NavBar = styled(AppBar)<{ theme?: Theme }>(({ theme }) => ({
`,
}));
export const StyledLink = styled(Link)<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.white,
export const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.white,
}));

View File

@ -1,4 +1,5 @@
import React, { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { getRegistryURL } from '../../utils/url';
import CopyToClipBoard from '../CopyToClipBoard';
@ -24,23 +25,24 @@ function renderHeadingClipboardSegments(title: string, text: string): React.Reac
const Help: React.FC = () => {
const registryUrl = getRegistryURL();
const { t } = useTranslation();
return (
<Card id="help-card">
<CardContent>
<Typography component="h2" gutterBottom={true} id={COMPONENT_HELP_ID} variant="h5">
{HELP_TITLE}
{t('help.title')}
</Typography>
<HelpTitle color="textSecondary" gutterBottom={true}>
{'To publish your first package just:'}
{t('help.sub-title')}
</HelpTitle>
{renderHeadingClipboardSegments('1. Login', `npm adduser --registry ${registryUrl}`)}
{renderHeadingClipboardSegments('2. Publish', `npm publish --registry ${registryUrl}`)}
<Text variant="body2">{'3. Refresh this page.'}</Text>
{renderHeadingClipboardSegments(t('help.first-step'), t('help.first-step-command-line', { registryUrl }))}
{renderHeadingClipboardSegments(t('help.second-step'), t('help.second-step-command-line', { registryUrl }))}
<Text variant="body2">{t('help.third-step')}</Text>
</CardContent>
<CardActions>
<Button color="primary" href="https://verdaccio.org/docs/en/installation" size="small">
{'Learn More'}
{t('button.learn-more')}
</Button>
</CardActions>
</Card>

View File

@ -68,7 +68,7 @@ exports[`<Help /> component should load the component in default state 1`] = `
<button
class="MuiButtonBase-root MuiIconButton-root emotion-4 emotion-5"
tabindex="0"
title="Copy to Clipboard"
title="Copy to clipboard"
type="button"
>
<span
@ -107,7 +107,7 @@ exports[`<Help /> component should load the component in default state 1`] = `
<button
class="MuiButtonBase-root MuiIconButton-root emotion-4 emotion-5"
tabindex="0"
title="Copy to Clipboard"
title="Copy to clipboard"
type="button"
>
<span

View File

@ -6,10 +6,15 @@ import { Svg, Img, ImgWrapper } from './styles';
import brazil from './img/brazil.svg';
import china from './img/china.svg';
import india from './img/india.svg';
import germany from './img/germany.svg';
import nicaragua from './img/nicaragua.svg';
import pakistan from './img/pakistan.svg';
import austria from './img/austria.svg';
import spain from './img/spain.svg';
import usa from './img/usa.svg';
import france from './img/france.svg';
import japan from './img/japan.svg';
import ukraine from './img/ukraine.svg';
import earth from './img/earth.svg';
import verdaccio from './img/verdaccio.svg';
import filebinary from './img/filebinary.svg';
@ -22,10 +27,15 @@ export interface IconsMap {
brazil: string;
spain: string;
china: string;
usa: string;
nicaragua: string;
pakistan: string;
austria: string;
france: string;
germany: string;
india: string;
japan: string;
ukraine: string;
earth: string;
verdaccio: string;
license: string;
@ -51,6 +61,11 @@ export const Icons: IconsMap = {
license,
time,
version,
germany,
usa,
france,
japan,
ukraine,
};
export interface Props {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="france"><path d="M38.345 88.273C17.167 88.273 0 105.44 0 126.618v258.759c0 21.177 17.167 38.345 38.345 38.345h132.322V88.273H38.345z" fill="#41479b"/><path fill="#f5f5f5" d="M170.67 88.277h170.67v335.45H170.67z"/><path d="M473.655 88.273H341.333v335.448h132.322c21.177 0 38.345-17.167 38.345-38.345V126.618c0-21.178-17.167-38.345-38.345-38.345z" fill="#ff4b55"/></svg>

After

Width:  |  Height:  |  Size: 434 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="germany"><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 fill="#ff4b55" d="M0 200.09h512V311.9H0z"/></svg>

After

Width:  |  Height:  |  Size: 407 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="japan"><path d="M473.655 88.275H38.345C17.167 88.275 0 105.442 0 126.62v258.76c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345V126.62c0-21.178-17.167-38.345-38.345-38.345z" fill="#f5f5f5"/><circle cx="256" cy="255.999" r="97.1" fill="#ff4b55"/></svg>

After

Width:  |  Height:  |  Size: 350 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="ukraine"><path d="M0 248v121.6C0 396.8 21.6 416 48 416h400c26.4 0 48-19.2 48-46.4V248H0z" fill="#fdce0c"/><path d="M248 248l197.6 168c26.4 0 50.4-19.2 50.4-46.4V248H248z" fill="#f4ba00"/><path d="M448 80H48C21.6 80 0 99.2 0 126.4V248h496V126.4c0-27.2-21.6-46.4-48-46.4z" fill="#44c1ef"/><path d="M448 80H48l200 168h248V126.4c0-27.2-21.6-46.4-48-46.4z" fill="#18b4ea"/><path d="M496 368.8c0 29.6-21.6 47.2-48 47.2H48c-26.4 0-48-20.8-48-48" fill="#f2a700"/><path d="M48 80h400c26.4 0 48 19.2 48 46.4V216" fill="#10a2e2"/></svg>

After

Width:  |  Height:  |  Size: 593 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -20,6 +20,7 @@ interface CommonStyleProps {
size: Breakpoint;
pointer?: boolean;
}
const commonStyle = ({ size = 'sm', pointer }: CommonStyleProps): object => ({
display: 'inline-block',
cursor: pointer ? 'pointer' : 'default',

View File

@ -1,5 +1,6 @@
import React, { useContext } from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import Text from '../../muiComponents/Text';
@ -14,6 +15,7 @@ const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
}));
const Install: React.FC = () => {
const { t } = useTranslation();
const detailContext = useContext(DetailContext);
const { packageMeta, packageName } = detailContext;
@ -23,7 +25,9 @@ const Install: React.FC = () => {
}
return (
<List data-testid={'installList'} subheader={<StyledText variant={'subtitle1'}>{'Installation'}</StyledText>}>
<List
data-testid={'installList'}
subheader={<StyledText variant={'subtitle1'}>{t('sidebar.installation.title')}</StyledText>}>
<InstallListItem dependencyManager={DependencyManager.NPM} packageName={packageName} />
<InstallListItem dependencyManager={DependencyManager.YARN} packageName={packageName} />
<InstallListItem dependencyManager={DependencyManager.PNPM} packageName={packageName} />

View File

@ -1,5 +1,6 @@
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import CopyToClipBoard from '../CopyToClipBoard';
import Avatar from '../../muiComponents/Avatar';
@ -43,14 +44,16 @@ interface Interface {
}
const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }) => {
const { t } = useTranslation();
switch (dependencyManager) {
case DependencyManager.NPM:
return (
<InstallItem button={true} data-testid={'installListItem-npm'}>
<PackageMangerAvatar alt="npm" src={npmLogo} />
<InstallListItemText
primary={<CopyToClipBoard text={`npm install ${packageName}`} />}
secondary={'Install using npm'}
primary={<CopyToClipBoard text={t('sidebar.installation.install-using-npm-command', { packageName })} />}
secondary={t('sidebar.installation.install-using-npm')}
/>
</InstallItem>
);
@ -59,8 +62,8 @@ const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }
<InstallItem button={true} data-testid={'installListItem-yarn'}>
<PackageMangerAvatar alt="yarn" src={yarnLogo} />
<InstallListItemText
primary={<CopyToClipBoard text={`yarn add ${packageName}`} />}
secondary={'Install using yarn'}
primary={<CopyToClipBoard text={t('sidebar.installation.install-using-yarn-command', { packageName })} />}
secondary={t('sidebar.installation.install-using-yarn')}
/>
</InstallItem>
);
@ -69,8 +72,8 @@ const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }
<InstallItem button={true} data-testid={'installListItem-pnpm'}>
<PackageMangerAvatar alt={'pnpm'} src={pnpmLogo} />
<InstallListItemText
primary={<CopyToClipBoard text={`pnpm install ${packageName}`} />}
secondary={'Install using pnpm'}
primary={<CopyToClipBoard text={t('sidebar.installation.install-using-pnpm-command', { packageName })} />}
secondary={t('sidebar.installation.install-using-pnpm')}
/>
</InstallItem>
);

View File

@ -94,7 +94,7 @@ exports[`<Install /> renders correctly 1`] = `
<button
class="MuiButtonBase-root MuiIconButton-root emotion-6 emotion-7"
tabindex="0"
title="Copy to Clipboard"
title="Copy to clipboard"
type="button"
>
<span
@ -161,7 +161,7 @@ exports[`<Install /> renders correctly 1`] = `
<button
class="MuiButtonBase-root MuiIconButton-root emotion-6 emotion-7"
tabindex="0"
title="Copy to Clipboard"
title="Copy to clipboard"
type="button"
>
<span
@ -228,7 +228,7 @@ exports[`<Install /> renders correctly 1`] = `
<button
class="MuiButtonBase-root MuiIconButton-root emotion-6 emotion-7"
tabindex="0"
title="Copy to Clipboard"
title="Copy to clipboard"
type="button"
>
<span

View File

@ -0,0 +1,222 @@
import React, { MouseEvent, useCallback, useState, useRef, useContext } from 'react';
import LanguageIcon from '@material-ui/icons/Language';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import i18next, { TFunction } from 'i18next';
import { useTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import Grow from '@material-ui/core/Grow';
import Popper from '@material-ui/core/Popper';
import MenuList from '@material-ui/core/MenuList';
import styled from '@emotion/styled';
import { Language } from '../../../i18n/config';
import ThemeContext from '../../design-tokens/ThemeContext';
import Paper from '../../muiComponents/Paper';
import MenuItem from '../../muiComponents/MenuItem';
import Button from '../../muiComponents/Button';
import Tooltip from '../../muiComponents/Tooltip';
import Divider from '../../muiComponents/Divider';
import Box from '../../muiComponents/Box';
import { Theme } from '../../design-tokens/theme';
import Link from '../Link';
import Icon from '../Icon';
const VERDACCIO_UI_GITHUB_REPOSITORY = 'https://github.com/verdaccio/ui';
const getTranslatedCurrentLanguageDetails = (
t: TFunction,
currentLanguage: string
): { translation: string; icon: React.ComponentProps<typeof Icon>['name'] } => {
switch (currentLanguage) {
case 'fr-FR':
return {
translation: t('lng.french'),
icon: 'france',
};
case 'pt-BR':
return {
translation: t('lng.portuguese'),
icon: 'brazil',
};
case 'de-DE':
return {
translation: t('lng.german'),
icon: 'germany',
};
case 'es-ES':
return {
translation: t('lng.spanish'),
icon: 'spain',
};
case 'zh-CN':
return {
translation: t('lng.chinese'),
icon: 'china',
};
case 'ja-JP':
return {
translation: t('lng.japanese'),
icon: 'japan',
};
case 'uk-UA':
return {
translation: t('lng.ukraine'),
icon: 'ukraine',
};
default:
return {
translation: t('lng.english'),
icon: 'usa',
};
}
};
const LanguageSwitch = () => {
const themeContext = useContext(ThemeContext);
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const anchorRef = useRef<HTMLButtonElement>(null);
if (!themeContext) {
throw Error(t('theme-context-not-correct-used'));
}
const languages = (i18next.options.resources ? Object.keys(i18next.options.resources) : []) as Array<Language>;
const currentLanguage = themeContext.language;
const { translation: userLanguage } = getTranslatedCurrentLanguageDetails(t, currentLanguage);
const handleToggle = useCallback(() => {
setOpen(prevOpen => !prevOpen);
}, []);
const handleClose = useCallback(
(event: MouseEvent<HTMLLIElement | Document | HTMLAnchorElement>) => {
if (anchorRef.current) {
if (anchorRef.current.contains(event.currentTarget)) {
return;
}
}
setOpen(false);
},
[setOpen]
);
const handleSwitchLanguage = useCallback(
(language: Language) => (event: MouseEvent<HTMLLIElement>) => {
themeContext.setLanguage(language);
handleClose(event);
},
[handleClose, themeContext]
);
// return focus to the button when we transitioned from !open -> open
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
if (anchorRef.current) {
anchorRef.current.focus();
}
}
prevOpen.current = open;
}, [open]);
return (
<Wrapper>
<Tooltip enterDelay={300} title={t('change-language')}>
<SwitchButton color="inherit" onClick={handleToggle} ref={anchorRef}>
<LanguageIcon />
<UserLanguage>{userLanguage}</UserLanguage>
<ExpandMoreIcon fontSize="small" />
</SwitchButton>
</Tooltip>
<Popper anchorEl={anchorRef.current} disablePortal={true} open={open} role={undefined} transition={true}>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
<StyledPaper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList>
{languages
.filter(language => language !== currentLanguage)
.map(language => {
const { icon, translation } = getTranslatedCurrentLanguageDetails(t, language);
return (
<StyledMenuItem
key={language}
onClick={handleSwitchLanguage(language)}
selected={userLanguage === translation}>
<Icon name={icon} size="md" />
{translation}
</StyledMenuItem>
);
})}
<Box my={1}>
<Divider />
</Box>
<MenuItem button={true}>
<StyledLink external={true} onClick={handleClose} to={VERDACCIO_UI_GITHUB_REPOSITORY}>
{`${t('help-to-translate')}`}
</StyledLink>
</MenuItem>
</MenuList>
</ClickAwayListener>
</StyledPaper>
</Grow>
)}
</Popper>
</Wrapper>
);
};
export default LanguageSwitch;
const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({
display: 'none',
[`@media screen and (min-width: ${theme && theme.breakPoints.medium}px)`]: {
display: 'inline-block',
},
}));
const UserLanguage = styled('span')<{ theme?: Theme }>(({ theme }) => ({
display: 'none',
[`@media screen and (min-width: ${theme && theme.breakPoints.medium}px)`]: {
display: 'inline-block',
},
}));
const SwitchButton = withStyles((theme: Theme) => ({
label: {
display: 'grid',
gridGap: theme?.spacing(1),
gridTemplateColumns: '24px 20px',
[`@media screen and (min-width: ${theme && theme.breakPoints.medium}px)`]: {
gridTemplateColumns: '24px 1fr 20px',
},
},
}))(Button);
const StyledMenuItem = withStyles((theme: Theme) => ({
root: {
display: 'grid',
cursor: 'pointer',
gridGap: theme?.spacing(0.5),
gridTemplateColumns: '20px 1fr',
alignItems: 'center',
'&:first-child': {
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
},
},
}))(MenuItem);
const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.type === 'dark' ? theme?.palette.white : theme?.palette.text.primary,
textDecoration: 'none',
}));
const StyledPaper = styled(Paper)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: theme?.palette.type === 'dark' ? theme?.palette.cyanBlue : theme?.palette.white,
}));

View File

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

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { MouseEvent } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import Text, { TextProps } from '../../muiComponents/Text';
@ -8,6 +8,7 @@ interface Props extends Pick<TextProps, 'variant'> {
className?: string;
to: string;
children?: React.ReactNode;
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
}
type LinkRef = HTMLAnchorElement;

View File

@ -1,5 +1,7 @@
import styled from '@emotion/styled';
import { Theme } from '../../design-tokens/theme';
export const Wrapper = styled('div')({
transform: 'translate(-50%, -50%)',
top: '50%',
@ -7,9 +9,9 @@ export const Wrapper = styled('div')({
position: 'absolute',
});
export const Badge = styled('div')({
export const Badge = styled('div')<{ theme?: Theme }>(({ theme }) => ({
margin: '0 0 30px 0',
borderRadius: 25,
boxShadow: '0 10px 20px 0 rgba(69, 58, 100, 0.2)',
background: '#f7f8f6',
});
background: theme?.palette.type === 'dark' ? theme?.palette.black : '#f7f8f6',
}));

View File

@ -1,8 +1,9 @@
import React from 'react';
import { render, waitForElement, fireEvent, waitForElementToBeRemoved } from '../../utils/test-react-testing-library';
import { render, waitForElement, fireEvent } from '../../utils/test-react-testing-library';
import AppContext, { AppContextProps } from '../../App/AppContext';
import api from '../../utils/api';
import translationEN from '../../../i18n/translations/en-US.json';
import LoginDialog from './LoginDialog';
@ -35,13 +36,13 @@ describe('<LoginDialog /> component', () => {
onClose: jest.fn(),
};
const { getByText } = render(
const { getByTestId } = render(
<AppContext.Provider value={appContextValue}>
<LoginDialog onClose={props.onClose} open={props.open} />
</AppContext.Provider>
);
const loginDialogHeading = await waitForElement(() => getByText('Sign in'));
const loginDialogHeading = await waitForElement(() => getByTestId('login-dialog-form-login-button'));
expect(loginDialogHeading).toBeTruthy();
});

View File

@ -1,4 +1,5 @@
import React, { useState, useContext, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { makeLogin } from '../../utils/login';
import storage from '../../utils/storage';
@ -16,10 +17,11 @@ interface Props {
}
const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
const { t } = useTranslation();
const appContext = useContext(AppContext);
if (!appContext) {
throw Error('The app Context was not correct used');
throw Error(t('app-context-not-correct-used'));
}
const [error, setError] = useState();
@ -43,7 +45,7 @@ const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
);
return (
<Dialog fullWidth={true} id="login--dialog" maxWidth="sm" onClose={onClose} open={open}>
<Dialog data-testid="login--dialog" fullWidth={true} id="login--dialog" maxWidth="sm" onClose={onClose} open={open}>
<LoginDialogCloseButton onClose={onClose} />
<DialogContent>
<LoginDialogHeader />

View File

@ -1,6 +1,7 @@
import React from 'react';
import styled from '@emotion/styled';
import CloseIcon from '@material-ui/icons/Close';
import { useTranslation } from 'react-i18next';
import DialogTitle from '../../muiComponents/DialogTitle';
import IconButton from '../../muiComponents/IconButton';
@ -17,12 +18,15 @@ interface Props {
onClose: () => void;
}
const LoginDialogCloseButton: React.FC<Props> = ({ onClose }) => (
<DialogTitle>
<StyledIconButton data-testid="close-login-dialog-button" onClick={onClose}>
<CloseIcon titleAccess="Close Dialog" />
</StyledIconButton>
</DialogTitle>
);
const LoginDialogCloseButton: React.FC<Props> = ({ onClose }) => {
const { t } = useTranslation();
return (
<DialogTitle>
<StyledIconButton data-testid="close-login-dialog-button" onClick={onClose}>
<CloseIcon titleAccess={t('button.close')} />
</StyledIconButton>
</DialogTitle>
);
};
export default LoginDialogCloseButton;

View File

@ -1,5 +1,6 @@
import React, { memo } from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import useForm from 'react-hook-form/dist/react-hook-form.ie11';
import TextField from '../../muiComponents/TextField';
@ -28,6 +29,7 @@ interface Props {
}
const LoginDialogForm = memo(({ onSubmit, error }: Props) => {
const { t } = useTranslation();
const {
register,
errors,
@ -48,13 +50,13 @@ const LoginDialogForm = memo(({ onSubmit, error }: Props) => {
helperText={errors.username?.message}
id="login--dialog-username"
inputRef={register({
required: { value: true, message: 'This field is required' },
minLength: { value: 2, message: 'This field required the min length of 2' },
required: { value: true, message: t('form-validation.required-field') },
minLength: { value: 2, message: t('form-validation.required-min-length', { length: 2 }) },
})}
label="Username"
label={t('form.username')}
margin="normal"
name="username"
placeholder="Your username"
placeholder={t('form-placeholder.username')}
required={true}
variant="outlined"
/>
@ -65,13 +67,13 @@ const LoginDialogForm = memo(({ onSubmit, error }: Props) => {
helperText={errors.password?.message}
id="login--dialog-password"
inputRef={register({
required: { value: true, message: 'This field is required' },
minLength: { value: 2, message: 'This field required the min length of 2' },
required: { value: true, message: t('form-validation.required-field') },
minLength: { value: 2, message: t('form-validation.required-min-length', { length: 2 }) },
})}
label="Password"
label={t('form.password')}
margin="normal"
name="password"
placeholder="Your strong password"
placeholder={t('form-placeholder.password')}
required={true}
type="password"
variant="outlined"
@ -79,13 +81,14 @@ const LoginDialogForm = memo(({ onSubmit, error }: Props) => {
{error && <LoginDialogFormError error={error} />}
<StyledButton
color="primary"
data-testid="login-dialog-form-login-button"
disabled={!isValid}
fullWidth={true}
id="login--dialog-button-submit"
size="large"
type="submit"
variant="contained">
{'Sign In'}
{t('button.login')}
</StyledButton>
</StyledForm>
);

View File

@ -8,13 +8,14 @@ import { Theme } from '../../design-tokens/theme';
import { LoginError } from '../../utils/login';
const StyledSnackbarContent = styled(SnackbarContent)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: theme.palette.error.dark,
backgroundColor: theme?.palette.error.dark,
color: theme?.palette.white,
}));
const StyledErrorIcon = styled(Error)<{ theme?: Theme }>(({ theme }) => ({
fontSize: 20,
opacity: 0.9,
marginRight: theme.spacing(1),
marginRight: theme?.spacing(1),
}));
export interface FormValues {

View File

@ -2,43 +2,46 @@ import React from 'react';
import styled from '@emotion/styled';
import LockOutlined from '@material-ui/icons/LockOutlined';
import CloseIcon from '@material-ui/icons/Close';
import { useTranslation } from 'react-i18next';
import Heading from '../../muiComponents/Heading';
import Avatar from '../../muiComponents/Avatar';
import Box from '../../muiComponents/Box';
import IconButton from '../../muiComponents/IconButton';
import { Theme } from '../../design-tokens/theme';
const StyledAvatar = styled(Avatar)<{ theme?: Theme }>(({ theme }) => ({
margin: theme.spacing(1),
backgroundColor: theme.palette.primary.main,
}));
const StyledIconButton = styled(IconButton)<{ theme?: Theme }>(({ theme }) => ({
position: 'absolute',
right: theme.spacing() / 2,
top: theme.spacing() / 2,
color: theme.palette.grey[500],
}));
interface Props {
onClose?: () => void;
}
const LoginDialogHeader: React.FC<Props> = ({ onClose }) => {
const { t } = useTranslation();
return (
<Box alignItems="center" display="flex" flexDirection="column" position="relative">
{onClose && (
<StyledIconButton aria-label="Close" onClick={onClose}>
<StyledIconButton aria-label={t('button.close')} onClick={onClose}>
<CloseIcon />
</StyledIconButton>
)}
<StyledAvatar>
<LockOutlined />
</StyledAvatar>
<Heading>{'Sign in'}</Heading>
<Heading>{t('button.login')}</Heading>
</Box>
);
};
export default LoginDialogHeader;
const StyledAvatar = styled(Avatar)<{ theme?: Theme }>(({ theme }) => ({
margin: theme?.spacing(1),
backgroundColor: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
color: theme?.palette.white,
}));
const StyledIconButton = styled(IconButton)<{ theme?: Theme }>(({ theme }) => ({
position: 'absolute',
right: theme?.spacing() / 2,
top: theme?.spacing() / 2,
color: theme?.palette.grey[500],
}));

View File

@ -1,31 +1,52 @@
import React from 'react';
import styled from '@emotion/styled';
import logo from './img/logo.svg';
import { Theme } from '../../design-tokens/theme';
import defaultLogo from './img/logo.svg';
import blackAndWithLogo from './img/logo-black-and-white.svg';
export enum Size {
Small = '40px',
Big = '90px',
}
const logos = {
light: defaultLogo,
dark: blackAndWithLogo,
};
const logo = window.VERDACCIO_LOGO;
interface Props {
size?: Size;
}
const StyledLogo = styled('div')<Props>(props => ({
const Logo: React.FC<Props> = ({ size = Size.Small }) => {
if (logo) {
return (
<ImageLogo>
<img alt="logo" height="40px" src={logo} />
</ImageLogo>
);
}
return <StyledLogo size={size} />;
};
export default Logo;
const ImageLogo = styled('div')({
fontSize: 0,
});
const StyledLogo = styled('div')<Props & { theme?: Theme }>(({ size, theme }) => ({
display: 'inline-block',
verticalAlign: 'middle',
boxSizing: 'border-box',
backgroundPosition: 'center',
backgroundSize: 'contain',
backgroundImage: `url(${logo})`,
backgroundImage: `url(${logos[theme?.palette.type]})`,
backgroundRepeat: ' no-repeat',
width: props.size,
height: props.size,
width: size,
height: size,
}));
const Logo: React.FC<Props> = ({ size = Size.Small }) => {
return <StyledLogo size={size} />;
};
export default Logo;

View File

@ -0,0 +1 @@
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-19.8%" y="-11.7%" width="139.6%" height="140.4%" filterUnits="objectBoundingBox" id="a"><feOffset dy="4" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0906646286 0" in="shadowBlurOuter1"/></filter><filter x="-33.9%" y="-50%" width="167.9%" height="272.7%" filterUnits="objectBoundingBox" id="c"><feOffset dy="4" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0906646286 0" in="shadowBlurOuter1"/></filter><path id="b" d="M48 16.729L32.672 47h-8.874L0 0h15.328l12.907 25.492 4.437-8.763H48z"/><path d="M35.2 11H28V8.643h8.4l1.2-2.357H32V3.929h6.8l.8-1.572H36V0h20l-5.6 11H35.2z" id="d"/></defs><g fill="none" fill-rule="evenodd"><rect fill="#000" width="100" height="100" rx="37"/><g transform="translate(22 30)"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill-opacity=".6" fill="#FFF" xlink:href="#b"/></g><g transform="translate(22 30)"><use fill="#000" filter="url(#c)" xlink:href="#d"/><use fill="#FFF" xlink:href="#d"/></g><path fill="#FFF" d="M54.785 77H45.88L22 30h15.38L58 70.718z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import Box from '../../muiComponents/Box';
import Button from '../../muiComponents/Button';
@ -9,22 +10,9 @@ import { Theme } from '../../design-tokens/theme';
import PackageImg from './img/package.svg';
export const NOT_FOUND_TEXT = "Sorry, we couldn't find it...";
export const LABEL_NOT_FOUND = "The page you're looking for doesn't exist.";
export const GO_TO_HOME_PAGE = 'Go to the home page';
const EmptyPackage = styled('img')({
width: '150px',
margin: '0 auto',
});
const StyledHeading = styled(Heading)<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.primary.main,
marginBottom: 16,
}));
const NotFound: React.FC = () => {
const history = useHistory();
const { t } = useTranslation();
const handleGoHome = useCallback(() => {
history.push('/');
@ -39,15 +27,25 @@ const NotFound: React.FC = () => {
flexGrow={1}
justifyContent="center"
p={2}>
<EmptyPackage alt="404 - Page not found" src={PackageImg} />
<EmptyPackage alt={t('error.404.page-not-found')} src={PackageImg} />
<StyledHeading className="not-found-text" variant="h4">
{NOT_FOUND_TEXT}
{t('error.404.sorry-we-could-not-find-it')}
</StyledHeading>
<Button onClick={handleGoHome} variant="contained">
{GO_TO_HOME_PAGE}
<Button data-testid="not-found-go-to-home-button" onClick={handleGoHome} variant="contained">
{t('button.go-to-the-home-page')}
</Button>
</Box>
);
};
export default NotFound;
const EmptyPackage = styled('img')({
width: '150px',
margin: '0 auto',
});
const StyledHeading = styled(Heading)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.white,
marginBottom: 16,
}));

View File

@ -3,7 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
import { render, fireEvent } from '../../utils/test-react-testing-library';
import NotFound, { GO_TO_HOME_PAGE } from './NotFound';
import NotFound from './NotFound';
describe('<NotFound /> component', () => {
test('should load the component in default state', () => {
@ -14,17 +14,17 @@ describe('<NotFound /> component', () => {
);
expect(container.firstChild).toMatchSnapshot();
});
test('go to Home Page button click', () => {
test('go to Home Page button click', async () => {
const spy = jest.spyOn(React, 'useCallback');
const { getByText } = render(
const { getByTestId } = render(
<Router>
<NotFound />
</Router>
);
const node = getByText(GO_TO_HOME_PAGE);
const node = getByTestId('not-found-go-to-home-button');
fireEvent.click(node);
expect(spy).toHaveBeenCalled();
});
});

View File

@ -27,6 +27,7 @@ exports[`<NotFound /> component should load the component in default state 1`] =
</h4>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained"
data-testid="not-found-go-to-home-button"
tabindex="0"
type="button"
>

View File

@ -1 +1 @@
export { default, NOT_FOUND_TEXT } from './NotFound';
export { default } from './NotFound';

View File

@ -2,12 +2,14 @@ import React from 'react';
import BugReport from '@material-ui/icons/BugReport';
import DownloadIcon from '@material-ui/icons/CloudDownload';
import HomeIcon from '@material-ui/icons/Home';
import { useTranslation } from 'react-i18next';
import { PackageMetaInterface, Author as PackageAuthor } from '../../../types/packageMeta';
import Tag from '../Tag';
import fileSizeSI from '../../utils/file-size';
import { formatDate, formatDateDistance } from '../../utils/package';
import { formatDate, formatDateDistance, getAuthorName } from '../../utils/package';
import Tooltip from '../../muiComponents/Tooltip';
import Link from '../Link';
import { isURL } from '../../utils/url';
import { downloadTarball } from '../ActionBar';
import ListItem from '../../muiComponents/ListItem';
@ -64,11 +66,13 @@ const Package: React.FC<PackageInterface> = ({
time,
version,
}) => {
const { t } = useTranslation();
const renderVersionInfo = (): React.ReactNode =>
version && (
<OverviewItem>
<Icon name={'version'} />
{`v${version}`}
{t('package.version', { version })}
</OverviewItem>
);
@ -77,7 +81,7 @@ const Package: React.FC<PackageInterface> = ({
<Author>
<Avatar alt={authorName} src={authorAvatar} />
<Details>
<Text text={authorName} />
<Text text={getAuthorName(authorName)} />
</Details>
</Author>
);
@ -103,7 +107,7 @@ const Package: React.FC<PackageInterface> = ({
time && (
<OverviewItem>
<Icon name="time" />
<Published>{`Published on ${formatDate(time)}`}</Published>
<Published>{t('package.published-on', { time: formatDate(time) })}</Published>
{formatDateDistance(time)}
</OverviewItem>
);
@ -111,26 +115,26 @@ const Package: React.FC<PackageInterface> = ({
const renderHomePageLink = (): React.ReactNode =>
homepage &&
isURL(homepage) && (
<a href={homepage} target={'_blank'}>
<Tooltip aria-label={'Homepage'} title={'Visit homepage'}>
<IconButton aria-label={'Homepage'}>
<Link external={true} to={homepage}>
<Tooltip aria-label={t('package.homepage')} title={t('package.visit-home-page')}>
<IconButton aria-label={t('package.homepage')}>
<HomeIcon />
</IconButton>
</Tooltip>
</a>
</Link>
);
const renderBugsLink = (): React.ReactNode =>
bugs &&
bugs.url &&
isURL(bugs.url) && (
<a href={bugs.url} target={'_blank'}>
<Tooltip aria-label={'Bugs'} title={'Open an issue'}>
<IconButton aria-label={'Bugs'}>
<Link external={true} to={bugs.url}>
<Tooltip aria-label={t('package.bugs')} title={t('package.open-an-issue')}>
<IconButton aria-label={t('package.bugs')}>
<BugReport />
</IconButton>
</Tooltip>
</a>
</Link>
);
const renderDownloadLink = (): React.ReactNode =>
@ -138,13 +142,13 @@ const Package: React.FC<PackageInterface> = ({
dist.tarball &&
isURL(dist.tarball) && (
// eslint-disable-next-line
<a onClick={downloadTarball(dist.tarball.replace(`https://registry.npmjs.org/`, window.location.href))} target={'_blank'}>
<Tooltip aria-label={'Download the tar file'} title={'Download tarball'}>
<IconButton aria-label={'Download'}>
<Link to="#" external={true} onClick={downloadTarball(dist.tarball.replace(`https://registry.npmjs.org/`, window.location.href))}>
<Tooltip aria-label={t('package.download', { what: t('package.the-tar-file') })} title={t('package.tarball')}>
<IconButton aria-label={t('package.download')}>
<DownloadIcon />
</IconButton>
</Tooltip>
</a>
</Link>
);
const renderPrimaryComponent = (): React.ReactNode => {
@ -155,7 +159,7 @@ const Package: React.FC<PackageInterface> = ({
<PackageTitle className="package-title">{packageName}</PackageTitle>
</WrapperLink>
</Grid>
<GridRightAligned item={true} xs={true}>
<GridRightAligned alignItems="center" container={true} item={true} justify="flex-end" xs={true}>
{renderHomePageLink()}
{renderBugsLink()}
{renderDownloadLink()}

View File

@ -15,7 +15,7 @@ export const OverviewItem = styled('span')<{ theme?: Theme }>(({ theme }) => ({
display: 'flex',
alignItems: 'center',
margin: '0 0 0 16px',
color: theme && theme.palette.greyLight2,
color: theme?.palette.type === 'light' ? theme?.palette.greyLight2 : theme?.palette.white,
fontSize: 12,
[`@media (max-width: ${theme && theme.breakPoints.medium}px)`]: {
':nth-of-type(3)': {
@ -29,20 +29,20 @@ export const OverviewItem = styled('span')<{ theme?: Theme }>(({ theme }) => ({
},
}));
export const Icon = styled(Ico)<{ theme?: Theme }>(props => ({
export const Icon = styled(Ico)<{ theme?: Theme }>(({ theme }) => ({
margin: '2px 10px 0 0',
fill: props.theme && props.theme.palette.greyLight2,
fill: theme?.palette.type === 'light' ? theme?.palette.greyLight2 : theme?.palette.white,
}));
export const Published = styled('span')<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.greyLight2,
export const Published = styled('span')<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.type === 'light' ? theme?.palette.greyLight2 : theme?.palette.white,
margin: '0 5px 0 0',
}));
export const Text = styled(Label)<{ theme?: Theme }>(props => ({
export const Text = styled(Label)<{ theme?: Theme }>(({ theme }) => ({
fontSize: '12px',
fontWeight: props.theme && props.theme.fontWeight.semiBold,
color: props.theme && props.theme.palette.greyLight2,
fontWeight: theme?.fontWeight.semiBold,
color: theme?.palette.type === 'light' ? theme?.palette.greyLight2 : theme?.palette.white,
}));
export const Details = styled('span')({
@ -71,11 +71,8 @@ export const PackageTitle = styled('span')<{ theme?: Theme }>(({ theme }) => ({
fontSize: 20,
display: 'block',
marginBottom: 12,
color: theme && theme.palette.eclipse,
color: theme?.palette.type == 'dark' ? theme?.palette.dodgerBlue : theme?.palette.eclipse,
cursor: 'pointer',
':hover': {
color: theme && theme.palette.black,
},
[`@media (max-width: ${theme && theme.breakPoints.small}px)`]: {
fontSize: 14,
marginBottom: 8,
@ -86,14 +83,15 @@ export const GridRightAligned = styled(Grid)({
textAlign: 'right',
});
export const PackageList = styled(List)<{ theme?: Theme }>(props => ({
export const PackageList = styled(List)<{ theme?: Theme }>(({ theme }) => ({
padding: '12px 0 12px 0',
':hover': {
backgroundColor: props.theme && props.theme.palette.greyLight3,
backgroundColor: theme?.palette?.type == 'dark' ? theme?.palette?.secondary.main : theme?.palette?.greyLight3,
},
'> :last-child': {
paddingTop: 0,
},
borderRadius: 4,
}));
export const IconButton = styled(MuiIconButton)({
@ -106,7 +104,8 @@ export const IconButton = styled(MuiIconButton)({
export const TagContainer = styled('span')<{ theme?: Theme }>(({ theme }) => ({
marginTop: 8,
marginBottom: 12,
display: 'block',
display: 'flex',
flexWrap: 'wrap',
[`@media (max-width: ${theme && theme.breakPoints.medium}px)`]: {
display: 'none',
},
@ -116,8 +115,8 @@ export const PackageListItemText = styled(ListItemText)({
paddingRight: 0,
});
export const Description = styled(Typography)<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.greyDark2,
export const Description = styled(Typography)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.type === 'light' ? theme?.palette.greyDark2 : theme?.palette.white,
fontSize: '14px',
paddingRight: 0,
}));

View File

@ -12,7 +12,9 @@ describe('<Readme /> component', () => {
test('should dangerously set html', () => {
const wrapper = mount(<Readme description="<h1>This is a test string</h1>" />);
expect(wrapper.html()).toEqual('<div class="markdown-body"><h1>This is a test string</h1></div>');
expect(wrapper.html()).toEqual(
'<div class="markdown-body css-beaqbv-Wrapper ecnabbe0"><h1>This is a test string</h1></div>'
);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@ -1,10 +1,19 @@
import React from 'react';
import 'github-markdown-css';
import styled from '@emotion/styled';
import { Theme } from '../../design-tokens/theme';
import { Props } from './types';
const Readme: React.FC<Props> = ({ description }) => (
<div className="markdown-body" dangerouslySetInnerHTML={{ __html: description }} />
<Wrapper className="markdown-body" dangerouslySetInnerHTML={{ __html: description }} />
);
export default Readme;
const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({
background: theme?.palette.white,
color: theme?.palette.black,
padding: theme?.spacing(2, 3),
}));

View File

@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Readme /> component should dangerously set html 1`] = `"<div class=\\"markdown-body\\"><h1>This is a test string</h1></div>"`;
exports[`<Readme /> component should dangerously set html 1`] = `"<div class=\\"markdown-body css-beaqbv-Wrapper ecnabbe0\\"><h1>This is a test string</h1></div>"`;
exports[`<Readme /> component should load the component in default state 1`] = `"<div class=\\"markdown-body\\">test</div>"`;
exports[`<Readme /> component should load the component in default state 1`] = `"<div class=\\"markdown-body css-beaqbv-Wrapper ecnabbe0\\">test</div>"`;

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import { css } from '@emotion/core';
import CopyToClipBoard from '../CopyToClipBoard';
import { getCLISetRegistry, getCLIChangePassword, getCLISetConfigRegistry } from '../../utils/cli-utils';
@ -48,10 +47,10 @@ const RegistryInfoContent: React.FC<Props> = props => {
return (
<>
<Tabs
color={'primary'}
data-testid={'tabs-el'}
indicatorColor="primary"
indicatorColor={'primary'}
onChange={handleChange}
textColor="primary"
value={tabPosition}
variant="fullWidth">
<Tab data-testid={'npm-tab'} label={NODE_MANAGER.npm} />
@ -69,14 +68,7 @@ const RegistryInfoContent: React.FC<Props> = props => {
const TabContainer = ({ children }): JSX.Element => {
return (
<CommandContainer>
<Typography
// className={css`
// padding: 0;
// min-height: 170;
// `}
component="div">
{children}
</Typography>
<Typography component="div">{children}</Typography>
</CommandContainer>
);
};

View File

@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../../muiComponents/Button';
import Dialog from '../../muiComponents/Dialog';
@ -7,18 +8,19 @@ import DialogActions from '../../muiComponents/DialogActions';
import { Title, Content } from './styles';
import { Props } from './types';
const LABEL = 'CLOSE';
const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose }) => (
<Dialog data-testid={'registryInfo--dialog'} id="registryInfo--dialog-container" onClose={onClose} open={open}>
<Title disableTypography={true}>{'Register Info'}</Title>
<Content>{children}</Content>
<DialogActions>
<Button color="inherit" id="registryInfo--dialog-close" onClick={onClose}>
{LABEL}
</Button>
</DialogActions>
</Dialog>
);
const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose }) => {
const { t } = useTranslation();
return (
<Dialog data-testid={'registryInfo--dialog'} id="registryInfo--dialog-container" onClose={onClose} open={open}>
<Title disableTypography={true}>{t('dialog.registry-info.title')}</Title>
<Content>{children}</Content>
<DialogActions>
<Button color="inherit" id="registryInfo--dialog-close" onClick={onClose}>
{t('button.close')}
</Button>
</DialogActions>
</Dialog>
);
};
export default RegistryInfoDialog;

View File

@ -10,6 +10,7 @@ export const Title = styled(DialogTitle)<{ theme?: Theme }>(props => ({
fontSize: props.theme && props.theme.fontSize.lg,
}));
export const Content = styled(DialogContent)({
export const Content = styled(DialogContent)<{ theme?: Theme }>(({ theme }) => ({
padding: '0 24px',
});
backgroundColor: theme?.palette.background.default,
}));

View File

@ -1,10 +1,12 @@
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import Avatar from '../../muiComponents/Avatar';
import Text from '../../muiComponents/Text';
import ListItem from '../../muiComponents/ListItem';
import ListItemText from '../../muiComponents/ListItemText';
import Link from '../Link';
import { isURL } from '../../utils/url';
import CopyToClipBoard from '../CopyToClipBoard';
import List from '../../muiComponents/List';
@ -18,8 +20,11 @@ const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
textTransform: 'capitalize',
}));
const GithubLink = styled('a')<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.primary.main,
const GithubLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.white,
':hover': {
color: theme?.palette.dodgerBlue,
},
}));
const RepositoryListItem = styled(ListItem)({
@ -44,6 +49,7 @@ const RepositoryAvatar = styled(Avatar)({
const Repository: React.FC = () => {
const detailContext = React.useContext(DetailContext);
const { t } = useTranslation();
const { packageMeta } = detailContext;
@ -64,13 +70,13 @@ const Repository: React.FC = () => {
const repositoryURL = getCorrectRepositoryURL();
return (
<List dense={true} subheader={<StyledText variant="subtitle1">{'Repository'}</StyledText>}>
<List dense={true} subheader={<StyledText variant="subtitle1">{t('sidebar.repository.title')}</StyledText>}>
<RepositoryListItem button={true}>
<RepositoryAvatar src={git} />
<RepositoryListItemText
primary={
<CopyToClipBoard text={repositoryURL}>
<GithubLink href={repositoryURL} target="_blank">
<GithubLink external={true} to={repositoryURL}>
{repositoryURL}
</GithubLink>
</CopyToClipBoard>

View File

@ -2,6 +2,7 @@ import React, { useState, FormEvent, useCallback } from 'react';
import debounce from 'lodash/debounce';
import { RouteComponentProps, withRouter } from 'react-router';
import { SuggestionSelectedEventData } from 'react-autosuggest';
import { useTranslation } from 'react-i18next';
import AutoComplete from '../AutoComplete';
import { callSearch } from '../../utils/calls';
@ -10,11 +11,11 @@ import SearchAdornment from './SearchAdornment';
const CONSTANTS = {
API_DELAY: 300,
PLACEHOLDER_TEXT: 'Search Packages',
ABORT_ERROR: 'AbortError',
};
const Search: React.FC<RouteComponentProps> = ({ history }) => {
const { t } = useTranslation();
const [suggestions, setSuggestions] = useState([]);
const [loaded, setLoaded] = useState(false);
const [search, setSearch] = useState('');
@ -141,7 +142,7 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
onCleanSuggestions={handlePackagesClearRequested}
onClick={handleClickSearch}
onSuggestionsFetch={debounce(handleFetchPackages, CONSTANTS.API_DELAY)}
placeholder={CONSTANTS.PLACEHOLDER_TEXT}
placeholder={t('search.packages')}
startAdornment={<SearchAdornment />}
suggestions={suggestions}
suggestionsError={error}

View File

@ -19,6 +19,6 @@ export const Wrapper = styled('div')<WrapperProps>(props => ({
}),
}));
export const Circular = styled(CircularProgress)<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.primary.main,
export const Circular = styled(CircularProgress)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.type === 'dark' ? theme?.palette.white : theme?.palette.primary.main,
}));

View File

@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import NoItems from '../NoItems';
@ -10,6 +11,7 @@ import { StyledText, Spacer, ListItemText } from './styles';
const UpLinks: React.FC = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
if (!packageMeta || !packageMeta._uplinks || !packageMeta.latest) {
return null;
@ -18,12 +20,12 @@ const UpLinks: React.FC = () => {
const { _uplinks: uplinks, latest } = packageMeta;
if (Object.keys(uplinks).length === 0) {
return <NoItems text={`${latest.name} has no uplinks.`} />;
return <NoItems text={t('uplinks.no-items', { name: latest.name })} />;
}
return (
<>
<StyledText variant="subtitle1">{'Uplinks'}</StyledText>
<StyledText variant="subtitle1">{t('uplinks.title')}</StyledText>
<List>
{Object.keys(uplinks)
.reverse()

View File

@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<UpLinks /> component should render the component when there is no uplink 1`] = `"<h6 class=\\"MuiTypography-root MuiTypography-subtitle1 MuiTypography-gutterBottom\\">verdaccio has no uplinks.</h6>"`;
exports[`<UpLinks /> component should render the component when there is no uplink 1`] = `"<h6 class=\\"MuiTypography-root MuiTypography-subtitle1 MuiTypography-gutterBottom\\">uplinks.no-items</h6>"`;
exports[`<UpLinks /> component should render the component with uplinks 1`] = `"<h6 class=\\"MuiTypography-root css-5wp24z-StyledText e14i1sy10 MuiTypography-subtitle1\\">Uplinks</h6><ul class=\\"MuiList-root MuiList-padding\\"><li class=\\"MuiListItem-root MuiListItem-gutters\\"><div class=\\"MuiListItemText-root css-1pxn9ma-ListItemText e14i1sy12\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">npmjs</span></div><div class=\\"css-t1rp47-Spacer e14i1sy11\\"></div><div class=\\"MuiListItemText-root css-1pxn9ma-ListItemText e14i1sy12\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">2 years ago</span></div></li></ul>"`;
exports[`<UpLinks /> component should render the component with uplinks 1`] = `"<h6 class=\\"MuiTypography-root css-5wp24z-StyledText e14i1sy10 MuiTypography-subtitle1\\">uplinks.title</h6><ul class=\\"MuiList-root MuiList-padding\\"><li class=\\"MuiListItem-root MuiListItem-gutters\\"><div class=\\"MuiListItemText-root css-1ork7s0-ListItemText e14i1sy12\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">npmjs</span></div><div class=\\"css-4lqckn-Spacer e14i1sy11\\"></div><div class=\\"MuiListItemText-root css-1ork7s0-ListItemText e14i1sy12\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">2 years ago</span></div></li></ul>"`;

View File

@ -8,15 +8,15 @@ export const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
}));
export const Spacer = styled('div')({
export const Spacer = styled('div')<{ theme?: Theme }>(({ theme }) => ({
flex: '1 1 auto',
borderBottom: '1px dotted rgba(0, 0, 0, 0.2)',
borderBottom: `1px dotted ${theme?.palette.type == 'light' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'} `,
whiteSpace: 'nowrap',
height: '0.5em',
});
}));
export const ListItemText = styled(MuiListItemText)({
export const ListItemText = styled(MuiListItemText)<{ theme?: Theme }>(({ theme }) => ({
flex: 'none',
color: 'black',
color: theme?.palette.type == 'light' ? theme?.palette.black : theme?.palette.white,
opacity: 0.6,
});
}));

View File

@ -4,8 +4,9 @@ import { MemoryRouter } from 'react-router-dom';
import { render, cleanup } from '../../utils/test-react-testing-library';
import { mount } from '../../utils/test-enzyme';
import { DetailContext, DetailContextProps } from '../../pages/Version';
import translationEN from '../../../i18n/translations/en-US.json';
import Versions, { LABEL_CURRENT_TAGS, LABEL_VERSION_HISTORY } from './Versions';
import Versions from './Versions';
import data from './__partials__/data.json';
const detailContextValue: Partial<DetailContextProps> = {
@ -35,8 +36,8 @@ describe('<Version /> component', () => {
test('should render versions', () => {
const { getByText } = render(<ComponentToBeRendered contextValue={detailContextValue} />);
expect(getByText(LABEL_VERSION_HISTORY)).toBeTruthy();
expect(getByText(LABEL_CURRENT_TAGS)).toBeTruthy();
expect(getByText(translationEN.versions['version-history'])).toBeTruthy();
expect(getByText(translationEN.versions['current-tags'])).toBeTruthy();
// pick some versions
expect(getByText('2.3.0')).toBeTruthy();
@ -48,8 +49,8 @@ describe('<Version /> component', () => {
<ComponentToBeRendered contextValue={{ packageName: detailContextValue.packageName }} />
);
expect(queryByText(LABEL_VERSION_HISTORY)).toBeFalsy();
expect(queryByText(LABEL_CURRENT_TAGS)).toBeFalsy();
expect(queryByText(translationEN.versions['version-history'])).toBeFalsy();
expect(queryByText(translationEN.versions['current-tags'])).toBeFalsy();
});
test.todo('should click on version link');

View File

@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import { DIST_TAGS } from '../../../lib/constants';
@ -7,12 +8,9 @@ import { StyledText } from './styles';
import VersionsTagList from './VersionsTagList';
import VersionsHistoryList from './VersionsHistoryList';
export const NOT_AVAILABLE = 'Not available';
export const LABEL_CURRENT_TAGS = 'Current Tags';
export const LABEL_VERSION_HISTORY = 'Version History';
const Versions: React.FC = () => {
const detailContext = useContext(DetailContext);
const { t } = useTranslation();
const { packageMeta, packageName } = detailContext;
@ -26,13 +24,13 @@ const Versions: React.FC = () => {
<>
{distTags && Object.keys(distTags).length > 0 && (
<>
<StyledText variant="subtitle1">{LABEL_CURRENT_TAGS}</StyledText>
<StyledText variant="subtitle1">{t('versions.current-tags')}</StyledText>
<VersionsTagList tags={distTags} />
</>
)}
{versions && Object.keys(versions).length > 0 && packageName && (
<>
<StyledText variant="subtitle1">{LABEL_VERSION_HISTORY}</StyledText>
<StyledText variant="subtitle1">{t('versions.version-history')}</StyledText>
<VersionsHistoryList packageName={packageName} time={time} versions={versions} />
</>
)}

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