Add ts-packages for shared Typescript packages using yarn workspaces

This commit is contained in:
Mark Sinclair
2022-03-03 15:23:16 +00:00
parent 8ec3c04a39
commit 31594c7a79
79 changed files with 33816 additions and 7 deletions
+1
View File
@@ -36,3 +36,4 @@ contracts/mixnet/Makefile
validator-config
*.patch
validator-api-config.toml
dist
+2
View File
@@ -28,6 +28,8 @@ Wallet build instructions are also available on [our docs site](https://nymtech.
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#FC1D60"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
@font-face {
font-family: 'Open Sans';
src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'),
url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations');
font-weight: 100 1000;
}
+10
View File
@@ -0,0 +1,10 @@
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#F51473"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 714 B

+13
View File
@@ -0,0 +1,13 @@
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#FC1D60"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z" fill="white"/>
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z" fill="white"/>
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

-6
View File
@@ -89,12 +89,6 @@
"lint": "eslint src",
"lint:fix": "eslint src --fix"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
+6
View File
@@ -0,0 +1,6 @@
{
"packages": [
"ts-packages/*"
],
"version": "0.0.0"
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@nymproject/nymsphere",
"version": "1.0.0",
"private": true,
"license": "Apache 2.0",
"workspaces": [
"ts-packages/*",
"nym-wallet"
],
"scripts": {
"build": "lerna run --scope @nymproject/mui-theme --scope @nymproject/react build --stream"
},
"devDependencies": {
"lerna": "^4.0.0"
}
}
+18
View File
@@ -0,0 +1,18 @@
# See http://editorconfig.org/
# EditorConfig helps developers define and maintain consistent coding styles
# between different editors and IDEs. The EditorConfig project consists of a
# file format for defining coding styles and a collection of text editor plugins
# that enable editors to read the file format and adhere to defined styles.
# EditorConfig files are easily readable and they work nicely with version
# control systems.
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
+6
View File
@@ -0,0 +1,6 @@
{
"root": true,
"extends": [
"@nymproject/eslint-config-react-typescript"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2
}
+55
View File
@@ -0,0 +1,55 @@
# Nym Typescript (and Javascript) packages
This directory has shared Typescript (and Javascript) libraries using `yarn workspaces`.
## Why `ts-packages`?
Naming stuff is hard. The `ts-` part means Typescript, because this monorepo also contains Rust crates. So we needed some way to indicate: "put your Typescript here".
Now you know! So, please, put your Typescript here. And your Javascript.
## How does it work?
In the root of this repository is [package.json](../package.json) that specifies an array of globs for packages that are shared:
```
{
"name": "@nymproject/nymsphere",
"version": "1.0.0",
"private": true,
"license": "Apache 2.0",
"workspaces": ["ts-packages/*", "nym-wallet"] <-------
}
```
There are some caveats:
- this only works with `yarn` and not `npm`
- `yarn` creates a single `node_modules` in the root for shared dependencies
- packages that use shared packages, need to be in a path specified in `workspaces`
- local packages take precedence over published packages on `npm`
## Building
From the [root of the repository](../README.md) run:
```
yarn
yarn build
```
This will build all libraries.
Now you can try out [react-webpack-with-theme-example](./react-webpack-with-theme-example) by running:
```
cd ts-packages/react-webpack-with-theme-example
yarn start
```
Our React components have a Storybook in [react-components](./react-components):
```
cd ts-packages/react-components
yarn storybook
```
@@ -0,0 +1,87 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true,
jest: true,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2019,
sourceType: 'module',
},
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
plugins: ['react', 'react-hooks', 'jsx-a11y', 'prettier', 'jest'],
extends: ['plugin:react/recommended', 'airbnb', 'prettier', 'plugin:jest/recommended', 'plugin:jest/style'],
ignorePatterns: ['dist/**/*', 'dist'],
rules: {
'jest/prefer-strict-equal': 'error',
'jest/prefer-to-have-length': 'warn',
'prettier/prettier': 'error',
'import/prefer-default-export': 'off',
'react/prop-types': 'off',
'react/jsx-filename-extension': 'off',
'react/jsx-props-no-spreading': 'off',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.test.[jt]s', '**/*.spec.[jt]s', '**/*.test.[jt]sx', '**/*.spec.[jt]sx'],
},
],
'import/extensions': [
'error',
'ignorePackages',
{
ts: 'never',
tsx: 'never',
js: 'never',
jsx: 'never',
},
],
},
overrides: [
{
files: '**/*.+(ts|tsx)',
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'no-use-before-define': [0],
'@typescript-eslint/no-use-before-define': [1],
'import/no-unresolved': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: ['**/*.test.ts', '**/*.spec.ts', '**/*.test.tsx', '**/*.spec.tsx'],
},
],
quotes: 'off',
'@typescript-eslint/quotes': [
2,
'single',
{
avoidEscape: true,
},
],
'@typescript-eslint/no-unused-vars': [2, { argsIgnorePattern: '^_' }],
},
},
],
settings: {
'import/resolver': {
'root-import': {
rootPathPrefix: '@',
rootPathSuffix: 'src',
extensions: ['.js', '.ts', '.tsx', '.jsx', '.mdx'],
},
},
},
};
@@ -0,0 +1,59 @@
{
"name": "@nymproject/eslint-config-react-typescript",
"description": "Import the default eslint config for nym",
"version": "1.0.0",
"license": "Apache-2.0",
"main": "index.js",
"peerDependencies": {
"react": "17",
"react-dom": "17",
"eslint": ">= 8",
"@typescript-eslint/eslint-plugin": ">= 5",
"@typescript-eslint/parser": ">= 5",
"eslint-config-airbnb": ">= 19",
"eslint-config-prettier": ">= 8",
"eslint-import-resolver-root-import": ">= 1",
"eslint-plugin-import": ">= 2",
"eslint-plugin-jest": ">= 26.1.1",
"eslint-plugin-jsx-a11y": ">= 6",
"eslint-plugin-prettier": ">= 4",
"eslint-plugin-react": ">= 7",
"eslint-plugin-react-hooks": ">= 4"
},
"devDependencies": {
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.5.1",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"typescript": "^4.6.2",
"ts-jest": "^27.0.5",
"jest": "^27.1.0",
"babel-plugin-root-import": "^5.1.0"
},
"eslintConfig" : {
"plugins": ["prettier"],
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"rules": {
"prettier/prettier": "error"
},
"root": true,
"env": {
"browser": false,
"es6": false,
"node": true
}
},
"scripts": {
"lint": "eslint *.js",
"lint:fix": "eslint *.js --fix"
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@nymproject/mui-theme",
"version": "1.0.0",
"license": "Apache-2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"react": "17",
"react-dom": "17",
"@mui/material": "^5.0.1",
"@mui/styles": "^5.0.1"
},
"devDependencies": {
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"prettier": "^2.5.1",
"typescript": "^4.6.2",
"ts-jest": "^27.0.5",
"jest": "^27.1.0",
"babel-plugin-root-import": "^5.1.0",
"@types/react": "^17.0.34",
"rimraf": "^3.0.2"
},
"scripts": {
"clean": "rimraf dist",
"build": "tsc --noEmit false",
"watch": "tsc --noEmit false -w",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
}
}
+4
View File
@@ -0,0 +1,4 @@
// defines the OpenSans font and processes the font files with Webpack
import '@assets/fonts/fonts.css';
export * from './theme';
+152
View File
@@ -0,0 +1,152 @@
import { PaletteMode } from '@mui/material';
import { PaletteOptions } from '@mui/material/styles';
/**
* This interface defines a palette used across Nym for branding
*/
export interface NymPalette {
highlight: string;
status: {
success: string;
info: string;
};
light: string;
dark: string;
muted: {
onDarkBg: string;
};
}
/**
* This interface defines the palette for a light or dark mode variant
*/
export interface NymPaletteVariant {
mode: PaletteMode;
background: {
main: string;
paper: string;
};
text: {
main: string;
};
topNav: {
background: string;
};
nav: {
text: string;
background: string;
hover: string;
};
mixnodes: {
status: {
active: string;
standby: string;
};
};
}
// -------------------------------------------------------------------------------------------------------------------
/**
* The Nym palette.
*
* IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct
* colours for dark/light mode.
*/
export const nymPalette: NymPalette = {
/** emphasises important elements */
highlight: '#FB6E4E',
/** statuses */
status: {
success: '#21D073',
info: '#60D7EF',
},
/** light and dark base values */
light: '#F4F6F8',
dark: '#121726',
/** muted on backgrounds */
muted: {
onDarkBg: '#666B77',
},
};
// -------------------------------------------------------------------------------------------------------------------
/**
* Dark mode variant
*/
export const darkMode: NymPaletteVariant = {
mode: 'dark',
background: {
main: nymPalette.dark,
paper: '#242C3D',
},
text: {
main: '#F2F2F2',
},
topNav: {
background: '#111826',
},
nav: {
text: '#F2F2F2',
background: '#242C3D',
hover: '#111826',
},
mixnodes: {
status: {
active: '#20D073',
standby: '#5FD7EF',
},
},
};
/**
* Light mode variant
*/
export const lightMode: NymPaletteVariant = {
mode: 'light',
background: {
main: '#F2F2F2',
paper: '#FFFFFF',
},
text: {
main: '#121726',
},
topNav: {
background: '#111826',
},
nav: {
text: '#F2F2F2',
background: '#242C3D',
hover: '#111826',
},
mixnodes: {
status: {
active: '#1CBB67',
standby: '#55C1D7',
},
},
};
/**
* Map a Nym palette variant onto the MUI palette
*/
export const variantToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({
text: {
primary: variant.text.main,
},
primary: {
main: nymPalette.highlight,
contrastText: '#fff',
},
background: {
default: variant.background.main,
paper: variant.background.paper,
},
});
+22
View File
@@ -0,0 +1,22 @@
import * as React from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { CssBaseline, PaletteMode } from '@mui/material';
import { getDesignTokens } from './theme';
/**
* Provides the theme for the Nym Components by reacting to the light/dark mode choice.
*
*/
export const NymThemeProvider: React.FC<{ mode: PaletteMode }> = ({ mode, children }) => {
const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
);
};
export type { NymPalette, NymPaletteVariant } from './common';
export type { NymTheme, NymPaletteWithExtensions, NymPaletteWithExtensionsOptions } from './theme';
@@ -0,0 +1,85 @@
import { darkMode, nymPalette, NymPaletteVariant } from './common';
/**
* A palette definition only for the Network Explorer that extends the Nym palette
*/
export interface NetworkExplorerPalette {
networkExplorer: {
map: {
stroke: string;
fills: string[];
};
background: {
tertiary: string;
};
topNav: {
background: string;
socialIcons: string;
appBar: string;
};
nav: {
selected: {
main: string;
nested: string;
};
background: string;
hover: string;
text: string;
};
footer: {
socialIcons: string;
};
mixnodes: {
status: {
active: string;
standby: string;
inactive: string;
};
};
};
}
/**
* Nym palette specific to the Network Explorer
*
* IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct
* colours for dark/light mode.
*/
export const networkExplorerPalette = (variant: NymPaletteVariant): NetworkExplorerPalette => ({
networkExplorer: {
/** world map styles */
map: {
stroke: '#333333',
fills: ['rgba(255,255,255,0.2)', '#EFEFEF', '#FBE7E1', '#F7D1C6', '#F09379'],
},
background: {
tertiary: variant.mode === 'light' ? '#F4F8FA' : '#323C51',
},
/** left nav styles */
nav: {
selected: {
main: '#111826',
nested: '#3C4558',
},
background: variant.nav.background,
hover: variant.nav.hover,
text: variant.nav.text,
},
topNav: {
...variant.topNav,
appBar: '#080715',
socialIcons: '#F2F2F2',
},
footer: {
socialIcons: variant.mode === 'light' ? nymPalette.muted.onDarkBg : darkMode.text.main,
},
mixnodes: {
status: {
active: variant.mixnodes.status.active,
standby: variant.mixnodes.status.standby,
inactive: variant.text.main,
},
},
},
});
+132
View File
@@ -0,0 +1,132 @@
import { createTheme, Palette, PaletteOptions, ThemeOptions } from '@mui/material/styles';
import { PaletteMode } from '@mui/material';
import { darkMode, lightMode, nymPalette, NymPalette, variantToMUIPalette } from './common';
import { NymWalletPalette, nymWalletPallete } from './wallet';
import { networkExplorerPalette, NetworkExplorerPalette } from './network-explorer';
/**
* To use the theme, copy the file in `../../template/mui-theme.d.ts` into `src/typings/mui-theme.d.ts` in your project.
*
* This will augment the types for `Theme` from `@mui/material/styles` with Nym theme types.
*/
/**
* "Namespace" in MUI palette for Nym that is a union of the base palette and product palettes
*/
export interface NymPaletteWithExtensions {
nym: NymPalette & NymWalletPalette & NetworkExplorerPalette;
}
/**
* Add anything Nym specific to the MUI theme.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface NymTheme {
palette: Palette & NymPaletteWithExtensions;
}
/**
* Type use by MUI's `createTheme` method
*/
export type NymPaletteWithExtensionsOptions = Partial<NymPaletteWithExtensions>;
/**
* Returns the Nym palette for light mode.
*/
const createLightModePalette = (): PaletteOptions & NymPaletteWithExtensionsOptions => ({
nym: {
...nymPalette,
...nymWalletPallete(lightMode),
...networkExplorerPalette(lightMode),
},
...variantToMUIPalette(lightMode),
});
/**
* Returns the Nym palette for dark mode.
*/
const createDarkModePalette = (): PaletteOptions & NymPaletteWithExtensionsOptions => ({
nym: {
...nymPalette,
...nymWalletPallete(darkMode),
...networkExplorerPalette(darkMode),
},
...variantToMUIPalette(darkMode),
});
/**
* Gets the theme options to be passed to `createTheme`.
*
* Based on pattern from https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette.
*
* @param mode The theme mode: 'light' or 'dark'
*/
export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
// first, create the palette from user's choice of light or dark mode
const { palette } = createTheme({
palette: {
mode,
...(mode === 'light' ? createLightModePalette() : createDarkModePalette()),
},
});
// then customise theme and components
return {
typography: {
fontFamily: [
'Open Sans',
'sans-serif',
'BlinkMacSystemFont',
'Roboto',
'Oxygen',
'Ubuntu',
'Helvetica Neue',
].join(','),
fontSize: 14,
fontWeightRegular: 500,
button: {
textTransform: 'none',
fontWeight: '600',
},
},
shape: {
borderRadius: 8,
},
transitions: {
duration: {
shortest: 150,
shorter: 200,
short: 250,
standard: 300,
complex: 375,
enteringScreen: 225,
leavingScreen: 195,
},
easing: {
easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
},
},
components: {
MuiButton: {
styleOverrides: {
sizeLarge: {
height: 55,
},
},
},
MuiStepIcon: {
styleOverrides: {
root: {
'&.Mui-completed': {
color: nymPalette.status.success,
},
'&.Mui-active': {
color: nymPalette.dark,
},
},
},
},
},
palette,
};
};
+16
View File
@@ -0,0 +1,16 @@
import { NymPaletteVariant } from './common';
/**
* This interface defines a palette used by the Nym wallet
*/
export interface NymWalletPalette {
wallet: {
fee: string;
};
}
export const nymWalletPallete = (_variant: NymPaletteVariant): NymWalletPalette => ({
wallet: {
fee: '#967FF0',
},
});
+44
View File
@@ -0,0 +1,44 @@
/* eslint-disable no-shadow,@typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface,import/no-extraneous-dependencies */
import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles';
import { PaletteMode } from '@mui/material';
/**
* If you are unfamiliar with Material UI theming, please read the following first:
* - https://mui.com/customization/theming/
* - https://mui.com/customization/palette/
* - https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette
*
* This file adds typings to the theme using Typescript's module augmentation.
*
* Read the following if you are unfamiliar with module augmentation and declaration merging. Then
* look at the recommendations from Material UI docs for implementation:
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
* - https://mui.com/customization/palette/#adding-new-colors
*
*
* IMPORTANT:
*
* The type augmentation must match MUI's definitions. So, notice the use of `interface` rather than
* `type Foo = { ... }` - this is necessary to merge the definitions.
*/
declare module '@mui/material/styles' {
import {
NymTheme,
NymPalette,
NymPaletteWithExtensions,
NymPaletteWithExtensionsOptions,
} from '@nymproject/mui-theme';
/**
* This augments the definitions of the MUI Theme with the Nym theme, as well as
* a partial `ThemeOptions` type used by `createTheme`
*
* IMPORTANT: only add extensions to the interfaces above, do not modify the lines below
*/
interface Theme extends NymTheme {}
interface ThemeOptions extends Partial<NymTheme> {}
interface Palette extends NymPaletteWithExtensions {}
interface PaletteOptions extends NymPaletteWithExtensionsOptions {}
}
+17
View File
@@ -0,0 +1,17 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist",
"jsx": "react-jsx",
"noEmit": true
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"dist"
]
}
@@ -0,0 +1,46 @@
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
"framework": "@storybook/react",
"core": {
"builder": "webpack5"
},
webpackFinal: async (config, { configType }) => {
// `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
// You can change the configuration based on that.
// 'PRODUCTION' is used when building the static version of storybook.
config.module.rules.forEach(rule => {
// look for SVG import rule and replace
// NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
if(rule.test?.toString().includes('svg')) {
rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
}
});
// handle asset loading with this
config.module.rules.unshift({
test: /\.svg(\?.*)?$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
});
config.resolve.extensions = ['.tsx', '.ts', '.js'];
config.resolve.plugins = [new TsconfigPathsPlugin()];
// Return the altered config
return config;
},
features: {
emotionAlias: false,
},
}
@@ -0,0 +1,9 @@
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
+62
View File
@@ -0,0 +1,62 @@
{
"name": "@nymproject/react",
"version": "1.0.0",
"license": "Apache-2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"@mui/material": ">= 5",
"@mui/styles": ">= 5",
"@mui/system": ">= 5",
"@nymproject/mui-theme": "1",
"react": "17",
"react-dom": "17"
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@mui/material": "^5.0.1",
"@mui/styles": "^5.0.1",
"@mui/system": "^5.0.1",
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@storybook/addon-actions": "^6.4.19",
"@storybook/addon-essentials": "^6.4.19",
"@storybook/addon-interactions": "^6.4.19",
"@storybook/addon-links": "^6.4.19",
"@storybook/builder-webpack5": "^6.4.19",
"@storybook/manager-webpack5": "^6.4.19",
"@storybook/react": "^6.4.19",
"@storybook/testing-library": "^0.0.9",
"@svgr/webpack": "^6.1.1",
"@types/react": "^17.0.39",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"babel-loader": "^8.2.3",
"babel-plugin-root-import": "^5.1.0",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"jest": "^27.1.0",
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.2"
},
"scripts": {
"clean": "rimraf dist",
"build": "tsc --noEmit false",
"watch": "tsc --noEmit false -w",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"storybook": "start-storybook -p 6006",
"storybook:build": "build-storybook"
},
"dependencies": {}
}
@@ -0,0 +1 @@
export * from './logo';
@@ -0,0 +1,4 @@
export interface LogoProps {
height?: number | string;
width?: number | string;
}
@@ -0,0 +1,5 @@
import * as React from 'react';
import Logo from '@assets/logo/logo-circle.svg';
import { LogoProps } from './LogoProps';
export const NymLogo: React.FC<LogoProps> = ({ height, width }) => <Logo height={height} width={width} />;
@@ -0,0 +1,5 @@
import * as React from 'react';
import Wordmark from '@assets/logo/logo-wordmark.svg';
import { LogoProps } from './LogoProps';
export const NymWordmark: React.FC<LogoProps> = ({ height, width }) => <Wordmark height={height} width={width} />;
@@ -0,0 +1,2 @@
export * from './NymLogo';
export * from './NymWordmark';
@@ -0,0 +1 @@
export * from './useIsMounted';
@@ -0,0 +1,14 @@
import { useRef, useEffect, useCallback } from 'react';
export function useIsMounted(): () => boolean {
const ref = useRef(false);
useEffect(() => {
ref.current = true;
return () => {
ref.current = false;
};
}, []);
return useCallback(() => ref.current, [ref]);
}
@@ -0,0 +1,3 @@
export * from './components';
export * from './hooks';
export { Playground } from './playground';
@@ -0,0 +1,10 @@
import { Button, Stack } from '@mui/material';
import * as React from 'react';
export const PlaygroundButtons: React.FC = () => (
<Stack spacing={2} direction="row">
<Button variant="text">Text</Button>
<Button variant="contained">Contained</Button>
<Button variant="outlined">Outlined</Button>
</Stack>
);
@@ -0,0 +1,15 @@
import * as React from 'react';
import Checkbox from '@mui/material/Checkbox';
const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
export const PlaygroundCheckboxes: React.FC = function () {
return (
<div>
<Checkbox {...label} defaultChecked />
<Checkbox {...label} />
<Checkbox {...label} disabled />
<Checkbox {...label} disabled checked />
</div>
);
};
@@ -0,0 +1,19 @@
import * as React from 'react';
const CONTENT = 'The quick brown fox jumped over the white fence';
const WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
export const PlaygroundFonts: React.FC = () => (
<div style={{ fontFamily: 'Open Sans' }}>
{WEIGHTS.map((fontWeight) => (
<div key={`weight-${fontWeight}`}>
<div style={{ fontWeight, fontSize: '30px' }}>{CONTENT}</div>
<div>
<code>Font weight: {fontWeight}</code>
</div>
<hr />
</div>
))}
</div>
);
@@ -0,0 +1,21 @@
import * as React from 'react';
import { PlaygroundButtons } from './buttons';
import { PlaygroundCheckboxes } from './checkboxes';
import { PlaygroundBasicSwitches } from './switches';
import { PlaygroundFonts } from './fonts';
export const Playground: React.FC = () => (
<>
<h2>Buttons</h2>
<PlaygroundButtons />
<h2>Checkboxes</h2>
<PlaygroundCheckboxes />
<h2>Switches</h2>
<PlaygroundBasicSwitches />
<h2>Fonts</h2>
<PlaygroundFonts />
</>
);
@@ -0,0 +1,15 @@
import * as React from 'react';
import Switch from '@mui/material/Switch';
const label = { inputProps: { 'aria-label': 'Switch demo' } };
export const PlaygroundBasicSwitches: React.FC = function () {
return (
<div>
<Switch {...label} defaultChecked />
<Switch {...label} />
<Switch {...label} disabled defaultChecked />
<Switch {...label} disabled />
</div>
);
};
@@ -0,0 +1,10 @@
import { Meta } from '@storybook/addon-docs';
import { NymLogo } from '../components';
<Meta title="Introduction" />
<NymLogo height={100} />
# Nym Design System
This is the Nym design system.
@@ -0,0 +1,21 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Box } from '@mui/material';
import { NymLogo, NymWordmark } from '../components';
export default {
title: 'Logo/Nym Logo',
component: NymLogo,
} as ComponentMeta<typeof NymLogo>;
export function Logo() {
return <NymLogo height={250} />;
}
export function Wordmark() {
return (
<div style={{ background: '#888', padding: '2rem' }}>
<NymWordmark height={250} />
</div>
);
}
@@ -0,0 +1,25 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { NymThemeProvider } from '@nymproject/mui-theme';
import { Playground } from '../playground';
export default {
title: 'Playground',
component: Playground,
} as ComponentMeta<typeof Playground>;
export function LightMode() {
return (
<NymThemeProvider mode="light">
<Playground />
</NymThemeProvider>
);
}
export function DarkMode() {
return (
<NymThemeProvider mode="dark">
<Playground />
</NymThemeProvider>
);
}
+37
View File
@@ -0,0 +1,37 @@
/* eslint-disable no-shadow,@typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface,import/no-extraneous-dependencies */
import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles';
import { NymTheme, NymPaletteWithExtensions, NymPaletteWithExtensionsOptions } from '@nymproject/mui-theme';
/**
* If you are unfamiliar with Material UI theming, please read the following first:
* - https://mui.com/customization/theming/
* - https://mui.com/customization/palette/
* - https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette
*
* This file adds typings to the theme using Typescript's module augmentation.
*
* Read the following if you are unfamiliar with module augmentation and declaration merging. Then
* look at the recommendations from Material UI docs for implementation:
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
* - https://mui.com/customization/palette/#adding-new-colors
*
*
* IMPORTANT:
*
* The type augmentation must match MUI's definitions. So, notice the use of `interface` rather than
* `type Foo = { ... }` - this is necessary to merge the definitions.
*/
declare module '@mui/material/styles' {
/**
* This augments the definitions of the MUI Theme with the Nym theme, as well as
* a partial `ThemeOptions` type used by `createTheme`
*
* IMPORTANT: only add extensions to the interfaces above, do not modify the lines below
*/
interface Theme extends NymTheme {}
interface ThemeOptions extends Partial<NymTheme> {}
interface Palette extends NymPaletteWithExtensions {}
interface PaletteOptions extends NymPaletteWithExtensionsOptions {}
}
+9
View File
@@ -0,0 +1,9 @@
declare module '*.jpeg' {
const value: any;
export default value;
}
declare module '*.jpg' {
const value: any;
export default value;
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.json' {
const content: any;
export default content;
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.png' {
const content: any;
export default content;
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.svg' {
const content: any;
export default content;
}
@@ -0,0 +1,19 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"noEmit": true,
"outDir": "./dist",
"declarationDir": "./dist"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"dist",
"src/stories",
"**/*.stories.*"
]
}
@@ -0,0 +1,3 @@
{
"presets": ["@babel/env", "@babel/react"]
}
@@ -0,0 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
@@ -0,0 +1,79 @@
{
"name": "@nymproject/react-webpack-with-theme-example",
"description": "An example project that uses React, Webpack, Typescript and the Nym theme + components library",
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"@mui/material": "^5.0.1",
"@mui/styles": "^5.0.1",
"@nymproject/mui-theme" : "^1.0.0",
"@nymproject/react" : "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@svgr/webpack": "^6.1.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/react": "^17.0.34",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"babel-loader": "^8.2.2",
"babel-plugin-root-import": "^5.1.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"prettier": "^2.5.1",
"react-refresh-typescript": "^2.0.3",
"style-loader": "^3.2.1",
"ts-jest": "^27.0.5",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.2",
"url-loader": "^4.1.1",
"webpack": "^5.64.3",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
},
"scripts": {
"start": "webpack serve --progress --port 3000",
"build": "webpack build --progress --config webpack.prod.js",
"build:dev": "webpack build --progress",
"build:serve": "npx serve dist",
"test": "jest",
"test:watch": "jest --watch",
"tsc": "tsc",
"tsc:watch": "tsc --watch",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
}
}
@@ -0,0 +1,72 @@
import * as React from 'react';
import { Box, Container, Grid, Typography } from '@mui/material';
import { NymLogo, Playground } from '@nymproject/react';
import { NymThemeProvider } from '@nymproject/mui-theme';
import { useTheme } from '@mui/material/styles';
import { ThemeToggle } from './ThemeToggle';
import { AppContextProvider, useAppContext } from './context';
export const App: React.FC = () => (
<AppContextProvider>
<AppTheme>
<Content />
</AppTheme>
</AppContextProvider>
);
export const AppTheme: React.FC = ({ children }) => {
const { mode } = useAppContext();
return <NymThemeProvider mode={mode}>{children}</NymThemeProvider>;
};
export const Content: React.FC = () => {
const { mode } = useAppContext();
const theme = useTheme();
const swatches: Record<string, string> = {
'palette.primary.main': theme.palette.primary.main,
'palette.secondary.main': theme.palette.secondary.main,
'palette.info.main': theme.palette.info.main,
'palette.success.main': theme.palette.success.main,
'palette.text.primary': theme.palette.text.primary,
'theme.palette.nym.networkExplorer.mixnodes.status.active':
theme.palette.nym.networkExplorer.mixnodes.status.active,
'theme.palette.nym.networkExplorer.mixnodes.status.standby':
theme.palette.nym.networkExplorer.mixnodes.status.standby,
};
return (
<Container sx={{ py: 4 }}>
<Box display="flex" flexDirection="row-reverse" pb={2}>
<ThemeToggle />
</Box>
<NymLogo height={50} />
<h1>Example App</h1>
<Box mb={10}>
<Typography sx={{ color: (theme) => theme.palette.nym.networkExplorer.mixnodes.status.active }}>
This is an example app that uses React, Typescript, Webpack and the Nym theme + components.
</Typography>
<h4>Some colours from the theme (mode = {mode}) are:</h4>
<Grid container spacing={2}>
{Object.keys(swatches).map((key) => (
<Grid item key={key}>
<Box display="flex" alignItems="center">
<svg height="50px" width="50px">
<rect width="100%" height="100%" fill={swatches[key]} />
</svg>
<Typography mx={2}>
<code>{swatches[key]}</code>
<br />
<code>{key}</code>
</Typography>
</Box>
</Grid>
))}
</Grid>
</Box>
<h1>Component playground</h1>
<Playground />
</Container>
);
};
@@ -0,0 +1,21 @@
import * as React from 'react';
import { Button, Typography } from '@mui/material';
import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
import { useAppContext } from './context';
export const ThemeToggle: React.FC = () => {
const { mode, toggleMode } = useAppContext();
return (
<Button variant="outlined" color="secondary" onClick={toggleMode} sx={{ display: 'flex', alignItems: 'centre' }}>
{mode === 'dark' ? (
<DarkModeIcon sx={{ color: (theme) => theme.palette.text.secondary }} />
) : (
<LightModeIcon sx={{ color: (theme) => theme.palette.text.secondary }} />
)}
<Typography ml={1} color={(theme) => theme.palette.primary.light}>
Switch to {mode === 'dark' ? 'light mode' : 'dark mode'}
</Typography>
</Button>
);
};
@@ -0,0 +1,34 @@
import { PaletteMode } from '@mui/material';
import * as React from 'react';
interface State {
mode: PaletteMode;
toggleMode: () => void;
}
const AppContext = React.createContext<State | undefined>(undefined);
export const useAppContext = (): State => {
const context = React.useContext<State | undefined>(AppContext);
if (!context) {
throw new Error('Please include a `import { AppContextProvider } from "./context"` before using this hook');
}
return context;
};
export const AppContextProvider: React.FC = ({ children }) => {
// light/dark mode
const [mode, setMode] = React.useState<PaletteMode>('dark');
const value = React.useMemo<State>(
() => ({
mode,
toggleMode: () => setMode((prevMode) => (prevMode !== 'light' ? 'light' : 'dark')),
}),
[mode],
);
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Nym Example with React, Typescript, Webpack</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="app"></div>
</body>
</html>
@@ -0,0 +1,5 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';
ReactDOM.render(<App />, document.getElementById('app'));
@@ -0,0 +1,13 @@
import React, { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { App } from '../App';
describe('App', () => {
beforeEach(() => {
render(<App />);
});
it('should render without exploding', () => {
const { container } = render(<App />);
expect(container.firstChild).toBeInTheDocument();
});
});
@@ -0,0 +1,37 @@
/* eslint-disable no-shadow,@typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface,import/no-extraneous-dependencies */
import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles';
import { NymTheme, NymPaletteWithExtensions, NymPaletteWithExtensionsOptions } from '@nymproject/mui-theme';
/**
* If you are unfamiliar with Material UI theming, please read the following first:
* - https://mui.com/customization/theming/
* - https://mui.com/customization/palette/
* - https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette
*
* This file adds typings to the theme using Typescript's module augmentation.
*
* Read the following if you are unfamiliar with module augmentation and declaration merging. Then
* look at the recommendations from Material UI docs for implementation:
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
* - https://mui.com/customization/palette/#adding-new-colors
*
*
* IMPORTANT:
*
* The type augmentation must match MUI's definitions. So, notice the use of `interface` rather than
* `type Foo = { ... }` - this is necessary to merge the definitions.
*/
declare module '@mui/material/styles' {
/**
* This augments the definitions of the MUI Theme with the Nym theme, as well as
* a partial `ThemeOptions` type used by `createTheme`
*
* IMPORTANT: only add extensions to the interfaces above, do not modify the lines below
*/
interface Theme extends NymTheme {}
interface ThemeOptions extends Partial<NymTheme> {}
interface Palette extends NymPaletteWithExtensions {}
interface PaletteOptions extends NymPaletteWithExtensionsOptions {}
}
@@ -0,0 +1,16 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"outDir": "./dist"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"build",
"dist"
]
}
@@ -0,0 +1,20 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"outDir": "./dist",
"declaration": false
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"build",
"dist",
"**/*.stories.*",
"**/*.test.*",
"**/*.spec.*"
]
}
@@ -0,0 +1,18 @@
const path = require('path');
const { mergeWithRules } = require('webpack-merge');
const { webpackCommon } = require('@nymproject/webpack');
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(webpackCommon(__dirname), {
entry: path.resolve(__dirname, 'src/index.tsx'),
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
});
@@ -0,0 +1,67 @@
const { mergeWithRules } = require('webpack-merge');
const webpack = require('webpack');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const ReactRefreshTypeScript = require('react-refresh-typescript');
const commonConfig = require('./webpack.common');
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(commonConfig, {
mode: 'development',
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
options: {
getCustomTransformers: () => ({
before: [ReactRefreshTypeScript()],
}),
// `ts-loader` does not work with HMR unless `transpileOnly` is used.
// If you need type checking, `ForkTsCheckerWebpackPlugin` is an alternative.
transpileOnly: true,
},
},
],
},
plugins: [
new ReactRefreshWebpackPlugin(),
// this can be included automatically by the dev server, however build mode fails if missing
new webpack.HotModuleReplacementPlugin(),
],
target: 'web',
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
historyApiFallback: true,
},
// recommended for faster rebuild
optimization: {
runtimeChunk: true,
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
cache: {
type: 'filesystem',
buildDependencies: {
// restart on config change
config: ['./webpack.config.js'],
},
},
});
@@ -0,0 +1,42 @@
const { mergeWithRules } = require('webpack-merge');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const commonConfig = require('./webpack.common');
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(commonConfig, {
mode: 'production',
// TODO: no source maps, add back
devtool: false,
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
optimization: {
minimizer: [`...`, new CssMinimizerPlugin()],
splitChunks: {
chunks: 'all',
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
output: {
pathinfo: false,
filename: '[name].[contenthash].js',
},
});
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{
"compileOnSave": false,
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": false,
"declaration": true,
"declarationMap": true,
"jsx": "react-jsx",
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@assets/*": ["../assets/*"]
},
},
"exclude": [
"jest.config.js",
"webpack.config.js",
"webpack.prod.js",
"webpack.common.js",
"node_modules",
"**/node_modules",
"dist",
"**/dist",
"scripts",
"jest",
"__tests__",
"**/__tests__",
"__jest__",
"**/__jest__",
"config/*"
]
}
+13
View File
@@ -0,0 +1,13 @@
{
"root": true,
"plugins": ["prettier"],
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"rules": {
"prettier/prettier": "error"
},
"env": {
"browser": false,
"es6": true,
"node": true
}
}
+9
View File
@@ -0,0 +1,9 @@
const webpackDevConfig = require('./webpack.dev');
const webpackProdConfig = require('./webpack.prod');
const webpackCommon = require('./webpack.common');
module.exports = {
webpackCommon,
webpackDevConfig,
webpackProdConfig,
};
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@nymproject/webpack",
"description": "Provides default Webpack 5 config and dependencies",
"version": "1.0.0",
"license": "Apache-2.0",
"main": "index.js",
"peerDependencies": {
"react": "17",
"react-dom": "17"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@svgr/webpack": "^6.1.1",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0",
"dotenv-webpack": "^7.0.3",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"prettier": "^2.5.1",
"style-loader": "^3.2.1",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"url-loader": "^4.1.1",
"webpack": "^5.64.3",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
},
"scripts": {
"lint": "eslint *.js",
"lint:fix": "eslint *.js --fix"
}
}
+82
View File
@@ -0,0 +1,82 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
// const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const WebpackFavicons = require('webpack-favicons');
const Dotenv = require('dotenv-webpack');
const path = require('path');
/**
* Creates the default Webpack config
* @param baseDir The base directory path, e.g. pass `__dirname` of the webpack config file using this method
*/
module.exports = (baseDir) => ({
module: {
rules: [
{
test: /\.tsx?$/,
use: [{ loader: 'ts-loader', options: { transpileOnly: true } }],
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
},
{
test: /\.(png|jpe?g|gif|md)$/i,
// More information here https://webpack.js.org/guides/asset-modules/
type: 'asset',
},
{
// See https://webpack.js.org/guides/asset-management/#loading-fonts
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.ya?ml$/,
type: 'json',
use: 'yaml-loader',
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
plugins: [new TsconfigPathsPlugin()],
alias: {
'react/jsx-runtime': require.resolve('react/jsx-runtime'),
},
},
plugins: [
// new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(baseDir, 'src/index.html'),
}),
new ForkTsCheckerWebpackPlugin({
typescript: {
mode: 'write-references',
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
}),
new WebpackFavicons({
src: path.resolve(__dirname, '../../assets/favicon/favicon.svg'), // the asset directory is relative to THIS file
}),
new Dotenv(),
],
output: {
path: path.resolve(baseDir, 'dist'),
publicPath: '/',
},
});
+22
View File
@@ -0,0 +1,22 @@
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common');
/**
* Creates the default Webpack dev config
* @param baseDir The base directory path, e.g. pass `__dirname` of the webpack config file using this method
*/
module.exports = (baseDir) =>
merge(common, {
mode: 'development',
entry: path.resolve(baseDir, '/src/index'),
devServer: {
port: 9000,
compress: true,
historyApiFallback: true,
hot: true,
client: {
overlay: false,
},
},
});
+25
View File
@@ -0,0 +1,25 @@
const path = require('path');
const { default: merge } = require('webpack-merge');
const common = require('./webpack.common');
/**
* Creates the default Webpack prod config
* @param baseDir The base directory path, e.g. pass `__dirname` of the webpack config file using this method
*/
module.exports = (baseDir) =>
merge(common, {
mode: 'production',
node: {
__dirname: false,
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [{ loader: 'ts-loader', options: { transpileOnly: true, configFile: 'tsconfig.prod.json' } }],
exclude: [/node_modules/, '**/*.stories.*', '**/*.test.*'],
},
],
},
entry: path.resolve(baseDir, 'src/index'),
});
+17451
View File
File diff suppressed because it is too large Load Diff