diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5447c55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +node_modules + +# testing +coverage + +# production +build + +# misc +.DS_Store +.env +npm-debug.log* +yarn-debug.log* +yarn-error.log* + diff --git a/package.json b/package.json new file mode 100644 index 0000000..0891e90 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "foobar", + "version": "0.1.0", + "private": true, + "devDependencies": { + "react-scripts": "0.9.5" + }, + "dependencies": { + "prop-types": "^15.5.8", + "react": "^15.5.4", + "react-dom": "^15.5.4", + "react-router": "^4.1.1", + "react-router-dom": "^4.1.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..5c125de Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..aab5e3b --- /dev/null +++ b/public/index.html @@ -0,0 +1,31 @@ + + + + + + + + React App + + +
+ + + diff --git a/src/CompleteProps/User.js b/src/CompleteProps/User.js new file mode 100644 index 0000000..4842c6e --- /dev/null +++ b/src/CompleteProps/User.js @@ -0,0 +1,17 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const User = (props) => ( +

+ {props.preferredName || props.name}: {props.email} +

+) + +// https://facebook.github.io/react/docs/typechecking-with-proptypes.html +User.propTypes = { + name: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + preferredName: PropTypes.string +} + +export default User diff --git a/src/CompleteProps/UserList.js b/src/CompleteProps/UserList.js new file mode 100644 index 0000000..c16297a --- /dev/null +++ b/src/CompleteProps/UserList.js @@ -0,0 +1,20 @@ +import React from 'react' +import PropTypes from 'prop-types' +import User from './User' + +const UserList = (props) => ( + +) + +// https://facebook.github.io/react/docs/typechecking-with-proptypes.html +UserList.propTypes = { + users: PropTypes.array.isRequired +} + +export default UserList diff --git a/src/CompleteProps/index.js b/src/CompleteProps/index.js new file mode 100644 index 0000000..75ce057 --- /dev/null +++ b/src/CompleteProps/index.js @@ -0,0 +1,16 @@ +import React from 'react' +import UserList from './UserList' + +const users = [ + { name: 'Michael', preferredName: 'Mike', email: 'mike@mikesmart.co.uk' }, + { name: 'Hugo', email: 'hugo.giraudel@gmail.com' } +] + +const UserIndex = () => ( +
+

Users:

