From 0fd6cae1acd882910bd3b8fc3566bde7e4af6cb5 Mon Sep 17 00:00:00 2001 From: MrJackdaw Date: Tue, 10 May 2022 07:08:40 -0700 Subject: [PATCH] v0.7.2 * update stdlib to v0.1.10-rc.4 * adds getting-started instructions to home * adds reusable hooks * updates reachduck --- .eslintrc.js | 7 +- package-lock.json | 28 ++--- package.json | 4 +- src/App.tsx | 50 ++++----- src/components/Common/Containers.tsx | 26 ++++- src/components/GlobalCountButton.tsx | 21 ++++ src/css/reset.scss | 8 ++ src/hooks/GlobalCount.ts | 15 +++ src/hooks/GlobalUser.ts | 21 ++++ src/routes/Demo.tsx | 52 ++++----- src/routes/Home.tsx | 156 +++++++++++++++++---------- 11 files changed, 254 insertions(+), 134 deletions(-) create mode 100644 src/components/GlobalCountButton.tsx create mode 100644 src/hooks/GlobalCount.ts create mode 100644 src/hooks/GlobalUser.ts diff --git a/.eslintrc.js b/.eslintrc.js index d1b9bbc..bd7d388 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,10 +13,11 @@ module.exports = { "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-unused-vars": ["error"], + "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-use-before-define": "off", - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': ['warn'], + "no-shadow": "off", + "@typescript-eslint/no-shadow": ["warn"], + "import/prefer-default-export": "off", "no-unused-vars": "off", "no-use-before-define": "off", "react/button-has-type": "off", diff --git a/package-lock.json b/package-lock.json index db21ca2..9e69e44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "version": "0.6.1", "dependencies": { "@jackcom/raphsducks": "^1.1.6", - "@jackcom/reachduck": "^0.2.10", + "@jackcom/reachduck": "^0.2.11", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", "@randlabs/myalgo-connect": "^1.1.3", - "@reach-sh/stdlib": "^0.1.10-rc.1", + "@reach-sh/stdlib": "^0.1.10-rc.4", "cryptocurrency-icons": "^0.18.0", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -3005,9 +3005,9 @@ "integrity": "sha512-RyzP6kJiEG7RlWonsY1t/JomhfVI89eezyyppdiZByY/+Qcp72CryBXsxiCfpev5/qWIExE5eR82Mx7x9PSv7w==" }, "node_modules/@jackcom/reachduck": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@jackcom/reachduck/-/reachduck-0.2.10.tgz", - "integrity": "sha512-VV1SLXn1Lb1uj3PvbcpAe96QKpqKmRXm6sSrCOSIuafT7YSxc/Q+zq13VEktRkQfaa10RTvTbjPB/sJv/vHeiw==" + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@jackcom/reachduck/-/reachduck-0.2.11.tgz", + "integrity": "sha512-SfmavYevE4j7Gze1SHBPS9W3+YeZxGjOrcxi2Qd2ginyqJBOHZYsy7Vf0lPhy/m50C4mzHusZoanhjEtJY9YFw==" }, "node_modules/@jest/console": { "version": "26.6.2", @@ -3675,9 +3675,9 @@ } }, "node_modules/@reach-sh/stdlib": { - "version": "0.1.10-rc.1", - "resolved": "https://registry.npmjs.org/@reach-sh/stdlib/-/stdlib-0.1.10-rc.1.tgz", - "integrity": "sha512-uVJpbcRHx/8Ei6Nnhh6mltNKS0jB8FS9B7q8PjiU39tkgbdkUK6bfbw1Jqmw3v5mKazIYXBMbelsTZ2we5a/iA==", + "version": "0.1.10-rc.4", + "resolved": "https://registry.npmjs.org/@reach-sh/stdlib/-/stdlib-0.1.10-rc.4.tgz", + "integrity": "sha512-Ufio7B+K9XzqkrSETzMm4o5jHdo3Zk6gmyjlASAPJtsc9ilTFMzLREUb7NyYpoHgbRIxvXsBP9tZvaF/lCVz8A==", "dependencies": { "@msgpack/msgpack": "^2.5.1", "@randlabs/myalgo-connect": "^1.1.2", @@ -27227,9 +27227,9 @@ "integrity": "sha512-RyzP6kJiEG7RlWonsY1t/JomhfVI89eezyyppdiZByY/+Qcp72CryBXsxiCfpev5/qWIExE5eR82Mx7x9PSv7w==" }, "@jackcom/reachduck": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@jackcom/reachduck/-/reachduck-0.2.10.tgz", - "integrity": "sha512-VV1SLXn1Lb1uj3PvbcpAe96QKpqKmRXm6sSrCOSIuafT7YSxc/Q+zq13VEktRkQfaa10RTvTbjPB/sJv/vHeiw==" + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@jackcom/reachduck/-/reachduck-0.2.11.tgz", + "integrity": "sha512-SfmavYevE4j7Gze1SHBPS9W3+YeZxGjOrcxi2Qd2ginyqJBOHZYsy7Vf0lPhy/m50C4mzHusZoanhjEtJY9YFw==" }, "@jest/console": { "version": "26.6.2", @@ -27734,9 +27734,9 @@ } }, "@reach-sh/stdlib": { - "version": "0.1.10-rc.1", - "resolved": "https://registry.npmjs.org/@reach-sh/stdlib/-/stdlib-0.1.10-rc.1.tgz", - "integrity": "sha512-uVJpbcRHx/8Ei6Nnhh6mltNKS0jB8FS9B7q8PjiU39tkgbdkUK6bfbw1Jqmw3v5mKazIYXBMbelsTZ2we5a/iA==", + "version": "0.1.10-rc.4", + "resolved": "https://registry.npmjs.org/@reach-sh/stdlib/-/stdlib-0.1.10-rc.4.tgz", + "integrity": "sha512-Ufio7B+K9XzqkrSETzMm4o5jHdo3Zk6gmyjlASAPJtsc9ilTFMzLREUb7NyYpoHgbRIxvXsBP9tZvaF/lCVz8A==", "requires": { "@msgpack/msgpack": "^2.5.1", "@randlabs/myalgo-connect": "^1.1.2", diff --git a/package.json b/package.json index 2672c33..020da35 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "private": true, "dependencies": { "@jackcom/raphsducks": "^1.1.6", - "@jackcom/reachduck": "^0.2.10", + "@jackcom/reachduck": "^0.2.11", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", "@randlabs/myalgo-connect": "^1.1.3", - "@reach-sh/stdlib": "^0.1.10-rc.1", + "@reach-sh/stdlib": "^0.1.10-rc.4", "cryptocurrency-icons": "^0.18.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/App.tsx b/src/App.tsx index e60f26b..34e4040 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,37 +8,26 @@ import AppHeader from "AppHeader"; import "./App.scss"; import { UIThemeType } from "types/shared"; import ActiveNotifications from "components/ActiveNotifications"; -import WalletNotFound from "components/Reach/WalletNotFound"; -import store from "state"; function App() { const sTheme = getTheme(); const [theme, setTheme] = useState(THEME[sTheme] || {}); - const [user, setUser] = useState(""); + // const [user, setUser] = useState(""); useEffect(() => { /* Change your document title here */ document.title = "Reach + ReactJS Starter App"; - /* Listen to theme changes; you can build on this functionality */ + /* Listen to theme changes; you can expand on this functionality */ const onTheme = (s: any) => setTheme(THEME[s.theme as UIThemeType]); /** * This is how you 1. Subscribe to a state, and 2. Get an 'unsubscribe' function. - * Over here, the App.tsx component is listening to two different state instances; - * one for just theme stuff, one for data that will come from using stdlib. - * You can create as many state instances (like `themeState` and `store`) as you need. + * The App.tsx component is listening to the `theme` state instance; when it changes, + * the UI will update accordingly. We return the "unsubscribe" fn so that React can + * cleanup when the component unmounts */ - const unsubTheme = themeState.subscribeToKeys(onTheme, ["theme"]); - const unsubReach = store.subscribeToKeys( - (s) => setUser(s.address || ""), - ["address"] - ); - - return function unsubAll() { - unsubTheme(); - unsubReach(); - }; + return themeState.subscribeToKeys(onTheme, ["theme"]); }); return ( @@ -54,21 +43,18 @@ function App() { {/* Routes */} - {user ? ( - - {routes.map(({ path, component, render }) => ( - - ))} - - ) : ( - - )} + + + {routes.map(({ path, component, render }) => ( + + ))} + diff --git a/src/components/Common/Containers.tsx b/src/components/Common/Containers.tsx index f8000f5..db9a846 100644 --- a/src/components/Common/Containers.tsx +++ b/src/components/Common/Containers.tsx @@ -5,6 +5,11 @@ type FlexContainerProps = { padded?: boolean; }; +export const ExLink = styled.a.attrs({ + target: "_blank", + rel: "noopener noreferrer", +})``; + /** General-purpose default container */ export const BaseContainer = styled.section``; @@ -35,9 +40,28 @@ export const GridContainer = styled.div` `; export const PageContainer = styled(FlexColumn)` + justify-content: flex-start; margin: 0 auto; - min-height: 70vmax; max-width: 80vmin; + min-height: 70vmax; + text-align: left; + + > h1, + > h2, + > h3, + > h4, + > h5, + > h6 { + margin: 0.5rem 0; + } + + > hr { + background-color: ${({ theme }) => theme.colors.primary}; + border: 0; + height: 1px; + margin: 1.5rem 0; + opacity: 0.6; + } `; export const Figure = styled.figure` diff --git a/src/components/GlobalCountButton.tsx b/src/components/GlobalCountButton.tsx new file mode 100644 index 0000000..222c7a1 --- /dev/null +++ b/src/components/GlobalCountButton.tsx @@ -0,0 +1,21 @@ +import { useGlobalCount } from "hooks/GlobalCount"; +import store from "state"; +import Button from "./Forms/Button"; + +const GlobalCountButton = () => { + const { globalCount } = useGlobalCount(); + + return ( + + ); +}; + +export default GlobalCountButton; + +// Update global state property +function incGlobalState(): void { + const { globalCount } = store.getState(); + store.globalCount(globalCount + 1); +} diff --git a/src/css/reset.scss b/src/css/reset.scss index 7dbd8b5..ff112eb 100644 --- a/src/css/reset.scss +++ b/src/css/reset.scss @@ -99,6 +99,14 @@ input[type=text] { border-bottom: 1px solid; } +pre, +code{ + background-color: #0003; + font-family: monospace; + font-weight: bolder; + padding: 0 0.2rem; +} + table { border-collapse: collapse; border-spacing: 0; diff --git a/src/hooks/GlobalCount.ts b/src/hooks/GlobalCount.ts new file mode 100644 index 0000000..c73b243 --- /dev/null +++ b/src/hooks/GlobalCount.ts @@ -0,0 +1,15 @@ +import { useEffect, useState } from "react"; +import store from "state"; + +export function useGlobalCount() { + const gState = store.getState(); + const [count, setCount] = useState(gState.globalCount); + const onAppState = (s: Partial) => { + if (s.globalCount) setCount(s.globalCount); + }; + + // Subscribe to global state, and unsubscribe on component unmount + useEffect(() => store.subscribeToKeys(onAppState, ["globalCount"])); + + return { globalCount: count }; +} diff --git a/src/hooks/GlobalUser.ts b/src/hooks/GlobalUser.ts new file mode 100644 index 0000000..8ab9722 --- /dev/null +++ b/src/hooks/GlobalUser.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; +import store from "state"; + +export function useGlobalUser() { + const gState = store.getState(); + const [user, setUser] = useState(gState.address); + const [account, setAccount] = useState(); + const [assets, setAssets] = useState(); + const onAppState = (s: Partial) => { + if (s.address) setUser(s.address); + if (s.account) setAccount(s.account); + if (s.assets) setAssets(s.assets); + }; + + // Subscribe to global state, and unsubscribe on component unmount + useEffect(() => + store.subscribeToKeys(onAppState, ["address", "account", "assets"]) + ); + + return { user, account, assets }; +} diff --git a/src/routes/Demo.tsx b/src/routes/Demo.tsx index 601a740..57dba5a 100644 --- a/src/routes/Demo.tsx +++ b/src/routes/Demo.tsx @@ -1,44 +1,44 @@ -import { useState, useEffect } from "react"; -import { FlexColumn } from "components/Common/Containers"; -import store from "state"; import { Link } from "react-router-dom"; +import styled from "styled-components"; +import { FlexColumn, PageContainer } from "components/Common/Containers"; +import { useGlobalCount } from "hooks/GlobalCount"; +import GlobalCountButton from "components/GlobalCountButton"; -const Demo = () => { - const gState = store.getState(); - const { globalCount: gCount } = gState; - const [count, setCount] = useState(gCount); - const onAppState = ({ globalCount }: Partial) => { - setCount(globalCount || 0); - }; +const GlobalCount = styled(FlexColumn)` + border: ${({ theme }) => `1px solid ${theme.colors.primary}`}; + border-radius: 4px; + padding: 1rem; + text-align: center; +`; - // Subscribe to global state, and unsubscribe on component unmount - useEffect(() => store.subscribeToKeys(onAppState, ["globalCount"])); +const Demo = () => { + const { globalCount } = useGlobalCount(); return ( - +

Demo.tsx

- This is a Demo page. It is a - - Route - - in your application. Look at routes/Demo.tsx to see how it works. -

-

- This route is linked up to the global state. You can modify or remove it - if not needed + This is a Demo page. It is a Route + in your application, and is linked up to the global state. Look at{" "} + routes/Demo.tsx to see how it works.

-

- Global Count: {count} -

+

Application State Demo

Global Count is a property in your global state. You can update it using a button on the Home route.

-
+ + +

+ Global Count: {globalCount} +

+ + +
+ ); }; diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index d254383..9439fe7 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -1,72 +1,116 @@ import { Face } from "@material-ui/icons"; -import { FlexColumn, FlexRow } from "components/Common/Containers"; -import { useEffect, useState, Fragment } from "react"; -import store from "state"; -import Button from "../components/Forms/Button"; +import { ExLink, PageContainer } from "components/Common/Containers"; +import GlobalCountButton from "components/GlobalCountButton"; +import { Fragment } from "react"; +import styled from "styled-components"; const links = [ { href: "https://reactjs.org", text: "Learn React" }, { href: "https://docs.reach.sh/index.html", text: "Reach Lang Docs" }, ]; -const Home = () => { - const gState = store.getState(); - const [state, setState] = useState>(gState); - const onAppState = (s: any) => setState((old) => ({ ...old, ...s })); +const Wrapper = styled(PageContainer)``; - // Subscribe to global state, and unsubscribe on component unmount - useEffect(() => store.subscribeToKeys(onAppState, ["globalCount"])); +const Home = () => ( + +

Home.tsx

+ +

+ This is your application's Home page. It is linked up to the + global state. Look in routes/Home.tsx to see how it works. +

+

Getting Started

+

+ This starter is meant to get you hacking quickly. You can freely change + styles (global or local), rename component files, and/or re-arrange things + to your liking. +

+

+ This project supports Typescript, SASS/SCSS, and{" "} + Material Icons + +

+
+

Application State

+

+ This project uses a simple global state management system. It is defined + in + src/state/index.ts; you can add (or remove) as many + properties as you want. There is a{" "} + + React hook + {" "} + that uses it in src/hooks/useGlobalUser.ts; you can use that + to create and share reusable hooks between components. +

+

+ The value below is in global state. You can view it on the Demo{" "} + page after changing it here. It will reset if you reload the window. +

- return ( - -

Home.tsx

+ - - This project supports Material Icons - - +
+

CSS and styles

+

+ This starter uses{" "} + + styled-components + + . It allows you to create reusable styled containers for your app, and + enables global app themes (if you want). +

-

- This is your application's Home page. -

-

- Look in routes/Home.tsx to see how it works. -

+

Global (Default) Styles

+

+ If you don't care about light/dark theme toggles, you can simply + write your global styles in src/App.scss. You might also want + to look at src/theme/theme.shared.ts to make sure there are + no other globals overriding your hard work. +

-

- This route is linked up to the global state. You can modify or remove it - if not needed -

+

Global Themes

+

Enabling global themes involves a couple of steps:

-

- {links.map(({ text, href }, i) => ( - - - {text} - - {i < links.length - 1 && " | "} - - ))} -

+
    +
  1. + Define your Light and Dark themes in{" "} + src/theme/. There is a file for each theme. +
  2. - - - - - ); -}; +
  3. + Import and link the setTheme function in{" "} + src/theme/index.ts to a UI component. When the component is + triggered (e.g. clicked), you can call the function with the + corresponding value (Dark or Light) and the UI + will update. +
  4. +
-export default Home; +

+ App.tsx is already subscribed to the theme file;{" "} + setTheme will also write the value to localStorage for you, + so that you can reload the page without needing to re-select the theme. +

+
+); -// Update global state property -function incAppState(): void { - const { globalCount } = store.getState(); - store.globalCount(globalCount + 1); -} +export default Home;