Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add ui-components package #2321

Merged
merged 4 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test-all-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ jobs:
run: cd packages/deploy-script-support && yarn test
env:
ESM_DISABLE_CACHE: true
- name: yarn test (ui-components)
run: cd packages/ui-components && yarn test
env:
ESM_DISABLE_CACHE: true

##############
# Long-running tests are executed individually.
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ bundle-*.js
.idea/

/packages.png
packages/ui-components/compiled
packages/ui-components/dist
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"packages/deployment",
"packages/notifier",
"packages/xsnap",
"packages/deploy-script-support"
"packages/deploy-script-support",
"packages/ui-components"
],
"devDependencies": {
"@typescript-eslint/parser": "^4.18.0",
Expand Down
24 changes: 24 additions & 0 deletions packages/ui-components/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
.eslintcache
34 changes: 34 additions & 0 deletions packages/ui-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# UI Components

katelynsills marked this conversation as resolved.
Show resolved Hide resolved
Reusable UI Components for [Agoric](https://agoric.com) [Dapps](https://agoric.com/documentation/dapps/), built with [React](https://reactjs.org) and [MaterialUI](https://materialui.com).

## NatAmountInput

A [React](https://reactjs.org) [MaterialUI TextField
Input](https://material-ui.com/api/text-field/) which allows the user
to enter a `Nat`. Handles `decimalPlaces` appropriately. This is a
controlled component.

Example:

```
import { NatAmountInput } from '@agoric/ui-components';

<NatAmountInput
label={label} // the label
value={amount && amount.value} // The value to display. Must be a Nat
decimalPlaces={purse.displayInfo && purse.displayInfo.decimalPlaces}
placesToShow={2}
disabled={disabled} // disable the input
error={amountError} // any error to display
onChange={onAmountChange} // a callback called on user input changing the value
onError={() => {}} // a callback called on errors
/>
```

## Yarn Test

```sh
yarn build
yarn test
```
3 changes: 3 additions & 0 deletions packages/ui-components/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-react"]
}
Empty file.
19 changes: 19 additions & 0 deletions packages/ui-components/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This file can contain .js-specific Typescript compiler config.
{
"compilerOptions": {
"target": "esnext",

"noEmit": true,
/*
// The following flags are for creating .d.ts files:
"noEmit": false,
"declaration": true,
"emitDeclarationOnly": true,
*/
"downlevelIteration": true,
"strictNullChecks": true,
"moduleResolution": "node",
"jsx": "react",
},
"include": ["src/**/*.js", "test/**/*.js", "exported.js"],
}
108 changes: 108 additions & 0 deletions packages/ui-components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"name": "@agoric/ui-components",
"version": "0.0.1",
"description": "Reusable UI Components for Agoric Dapps, built with React and MaterialUI",
"main": "src/index.js",
"peerDependencies": {
"@agoric/assert": "^0.2.3",
"@agoric/ertp": "^0.10.0",
"@agoric/eventual-send": "^0.13.3",
"@agoric/install-ses": "^0.5.3",
"@material-ui/core": "^4.11.3",
"react": "^16.14.0",
"react-dom": "^16.8.0"
},
"scripts": {
"test": "BABEL_ENV='test' ava",
"build:tests": "rm -rf compiled && BABEL_ENV='test' ./node_modules/.bin/babel test/components --out-dir compiled/test/components",
"build:src": "rm -rf dist && BABEL_ENV='test' ./node_modules/.bin/babel src --out-dir dist",
"build": "yarn build:src && yarn build:tests",
"lint-fix": "yarn lint --fix",
"lint": "yarn lint:types && yarn lint:eslint",
"lint-check": "yarn lint",
"lint:eslint": "eslint '**/*.js'",
"lint:types": "tsc -p jsconfig.json"
},
"eslintConfig": {
"extends": [
"react-app",
"@agoric"
],
"rules": {
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": [
"error"
]
}
},
"eslintIgnore": [
"dist",
"compiled"
],
"prettier": {
"trailingComma": "all",
"singleQuote": true
},
"devDependencies": {
"@babel/cli": "^7.12.13",
"@babel/core": "^7.12.13",
"@babel/plugin-syntax-jsx": "^7.12.1",
"@material-ui/core": "4.11.3",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"ava": "^3.15.0",
"babel-eslint": "^10.1.0",
"babel-plugin-named-asset-import": "^0.3.7",
"babel-preset-react-app": "^10.0.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"esm": "^3.2.25",
"prettier": "^1.19.0",
"react": "^16.14.0",
"react-dom": "^16.8.0",
"typescript": "^4.2.3"
},
"ava": {
"files": [
"compiled/test/components/**/test-*.js",
"test/**/*.js",
"!test/components"
],
"require": [
"esm",
"./test/_setup-enzyme-adapter.js"
]
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Agoric/agoric-sdk.git"
},
"keywords": [
"smart",
"contract",
"cryptocurrency",
"exchange",
"tokens"
],
"author": "Agoric",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/Agoric/agoric-sdk/issues"
},
"homepage": "https://github.com/Agoric/agoric-sdk#readme",
"files": [
"src",
"dist",
"NEWS.md",
"exported.js"
],
"dependencies": {
"@agoric/nat": "^4.0.0"
}
}
46 changes: 46 additions & 0 deletions packages/ui-components/src/components/NatAmountInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { TextField } from '@material-ui/core';