+ +
+) + +export default UserIndex diff --git a/src/Forms/container.js b/src/Forms/container.js new file mode 100644 index 0000000..327b266 --- /dev/null +++ b/src/Forms/container.js @@ -0,0 +1,30 @@ +import React from 'react' +import PropTypes from 'prop-types' +import UserList from '../CompleteProps/UserList' + +class Search extends React.Component { + static propTypes = { + users: PropTypes.arrayOf(PropTypes.object).isRequired, + search: PropTypes.string + } + + getFilteredUsers = () => { + if (!this.props.search) { + return this.props.users + } + + return this.props.users.filter(this.isUserMatching) + } + + isUserMatching = (user) => { + return user.name.toLowerCase().indexOf(this.props.search.toLowerCase()) > -1 + } + + render () { + return ( + + ) + } +} + +export default Search diff --git a/src/Forms/data.json b/src/Forms/data.json new file mode 100644 index 0000000..b6b49a9 --- /dev/null +++ b/src/Forms/data.json @@ -0,0 +1,363 @@ +[ + { + "id": "59076466809fe801e7797e0f", + "name": "Dillon", + "email": "dillonlevy@devit.com", + "preferredName": "Dil" + }, + { + "id": "590764669327dac55f7cb30a", + "name": "Harmon", + "email": "harmonlevy@devit.com", + "preferredName": "Har" + }, + { + "id": "5907646655d0a24dab735af4", + "name": "Randolph", + "email": "randolphlevy@devit.com", + "preferredName": "Ran" + }, + { + "id": "590764662c63becda266b333", + "name": "Moreno", + "email": "morenolevy@devit.com", + "preferredName": "Mor" + }, + { + "id": "590764664a1536562d34a689", + "name": "Annmarie", + "email": "annmarielevy@devit.com", + "preferredName": "Ann" + }, + { + "id": "5907646626d66705fdf1fa6a", + "name": "Alfreda", + "email": "alfredalevy@devit.com", + "preferredName": "Alf" + }, + { + "id": "590764660e3f274f81c57736", + "name": "Savage", + "email": "savagelevy@devit.com", + "preferredName": "Sav" + }, + { + "id": "590764667e35768cac45b7fe", + "name": "Rita", + "email": "ritalevy@devit.com" + }, + { + "id": "59076466b8322c086ba4f323", + "name": "Calderon", + "email": "calderonlevy@devit.com", + "preferredName": "Cal" + }, + { + "id": "5907646641df9a12a2d1aa9d", + "name": "Reid", + "email": "reidlevy@devit.com" + }, + { + "id": "59076466071b5dbf7674cfbe", + "name": "Lynn", + "email": "lynnlevy@devit.com" + }, + { + "id": "590764660b5a81c8248aceea", + "name": "Evangelina", + "email": "evangelinalevy@devit.com", + "preferredName": "Eva" + }, + { + "id": "590764663d1d79db2415284e", + "name": "Hawkins", + "email": "hawkinslevy@devit.com", + "preferredName": "Haw" + }, + { + "id": "59076466a1e99271c54adc4f", + "name": "Willis", + "email": "willislevy@devit.com", + "preferredName": "Wil" + }, + { + "id": "590764664815382df91e95a0", + "name": "Eaton", + "email": "eatonlevy@devit.com" + }, + { + "id": "59076466b33b8b7a140e5a72", + "name": "Frye", + "email": "fryelevy@devit.com" + }, + { + "id": "590764669b82ce873d7c4a54", + "name": "Odom", + "email": "odomlevy@devit.com" + }, + { + "id": "5907646680b6d535a8ed0478", + "name": "Peterson", + "email": "petersonlevy@devit.com", + "preferredName": "Pet" + }, + { + "id": "59076466de0401403df67b15", + "name": "Mays", + "email": "mayslevy@devit.com" + }, + { + "id": "590764663a1c992d567aacc3", + "name": "Jimenez", + "email": "jimenezlevy@devit.com", + "preferredName": "Jim" + }, + { + "id": "59076466add165939e7c84ed", + "name": "Boyd", + "email": "boydlevy@devit.com" + }, + { + "id": "59076466311639aca415f399", + "name": "Lottie", + "email": "lottielevy@devit.com", + "preferredName": "Lot" + }, + { + "id": "5907646603e11d0b93fb1a33", + "name": "Cornelia", + "email": "cornelialevy@devit.com", + "preferredName": "Cor" + }, + { + "id": "59076466ccb7cf217400cff4", + "name": "Gracie", + "email": "gracielevy@devit.com", + "preferredName": "Gra" + }, + { + "id": "59076466f78544075058be06", + "name": "Stacey", + "email": "staceylevy@devit.com", + "preferredName": "Sta" + }, + { + "id": "59076466721c31e7bc0057eb", + "name": "Rhoda", + "email": "rhodalevy@devit.com" + }, + { + "id": "590764668c73bd15506c912b", + "name": "Riddle", + "email": "riddlelevy@devit.com", + "preferredName": "Rid" + }, + { + "id": "590764662fd1c144ec145116", + "name": "Clayton", + "email": "claytonlevy@devit.com", + "preferredName": "Cla" + }, + { + "id": "5907646601b148f674289116", + "name": "Jocelyn", + "email": "jocelynlevy@devit.com", + "preferredName": "Joc" + }, + { + "id": "5907646673e1949f5b7dafd7", + "name": "Puckett", + "email": "puckettlevy@devit.com", + "preferredName": "Puc" + }, + { + "id": "590764668c3d40d54fdd0b0b", + "name": "Leona", + "email": "leonalevy@devit.com" + }, + { + "id": "59076466da0bb7c7611ffa79", + "name": "Mai", + "email": "mailevy@devit.com" + }, + { + "id": "5907646647b06bfd655c5965", + "name": "Schneider", + "email": "schneiderlevy@devit.com", + "preferredName": "Sch" + }, + { + "id": "59076466aac4f06526149dd1", + "name": "Lupe", + "email": "lupelevy@devit.com" + }, + { + "id": "5907646668cc0cc8b41134c2", + "name": "Ruiz", + "email": "ruizlevy@devit.com" + }, + { + "id": "59076466efd2214a723d2de9", + "name": "Walters", + "email": "walterslevy@devit.com", + "preferredName": "Wal" + }, + { + "id": "5907646643217651d903e376", + "name": "Blake", + "email": "blakelevy@devit.com" + }, + { + "id": "590764664c78d611f4466fe8", + "name": "Whitehead", + "email": "whiteheadlevy@devit.com", + "preferredName": "Whi" + }, + { + "id": "590764664e6ade01a4c16d39", + "name": "Doyle", + "email": "doylelevy@devit.com" + }, + { + "id": "59076466820f73920ff64ae7", + "name": "Porter", + "email": "porterlevy@devit.com", + "preferredName": "Por" + }, + { + "id": "590764663aad1dc6e5125745", + "name": "Gilliam", + "email": "gilliamlevy@devit.com", + "preferredName": "Gil" + }, + { + "id": "59076466c5581dfc8f49b9c8", + "name": "Katheryn", + "email": "katherynlevy@devit.com", + "preferredName": "Kat" + }, + { + "id": "59076466b1de9d6c39ce906f", + "name": "Sherrie", + "email": "sherrielevy@devit.com", + "preferredName": "She" + }, + { + "id": "590764667d6d4989c0a37bf5", + "name": "Karina", + "email": "karinalevy@devit.com", + "preferredName": "Kar" + }, + { + "id": "5907646660e24ef89eb7d686", + "name": "Lilly", + "email": "lillylevy@devit.com" + }, + { + "id": "59076466b069b0bfdf48e64b", + "name": "Dawson", + "email": "dawsonlevy@devit.com", + "preferredName": "Daw" + }, + { + "id": "59076466d4cb5131f7768df6", + "name": "Verna", + "email": "vernalevy@devit.com" + }, + { + "id": "59076466dee5c3ce0ee737c3", + "name": "Dollie", + "email": "dollielevy@devit.com", + "preferredName": "Dol" + }, + { + "id": "59076466aafed31706977002", + "name": "Hebert", + "email": "hebertlevy@devit.com", + "preferredName": "Heb" + }, + { + "id": "5907646674e2dadd6ab3409c", + "name": "Madeline", + "email": "madelinelevy@devit.com", + "preferredName": "Mad" + }, + { + "id": "5907646684cad8d00598db96", + "name": "Crystal", + "email": "crystallevy@devit.com", + "preferredName": "Cry" + }, + { + "id": "5907646685936858fd819ca9", + "name": "Clements", + "email": "clementslevy@devit.com", + "preferredName": "Cle" + }, + { + "id": "5907646649705e762c467a0b", + "name": "Kayla", + "email": "kaylalevy@devit.com" + }, + { + "id": "5907646697faed3ff14a58f7", + "name": "Teri", + "email": "terilevy@devit.com" + }, + { + "id": "590764664fc3e30c5c3100ba", + "name": "Cote", + "email": "cotelevy@devit.com" + }, + { + "id": "59076466d4549311ae634858", + "name": "Gwendolyn", + "email": "gwendolynlevy@devit.com", + "preferredName": "Gwe" + }, + { + "id": "59076466fe6d792ce337ae1e", + "name": "Marsh", + "email": "marshlevy@devit.com" + }, + { + "id": "59076466219e119c74a71e43", + "name": "Farley", + "email": "farleylevy@devit.com", + "preferredName": "Far" + }, + { + "id": "5907646648720a7dfdbbb875", + "name": "Renee", + "email": "reneelevy@devit.com" + }, + { + "id": "59076466d6c22ac851510cf7", + "name": "Rosanna", + "email": "rosannalevy@devit.com", + "preferredName": "Ros" + }, + { + "id": "5907646608c0655a72a57916", + "name": "Walton", + "email": "waltonlevy@devit.com", + "preferredName": "Wal" + }, + { + "id": "590764667afde087e4d03bbf", + "name": "Romero", + "email": "romerolevy@devit.com", + "preferredName": "Rom" + }, + { + "id": "590764667258d71a474bf55a", + "name": "Selma", + "email": "selmalevy@devit.com" + }, + { + "id": "590764666011136e283266c0", + "name": "Stephenson", + "email": "stephensonlevy@devit.com", + "preferredName": "Ste" + } +] diff --git a/src/Forms/getSearchParams.js b/src/Forms/getSearchParams.js new file mode 100644 index 0000000..c5ba319 --- /dev/null +++ b/src/Forms/getSearchParams.js @@ -0,0 +1,21 @@ +const getSearchParams = (searchString = '?') => { + // Remove question mark + // (e.g. `?foo=bar&baz=qux` -> `foo=bar&baz=qux`) + const searchItems = searchString.slice(1) + + // Split variables + // (e.g. `foo=bar&baz=qux` -> [ 'foo=bar', 'baz=qux' ]) + const searchPairs = searchItems.split('&') + + // Construct an object from pairs + // (e.g. [ 'foo=bar', 'baz=qux' ] -> { foo: 'bar', baz: 'qux' }) + const searchParams = searchPairs.reduce((params, pair) => { + const items = pair.split('=') + params[items[0]] = items[1] + return params + }, {}) + + return searchParams +} + +export default getSearchParams diff --git a/src/Forms/index.js b/src/Forms/index.js new file mode 100644 index 0000000..03130d6 --- /dev/null +++ b/src/Forms/index.js @@ -0,0 +1,64 @@ +import React from 'react' +import PropTypes from 'prop-types' +import List from './container' +import users from './data' +import getSearchParams from './getSearchParams' + +class Search extends React.Component { + static contextTypes = { + router: PropTypes.object.isRequired + } + + constructor (props) { + super(props) + + const params = getSearchParams(this.props.location.search) + + this.state = { + search: params.search + } + } + + componentDidUpdate (prevProps) { + const prevParams = getSearchParams(prevProps.location.search) + const params = getSearchParams(this.props.location.search) + if (prevParams.search !== params.search) { + // console.log(prevParams.search, params.search) + // this.updateSearch(params.search) + } + } + + updateSearch = (value) => { + this.context.router.history.push('?search=' + encodeURIComponent(value)) + } + + handleChange = (event) => { + this.setState({ search: event.target.value }) + } + + handleSubmit = (event) => { + event.preventDefault() + this.updateSearch(this.state.search) + } + + render () { + return ( +
+
+ + + +
+ +
+ ) + } +} + +export default Search diff --git a/src/HelloWorld/index.js b/src/HelloWorld/index.js new file mode 100644 index 0000000..7c20ada --- /dev/null +++ b/src/HelloWorld/index.js @@ -0,0 +1,5 @@ +import React from 'react' + +const HelloWorld = () =>

