From 65e155f9c6ddf9633f3e158545d3345484d65444 Mon Sep 17 00:00:00 2001
From: Frank Sauerburger <frank@sauerburger.com>
Date: Tue, 26 Mar 2019 12:18:49 +0100
Subject: [PATCH] Style most components

---
 app/components/App.jsx         |  11 ++-
 app/components/InputPanel.jsx  |  17 +++-
 app/components/OutputPanel.jsx |  15 +++-
 app/components/RouteTile.jsx   |  65 +++++++++++++--
 app/helpers/index.js           |   7 ++
 app/reducers/index.js          |   8 +-
 app/styles/application.css     |   6 +-
 package-lock.json              | 139 +++++++++++++++++++++++++++++++--
 package.json                   |   3 +-
 9 files changed, 243 insertions(+), 28 deletions(-)

diff --git a/app/components/App.jsx b/app/components/App.jsx
index 21d6274..5627eab 100644
--- a/app/components/App.jsx
+++ b/app/components/App.jsx
@@ -1,12 +1,19 @@
 import React from 'react'
 import OutputPanel from './../containers/OutputPanel'
 import InputPanel from './../containers/InputPanel'
+import styled from 'styled-components'
 
+const Div = styled.div`
+  @media only screen and (min-width: 820px) {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+  }
+`
 const App = () => (
-  <div>
+  <Div>
     <InputPanel />
     <OutputPanel />
-  </div>
+  </Div>
 )
 
 export default App
diff --git a/app/components/InputPanel.jsx b/app/components/InputPanel.jsx
index b59a6b6..b05a8f0 100644
--- a/app/components/InputPanel.jsx
+++ b/app/components/InputPanel.jsx
@@ -1,18 +1,27 @@
-import React from 'react';
+import React from 'react'
 import PropTypes from 'prop-types'
 import RouteTile from './RouteTile'