import { parseAsNat } from '../display/natValue/parseAsNat';
import { stringifyNat } from '../display/natValue/stringifyNat';

// https://material-ui.com/api/text-field/

const NatAmountInput = ({
label,
value,
decimalPlaces = 0,
placesToShow = 2,
disabled,
error,
onChange,
}) => {
// No negative values allowed in the input
const noNegativeValues = {
inputProps: { min: 0 },
};

const preventSubtractChar = e => {
if (e.key === 'Subtract') {
e.preventDefault();
e.stopPropagation();
}
};

return (
<TextField
label={label}
type="number"
variant="outlined"
fullWidth
InputProps={noNegativeValues}
onChange={ev => onChange(parseAsNat(ev.target.value, decimalPlaces))}
onKeyPress={preventSubtractChar}
value={stringifyNat(value, decimalPlaces, placesToShow)}
disabled={disabled}
error={error}
/>
);
};

export default NatAmountInput;
111 changes: 111 additions & 0 deletions packages/ui-components/src/display/display.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// @ts-check
import { assert, details } from '@agoric/assert';
import { MathKind } from '@agoric/ertp';
import '@agoric/ertp/exported';

import { parseAsNat } from './natValue/parseAsNat';
import { stringifyNat } from './natValue/stringifyNat';
import { parseAsSet } from './setValue/parseAsSet';
import { stringifySet } from './setValue/stringifySet';

/**
*
* @param {string} str - string to parse as a value
* @param {AmountMathKind} [mathKind] - mathKind of the value
* @param {number} [decimalPlaces] - places to move the decimal to the left
* @returns {Value}
*/
export const parseAsValue = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why clutter the name with As? parseValue says what I want it to do and what I'm expecting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, it wasn't clear in parseX whether x was the input type or the output type. In parseValue, value is the output type, but that doesn't match how we usually say it in English: "parse this string as a value". In English, the input directly follows parse, and the output is prefixed with as. So to make the name clearer since it is the opposite of how we would speak normally, I added the As.

str,
mathKind = MathKind.NAT,
decimalPlaces = 0,
) => {
if (mathKind === MathKind.NAT) {
return parseAsNat(str, decimalPlaces);
}
if (mathKind === MathKind.SET) {
return parseAsSet(str);
}
assert.fail(details`MathKind ${mathKind} must be NAT or SET`);
};

/**
* @param {string} str - string to parse as a value
* @param {Brand} brand - brand to use in the amount
* @param {AmountMathKind} [mathKind] - mathKind of the value
* @param {number} [decimalPlaces] - places to move the decimal to the left
* @returns {Amount}
*/
export const parseAsAmount = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless it's well motivated, the As is also an unnecessary renaming over what's there, resulting in merge overhead. Of course it might be well-motivated.

str,
brand,
mathKind = MathKind.NAT,
decimalPlaces = 0,
) => {
return { brand, value: parseAsValue(str, mathKind, decimalPlaces) };
};

/**
*
* @param {Value} value - value to stringify
* @param {AmountMathKind} [mathKind] - mathKind of the value
* @param {number} [decimalPlaces] - places to move the decimal to the
* right in the string
* @param {number} [placesToShow] - places after the decimal to show
* @returns {string}
*/
export const stringifyValue = (
value,
mathKind = MathKind.NAT,
decimalPlaces = 0,
placesToShow = 2,
) => {
if (mathKind === MathKind.NAT) {
// @ts-ignore Value is a Nat
return stringifyNat(value, decimalPlaces, placesToShow);
}
if (mathKind === MathKind.SET) {
// @ts-ignore Value is a SetValue
return stringifySet(value);
}
assert.fail(details`MathKind ${mathKind} must be NAT or SET`);
};

/**
* Stringify the value of a purse
*
* @param {any} purse
* @returns {string}
*/
export const stringifyPurseValue = purse => {
if (!purse) {
return '0';
}
return stringifyValue(
purse.value,
purse.displayInfo.mathKind,
purse.displayInfo.decimalPlaces,
);
};

/**
* Stringify the value in an amount
*
* @param {Amount} amount
* @param {AmountMathKind} [mathKind] - mathKind of the value
* @param {number} [decimalPlaces] - places to move the decimal to the
* right in the string
* @param {number} [placesToShow] - places after the decimal to show
* @returns {string}
*/
export function stringifyAmountValue(
amount,
mathKind,
decimalPlaces,
placesToShow,
) {
if (!amount) {
return '0';
}
return stringifyValue(amount.value, mathKind, decimalPlaces, placesToShow);
}
Loading