Hello world

+ +export default HelloWorld diff --git a/src/HigherOrder/index.js b/src/HigherOrder/index.js new file mode 100644 index 0000000..383c3a0 --- /dev/null +++ b/src/HigherOrder/index.js @@ -0,0 +1,11 @@ +import React from 'react' +import Trackable from './trackable' + +const Page = (props) => ( +
+

Tasks (15 minutes):

+

Write a higher-order function that provides the viewport width to the wrapped component as a viewportWidth property. Do not forget to unregister the listeners when/if component unmounts!

+
+) + +export default Trackable(Page) diff --git a/src/HigherOrder/trackable.js b/src/HigherOrder/trackable.js new file mode 100644 index 0000000..efc2695 --- /dev/null +++ b/src/HigherOrder/trackable.js @@ -0,0 +1,34 @@ +import React from 'react' + +const Trackable = (Component) => { + class TrackableComponent extends React.Component { + componentDidMount () { + console.log(`Component ${Component.name} mounted.`) + } + + componentWillUnmount () { + console.log(`Component ${Component.name} will unmount.`) + } + + handleClick = (event) => { + console.log(`Component ${Component.name} clicked.`) + + if (typeof this.props.onClick === 'function') { + this.props.onClick(event) + } + } + + render () { + return ( + + ) + } + } + + return TrackableComponent +} + +export default Trackable diff --git a/src/HigherOrder/viewport-width.js b/src/HigherOrder/viewport-width.js new file mode 100644 index 0000000..4e3e390 --- /dev/null +++ b/src/HigherOrder/viewport-width.js @@ -0,0 +1,15 @@ +import React from 'react' + +const ViewportWidthProvider = (Component) => { + class ViewportWidth extends React.Component { + render () { + return ( + // Replace `viewportWidth` prop value with the actual viewportWidth + + ) + } + } +} + + +export default ViewportWidthProvider diff --git a/src/Home/index.js b/src/Home/index.js new file mode 100644 index 0000000..f4460ed --- /dev/null +++ b/src/Home/index.js @@ -0,0 +1,111 @@ +import React from 'react' +import { Link } from 'react-router-dom' + +export default () => ( +
+

React is undefined

+ +

What is it, how does it work, what’s the point? (30 mins)

+ +
    +
  • +

    Why choose React, what does it help with?

    + +
      +
    • Declarative
    • +
    • Library, not a framework
    • +
    • Rich third-party ecosystem
    • +
    +
  • +
  • +

    Reverse-tree based architecture

    + +
      +
    • Root component
    • +
    • Sub-components…
    • +
    +
  • +
  • +

    Component based approach with props

    +
      +
    • + Hello World (without props) +
    • +
    • + Hello World (with props) +
    • +
    • + Moar props +
    • +
    +
  • +
  • +

    React component lifecycle

    +
      +
    • + Stateful component +
    • +
    +
  • +
  • +

    Higher Order Components

    +
      +
    • + Higher-order components +
    • +
    +
  • +
  • Component state - container

    + +
      +
    • https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
    • +
    • More complex example, text filter component
    • +
  • +
+ +

Use this knowledge to build something (60 mins)

+ +
    +
  • Simple index.html/CodePen, build a simple example, css block
  • +
  • CRA
  • +
+ +

--- break ---

+ +

Build systems

+ +
    +
  • Webpack the basics
  • +
  • Webpack the complicated
  • +
+ +

Isomorphism

+ +
    +
  • Server setup (ReactDOMServer, JSX in Node)
  • +
  • Caching?
  • +
+ +

Styling

+ +
    +
  • CSS
  • +
  • Modules?
  • +
  • CSS in JS (Fela, styled-components)
  • +
+ +

--- break ---

+ +

Bonus content

+ +
    +
  • Flux based state management
  • +
  • Router
  • +
  • Refs and third parties
  • +
  • Events (Keyboard Click Events)
  • +
  • Context - when to use it, and when really not to use it
  • +
  • Controlled components
  • +
  • Prop/State Hoisting (Data flow bottom to top)
  • +
+
+) diff --git a/src/SimpleProps/index.js b/src/SimpleProps/index.js new file mode 100644 index 0000000..9368832 --- /dev/null +++ b/src/SimpleProps/index.js @@ -0,0 +1,6 @@ +import React from 'react' + +const Hello = (props) =>

Hello {props.name}

+const HelloWithProps = (props) => + +export default HelloWithProps diff --git a/src/SimpleProps/moar.js b/src/SimpleProps/moar.js new file mode 100644 index 0000000..9f079af --- /dev/null +++ b/src/SimpleProps/moar.js @@ -0,0 +1,42 @@ +import React from 'react' +import PropTypes 'prop-types' + +const User = (props) => ( +

+ {props.preferredName || props.name}: {props.email} +

+) + +User.propTypes = { + name: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + preferredName: Proptypes.string +} + +const UserList = (props) => ( +
    + {props.users.map((user) => ( +
  • + +
  • + ))} +
+) + +UserList.propTypes = { + users: PropTypes.array.isRequired +} + +const users = [ + { name: 'Michael', preferredName: 'Mike', email: 'mike@mikesmart.co.uk' }, + { name: 'Hugo', email: 'hugo.giraudel@gmail.com' } +] + +const UserIndex = () => ( +
+

Users:

+ +
+) + +export default UserIndex diff --git a/src/State/ask-before-unload.js b/src/State/ask-before-unload.js new file mode 100644 index 0000000..24c5696 --- /dev/null +++ b/src/State/ask-before-unload.js @@ -0,0 +1,11 @@ +import React from 'react' + +class AskBeforeUnload extends React.Component { + render () { + return ( +

The browser should ask for confirmation before reloading the page.

+ ) + } +} + +export default AskBeforeUnload diff --git a/src/State/index.js b/src/State/index.js new file mode 100644 index 0000000..173a484 --- /dev/null +++ b/src/State/index.js @@ -0,0 +1,18 @@ +import Timer from './timer' +import React from 'react' + +const Page = () => ( +
+ +
+

Pick a task below (15 minutes):

+
    +
  • [Easy] Make the Timer component accept an initial property to set the default value for seconds.
  • +
  • [Medium] Build a component that ask for confirmation before leaving the page.
  • +
  • [Medium] Display the live mouse position on the page.
  • +
  • [Hard] Find a simple third-party library and initialise it inside your component (e.g.: Flickity, a11y-dialog…).
  • +
+
+) + +export default Page diff --git a/src/State/mouse-position.js b/src/State/mouse-position.js new file mode 100644 index 0000000..6920857 --- /dev/null +++ b/src/State/mouse-position.js @@ -0,0 +1,12 @@ +import React from 'react' + +class MousePosition extends React.Component { + render () { + return ( + // Adjust this to log the current mouse position on screen. +

Mouse is currently at position X:Y.

+ ) + } +} + +export default MousePosition diff --git a/src/State/timer.js b/src/State/timer.js new file mode 100644 index 0000000..2f8bc4c --- /dev/null +++ b/src/State/timer.js @@ -0,0 +1,37 @@ +import React from 'react' + +class Timer extends React.Component { + constructor (props) { + super(props) + + this.state = { + seconds: 0 + } + } + + componentWillMount () { + this.timer = setInterval(this.increment, 1000) + } + + componentWillUnmount () { + clearInterval(this.timer) + } + + increment = () => { + this.setState({ + seconds: this.state.seconds + 1 + }) + } + + pluraliseSeconds = () => { + return this.state.seconds === 1 ? 'second' : 'seconds' + } + + render () { + return ( +

👋🏻 I have been loaded for {this.state.seconds} {this.pluraliseSeconds()}.

+ ) + } +} + +export default Timer diff --git a/src/__internals/Page/index.js b/src/__internals/Page/index.js new file mode 100644 index 0000000..1c6f705 --- /dev/null +++ b/src/__internals/Page/index.js @@ -0,0 +1,10 @@ +import React from 'react' +import './styles.css' + +const Page = (props) => ( +
+ {props.children} +
+) + +export default Page diff --git a/src/__internals/Page/styles.css b/src/__internals/Page/styles.css new file mode 100644 index 0000000..6b7b190 --- /dev/null +++ b/src/__internals/Page/styles.css @@ -0,0 +1,8 @@ +.Page { + max-width: 800px; + width: 100%; + margin: 0 auto; + padding: 1em; + background-color: white; + height: 100%; +} diff --git a/src/__internals/Router/index.js b/src/__internals/Router/index.js new file mode 100644 index 0000000..8cc57ed --- /dev/null +++ b/src/__internals/Router/index.js @@ -0,0 +1,38 @@ +import React from 'react' +import { Link, Route } from 'react-router-dom' +import Home from '../../Home' +import HelloWorld from '../../HelloWorld' +import Props from '../../SimpleProps' +import MoreProps from '../../CompleteProps' +import State from '../../State' +import HigherOrder from '../../HigherOrder' +import Forms from '../../Forms' +import './styles.css' + +class Navigation extends React.Component { + render() { + return ( +
+
+ Home + Hello World + Components with props + Components with more props + Stateful components + Higher-order functions + Forms +
+ + + + + + + + +
+ ) + } +} + +export default Navigation diff --git a/src/__internals/Router/styles.css b/src/__internals/Router/styles.css new file mode 100644 index 0000000..8d2b8f5 --- /dev/null +++ b/src/__internals/Router/styles.css @@ -0,0 +1,13 @@ +.Navigation { + border-bottom: 1px solid silver; + padding-bottom: 1em; + margin: 0 -0.5ch; +} + +.Link { + display: inline-block; + text-decoration: none; + color: deepskyblue; + border-bottom: 1px solid; + margin: 0 0.5ch; +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..e8c8e4a --- /dev/null +++ b/src/index.css @@ -0,0 +1,23 @@ +html { + box-sizing: border-box; + background-color: #EFEFEF; + height: 100%; +} + +* { + box-sizing: inherit; +} + +body { + margin: 0; + height: 100%; + font: 120% / 1.4 sans-serif; +} + +ul { + padding: 0 0 0 1em; +} + +#root { + height: 100%; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..bd2ce3b --- /dev/null +++ b/src/index.js @@ -0,0 +1,15 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import { HashRouter } from 'react-router-dom' +import Router from './__internals/Router' +import Page from './__internals/Page' +import './index.css' + +ReactDOM.render( + + + + + , + document.getElementById('root') +)