+import styled from 'styled-components'
+
+const Div = styled.div`
+  background-color: #fff;
+  margin: 1em;
+  border: 1px solid #ccc;
+  padding: 1em;
+`
 
 const InputPanel = ({routes, onAdd, onDelete, onClear, onUpdate}) => (
-  <div>
+  <Div>
     {routes.map((stops, i) => (
       <RouteTile
+        title={"Route " + (i+1)}
         onDelete={() => onDelete(i)} 
         onClear={() => onClear(i)} 
         onAdd={(url) => onUpdate(i, url)}
         key={i} stops={stops} />
     ))}
-    <RouteTile onAdd={onAdd} stops={[]} />
-  </div>
+    <RouteTile title={"Route " + (routes.length+1)} onAdd={onAdd} stops={[]} />
+  </Div>
 )
 
 InputPanel.propTypes = {
diff --git a/app/components/OutputPanel.jsx b/app/components/OutputPanel.jsx
index 2572612..e9263df 100644
--- a/app/components/OutputPanel.jsx
+++ b/app/components/OutputPanel.jsx
@@ -2,12 +2,19 @@ import React from 'react'
 import PropTypes from 'prop-types'
 import RouteTile from './RouteTile'
 import { buildUrl } from '../helpers'
+import styled from 'styled-components'
+
+const Div = styled.div`
+  background-color: #fff;
+  margin: 1em;
+  padding: 1em;
+  border: 1px solid #ccc;
+`
 
 const OutputPanel = ({stops}) => (
-  <div>
-    <h2>Complete Route</h2>
-    <RouteTile url={buildUrl(stops)} stops={stops} />
-  </div>
+  <Div>
+    <RouteTile title="Complete Route" url={buildUrl(stops)} stops={stops} />
+  </Div>
 )
 
 OutputPanel.propTypes = {
diff --git a/app/components/RouteTile.jsx b/app/components/RouteTile.jsx
index 4881fca..e2afc3c 100644
--- a/app/components/RouteTile.jsx
+++ b/app/components/RouteTile.jsx
@@ -1,11 +1,56 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import styled from 'styled-components'
 
-const RouteTile = ({stops, onAdd, url, onDelete, onClear}) => {
+const Button = styled.input`
+  color: #fff;
+  background-color: #0e2f43;
+  border: none;
+  font-size: 24px;
+  line-height: 34px;
+  height: 34px;
+  padding: 0;
+  width: 34px;
+  margin: 1px;
+  
+  &:hover {
+    background-color: #2d8891;
+  }
+`
+
+const TextField = styled.input`
+  color: #000;
+  background-color: #eee;
+  border: none;
+  font-size: 24px;
+  line-height: 34px;
+  height: 34px;
+  padding: 0;
+  text-align: center;
+  width: 10em;
+  margin: 1px;
+  box-sizing: border-box;
+
+  
+  &:focus {
+     border: 1px solid #2d8891;
+  }
+`
+
+const Div = styled.div`
+  border: 1px dotted #aaa;
+  background-color: #eee;
+  font-family: monospace;
+  padding: 1em;
+`
+
+const RouteTile = ({title, stops, onAdd, url, onDelete, onClear}) => {
   let input
+  let output
 
   return stops.length == 0 && url === undefined ?
   <div>
+      <h2>{title}</h2>
       <form onSubmit={(e) => {
         e.preventDefault()
         if (input.value.trim() == "") {
@@ -14,15 +59,22 @@ const RouteTile = ({stops, onAdd, url, onDelete, onClear}) => {
         onAdd(input.value.trim())
         input.value = ""
       }}>
-        <input type="text" ref={node => {input = node}} />
-        <input type="submit" value="+" />
+        <TextField type="text" placeholder="Add link to route" ref={node => {input = node}} />
+        <Button type="submit" value="+" />
       </form>
   </div>
   :
   <div>
-    { url === undefined && <input onClick={onDelete} type="button" value="-" /> }
-    { url === undefined && <input onClick={onClear} type="button" value="&#x1F589;" /> }
-    { url !== undefined && <div className="url">{url}</div> }
+    <h2>{title}</h2>
+    { url === undefined && <Button style={{float: 'right'}} onClick={onDelete} type="button" value="&#x1F5D9;" /> }
+    { url === undefined && <Button style={{float: 'right'}} onClick={onClear} type="button" value="&#x1F589;" /> }
+    { url !== undefined && <Div
+        onClick={(e) => {
+          var range = document.createRange();
+          range.selectNode(e.currentTarget);
+          window.getSelection().removeAllRanges();
+          window.getSelection().addRange(range);
+        }} className="url">{url}</Div> }
     <ul>
       {stops.map((stop, i) => (
         <li key={i}>{stop}</li>
@@ -37,6 +89,7 @@ RouteTile.propTypes = {
   onAdd: PropTypes.func,
   onDelete: PropTypes.func,
   onClear: PropTypes.func,
+  title: PropTypes.string.isRequired,
 }
 
 export default RouteTile
diff --git a/app/helpers/index.js b/app/helpers/index.js
index 2ba3434..c1b0986 100644
--- a/app/helpers/index.js
+++ b/app/helpers/index.js
@@ -25,3 +25,10 @@ export const buildUrl = (stops) => {
   }
   return "https://www.google.com/maps/dir/" + stops.join("/") + "/"
 }
+
+export const dropTail = (array) => {
+  while (array.length > 0 && array.slice(-1)[0].length == 0) {
+    array.pop()
+  }
+  return array
+}
diff --git a/app/reducers/index.js b/app/reducers/index.js
index a5a227d..ce6d723 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -1,5 +1,5 @@
 import * as at from '../actions/actionTypes'
-import { extractStops } from '../helpers'
+import { extractStops, dropTail } from '../helpers'
 
 
 const initialState = {
@@ -12,13 +12,13 @@ const reducer = (state = initialState, action) => {
       return {routes: state.routes.concat([extractStops(action.payload)])}
 
     case at.DELETE_ROUTE:
-      return {routes: state.routes.filter((v, i) => i != action.payload)}
+      return {routes: dropTail(state.routes.filter((v, i) => i != action.payload))}
 
     case at.CLEAR_ROUTE:
-      return {routes: state.routes.map((v, i) => i == action.payload ? [] : v)}
+      return {routes: dropTail(state.routes.map((v, i) => i == action.payload ?  [] : v))}
 
     case at.UPDATE_URL:
-      return {routes: state.routes.map((v, i) => i == action.payload.index ?  extractStops(action.payload.url) : v)}
+      return {routes: dropTail(state.routes.map((v, i) => i == action.payload.index ?  extractStops(action.payload.url) : v))}
 
     default:
       return state
diff --git a/app/styles/application.css b/app/styles/application.css
index 88eab34..881569e 100644
--- a/app/styles/application.css
+++ b/app/styles/application.css
@@ -21,8 +21,11 @@ header h1 {
   margin: 0.8em auto 0.4em;
 }
 
-header {
+h1, h2 {
   font-family: "Suprema", sans-serif;
+}
+
+header {
   border-top: 1px solid #0e2f43;
   margin: 0px;
   background-color: #0e2f43;
@@ -34,6 +37,7 @@ main {
   margin: 0px auto 5em;
   padding: 0em 1em;
   text-align: justify;
+  position: relative;
 }
 
 @media only screen and (min-width: 820px) {
diff --git a/package-lock.json b/package-lock.json
index 56f0aa9..615c2c2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,57 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "@babel/helper-annotate-as-pure": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz",
+      "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==",
+      "requires": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-module-imports": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
+      "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
+      "requires": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/types": {
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz",
+      "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==",
+      "requires": {
+        "esutils": "^2.0.2",
+        "lodash": "^4.17.11",
+        "to-fast-properties": "^2.0.0"
+      },
+      "dependencies": {
+        "to-fast-properties": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+          "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
+        }
+      }
+    },
+    "@emotion/is-prop-valid": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz",
+      "integrity": "sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==",
+      "requires": {
+        "@emotion/memoize": "0.7.1"
+      }
+    },
+    "@emotion/memoize": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz",
+      "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg=="
+    },
+    "@emotion/unitless": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.3.tgz",
+      "integrity": "sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg=="
+    },
     "accepts": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
@@ -512,6 +563,17 @@
         "babel-runtime": "^6.22.0"
       }
     },
+    "babel-plugin-styled-components": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.0.tgz",
+      "integrity": "sha512-sQVKG8irFXx14ZfaK1bBePirfkacl3j8nZwSZK+ZjsbnadRHKQTbhXbe/RB1vT6Vgkz45E+V95LBq4KqdhZUNw==",
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.0.0",
+        "@babel/helper-module-imports": "^7.0.0",
+        "babel-plugin-syntax-jsx": "^6.18.0",
+        "lodash": "^4.17.10"
+      }
+    },
     "babel-plugin-syntax-async-functions": {
       "version": "6.13.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
@@ -533,8 +595,7 @@
     "babel-plugin-syntax-jsx": {
       "version": "6.18.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
-      "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=",
-      "dev": true
+      "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
     },
     "babel-plugin-syntax-trailing-function-commas": {
       "version": "6.22.0",
@@ -1723,6 +1784,11 @@
       "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
       "dev": true
     },
+    "camelize": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+      "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+    },
     "caniuse-lite": {
       "version": "1.0.30000951",
       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000951.tgz",
@@ -2447,6 +2513,21 @@
         "randombytes": "^2.0.0"
       }
     },
+    "css-color-keywords": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+      "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
+    },
+    "css-to-react-native": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.0.tgz",
+      "integrity": "sha512-IhR7bNIrCFwbJbKZOAjNDZdwpsbjTN6f1agXeELHDqg1wHPA8c2QLruttKOW7hgMGetkfraRJCIEMrptifBfVw==",
+      "requires": {
+        "camelize": "^1.0.0",
+        "css-color-keywords": "^1.0.0",
+        "postcss-value-parser": "^3.3.0"
+      }
+    },
     "damerau-levenshtein": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
@@ -3377,8 +3458,7 @@
     "esutils": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
-      "dev": true
+      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
     },
     "etag": {
       "version": "1.8.1",
@@ -4323,8 +4403,7 @@
     "has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
-      "dev": true
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
     },
     "has-symbols": {
       "version": "1.0.0",
@@ -5046,6 +5125,11 @@
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
       "dev": true
     },
+    "memoize-one": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz",
+      "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw=="
+    },
     "merge-descriptors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -5732,6 +5816,11 @@
       "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
       "dev": true
     },
+    "postcss-value-parser": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+      "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
+    },
     "prelude-ls": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -7001,6 +7090,44 @@
       "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
       "dev": true
     },
+    "styled-components": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.2.0.tgz",
+      "integrity": "sha512-L/LzkL3ZbBhqIVHdR7DbYujy4tqvTNRfc+4JWDCYyhTatI+8CRRQUmdaR0+ARl03DWsfKLhjewll5uNLrqrl4A==",
+      "requires": {
+        "@babel/helper-module-imports": "^7.0.0",
+        "@emotion/is-prop-valid": "^0.7.3",
+        "@emotion/unitless": "^0.7.0",
+        "babel-plugin-styled-components": ">= 1",
+        "css-to-react-native": "^2.2.2",
+        "memoize-one": "^5.0.0",
+        "prop-types": "^15.5.4",
+        "react-is": "^16.6.0",
+        "stylis": "^3.5.0",
+        "stylis-rule-sheet": "^0.0.10",
+        "supports-color": "^5.5.0"
+      },
+      "dependencies": {
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
+    "stylis": {
+      "version": "3.5.4",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
+      "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q=="
+    },
+    "stylis-rule-sheet": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
+      "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw=="
+    },
     "supports-color": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
diff --git a/package.json b/package.json
index d899e89..33f6457 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,8 @@
     "react": "^16.2",
     "react-dom": "^16.2",
     "react-redux": "~5.0.6",
-    "redux": "~3.7.2"
+    "redux": "~3.7.2",
+    "styled-components": "^4.2.0"
   },
   "devDependencies": {
     "auto-reload-brunch": "^2",
-- 
GitLab