├── .eslintrc ├── .prettierrc ├── 01-imperative-to-declarative ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ │ └── index.css │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.final.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ ├── SineWave.js │ │ ├── createOscillator.js │ │ └── index.css │ └── yarn.lock ├── 02-hocs-render-props ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.hoc.js │ │ ├── App.hooks.js │ │ ├── App.render-prop.js │ │ ├── App.start.js │ │ ├── images │ │ │ ├── earth.jpg │ │ │ ├── galaxy.jpg │ │ │ └── trees.jpg │ │ ├── index.js │ │ └── lib │ │ │ ├── createMediaListener.js │ │ │ ├── images │ │ │ ├── earth.jpg │ │ │ ├── galaxy.jpg │ │ │ └── trees.jpg │ │ │ ├── index.css │ │ │ └── screens.js │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.final.hoc.js │ ├── App.final.render-props.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ ├── LoadingDots.js │ │ ├── Map.js │ │ ├── getAddressFromCoords.js │ │ └── index.css │ └── yarn.lock ├── 03-clone-element ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ ├── index.css │ │ └── noise.png └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.cloneElement.js │ ├── App.context.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ ├── index.css │ │ └── text.js │ └── yarn.lock ├── 04-context ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ │ ├── index.css │ │ │ ├── mariobros.mp3 │ │ │ └── podcast.mp4 │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.final.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ ├── index.css │ │ └── text.js │ └── yarn.lock ├── 05-portals ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── .App.js.swo │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ │ ├── .App.js.swo │ │ │ └── index.css │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.final.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ └── index.css │ └── yarn.lock ├── 06-wai-aria ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ │ ├── index.css │ │ │ └── noise.png │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── .App.js.swn │ ├── .App.js.swo │ ├── App.final.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ ├── .App.js.swn │ │ ├── .App.js.swo │ │ └── index.css │ └── yarn.lock ├── 07-gsbu ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ │ ├── FadeIn.js │ │ │ ├── avatars │ │ │ ├── EI.png │ │ │ ├── GC.png │ │ │ ├── MP.png │ │ │ └── TG.png │ │ │ ├── index.css │ │ │ └── messages.js │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.final.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ └── index.css │ └── yarn.lock ├── 08-gdsfp ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ │ ├── digital-7 │ │ │ ├── digital-7 (italic).ttf │ │ │ ├── digital-7 (mono italic).ttf │ │ │ ├── digital-7 (mono).ttf │ │ │ ├── digital-7.ttf │ │ │ └── readme.txt │ │ │ └── index.css │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── App.final.js │ ├── App.start.js │ ├── index.js │ └── lib │ │ └── index.css │ └── yarn.lock ├── 09-suspense ├── exercise │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.solution.js │ │ ├── App.start.js │ │ ├── index.js │ │ └── lib │ │ │ ├── cache.js │ │ │ └── index.css │ └── yarn.lock └── lecture │ ├── .gitignore │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.Refactor.js │ ├── App.js │ ├── index.js │ └── lib │ │ ├── Competitions.js │ │ ├── ManageScroll.js │ │ ├── index.css │ │ └── utils.js │ └── yarn.lock ├── 10-carousel ├── .eslintrc ├── .gitignore ├── README.md ├── config │ ├── env.js │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── paths.js │ ├── polyfills.js │ ├── webpack.config.dev.js │ ├── webpack.config.prod.js │ └── webpackDevServer.config.js ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── scripts │ ├── build.js │ ├── start.js │ └── test.js └── src │ ├── .App.Media.start.js.swo │ ├── App.exercise.js │ ├── App.solution.js │ ├── Progress.js │ ├── index.js │ ├── useProgress.js │ └── whatevs │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── index.css │ └── slides.js ├── README.md ├── package.json ├── scripts └── install.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/.prettierrc -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0" 8 | }, 9 | "devDependencies": { 10 | "react-scripts": "1.0.10" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | } 18 | } -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/01-imperative-to-declarative/exercise/public/favicon.ico -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/src/App.solution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | class DocumentTitle extends Component { 4 | componentDidMount() { 5 | document.title = this.props.children; 6 | } 7 | 8 | componentDidUpdate() { 9 | document.title = this.props.children; 10 | } 11 | 12 | render() { 13 | return null; 14 | } 15 | } 16 | 17 | class App extends Component { 18 | state = { 19 | completed: 0, 20 | todos: ["Wake up", "Eat a taco", "Avoid twitter"] 21 | }; 22 | 23 | render() { 24 | let { todos, completed } = this.state; 25 | let incomplete = todos.length - completed; 26 | 27 | return ( 28 |
29 | {`Todos (${incomplete})`} 30 | 31 |

Todos ({incomplete})

32 | 33 |
{ 35 | let todo = event.target.elements[0].value; 36 | event.preventDefault(); 37 | event.target.reset(); 38 | this.setState(state => { 39 | return { todos: state.todos.concat([todo]) }; 40 | }); 41 | }} 42 | > 43 | 44 |
45 | 46 | 68 |
69 | ); 70 | } 71 | } 72 | 73 | export default App; 74 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Instructions: 4 | 5 | Goal: Update the document title to say "Todos ([incomplete])" as the data changes. 6 | 7 | - Make a `` component 8 | - Pass it a prop with the string for the title 9 | - Use lifecycle hooks to keep it up to date with the data 10 | 11 | Tips: 12 | 13 | - You'll need two lifecycle hooks 14 | - You'll need string interpolation `it looks ${like} this` 15 | - the DOM API to update the title is `document.title = "some string"` 16 | 17 | */ 18 | 19 | import React, { Component } from "react"; 20 | 21 | class App extends Component { 22 | state = { 23 | completed: 0, 24 | todos: ["Wake up", "Eat a taco", "Avoid twitter"] 25 | }; 26 | 27 | render() { 28 | let { todos, completed } = this.state; 29 | let incomplete = todos.length - completed; 30 | 31 | return ( 32 |
33 |

Todos ({incomplete})

34 | 35 |
{ 37 | let todo = event.target.elements[0].value; 38 | event.preventDefault(); 39 | event.target.reset(); 40 | this.setState(state => { 41 | return { todos: state.todos.concat([todo]) }; 42 | }); 43 | }} 44 | > 45 | 46 |
47 | 48 | 70 |
71 | ); 72 | } 73 | } 74 | 75 | export default App; 76 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/exercise/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 4 | } 5 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0" 8 | }, 9 | "devDependencies": { 10 | "react-scripts": "1.0.10" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/01-imperative-to-declarative/lecture/public/favicon.ico -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/src/App.final.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import createOscillator from "./lib/createOscillator"; 3 | import SineWave from "./lib/SineWave"; 4 | 5 | class Tone extends React.Component { 6 | oscillator = createOscillator(); 7 | 8 | componentDidMount() { 9 | this.doImperativeStuff(); 10 | } 11 | 12 | componentDidUpdate() { 13 | this.doImperativeStuff(); 14 | } 15 | 16 | doImperativeStuff() { 17 | const { isPlaying, pitch, volume } = this.props; 18 | if (isPlaying) { 19 | this.oscillator.play(); 20 | } else { 21 | this.oscillator.stop(); 22 | } 23 | this.oscillator.setPitchBend(pitch); 24 | this.oscillator.setVolume(volume); 25 | } 26 | 27 | render() { 28 | return null; 29 | } 30 | } 31 | 32 | class App extends React.Component { 33 | state = { 34 | isPlaying: false, 35 | pitch: 0, 36 | volume: 0 37 | }; 38 | 39 | play = () => { 40 | this.setState({ isPlaying: true }); 41 | }; 42 | 43 | stop = () => { 44 | this.setState({ isPlaying: false }); 45 | }; 46 | 47 | changeTone = event => { 48 | const { clientX, clientY } = event; 49 | const { top, right, bottom, left } = event.target.getBoundingClientRect(); 50 | const pitch = (clientX - left) / (right - left); 51 | const volume = 1 - (clientY - top) / (bottom - top); 52 | this.setState({ pitch, volume }); 53 | }; 54 | 55 | render() { 56 | return ( 57 |
58 |
64 | 71 | 76 |
77 |
◀︎ Pitch ▶︎
78 |
◀︎ Volume ▶︎
79 |
80 | ); 81 | } 82 | } 83 | 84 | export default App; 85 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import createOscillator from "./lib/createOscillator"; 3 | 4 | class App extends React.Component { 5 | oscillator = createOscillator(); 6 | 7 | play = () => { 8 | this.oscillator.play(); 9 | }; 10 | 11 | stop = () => { 12 | this.oscillator.stop(); 13 | }; 14 | 15 | changeTone = event => { 16 | const { clientX, clientY } = event; 17 | const { top, right, bottom, left } = event.target.getBoundingClientRect(); 18 | const pitch = (clientX - left) / (right - left); 19 | const volume = 1 - (clientY - top) / (bottom - top); 20 | this.oscillator.setPitchBend(pitch); 21 | this.oscillator.setVolume(volume); 22 | }; 23 | 24 | render() { 25 | return ( 26 |
27 |
33 |
◀︎ Pitch ▶︎
34 |
◀︎ Volume ▶︎
35 |
36 | ); 37 | } 38 | } 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/src/lib/SineWave.js: -------------------------------------------------------------------------------- 1 | // modified from http://codepen.io/enxaneta/pen/jbVLGb/, see copyright there 2 | 3 | import React from "react"; 4 | 5 | class SineWave extends React.Component { 6 | componentDidMount() { 7 | this.ctx = this.node.getContext("2d"); 8 | const { width, height } = this.node.getBoundingClientRect(); 9 | this.width = width; 10 | this.height = height; 11 | this.renderCanvas(true); 12 | } 13 | 14 | componentDidUpdate(prevProps) { 15 | if (!prevProps.draw && this.props.draw) { 16 | this.renderCanvas(); 17 | } 18 | } 19 | 20 | renderCanvas(force) { 21 | const { ctx, width, height } = this; 22 | let phi = 0; 23 | let frames = 0; 24 | ctx.lineWidth = 4; 25 | 26 | const draw = () => { 27 | const amplitude = height * this.props.amplitude; 28 | const frequency = this.props.frequency / 2; 29 | const offset = (height - amplitude) / 2; 30 | frames++; 31 | phi = frames / 30; 32 | 33 | ctx.clearRect(0, 0, width, height); 34 | ctx.strokeStyle = "white"; 35 | ctx.moveTo(0, height); 36 | ctx.beginPath(); 37 | for (var x = 0; x < width; x++) { 38 | let y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2; 39 | ctx.lineTo(x, y + offset); // 40 = offset 40 | } 41 | ctx.stroke(); 42 | if (this.props.draw) { 43 | window.requestAnimationFrame(draw); 44 | } 45 | }; 46 | if (force || this.props.draw) { 47 | window.requestAnimationFrame(draw); 48 | } 49 | } 50 | 51 | render() { 52 | const { width, height } = this.props; 53 | return (this.node = n)} width={width} height={height} />; 54 | } 55 | } 56 | 57 | export default SineWave; 58 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/src/lib/createOscillator.js: -------------------------------------------------------------------------------- 1 | class Oscillator { 2 | constructor(audioContext) { 3 | this.audioContext = audioContext; 4 | this.oscillatorNode = this.audioContext.createOscillator(); 5 | this.oscillatorNode.start(0); 6 | this.gainNode = this.audioContext.createGain(); 7 | this.pitchBase = 50; 8 | this.pitchBend = 0; 9 | this.pitchRange = 2000; 10 | this.volume = 0.5; 11 | this.maxVolume = 0.5; 12 | this.frequency = this.pitchBase; 13 | this.hasConnected = false; 14 | this.frequency = this.pitchBase; 15 | } 16 | 17 | play() { 18 | this.oscillatorNode.connect(this.gainNode); 19 | this.hasConnected = true; 20 | } 21 | 22 | stop() { 23 | if (this.hasConnected) { 24 | this.oscillatorNode.disconnect(this.gainNode); 25 | this.hasConnected = false; 26 | } 27 | } 28 | 29 | setType(type) { 30 | this.oscillatorNode.type = type; 31 | } 32 | 33 | setPitchBend(v) { 34 | this.pitchBend = v; 35 | this.frequency = this.pitchBase + this.pitchBend * this.pitchRange; 36 | this.oscillatorNode.frequency.value = this.frequency; 37 | } 38 | 39 | setVolume(v) { 40 | this.volume = this.maxVolume * v; 41 | this.gainNode.gain.value = this.volume; 42 | } 43 | 44 | connect(output) { 45 | this.gainNode.connect(output); 46 | } 47 | } 48 | 49 | function createOscillator() { 50 | const audioContext = new AudioContext(); 51 | const oscillator = new Oscillator(audioContext); 52 | oscillator.connect(audioContext.destination); 53 | return oscillator; 54 | } 55 | 56 | export default createOscillator; 57 | -------------------------------------------------------------------------------- /01-imperative-to-declarative/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 40px; 3 | background: radial-gradient( 4 | ellipse at center, 5 | #fac695 0%, 6 | #f5ab66 47%, 7 | #ef8d31 100% 8 | ); 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 10 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 11 | } 12 | 13 | .App { 14 | width: 400px; 15 | height: 400px; 16 | margin: auto; 17 | position: relative; 18 | } 19 | 20 | .theremin { 21 | background: hsla(0, 100%, 100%, 0.5); 22 | cursor: crosshair; 23 | width: 100%; 24 | height: 100%; 25 | } 26 | 27 | .label { 28 | color: white; 29 | text-transform: uppercase; 30 | font-size: 24px; 31 | font-weight: bold; 32 | position: absolute; 33 | } 34 | 35 | .volume { 36 | position: absolute; 37 | line-height: 1em; 38 | bottom: calc(50% - 0.5em); 39 | left: calc(-50% - 1em); 40 | width: 100%; 41 | transform: rotate(-90deg); 42 | text-align: center; 43 | } 44 | 45 | .pitch { 46 | width: 100%; 47 | text-align: center; 48 | bottom: -1.5em; 49 | } 50 | 51 | canvas { 52 | cursor: none; 53 | } 54 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.8.1", 7 | "react-dom": "^16.8.1", 8 | "react-transition-group": "^2.4.0" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/exercise/public/favicon.ico -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/App.hoc.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import createMediaListener from "./lib/createMediaListener"; 3 | import { Galaxy, Trees, Earth } from "./lib/screens"; 4 | 5 | const withMedia = queries => Comp => { 6 | const media = createMediaListener(queries); 7 | 8 | return class WithMedia extends React.Component { 9 | state = { 10 | media: media.getState() 11 | }; 12 | 13 | componentDidMount() { 14 | media.listen(media => this.setState({ media })); 15 | } 16 | 17 | componentWillUnmount() { 18 | media.dispose(); 19 | } 20 | 21 | render() { 22 | return ; 23 | } 24 | }; 25 | }; 26 | 27 | class App extends React.Component { 28 | render() { 29 | const { media } = this.props; 30 | 31 | return ( 32 |
33 | {media.big ? ( 34 | 35 | ) : media.tiny ? ( 36 | 37 | ) : ( 38 | 39 | )} 40 |
41 | ); 42 | } 43 | } 44 | 45 | const AppWithMedia = withMedia({ 46 | big: "(min-width : 1000px)", 47 | tiny: "(max-width: 600px)" 48 | })(App); 49 | 50 | export default AppWithMedia; 51 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/App.hooks.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import createMediaListener from "./lib/createMediaListener"; 3 | import { Galaxy, Trees, Earth } from "./lib/screens"; 4 | 5 | const media = createMediaListener({ 6 | big: "(min-width : 1000px)", 7 | tiny: "(max-width: 600px)" 8 | }); 9 | 10 | // 11 | 12 | // 13 | 14 | class Media extends React.Component { 15 | // static propTypes = { 16 | // query: PropTypes.string.isRequired 17 | // }; 18 | 19 | state = { media: media.getState() }; 20 | 21 | componentDidMount() { 22 | media.listen(media => this.setState({ media })); 23 | } 24 | 25 | componentDidUpdate(prevProps) { 26 | if (this.props.query !== prevProps.query) { 27 | // query prop changed! 28 | // time to teardown the old listener and setup 29 | // the new listener 30 | } 31 | } 32 | 33 | componentWillUnmount() { 34 | media.dispose(); 35 | } 36 | 37 | render() { 38 | return this.props.children(this.state.media); 39 | } 40 | } 41 | 42 | function useMedia(query) { 43 | const [mediaState, updateMedia] = useState(true); 44 | 45 | useEffect( 46 | () => { 47 | // componentDidMount or componentDidUpdate 48 | const match = window.matchMedia(query); 49 | 50 | function handleMatchChange() { 51 | updateMedia(match.matches); 52 | } 53 | 54 | handleMatchChange(); 55 | 56 | match.addListener(handleMatchChange); 57 | 58 | return () => { 59 | // componentWillUnmount 60 | match.removeListener(handleMatchChange); 61 | }; 62 | }, 63 | [query] 64 | ); 65 | 66 | return mediaState; 67 | } 68 | 69 | function App() { 70 | const [flipped, setFlipped] = useState(false); 71 | 72 | const bigQuery = "(min-width: 1000px)"; 73 | const smallQuery = "(max-width: 600px)"; 74 | 75 | const big = useMedia(flipped ? smallQuery : bigQuery); 76 | const small = useMedia(flipped ? bigQuery : smallQuery); 77 | 78 | return ( 79 |
80 | 93 | 94 | {big ? ( 95 | 96 | ) : small ? ( 97 | 98 | ) : ( 99 | 100 | )} 101 |
102 | ); 103 | } 104 | 105 | export default App; 106 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/App.render-prop.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import createMediaListener from "./lib/createMediaListener"; 3 | import { Galaxy, Trees, Earth } from "./lib/screens"; 4 | 5 | class Media extends React.Component { 6 | media = createMediaListener(this.props.queries); 7 | 8 | state = { 9 | media: this.media.getState() 10 | }; 11 | 12 | componentDidMount() { 13 | this.media.listen(media => this.setState({ media })); 14 | } 15 | 16 | componentDidUpdate(prevProps) { 17 | if (this.props.queries !== prevProps.queries) { 18 | this.media.dispose(); 19 | this.media = createMediaListener( 20 | this.props.queries 21 | ); 22 | this.media.listen(media => 23 | this.setState({ media }) 24 | ); 25 | } 26 | } 27 | 28 | componentWillUnmount() { 29 | this.media.dispose(); 30 | } 31 | 32 | render() { 33 | return this.props.children(this.state.media); 34 | } 35 | } 36 | 37 | class App extends React.Component { 38 | state = { 39 | queries: { 40 | big: "(min-width : 1000px)", 41 | tiny: "(max-width: 600px)" 42 | } 43 | }; 44 | 45 | render() { 46 | return ( 47 | 48 | {media => ( 49 |
50 | {media.big ? ( 51 | 52 | ) : media.tiny ? ( 53 | 54 | ) : ( 55 | 56 | )} 57 |
58 | )} 59 |
60 | ); 61 | } 62 | } 63 | 64 | export default App; 65 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import createMediaListener from "./lib/createMediaListener"; 3 | import { Galaxy, Trees, Earth } from "./lib/screens"; 4 | 5 | const media = createMediaListener({ 6 | big: "(min-width : 1000px)", 7 | tiny: "(max-width: 600px)" 8 | }); 9 | 10 | class App extends React.Component { 11 | state = { 12 | media: media.getState() 13 | }; 14 | 15 | componentDidMount() { 16 | media.listen(media => this.setState({ media })); 17 | } 18 | 19 | componentWillUnmount() { 20 | media.dispose(); 21 | } 22 | 23 | render() { 24 | const { media } = this.state; 25 | 26 | return ( 27 |
28 | {media.big ? ( 29 | 30 | ) : media.tiny ? ( 31 | 32 | ) : ( 33 | 34 | )} 35 |
36 | ); 37 | } 38 | } 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/images/earth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/exercise/src/images/earth.jpg -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/images/galaxy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/exercise/src/images/galaxy.jpg -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/images/trees.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/exercise/src/images/trees.jpg -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/lib/createMediaListener.js: -------------------------------------------------------------------------------- 1 | /* 2 | const listener = createMediaListener({ 3 | mobile: "(max-width: 767px)", 4 | small: "(max-width: 568px), (max-height: 400px)" 5 | }) 6 | 7 | listener.listen((state) => {}) 8 | listener.getState() 9 | listenter.dispose() 10 | */ 11 | 12 | export default media => { 13 | let transientListener = null; 14 | 15 | const mediaKeys = Object.keys(media); 16 | 17 | const queryLists = mediaKeys.reduce((queryLists, key) => { 18 | queryLists[key] = window.matchMedia(media[key]); 19 | return queryLists; 20 | }, {}); 21 | 22 | const mediaState = mediaKeys.reduce((state, key) => { 23 | state[key] = queryLists[key].matches; 24 | return state; 25 | }, {}); 26 | 27 | const notify = () => { 28 | if (transientListener != null) transientListener(mediaState); 29 | }; 30 | 31 | const mutateMediaState = (key, val) => { 32 | mediaState[key] = val; 33 | notify(); 34 | }; 35 | 36 | const listeners = mediaKeys.reduce((listeners, key) => { 37 | listeners[key] = event => { 38 | mutateMediaState(key, event.matches); 39 | }; 40 | return listeners; 41 | }, {}); 42 | 43 | const listen = listener => { 44 | transientListener = listener; 45 | mediaKeys.forEach(key => { 46 | queryLists[key].addListener(listeners[key]); 47 | }); 48 | }; 49 | 50 | const dispose = () => { 51 | transientListener = null; 52 | mediaKeys.forEach(key => { 53 | queryLists[key].removeListener(listeners[key]); 54 | }); 55 | }; 56 | 57 | const getState = () => mediaState; 58 | 59 | return { listen, dispose, getState }; 60 | }; 61 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/lib/images/earth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/exercise/src/lib/images/earth.jpg -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/lib/images/galaxy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/exercise/src/lib/images/galaxy.jpg -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/lib/images/trees.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/exercise/src/lib/images/trees.jpg -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #000; 3 | } 4 | 5 | .fade-enter { 6 | opacity: 0.01; 7 | } 8 | 9 | .fade-enter.fade-enter-active { 10 | opacity: 1; 11 | transition: opacity 200ms ease-in; 12 | } 13 | 14 | .fade-exit { 15 | opacity: 1; 16 | } 17 | 18 | .fade-exit.fade-exit-active { 19 | opacity: 0.01; 20 | transition: opacity 200ms ease-in; 21 | } 22 | -------------------------------------------------------------------------------- /02-hocs-render-props/exercise/src/lib/screens.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import galaxy from "./images/galaxy.jpg"; 3 | import earth from "./images/earth.jpg"; 4 | import trees from "./images/trees.jpg"; 5 | 6 | const makeBackgroundComponent = background => props => ( 7 |
19 | ); 20 | 21 | export const Galaxy = makeBackgroundComponent(galaxy); 22 | export const Earth = makeBackgroundComponent(earth); 23 | export const Trees = makeBackgroundComponent(trees); 24 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0", 8 | "react-google-maps": "^9.4.5" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/02-hocs-render-props/lecture/public/favicon.ico -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/App.final.hoc.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LoadingDots from "./lib/LoadingDots"; 3 | import Map from "./lib/Map"; 4 | 5 | let withGeo = Comp => 6 | class GeoPosition extends React.Component { 7 | state = { 8 | coords: null, 9 | error: null 10 | }; 11 | 12 | componentDidMount() { 13 | this.geoId = navigator.geolocation.watchPosition( 14 | position => { 15 | this.setState({ 16 | coords: { 17 | lat: position.coords.latitude, 18 | lng: position.coords.longitude 19 | } 20 | }); 21 | }, 22 | error => { 23 | this.setState({ error }); 24 | } 25 | ); 26 | } 27 | 28 | componentWillUnmount() { 29 | navigator.geolocation.clearWatch(this.geoId); 30 | } 31 | 32 | render() { 33 | return ; 34 | } 35 | }; 36 | 37 | class App extends React.Component { 38 | render() { 39 | return ( 40 |
41 | {this.props.error ? ( 42 |
Error: {this.props.error.message}
43 | ) : this.props.coords ? ( 44 | 49 | ) : ( 50 | 51 | )} 52 |
53 | ); 54 | } 55 | } 56 | 57 | export default withGeo(App); 58 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/App.final.render-props.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LoadingDots from "./lib/LoadingDots"; 3 | import Map from "./lib/Map"; 4 | import getAddressFromCoords from "./lib/getAddressFromCoords"; 5 | 6 | class GeoPosition extends React.Component { 7 | state = { 8 | coords: null, 9 | error: null 10 | }; 11 | 12 | componentDidMount() { 13 | this.geoId = navigator.geolocation.watchPosition( 14 | position => { 15 | this.setState({ 16 | coords: { 17 | lat: position.coords.latitude, 18 | lng: position.coords.longitude 19 | } 20 | }); 21 | }, 22 | error => { 23 | this.setState({ error }); 24 | } 25 | ); 26 | } 27 | 28 | componentWillUnmount() { 29 | navigator.geolocation.clearWatch(this.geoId); 30 | } 31 | 32 | render() { 33 | return this.props.children(this.state); 34 | } 35 | } 36 | 37 | class Address extends React.Component { 38 | state = { 39 | address: null 40 | }; 41 | 42 | async componentDidMount() { 43 | const { coords: { lat: latitude, lng: longitude } } = this.props; 44 | this.setState({ 45 | address: await getAddressFromCoords(latitude, longitude) 46 | }); 47 | } 48 | 49 | render() { 50 | return this.props.children(this.state.address); 51 | } 52 | } 53 | 54 | class App extends React.Component { 55 | render() { 56 | return ( 57 | 58 | {state => ( 59 |
60 | {state.error ? ( 61 |
Error: {state.error.message}
62 | ) : state.coords ? ( 63 |
64 | {address => ( 65 | 70 | )} 71 |
72 | ) : ( 73 | 74 | )} 75 |
76 | )} 77 |
78 | ); 79 | } 80 | } 81 | 82 | export default App; 83 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LoadingDots from "./lib/LoadingDots"; 3 | import Map from "./lib/Map"; 4 | import getAddressFromCoords from "./lib/getAddressFromCoords"; 5 | 6 | class App extends React.Component { 7 | state = { 8 | coords: null, 9 | error: null 10 | }; 11 | 12 | componentDidMount() { 13 | this.geoId = navigator.geolocation.watchPosition( 14 | position => { 15 | this.setState({ 16 | coords: { 17 | lat: position.coords.latitude, 18 | lng: position.coords.longitude 19 | } 20 | }); 21 | }, 22 | error => { 23 | this.setState({ error }); 24 | } 25 | ); 26 | } 27 | 28 | componentWillUnmount() { 29 | navigator.geolocation.clearWatch(this.geoId); 30 | } 31 | 32 | render() { 33 | return ( 34 |
35 | {this.state.error ? ( 36 |
Error: {this.state.error.message}
37 | ) : this.state.coords ? ( 38 | 43 | ) : ( 44 | 45 | )} 46 |
47 | ); 48 | } 49 | } 50 | 51 | export default App; 52 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | 5 | import App from "./App.start"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/lib/LoadingDots.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class LoadingDots extends React.Component { 4 | static defaultProps = { 5 | interval: 300, 6 | dots: 3 7 | }; 8 | 9 | state = { frame: 1 }; 10 | 11 | componentDidMount() { 12 | this.interval = setInterval(() => { 13 | this.setState({ 14 | frame: this.state.frame + 1 15 | }); 16 | }, this.props.interval); 17 | } 18 | 19 | componentWillUnmount() { 20 | clearInterval(this.interval); 21 | } 22 | 23 | render() { 24 | let dots = this.state.frame % (this.props.dots + 1); 25 | let text = ""; 26 | while (dots > 0) { 27 | text += "."; 28 | dots--; 29 | } 30 | return {text} ; 31 | } 32 | } 33 | 34 | export default LoadingDots; 35 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/lib/Map.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { 4 | withGoogleMap, 5 | GoogleMap, 6 | Marker, 7 | InfoWindow 8 | } from "react-google-maps"; 9 | 10 | const loadMaps = cb => { 11 | // google maps script loading garbage 12 | const KEY = "AIzaSyDFwu1MmuOatqW-283LSCbsxqHcp89ouiw"; 13 | const URL = `https://maps.googleapis.com/maps/api/js?key=${KEY}&callback=_mapsLoaded`; 14 | window._mapsLoaded = cb; 15 | const script = document.createElement("script"); 16 | script.src = URL; 17 | document.body.appendChild(script); 18 | }; 19 | 20 | const InnerMap = withGoogleMap(({ lat, lng, info }) => ( 21 | 22 | 23 | {info && ( 24 | 25 |
{info}
26 |
27 | )} 28 |
29 |
30 | )); 31 | 32 | class Map extends React.Component { 33 | componentWillMount() { 34 | if (!window.google) { 35 | loadMaps(() => { 36 | this.forceUpdate(); 37 | }); 38 | } 39 | } 40 | 41 | render() { 42 | const { lat, lng, info } = this.props; 43 | return window.google ? ( 44 | } 46 | mapElement={
} 47 | lat={lat} 48 | lng={lng} 49 | info={info} 50 | /> 51 | ) : null; 52 | } 53 | } 54 | 55 | export default Map; 56 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/lib/getAddressFromCoords.js: -------------------------------------------------------------------------------- 1 | const GoogleMapsAPI = "https://maps.googleapis.com/maps/api"; 2 | 3 | const getAddressFromCoords = (latitude, longitude) => { 4 | const url = `${GoogleMapsAPI}/geocode/json?latlng=${latitude},${longitude}`; 5 | 6 | return fetch(url) 7 | .then(res => res.json()) 8 | .then(json => { 9 | return json.results[0].formatted_address; 10 | }); 11 | }; 12 | 13 | export default getAddressFromCoords; 14 | -------------------------------------------------------------------------------- /02-hocs-render-props/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background: rgb(229, 227, 223); 4 | } 5 | 6 | 7 | .container, 8 | .map { 9 | width: 100vw; 10 | height: 100vh; 11 | } 12 | 13 | .loading-dots { 14 | font-size: 200px; 15 | display: block; 16 | text-align: center; 17 | color: white; 18 | } 19 | -------------------------------------------------------------------------------- /03-clone-element/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /03-clone-element/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "03", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.8.1", 7 | "react-dom": "^16.8.1", 8 | "react-icons": "2" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /03-clone-element/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/03-clone-element/exercise/public/favicon.ico -------------------------------------------------------------------------------- /03-clone-element/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /03-clone-element/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /03-clone-element/exercise/src/App.solution.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import FaPlay from "react-icons/lib/fa/play"; 3 | import FaPause from "react-icons/lib/fa/pause"; 4 | import FaForward from "react-icons/lib/fa/forward"; 5 | import FaBackward from "react-icons/lib/fa/backward"; 6 | 7 | function RadioGroup({ defaultValue, legend, children }) { 8 | const [value, setValue] = useState(defaultValue); 9 | const clones = React.Children.map(children, child => { 10 | return React.cloneElement(child, { 11 | isActive: child.props.value === value, 12 | onSelect: () => setValue(child.props.value) 13 | }); 14 | }); 15 | return ( 16 |
17 | {legend} 18 | {clones} 19 |
20 | ); 21 | } 22 | 23 | function RadioButton({ onSelect, isActive, children }) { 24 | const className = "radio-button " + (isActive ? "active" : ""); 25 | return ( 26 | 29 | ); 30 | } 31 | 32 | function App() { 33 | return ( 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | ); 51 | } 52 | 53 | export default App; 54 | -------------------------------------------------------------------------------- /03-clone-element/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import FaPlay from "react-icons/lib/fa/play"; 3 | import FaPause from "react-icons/lib/fa/pause"; 4 | import FaForward from "react-icons/lib/fa/forward"; 5 | import FaBackward from "react-icons/lib/fa/backward"; 6 | 7 | function RadioGroup({ defaultValue, legend, children }) { 8 | return ( 9 |
10 | {legend} 11 | {children} 12 |
13 | ); 14 | } 15 | 16 | function RadioButton({ children }) { 17 | const isActive = false; // <-- should come from somewhere 18 | const className = "radio-button " + (isActive ? "active" : ""); 19 | return ; 20 | } 21 | 22 | function App() { 23 | return ( 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | ); 41 | } 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /03-clone-element/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /03-clone-element/exercise/src/lib/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/03-clone-element/exercise/src/lib/noise.png -------------------------------------------------------------------------------- /03-clone-element/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /03-clone-element/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "03", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0", 8 | "react-icons": "^2.2.5" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /03-clone-element/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/03-clone-element/lecture/public/favicon.ico -------------------------------------------------------------------------------- /03-clone-element/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /03-clone-element/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /03-clone-element/lecture/src/App.cloneElement.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import FaAutomobile from "react-icons/lib/fa/automobile"; 3 | import FaBed from "react-icons/lib/fa/bed"; 4 | import FaPlane from "react-icons/lib/fa/plane"; 5 | import FaSpaceShuttle from "react-icons/lib/fa/space-shuttle"; 6 | import * as text from "./lib/text"; 7 | 8 | class Tabs extends Component { 9 | state = { 10 | activeIndex: 0 11 | }; 12 | 13 | selectTabIndex = activeIndex => { 14 | this.setState({ activeIndex }); 15 | }; 16 | 17 | render() { 18 | const children = React.Children.map(this.props.children, child => { 19 | return React.cloneElement(child, { 20 | activeIndex: this.state.activeIndex, 21 | onSelectTab: this.selectTabIndex 22 | }); 23 | }); 24 | return
{children}
; 25 | } 26 | } 27 | 28 | class TabList extends Component { 29 | render() { 30 | const { activeIndex } = this.props; 31 | const children = React.Children.map(this.props.children, (child, index) => { 32 | return React.cloneElement(child, { 33 | isActive: index === activeIndex, 34 | onSelect: () => this.props.onSelectTab(index) 35 | }); 36 | }); 37 | return
{children}
; 38 | } 39 | } 40 | 41 | class Tab extends Component { 42 | render() { 43 | const { isActive, isDisabled, onSelect } = this.props; 44 | return ( 45 |
51 | {this.props.children} 52 |
53 | ); 54 | } 55 | } 56 | 57 | class TabPanels extends Component { 58 | render() { 59 | const { activeIndex, children } = this.props; 60 | return
{children[activeIndex]}
; 61 | } 62 | } 63 | 64 | class TabPanel extends Component { 65 | render() { 66 | return this.props.children; 67 | } 68 | } 69 | 70 | class DataTabs extends Component { 71 | render() { 72 | const { data } = this.props; 73 | return ( 74 | 75 | 76 | {data.map((tab, index) => {tab.label})} 77 | 78 | 79 | {data.map((tab, index) => ( 80 | {tab.content} 81 | ))} 82 | 83 | 84 | ); 85 | } 86 | } 87 | 88 | class App extends Component { 89 | render() { 90 | return ( 91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | {text.cars} 109 | {text.hotels} 110 | {text.flights} 111 | {text.space} 112 | 113 | 114 |
115 | ); 116 | } 117 | } 118 | 119 | export default App; 120 | -------------------------------------------------------------------------------- /03-clone-element/lecture/src/App.context.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | createContext 4 | } from "react"; 5 | import FaAutomobile from "react-icons/lib/fa/automobile"; 6 | import FaBed from "react-icons/lib/fa/bed"; 7 | import FaPlane from "react-icons/lib/fa/plane"; 8 | import FaSpaceShuttle from "react-icons/lib/fa/space-shuttle"; 9 | import * as text from "./lib/text"; 10 | 11 | let TabsContext = createContext(); 12 | 13 | class Tabs extends Component { 14 | state = { 15 | activeIndex: 0, 16 | selectTabIndex: activeIndex => { 17 | this.setState({ activeIndex }); 18 | } 19 | }; 20 | 21 | render() { 22 | return ( 23 | 24 |
25 | {this.props.children} 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | let TabList = props => { 33 | return ( 34 | 35 | {context => { 36 | let children = React.Children.map( 37 | props.children, 38 | (child, index) => { 39 | return React.cloneElement(child, { 40 | isActive: index === context.activeIndex, 41 | onActivate: () => 42 | context.selectTabIndex(index) 43 | }); 44 | } 45 | ); 46 | return
{children}
; 47 | }} 48 |
49 | ); 50 | }; 51 | 52 | let Tab = props => { 53 | const isDisabled = props.isDisabled; 54 | const isActive = props.isActive; 55 | return ( 56 |
66 | {props.children} 67 |
68 | ); 69 | }; 70 | 71 | let TabPanels = props => { 72 | return ( 73 | 74 | {context => ( 75 |
76 | {props.children[context.activeIndex]} 77 |
78 | )} 79 |
80 | ); 81 | }; 82 | 83 | let TabPanel = props => props.children; 84 | 85 | class App extends Component { 86 | render() { 87 | return ( 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | {text.cars} 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | {text.cars} 124 | {text.hotels} 125 | {text.flights} 126 | {text.space} 127 | 128 | 129 | 130 | {text.flights} 131 | {text.space} 132 | 133 | 134 |
135 | ); 136 | } 137 | } 138 | 139 | export default App; 140 | -------------------------------------------------------------------------------- /03-clone-element/lecture/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import FaAutomobile from "react-icons/lib/fa/automobile"; 3 | import FaBed from "react-icons/lib/fa/bed"; 4 | import FaPlane from "react-icons/lib/fa/plane"; 5 | import FaSpaceShuttle from "react-icons/lib/fa/space-shuttle"; 6 | import * as text from "./lib/text"; 7 | 8 | class Tabs extends Component { 9 | state = { 10 | activeIndex: 0 11 | }; 12 | 13 | selectTabIndex = activeIndex => { 14 | this.setState({ activeIndex }); 15 | }; 16 | 17 | render() { 18 | const { data } = this.props; 19 | return ( 20 |
21 |
22 | {data.map((tab, index) => { 23 | const isActive = index === this.state.activeIndex; 24 | return ( 25 |
this.selectTabIndex(index)} 29 | > 30 | {tab.label} 31 |
32 | ); 33 | })} 34 |
35 |
{data[this.state.activeIndex].content}
36 |
37 | ); 38 | } 39 | } 40 | 41 | class App extends Component { 42 | render() { 43 | const tabData = [ 44 | { 45 | label: , 46 | content: text.cars 47 | }, 48 | { 49 | label: , 50 | content: text.hotels 51 | }, 52 | { 53 | label: , 54 | content: text.flights 55 | }, 56 | { 57 | label: , 58 | content: text.space 59 | } 60 | ]; 61 | return ( 62 |
63 | 64 |
65 | ); 66 | } 67 | } 68 | 69 | export default App; 70 | -------------------------------------------------------------------------------- /03-clone-element/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /03-clone-element/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 4 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 5 | } 6 | 7 | h2 { 8 | text-transform: uppercase; 9 | } 10 | 11 | .App { 12 | padding-top: 55px; 13 | color: hsl(240, 5%, 20%); 14 | height: 100vh; 15 | transition: background-color 300ms ease; 16 | background-image: radial-gradient( 17 | ellipse at center, 18 | hsla(0, 100%, 100%, 0.5) 0%, 19 | hsla(0, 100%, 100%, 0) 100% 20 | ); 21 | background-color: hsl(199, 94%, 67%); 22 | } 23 | 24 | .Tabs { 25 | width: 500px; 26 | margin: auto; 27 | background: white; 28 | box-shadow: 0px 10px 40px hsla(0, 0%, 0%, 0.25); 29 | } 30 | 31 | .tabs { 32 | display: flex; 33 | border-bottom: solid 2px rgb(231, 236, 238); 34 | } 35 | 36 | .tab { 37 | flex: 1; 38 | padding: 10px; 39 | background: white; 40 | text-align: center; 41 | color: hsl(240, 5%, 60%); 42 | font-size: 200%; 43 | cursor: pointer; 44 | } 45 | 46 | .tab:hover { 47 | color: hsl(240, 5%, 70%); 48 | } 49 | 50 | .tab:active { 51 | transform: scale(0.95); 52 | color: hsl(199, 94%, 67%); 53 | } 54 | 55 | .tab.active { 56 | color: hsl(199, 94%, 67%); 57 | } 58 | 59 | .tab.disabled { 60 | opacity: 0.25; 61 | } 62 | 63 | .panels { 64 | background: rgb(244, 249, 252); 65 | min-height: 300px; 66 | padding: 20px; 67 | } 68 | -------------------------------------------------------------------------------- /03-clone-element/lecture/src/lib/text.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const cars = ( 4 |
5 |

Car rentals

6 |

We offer the finest selection of cars:

7 |
    8 |
  • Sedans
  • 9 |
  • Donks
  • 10 |
  • Hoopdies
  • 11 |
  • Ghetto Sleds
  • 12 |
  • Trucks
  • 13 |
  • Wild Wheels
  • 14 |
15 |
16 | ); 17 | 18 | export const hotels = ( 19 |
20 |

Hotels

21 |

22 | The best hotels at the best price. Whether you're looking for a romantic 23 | get-away or just need a place to crash that offers free breakfast and a 24 | hot tub, we have the best hotels around. 25 |

26 |
27 | ); 28 | 29 | export const flights = ( 30 |
31 |

Flights

32 |

33 | Cheap fare and great flights. We have the most non-stop from major cities 34 | across the world than anybody else. 35 |

36 |
37 | ); 38 | 39 | export const space = ( 40 |
41 |

Space

42 |

43 | Sometimes when I watch shows like Passengers, and they're experiencing 44 | intergallactic travel, slingshotting around a star and gazing at it out 45 | the window, or standing outside the ship staring into the universe, I get 46 | actually angry that I won't experience it. Time to find a religion that 47 | believes we can create worlds and travel from one to the other. 48 |

49 |
50 | ); 51 | -------------------------------------------------------------------------------- /04-context/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /04-context/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "03", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.8.1", 7 | "react-dom": "^16.8.1", 8 | "react-icons": "2" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /04-context/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/04-context/exercise/public/favicon.ico -------------------------------------------------------------------------------- /04-context/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /04-context/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /04-context/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.solution"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /04-context/exercise/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 4 | background: radial-gradient(ellipse at center, #fff 0%, #ddd 100%); 5 | height: 100vh; 6 | } 7 | 8 | .exercise { 9 | padding-top: 40px; 10 | width: 500px; 11 | margin: auto; 12 | } 13 | 14 | .audio-player { 15 | display: block; 16 | padding: 10px; 17 | margin: 40px 0; 18 | } 19 | 20 | .progress { 21 | width: 100%; 22 | border: 1px solid #ccc; 23 | background: #fff; 24 | margin: 0.5em 0; 25 | } 26 | 27 | .progress-bar { 28 | height: 1em; 29 | background: hsl(200, 50%, 50%); 30 | } 31 | 32 | .icon-button { 33 | background: none; 34 | border: none; 35 | padding: none; 36 | display: inline-block; 37 | font: inherit; 38 | color: #666; 39 | line-height: 0; 40 | padding: 0; 41 | vertical-align: bottom; 42 | } 43 | 44 | .icon-button:active { 45 | color: #333; 46 | outline: none; 47 | } 48 | 49 | .icon-button:disabled { 50 | color: #ccc; 51 | } 52 | -------------------------------------------------------------------------------- /04-context/exercise/src/lib/mariobros.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/04-context/exercise/src/lib/mariobros.mp3 -------------------------------------------------------------------------------- /04-context/exercise/src/lib/podcast.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/04-context/exercise/src/lib/podcast.mp4 -------------------------------------------------------------------------------- /04-context/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /04-context/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "03", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.8.1", 7 | "react-dom": "^16.8.1", 8 | "react-icons": "^2.2.5" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /04-context/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/04-context/lecture/public/favicon.ico -------------------------------------------------------------------------------- /04-context/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /04-context/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /04-context/lecture/src/App.final.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useMemo, useContext } from "react"; 2 | import FaAutomobile from "react-icons/lib/fa/automobile"; 3 | import FaBed from "react-icons/lib/fa/bed"; 4 | import FaPlane from "react-icons/lib/fa/plane"; 5 | import FaSpaceShuttle from "react-icons/lib/fa/space-shuttle"; 6 | import * as text from "./lib/text"; 7 | 8 | const TabsContext = createContext(); 9 | 10 | function Tabs({ children }) { 11 | const [activeIndex, setActiveIndex] = useState(0); 12 | const context = useMemo(() => { 13 | return { 14 | activeIndex, 15 | onSelectTab: setActiveIndex 16 | }; 17 | }, [activeIndex]); 18 | 19 | return ( 20 | 21 |
{children}
22 |
23 | ); 24 | } 25 | 26 | function TabList({ children }) { 27 | const { activeIndex, onSelectTab } = useContext(TabsContext); 28 | const clones = React.Children.map(children, (child, index) => { 29 | return React.cloneElement(child, { 30 | isActive: index === activeIndex, 31 | onSelect: () => onSelectTab(index) 32 | }); 33 | }); 34 | return
{clones}
; 35 | } 36 | 37 | function Tab({ isActive, isDisabled, onSelect, children }) { 38 | return ( 39 |
43 | {children} 44 |
45 | ); 46 | } 47 | 48 | function TabPanels({ children }) { 49 | const { activeIndex } = useContext(TabsContext); 50 | return
{children[activeIndex]}
; 51 | } 52 | 53 | function TabPanel({ children }) { 54 | return children; 55 | } 56 | 57 | function App() { 58 | return ( 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {text.cars} 77 | {text.hotels} 78 | {text.flights} 79 | {text.space} 80 | 81 | 82 |
83 | ); 84 | } 85 | 86 | export default App; 87 | -------------------------------------------------------------------------------- /04-context/lecture/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import FaAutomobile from "react-icons/lib/fa/automobile"; 3 | import FaBed from "react-icons/lib/fa/bed"; 4 | import FaPlane from "react-icons/lib/fa/plane"; 5 | import FaSpaceShuttle from "react-icons/lib/fa/space-shuttle"; 6 | import * as text from "./lib/text"; 7 | 8 | function Tabs({ children }) { 9 | const [activeIndex, setActiveIndex] = useState(0); 10 | const clones = React.Children.map(children, child => { 11 | return React.cloneElement(child, { 12 | activeIndex, 13 | onSelectTab: setActiveIndex 14 | }); 15 | }); 16 | return
{clones}
; 17 | } 18 | 19 | function TabList({ activeIndex, onSelectTab, children }) { 20 | const clones = React.Children.map(children, (child, index) => { 21 | return React.cloneElement(child, { 22 | isActive: index === activeIndex, 23 | onSelect: () => onSelectTab(index) 24 | }); 25 | }); 26 | return
{clones}
; 27 | } 28 | 29 | function Tab({ isActive, isDisabled, onSelect, children }) { 30 | return ( 31 |
35 | {children} 36 |
37 | ); 38 | } 39 | 40 | function TabPanels({ activeIndex, children }) { 41 | return
{children[activeIndex]}
; 42 | } 43 | 44 | function TabPanel({ children }) { 45 | return children; 46 | } 47 | 48 | function App() { 49 | return ( 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {text.cars} 68 | {text.hotels} 69 | {text.flights} 70 | {text.space} 71 | 72 | 73 |
74 | ); 75 | } 76 | 77 | export default App; 78 | -------------------------------------------------------------------------------- /04-context/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /04-context/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 4 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 5 | } 6 | 7 | h2 { 8 | text-transform: uppercase; 9 | } 10 | 11 | .App { 12 | padding-top: 55px; 13 | color: hsl(240, 5%, 20%); 14 | height: 100vh; 15 | transition: background-color 300ms ease; 16 | background-image: radial-gradient( 17 | ellipse at center, 18 | hsla(0, 100%, 100%, 0.5) 0%, 19 | hsla(0, 100%, 100%, 0) 100% 20 | ); 21 | background-color: hsl(199, 94%, 67%); 22 | } 23 | 24 | .Tabs { 25 | width: 500px; 26 | margin: auto; 27 | background: white; 28 | box-shadow: 0px 10px 40px hsla(0, 0%, 0%, 0.25); 29 | } 30 | 31 | .tabs { 32 | display: flex; 33 | border-bottom: solid 2px rgb(231, 236, 238); 34 | } 35 | 36 | .tab { 37 | flex: 1; 38 | padding: 10px; 39 | background: white; 40 | text-align: center; 41 | color: hsl(240, 5%, 60%); 42 | font-size: 200%; 43 | cursor: pointer; 44 | } 45 | 46 | .tab:hover { 47 | color: hsl(240, 5%, 70%); 48 | } 49 | 50 | .tab:active { 51 | transform: scale(0.95); 52 | color: hsl(199, 94%, 67%); 53 | } 54 | 55 | .tab.active { 56 | color: hsl(199, 94%, 67%); 57 | } 58 | 59 | .tab.disabled { 60 | opacity: 0.25; 61 | } 62 | 63 | .panels { 64 | background: rgb(244, 249, 252); 65 | min-height: 300px; 66 | padding: 20px; 67 | } 68 | -------------------------------------------------------------------------------- /04-context/lecture/src/lib/text.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const cars = ( 4 |
5 |

Car rentals

6 |

We offer the finest selection of cars:

7 |
    8 |
  • Sedans
  • 9 |
  • Donks
  • 10 |
  • Hoopdies
  • 11 |
  • Ghetto Sleds
  • 12 |
  • Trucks
  • 13 |
  • Wild Wheels
  • 14 |
15 |
16 | ); 17 | 18 | export const hotels = ( 19 |
20 |

Hotels

21 |

22 | The best hotels at the best price. Whether you're looking for a romantic 23 | get-away or just need a place to crash that offers free breakfast and a 24 | hot tub, we have the best hotels around. 25 |

26 |
27 | ); 28 | 29 | export const flights = ( 30 |
31 |

Flights

32 |

33 | Cheap fare and great flights. We have the most non-stop from major cities 34 | across the world than anybody else. 35 |

36 |
37 | ); 38 | 39 | export const space = ( 40 |
41 |

Space

42 |

43 | Sometimes when I watch shows like Passengers, and they're experiencing 44 | intergallactic travel, slingshotting around a star and gazing at it out 45 | the window, or standing outside the ship staring into the universe, I get 46 | actually angry that I won't experience it. Time to find a religion that 47 | believes we can create worlds and travel from one to the other. 48 |

49 |
50 | ); 51 | -------------------------------------------------------------------------------- /05-portals/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /05-portals/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "04", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reach/rect": "^0.1.1", 7 | "react": "^16.5.0", 8 | "react-dom": "^16.5.0" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /05-portals/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/05-portals/exercise/public/favicon.ico -------------------------------------------------------------------------------- /05-portals/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /05-portals/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /05-portals/exercise/src/.App.js.swo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/05-portals/exercise/src/.App.js.swo -------------------------------------------------------------------------------- /05-portals/exercise/src/App.solution.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createPortal } from "react-dom"; 3 | import PropTypes from "prop-types"; 4 | import Rect from "@reach/rect"; 5 | 6 | class Portal extends React.Component { 7 | state = { 8 | mounted: false 9 | }; 10 | 11 | componentDidMount() { 12 | this.node = document.createElement("div"); 13 | document.body.appendChild(this.node); 14 | this.setState({ mounted: true }); 15 | } 16 | 17 | componentWillUnmount() { 18 | document.body.removeChild(this.node); 19 | } 20 | 21 | render() { 22 | return this.state.mounted 23 | ? createPortal(this.props.children, this.node) 24 | : null; 25 | } 26 | } 27 | 28 | class Select extends React.Component { 29 | static propTypes = { 30 | onChange: PropTypes.func, 31 | value: PropTypes.any, 32 | defaultValue: PropTypes.any 33 | }; 34 | 35 | state = { 36 | value: this.props.defaultValue, 37 | isOpen: false 38 | }; 39 | 40 | handleToggle = () => { 41 | this.setState({ 42 | isOpen: !this.state.isOpen 43 | }); 44 | }; 45 | 46 | isControlled() { 47 | return this.props.value != null; 48 | } 49 | 50 | render() { 51 | const { isOpen } = this.state; 52 | let label; 53 | const children = React.Children.map(this.props.children, child => { 54 | const { value } = this.isControlled() ? this.props : this.state; 55 | if (child.props.value === value) { 56 | label = child.props.children; 57 | } 58 | 59 | return React.cloneElement(child, { 60 | onSelect: () => { 61 | if (this.isControlled()) { 62 | this.props.onChange(child.props.value); 63 | } else { 64 | this.setState({ value: child.props.value }); 65 | } 66 | } 67 | }); 68 | }); 69 | 70 | return ( 71 | 72 | {({ rect, ref }) => ( 73 |
74 | 77 | {isOpen && ( 78 | 79 |
    87 | {children} 88 |
89 |
90 | )} 91 |
92 | )} 93 |
94 | ); 95 | } 96 | } 97 | 98 | class Option extends React.Component { 99 | render() { 100 | return ( 101 |
  • 102 | {this.props.children} 103 |
  • 104 | ); 105 | } 106 | } 107 | 108 | class App extends React.Component { 109 | render() { 110 | return ( 111 |
    112 |
    113 |

    Portal Party

    114 | 120 |
    121 |
    122 | ); 123 | } 124 | } 125 | 126 | export default App; 127 | -------------------------------------------------------------------------------- /05-portals/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Have `Portal` create a new DOM element, append it to document.body and then 4 | render its children into a portal. We want to have portal create the new dom 5 | node when it mounts, and remove it when it unmounts. 6 | 7 | Tips: 8 | 9 | - in componentDidMount, create a new dom node and append it to the body 10 | - `document.createElement('div')` 11 | - `document.body.append(node)` 12 | - in componentWillUnmount, remove the node from the body with 13 | `document.body.removeChild(node)` 14 | 15 | Finally, the menu will be rendered out of DOM context so the styles will be all 16 | wrong, you'll need to provide a `style` prop with fixed position and left/top 17 | values. To help out, we've imported `Rect`. Go check the docs for Rect 18 | (https://ui.reach.tech/rect) and then use it to put the menu by the button. 19 | 20 | */ 21 | 22 | import React from "react"; 23 | import { createPortal } from "react-dom"; 24 | import Rect from "@reach/rect"; 25 | 26 | class Portal extends React.Component { 27 | render() { 28 | return this.props.children; 29 | } 30 | } 31 | 32 | class Select extends React.Component { 33 | state = { 34 | value: this.props.defaultValue, 35 | isOpen: false 36 | }; 37 | 38 | handleToggle = () => { 39 | this.setState({ 40 | isOpen: !this.state.isOpen 41 | }); 42 | }; 43 | 44 | render() { 45 | const { isOpen } = this.state; 46 | let label; 47 | const children = React.Children.map(this.props.children, child => { 48 | const { value } = this.state; 49 | if (child.props.value === value) { 50 | label = child.props.children; 51 | } 52 | 53 | return React.cloneElement(child, { 54 | onSelect: () => { 55 | this.setState({ value: child.props.value }); 56 | } 57 | }); 58 | }); 59 | 60 | return ( 61 |
    62 | 65 | {isOpen && ( 66 | 67 |
      {children}
    68 |
    69 | )} 70 |
    71 | ); 72 | } 73 | } 74 | 75 | class Option extends React.Component { 76 | render() { 77 | return ( 78 |
  • 79 | {this.props.children} 80 |
  • 81 | ); 82 | } 83 | } 84 | 85 | class App extends React.Component { 86 | render() { 87 | return ( 88 |
    89 |
    90 |

    Portal Party

    91 | 97 |
    98 |
    99 | ); 100 | } 101 | } 102 | 103 | export default App; 104 | -------------------------------------------------------------------------------- /05-portals/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /05-portals/exercise/src/lib/.App.js.swo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/05-portals/exercise/src/lib/.App.js.swo -------------------------------------------------------------------------------- /05-portals/exercise/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: radial-gradient( 3 | ellipse at center, 4 | hsl(200, 80%, 90%) 0%, 5 | hsl(200, 80%, 80%) 100% 6 | ); 7 | height: 100vh; 8 | color: hsl(200, 80%, 20%); 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 10 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 11 | } 12 | 13 | .app { 14 | margin: auto; 15 | display: flex; 16 | } 17 | 18 | .block { 19 | margin: 100px 50px; 20 | border-radius: 4px; 21 | flex: 1; 22 | background: hsla(0, 0%, 100%, 0.75); 23 | height: 400px; 24 | padding: 20px; 25 | } 26 | 27 | h2 { 28 | margin: 0 0 20px 0; 29 | text-transform: uppercase; 30 | } 31 | 32 | .select { 33 | display: inline-block; 34 | margin: 4px 0; 35 | position: relative; 36 | } 37 | 38 | .label { 39 | background: hsla(200, 0%, 100%, 0.95); 40 | border: 1px solid hsla(200, 0%, 0%, 0.2); 41 | font: inherit; 42 | padding: 4px 8px; 43 | cursor: default; 44 | } 45 | 46 | .arrow { 47 | float: right; 48 | padding-left: 8px; 49 | color: hsla(200, 0%, 0%, 0.5); 50 | } 51 | 52 | .options { 53 | outline: none; 54 | border: solid 1px hsl(200, 0%, 70%); 55 | overflow: hidden; 56 | border-radius: 4px; 57 | list-style: none; 58 | background: hsla(200, 0%, 95%, 0.95); 59 | box-shadow: 2px 2px 8px hsla(200, 0%, 0%, 0.25); 60 | width: 200px; 61 | padding: 0; 62 | margin: 0; 63 | margin-left: -20px; 64 | } 65 | 66 | .options.is-closed { 67 | display: none; 68 | } 69 | 70 | .option { 71 | padding: 4px 24px; 72 | cursor: default; 73 | position: relative; 74 | } 75 | 76 | .option.selected:before { 77 | content: "✔︎"; 78 | position: absolute; 79 | left: 7px; 80 | top: 0.35em; 81 | font-size: 75%; 82 | } 83 | 84 | .option:hover { 85 | background: hsl(200, 80%, 50%); 86 | color: white; 87 | } 88 | -------------------------------------------------------------------------------- /05-portals/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /05-portals/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "04", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0" 8 | }, 9 | "devDependencies": { 10 | "react-scripts": "1.0.10" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /05-portals/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/05-portals/lecture/public/favicon.ico -------------------------------------------------------------------------------- /05-portals/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /05-portals/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /05-portals/lecture/src/App.final.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createPortal } from "react-dom"; 3 | 4 | let portalNode = document.createElement("div"); 5 | document.body.appendChild(portalNode); 6 | 7 | class Dialog extends React.Component { 8 | render() { 9 | return createPortal( 10 |
    20 |
    33 | {this.props.children} 34 |
    35 |
    , 36 | portalNode 37 | ); 38 | } 39 | } 40 | 41 | let bustthisstuff = { 42 | height: "300px", 43 | overflow: "hidden", 44 | position: "absolute" 45 | }; 46 | 47 | class App extends React.Component { 48 | state = { 49 | showDialog: false 50 | }; 51 | 52 | render() { 53 | return ( 54 |
    55 |

    Would you like a banana cream pie?!

    56 | 63 | {this.state.showDialog && ( 64 | 65 |

    Banana Cream Pie Delivery

    66 |
    { 68 | event.preventDefault(); 69 | this.setState({ showDialog: false }); 70 | }} 71 | > 72 |

    73 | 76 |

    77 |

    78 | 81 |

    82 |

    83 | 84 |

    85 |
    86 |
    87 | )} 88 |
    89 | ); 90 | } 91 | } 92 | 93 | export default App; 94 | -------------------------------------------------------------------------------- /05-portals/lecture/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class Dialog extends React.Component { 4 | render() { 5 | return ( 6 |
    16 |
    29 | {this.props.children} 30 |
    31 |
    32 | ); 33 | } 34 | } 35 | 36 | let bustthisstuff = { 37 | height: "300px", 38 | overflow: "hidden", 39 | position: "absolute" 40 | }; 41 | 42 | class App extends React.Component { 43 | state = { 44 | showDialog: false 45 | }; 46 | 47 | render() { 48 | return ( 49 |
    50 |

    Would you like a banana cream pie?!

    51 | 58 | {this.state.showDialog && ( 59 | 60 |

    Banana Cream Pie Delivery

    61 |
    { 63 | event.preventDefault(); 64 | this.setState({ showDialog: false }); 65 | }} 66 | > 67 |

    68 | 71 |

    72 |

    73 | 76 |

    77 |

    78 | 79 |

    80 |
    81 |
    82 | )} 83 |
    84 | ); 85 | } 86 | } 87 | 88 | export default App; 89 | -------------------------------------------------------------------------------- /05-portals/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /05-portals/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 4 | } 5 | -------------------------------------------------------------------------------- /06-wai-aria/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /06-wai-aria/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "03", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0", 8 | "react-icons": "2" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /06-wai-aria/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/06-wai-aria/exercise/public/favicon.ico -------------------------------------------------------------------------------- /06-wai-aria/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /06-wai-aria/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /06-wai-aria/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Follow the WAI ARIA Radio Group example at: 4 | https://www.w3.org/TR/wai-aria-practices-1.1/examples/radio/radio-1/radio-1.html 5 | 6 | - Turn the span into a button to get keyboard and focus events 7 | - Use tabIndex to allow only the active button to be tabbable 8 | - Use left/right arrows to select the next/previous radio button 9 | - Tip: you can figure out the next value with React.Children.forEach(fn), 10 | or React.Children.toArray(children).reduce(fn) 11 | - Move the focus in cDU to the newly selected item 12 | - Tip: do it in RadioOption not RadioGroup 13 | - Tip: you'll need a ref 14 | - Add the aria attributes 15 | - radiogroup 16 | - radio 17 | - aria-checked 18 | - aria-label on the icons 19 | 20 | */ 21 | import React, { Component } from "react"; 22 | import FaPlay from "react-icons/lib/fa/play"; 23 | import FaPause from "react-icons/lib/fa/pause"; 24 | import FaForward from "react-icons/lib/fa/forward"; 25 | import FaBackward from "react-icons/lib/fa/backward"; 26 | 27 | class RadioGroup extends Component { 28 | state = { 29 | value: this.props.defaultValue 30 | }; 31 | 32 | render() { 33 | const children = React.Children.map(this.props.children, child => { 34 | return React.cloneElement(child, { 35 | isActive: child.props.value === this.state.value, 36 | onSelect: () => this.setState({ value: child.props.value }) 37 | }); 38 | }); 39 | return ( 40 |
    41 | {this.props.legend} 42 | {children} 43 |
    44 | ); 45 | } 46 | } 47 | 48 | class RadioButton extends Component { 49 | render() { 50 | const { isActive, onSelect } = this.props; 51 | const className = "radio-button " + (isActive ? "active" : ""); 52 | return ( 53 | 54 | {this.props.children} 55 | 56 | ); 57 | } 58 | } 59 | 60 | class App extends Component { 61 | render() { 62 | return ( 63 |
    64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
    79 | ); 80 | } 81 | } 82 | 83 | export default App; 84 | -------------------------------------------------------------------------------- /06-wai-aria/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /06-wai-aria/exercise/src/lib/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/06-wai-aria/exercise/src/lib/noise.png -------------------------------------------------------------------------------- /06-wai-aria/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /06-wai-aria/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "05", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reach/rect": "^0.1.1", 7 | "react": "^16.5.0", 8 | "react-dom": "^16.5.0", 9 | "react-google-maps": "^7.2.0" 10 | }, 11 | "devDependencies": { 12 | "react-scripts": "1.0.10" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /06-wai-aria/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/06-wai-aria/lecture/public/favicon.ico -------------------------------------------------------------------------------- /06-wai-aria/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /06-wai-aria/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /06-wai-aria/lecture/src/.App.js.swn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/06-wai-aria/lecture/src/.App.js.swn -------------------------------------------------------------------------------- /06-wai-aria/lecture/src/.App.js.swo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/06-wai-aria/lecture/src/.App.js.swo -------------------------------------------------------------------------------- /06-wai-aria/lecture/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import Rect from "@reach/rect"; 4 | 5 | class Select extends React.Component { 6 | static propTypes = { 7 | onChange: PropTypes.func, 8 | value: PropTypes.any, 9 | defaultValue: PropTypes.any 10 | }; 11 | 12 | state = { 13 | value: this.props.defaultValue, 14 | isOpen: false 15 | }; 16 | 17 | handleToggle = () => { 18 | this.setState({ 19 | isOpen: !this.state.isOpen 20 | }); 21 | }; 22 | 23 | isControlled() { 24 | return this.props.value != null; 25 | } 26 | 27 | render() { 28 | const { isOpen } = this.state; 29 | let label; 30 | const children = React.Children.map(this.props.children, child => { 31 | const { value } = this.isControlled() ? this.props : this.state; 32 | if (child.props.value === value) { 33 | label = child.props.children; 34 | } 35 | 36 | return React.cloneElement(child, { 37 | onSelect: () => { 38 | if (this.isControlled()) { 39 | this.props.onChange(child.props.value); 40 | } else { 41 | this.setState({ value: child.props.value }); 42 | } 43 | } 44 | }); 45 | }); 46 | 47 | return ( 48 | 49 | {({ rect, ref }) => ( 50 |
    51 | 54 | {isOpen && ( 55 |
      63 | {children} 64 |
    65 | )} 66 |
    67 | )} 68 |
    69 | ); 70 | } 71 | } 72 | 73 | class Option extends React.Component { 74 | render() { 75 | return ( 76 |
  • 77 | {this.props.children} 78 |
  • 79 | ); 80 | } 81 | } 82 | 83 | class App extends React.Component { 84 | state = { 85 | selectValue: "dosa" 86 | }; 87 | 88 | setToMintChutney = () => { 89 | this.setState({ 90 | selectValue: "mint-chutney" 91 | }); 92 | }; 93 | 94 | render() { 95 | return ( 96 |
    97 |
    98 |

    WAI-ARIA

    99 | 105 |
    106 |
    107 | ); 108 | } 109 | } 110 | 111 | export default App; 112 | -------------------------------------------------------------------------------- /06-wai-aria/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./Disclosure"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /06-wai-aria/lecture/src/lib/.App.js.swn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/06-wai-aria/lecture/src/lib/.App.js.swn -------------------------------------------------------------------------------- /06-wai-aria/lecture/src/lib/.App.js.swo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/06-wai-aria/lecture/src/lib/.App.js.swo -------------------------------------------------------------------------------- /06-wai-aria/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100vh; 3 | color: hsl(200, 80%, 20%); 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 5 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 6 | } 7 | 8 | .app { 9 | margin: auto; 10 | display: flex; 11 | } 12 | 13 | .block { 14 | margin: 100px 50px; 15 | border-radius: 4px; 16 | flex: 1; 17 | background: hsla(0, 0%, 100%, 0.75); 18 | height: 400px; 19 | padding: 20px; 20 | } 21 | 22 | h2 { 23 | margin: 0 0 20px 0; 24 | text-transform: uppercase; 25 | } 26 | 27 | .select { 28 | display: inline-block; 29 | margin: 4px 0; 30 | } 31 | 32 | .label { 33 | background: hsla(200, 0%, 100%, 0.95); 34 | border: 1px solid hsla(200, 0%, 0%, 0.2); 35 | font: inherit; 36 | padding: 4px 8px; 37 | cursor: default; 38 | } 39 | 40 | .arrow { 41 | float: right; 42 | padding-left: 8px; 43 | color: hsla(200, 0%, 0%, 0.5); 44 | } 45 | 46 | .options { 47 | outline: none; 48 | border: solid 1px hsl(200, 0%, 70%); 49 | overflow: hidden; 50 | border-radius: 4px; 51 | list-style: none; 52 | background: hsla(200, 0%, 95%, 0.95); 53 | box-shadow: 2px 2px 8px hsla(200, 0%, 0%, 0.25); 54 | width: 200px; 55 | padding: 0; 56 | margin: 0; 57 | } 58 | 59 | .options.is-closed { 60 | display: none; 61 | } 62 | 63 | .option { 64 | padding: 4px 24px; 65 | cursor: default; 66 | position: relative; 67 | } 68 | 69 | .option.selected:before { 70 | content: "✔︎"; 71 | position: absolute; 72 | left: 7px; 73 | top: 0.35em; 74 | font-size: 75%; 75 | } 76 | 77 | .option.pre-selection { 78 | background: hsl(200, 80%, 50%); 79 | color: white; 80 | } 81 | 82 | dl.faq button { 83 | margin: 0; 84 | padding: 0; 85 | margin-top: 1em; 86 | font-weight: bold; 87 | font-size: 110%; 88 | border: thin solid transparent; 89 | background-color: transparent; 90 | padding-left: 0.125em; 91 | } 92 | 93 | dl dd { 94 | margin: 0; 95 | padding: 0; 96 | margin-left: 1.5em; 97 | } 98 | 99 | dl.faq .desc { 100 | margin: 0; 101 | padding: 0.5em; 102 | font-size: 110%; 103 | background-color: #fffedb; 104 | } 105 | 106 | dl.faq button:hover, 107 | dl.faq button:focus { 108 | background-color: #eee; 109 | } 110 | 111 | dl.faq button:focus { 112 | border-color: #663300; 113 | } 114 | 115 | dl.faq button:hover { 116 | text-decoration: underline; 117 | } 118 | 119 | dl.faq button:active { 120 | background-color: #bbb; 121 | } 122 | 123 | dl.faq button[aria-expanded="false"]:before { 124 | content: "▸"; 125 | padding-right: 0.35em; 126 | } 127 | 128 | dl.faq button[aria-expanded="true"]:before { 129 | content: "▾"; 130 | padding-right: 0.35em; 131 | } 132 | -------------------------------------------------------------------------------- /07-gsbu/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /07-gsbu/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0" 8 | }, 9 | "devDependencies": { 10 | "react-scripts": "1.0.10" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | } 18 | } -------------------------------------------------------------------------------- /07-gsbu/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/07-gsbu/exercise/public/favicon.ico -------------------------------------------------------------------------------- /07-gsbu/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /07-gsbu/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /07-gsbu/exercise/src/App.solution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import subscribeToMessages from "./lib/messages"; 3 | import FadeIn from "./lib/FadeIn"; 4 | 5 | class PinScrollToBottom extends Component { 6 | componentDidMount() { 7 | this.scroll(); 8 | } 9 | 10 | componentDidUpdate(prevProps, prevState, atBottom) { 11 | if (atBottom) { 12 | this.scroll(); 13 | } 14 | } 15 | 16 | scroll() { 17 | document.documentElement.scrollTop = document.documentElement.scrollHeight; 18 | } 19 | 20 | getSnapshotBeforeUpdate() { 21 | const { scrollHeight, scrollTop, clientHeight } = document.documentElement; 22 | const atBottom = scrollHeight < scrollTop + clientHeight + 20; 23 | return atBottom; 24 | } 25 | 26 | render() { 27 | return this.props.children; 28 | } 29 | } 30 | 31 | class App extends Component { 32 | state = { 33 | messages: [] 34 | }; 35 | 36 | componentDidMount() { 37 | subscribeToMessages(message => { 38 | this.setState({ 39 | messages: this.state.messages.concat([message]) 40 | }); 41 | }); 42 | } 43 | 44 | render() { 45 | const { messages } = this.state; 46 | return ( 47 |
    48 | 53 | 54 |
      55 | {messages.map((message, index) => ( 56 | 57 |
    1. 58 |
      64 |
      {message.text}
      65 |
    2. 66 |
      67 | ))} 68 |
    69 |
    70 |
    71 | ); 72 | } 73 | } 74 | 75 | export default App; 76 | -------------------------------------------------------------------------------- /07-gsbu/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | EXERCISE: Edit `PinScrollToBottom` to manage the window's scroll position. 4 | Whenever new messages come in, scroll to the bottom. However, if the user has 5 | scrolled up at all, *don't* scroll them done cause that's super annoying. 6 | 7 | Tips: 8 | 9 | - You can scroll down with: 10 | `document.documentElement.scrollTop = document.documentElement.scrollHeight` 11 | - Measure the scroll position in `getSnapshotBeforeUpdate` 12 | - You'll need these measurements: 13 | `let { scrollTop, scrollHeight, clientHeight} = document.documentElement` 14 | 15 | */ 16 | 17 | import React, { Component } from "react"; 18 | import subscribeToMessages from "./lib/messages"; 19 | import FadeIn from "./lib/FadeIn"; 20 | 21 | class PinScrollToBottom extends Component { 22 | render() { 23 | return this.props.children; 24 | } 25 | } 26 | 27 | class App extends Component { 28 | state = { 29 | messages: [] 30 | }; 31 | 32 | componentDidMount() { 33 | subscribeToMessages(message => { 34 | this.setState({ 35 | messages: this.state.messages.concat([message]) 36 | }); 37 | }); 38 | } 39 | 40 | render() { 41 | const { messages } = this.state; 42 | return ( 43 |
    44 | 49 | 50 |
      51 | {messages.map((message, index) => ( 52 | 53 |
    1. 54 |
      60 |
      {message.text}
      61 |
    2. 62 |
      63 | ))} 64 |
    65 |
    66 |
    67 | ); 68 | } 69 | } 70 | 71 | export default App; 72 | -------------------------------------------------------------------------------- /07-gsbu/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /07-gsbu/exercise/src/lib/FadeIn.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | class FadeIn extends Component { 4 | state = { 5 | mounted: false 6 | }; 7 | 8 | componentDidMount() { 9 | setTimeout(() => { 10 | this.setState({ mounted: true }); 11 | }, 0); 12 | } 13 | 14 | render() { 15 | const { className } = this.props.children.props; 16 | return React.cloneElement(this.props.children, { 17 | className: className + (this.state.mounted ? " fade-in" : "") 18 | }); 19 | } 20 | } 21 | 22 | export default FadeIn; 23 | -------------------------------------------------------------------------------- /07-gsbu/exercise/src/lib/avatars/EI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/07-gsbu/exercise/src/lib/avatars/EI.png -------------------------------------------------------------------------------- /07-gsbu/exercise/src/lib/avatars/GC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/07-gsbu/exercise/src/lib/avatars/GC.png -------------------------------------------------------------------------------- /07-gsbu/exercise/src/lib/avatars/MP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/07-gsbu/exercise/src/lib/avatars/MP.png -------------------------------------------------------------------------------- /07-gsbu/exercise/src/lib/avatars/TG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/07-gsbu/exercise/src/lib/avatars/TG.png -------------------------------------------------------------------------------- /07-gsbu/exercise/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 3 | } 4 | 5 | .app { 6 | width: 80vw; 7 | margin: auto; 8 | font-size: 3vh; 9 | } 10 | 11 | .messages { 12 | list-style: none; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | .message { 18 | display: flex; 19 | align-items: top; 20 | margin: 2vh; 21 | opacity: 0; 22 | transition: opacity 650ms ease; 23 | } 24 | 25 | .message.fade-in { 26 | opacity: 1; 27 | } 28 | 29 | .avatar { 30 | width: 15vh; 31 | height: 15vh; 32 | background-size: cover; 33 | border-radius: 50%; 34 | margin: 2vh; 35 | } 36 | 37 | .text { 38 | flex: 1; 39 | padding-top: 1em; 40 | } 41 | 42 | .link { 43 | text-align: center; 44 | margin: 5vh; 45 | } 46 | 47 | .link a { 48 | color: #e94949; 49 | text-decoration: none; 50 | } 51 | -------------------------------------------------------------------------------- /07-gsbu/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /07-gsbu/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reach/alert": "^0.1.1", 7 | "react": "^16.5.0", 8 | "react-dom": "^16.5.0" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /07-gsbu/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/07-gsbu/lecture/public/favicon.ico -------------------------------------------------------------------------------- /07-gsbu/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /07-gsbu/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /07-gsbu/lecture/src/App.final.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Alert from "@reach/alert"; 3 | 4 | let sleep = ms => new Promise(res => setTimeout(res, ms)); 5 | 6 | let subscribeEmail = email => 7 | new Promise(async resolve => { 8 | console.log("subscribing", email); 9 | await sleep(4000); 10 | console.log("subscribed!"); 11 | resolve(); 12 | }); 13 | 14 | class App extends Component { 15 | state = { 16 | state: "idle" // "subscribing", "subscribed" 17 | }; 18 | 19 | inputRef = React.createRef(); 20 | 21 | contentRef = React.createRef(); 22 | 23 | getSnapshotBeforeUpdate() { 24 | return document.activeElement !== this.contentRef.current; 25 | } 26 | 27 | componentDidUpdate(prevProps, prevState, userMovedFocus) { 28 | if (prevState.state === "idle" && this.state.state === "subscribing") { 29 | this.contentRef.current.focus(); 30 | } else if ( 31 | !userMovedFocus && 32 | prevState.state === "subscribed" && 33 | this.state.state === "idle" 34 | ) { 35 | this.inputRef.current.focus(); 36 | } 37 | } 38 | 39 | render() { 40 | let { state } = this.state; 41 | return ( 42 |
    43 |
    44 |

    Subscribe to our mailing list!

    45 | {state === "idle" ? ( 46 |
    { 48 | event.preventDefault(); 49 | this.setState({ state: "subscribing" }); 50 | await subscribeEmail(this.inputRef.current.value); 51 | this.setState({ state: "subscribed" }); 52 | await sleep(5000); 53 | this.setState({ state: "idle" }); 54 | }} 55 | > 56 | {" "} 61 | 62 |
    63 | ) : ( 64 |
    65 | {state === "subscribing" ? ( 66 | Subscribing ... 67 | ) : state === "subscribed" ? ( 68 | You're subscribed, thanks! 69 | ) : null} 70 |
    71 | )} 72 |
    73 | 74 |
    75 | 76 |

    Here's some other stuff on the page

    77 | 78 | 79 |
    80 | ); 81 | } 82 | } 83 | 84 | export default App; 85 | -------------------------------------------------------------------------------- /07-gsbu/lecture/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Alert from "@reach/alert"; 3 | 4 | let sleep = ms => new Promise(res => setTimeout(res, ms)); 5 | 6 | let subscribeEmail = email => 7 | new Promise(async resolve => { 8 | console.log("subscribing", email); 9 | await sleep(4000); 10 | console.log("subscribed!"); 11 | resolve(); 12 | }); 13 | 14 | class App extends Component { 15 | state = { 16 | state: "idle" // "subscribing", "subscribed" 17 | }; 18 | 19 | inputRef = React.createRef(); 20 | 21 | contentRef = React.createRef(); 22 | 23 | componentDidUpdate() { 24 | let { state } = this.state; 25 | if (state === "idle") { 26 | this.inputRef.current.focus(); 27 | } else { 28 | this.contentRef.current.focus(); 29 | } 30 | } 31 | 32 | render() { 33 | let { state } = this.state; 34 | return ( 35 |
    36 |
    37 |

    Subscribe to our mailing list!

    38 | {state === "idle" ? ( 39 |
    { 41 | event.preventDefault(); 42 | this.setState({ state: "subscribing" }); 43 | await subscribeEmail(this.inputRef.current.value); 44 | this.setState({ state: "subscribed" }); 45 | await sleep(5000); 46 | this.setState({ state: "idle" }); 47 | }} 48 | > 49 | {" "} 54 | 55 |
    56 | ) : ( 57 |
    58 | {state === "subscribing" ? ( 59 | Subscribing ... 60 | ) : state === "subscribed" ? ( 61 | You're subscribed, thanks! 62 | ) : null} 63 |
    64 | )} 65 |
    66 | 67 |
    68 | 69 |

    Here's some other stuff on the page

    70 | 71 | 72 |
    73 | ); 74 | } 75 | } 76 | 77 | export default App; 78 | -------------------------------------------------------------------------------- /07-gsbu/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.final"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /07-gsbu/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 4 | } 5 | 6 | hr { 7 | margin-top: 2rem; 8 | margin-bottom: 2rem; 9 | } 10 | -------------------------------------------------------------------------------- /08-gdsfp/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /08-gdsfp/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.5.0", 7 | "react-dom": "^16.5.0" 8 | }, 9 | "devDependencies": { 10 | "react-scripts": "1.0.10" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | } 18 | } -------------------------------------------------------------------------------- /08-gdsfp/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/08-gdsfp/exercise/public/favicon.ico -------------------------------------------------------------------------------- /08-gdsfp/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux TodoMVC Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/App.solution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | const getQuote = () => { 4 | return 30 + Math.random() * 10; 5 | }; 6 | 7 | class App extends Component { 8 | state = { 9 | price: getQuote() 10 | }; 11 | 12 | componentDidMount() { 13 | setInterval(this.fetch, 2000); 14 | } 15 | 16 | fetch = async () => { 17 | this.setState({ price: getQuote() }); 18 | }; 19 | 20 | render() { 21 | return ( 22 |
    23 |

    Stock Price

    24 | 25 |
    26 | ); 27 | } 28 | } 29 | 30 | class PriceDisplay extends React.Component { 31 | state = { 32 | price: this.props.price, 33 | direction: "initial" 34 | }; 35 | 36 | static getDerivedStateFromProps(props, state, more) { 37 | if (props.price > state.price) { 38 | return { 39 | price: props.price, 40 | direction: "up" 41 | }; 42 | } else if (props.price < state.price) { 43 | return { 44 | price: props.price, 45 | direction: "down" 46 | }; 47 | } 48 | return null; 49 | } 50 | 51 | render() { 52 | return ( 53 |
    68 | 69 | {this.state.direction === "up" && "▲"} 70 | {this.state.direction === "down" && "▼"} 71 | 72 | 73 | {this.props.price.toFixed(2)} 74 | 75 |
    76 | ); 77 | } 78 | } 79 | 80 | export default App; 81 | -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | const getQuote = () => { 4 | return 30 + Math.random() * 10; 5 | }; 6 | 7 | class App extends Component { 8 | state = { 9 | price: getQuote() 10 | }; 11 | 12 | componentDidMount() { 13 | setInterval(this.fetch, 2000); 14 | } 15 | 16 | fetch = async () => { 17 | this.setState({ price: getQuote() }); 18 | }; 19 | 20 | render() { 21 | return ( 22 |
    23 |

    Stock Price

    24 | 25 |
    26 | ); 27 | } 28 | } 29 | 30 | class PriceDisplay extends React.Component { 31 | render() { 32 | let direction = "up"; // get this from state instead 33 | return ( 34 |
    49 | 50 | {direction === "up" && "▲"} 51 | {direction === "down" && "▼"} 52 | 53 | 54 | {this.props.price.toFixed(2)} 55 | 56 |
    57 | ); 58 | } 59 | } 60 | 61 | export default App; 62 | 63 | // import React, { Component } from "react"; 64 | 65 | // const getQuote = () => { 66 | // return 30 + Math.random() * 10; 67 | // }; 68 | 69 | // class App extends Component { 70 | // state = { 71 | // price: getQuote() 72 | // }; 73 | 74 | // componentDidMount() { 75 | // setInterval(this.fetch, 2000); 76 | // } 77 | 78 | // fetch = async () => { 79 | // this.setState({ price: getQuote() }); 80 | // }; 81 | 82 | // render() { 83 | // return ( 84 | //
    85 | //

    Stock Price

    86 | // 87 | //
    88 | // ); 89 | // } 90 | // } 91 | 92 | // class PriceDisplay extends React.Component { 93 | // state = {}; 94 | 95 | // static getDerivedStateFromProps(props, state, more) { 96 | // if (!state.price) { 97 | // return { 98 | // price: props.price, 99 | // direction: "initial" 100 | // }; 101 | // } else { 102 | // if (props.price > state.price) { 103 | // return { 104 | // price: props.price, 105 | // direction: "up" 106 | // }; 107 | // } else if (props.price < state.price) { 108 | // return { 109 | // price: props.price, 110 | // direction: "down" 111 | // }; 112 | // } 113 | // } 114 | // } 115 | 116 | // render() { 117 | // return ( 118 | //
    135 | // 136 | // {this.state.direction === "up" && "▲"} 137 | // {this.state.direction === "down" && "▼"} 138 | // 139 | // 140 | // {this.props.price.toFixed(2)} 141 | // 142 | //
    143 | // ); 144 | // } 145 | // } 146 | 147 | // export default App; 148 | -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.start"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/lib/digital-7/digital-7 (italic).ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/08-gdsfp/exercise/src/lib/digital-7/digital-7 (italic).ttf -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/lib/digital-7/digital-7 (mono italic).ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/08-gdsfp/exercise/src/lib/digital-7/digital-7 (mono italic).ttf -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/lib/digital-7/digital-7 (mono).ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/08-gdsfp/exercise/src/lib/digital-7/digital-7 (mono).ttf -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/lib/digital-7/digital-7.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/08-gdsfp/exercise/src/lib/digital-7/digital-7.ttf -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/lib/digital-7/readme.txt: -------------------------------------------------------------------------------- 1 | True Type Fonts: DIGITAL-7 version 1.02 2 | 3 | 4 | EULA 5 | -==- 6 | The fonts Digital-7 is freeware for home using. 7 | 8 | 9 | DESCRIPTION 10 | -=========- 11 | 12 | This font created specially for program Calculator-7 (download shareware version: http://www.styleseven.com/ and use 7 days fo free). 13 | 14 | The program Calculator-7 offers you the following possibilities: 15 | * calculate using seven operator: addition, subtraction, multiply, divide, percent, square root, 1 divide to X; 16 | * set decimal position (0, 2, 3, float) and round type (up, mathematical, down); 17 | * customize an appearance of work window: scale, fonts for digital panel and buttons, background color; 18 | * customize an appearance of number in digital panel: leading zero for decimal, thousand separator, decimal separator, digit grouping; 19 | * calculate total from clipboard (copy data to clipboard from table or text and press one button). 20 | 21 | 22 | Files in digital-7_font.zip: 23 | readme.txt this file; 24 | digital-7.ttf digital-7 regular font; 25 | digital-7 (italic).ttf digital-7 italic font; 26 | digital-7 (mono).ttf digital-7 mono font; 27 | digital-7 (mono italic).ttf digital-7 mono font. 28 | 29 | Please visit http://www.styleseven.com/ for download our other products as freeware as shareware. 30 | We will welcome any useful suggestions and comments; please send them to ms-7@styleseven.com 31 | 32 | 33 | FREEWARE USE (NOTES) 34 | -=================- 35 | Also you may: 36 | * Use the font in freeware software (credit needed); 37 | * Use the font for your education process. 38 | 39 | 40 | COMMERCIAL OR BUSINESS USE 41 | -========================- 42 | 43 | You can buy font for commercial use here ($24.95): http://store.esellerate.net/s.aspx?s=STR0331655240 44 | You may: 45 | * Include the font to your installation; 46 | * Use one license up to 100 computers in your office. 47 | Please contact us for any questions. 48 | 49 | 50 | WHAT IS NEW? 51 | -==========- 52 | 53 | Version 1.01 April 05 2009 54 | -------------------------- 55 | * Change Typeface name for fonts "Digital-7 (mono)" and "Digital-7 (italic)" (now available all fonts for select in application, for example Word Pad). 56 | * Corrected symbol ':'. 57 | 58 | Version 1.01 April 07 2011 59 | -------------------------- 60 | * Embedding is allowed. 61 | 62 | Version 1.1 June 07 2013 63 | -------------------------- 64 | * Mono Italic font is added. 65 | 66 | 67 | AUTHOR 68 | -====- 69 | 70 | Sizenko Alexander 71 | Style-7 72 | http://www.styleseven.com 73 | Created: October 7 2008 -------------------------------------------------------------------------------- /08-gdsfp/exercise/src/lib/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Digital"; 3 | src: url("./digital-7/digital-7.ttf"); 4 | } 5 | 6 | body { 7 | background: #eee; 8 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, 9 | Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 10 | } 11 | 12 | .App { 13 | text-align: center; 14 | } 15 | 16 | .App-logo { 17 | animation: App-logo-spin infinite 20s linear; 18 | height: 80px; 19 | } 20 | 21 | .App-header { 22 | background-color: #222; 23 | height: 150px; 24 | padding: 20px; 25 | color: white; 26 | } 27 | 28 | .App-title { 29 | font-size: 1.5em; 30 | } 31 | 32 | .App-intro { 33 | font-size: large; 34 | } 35 | 36 | @keyframes App-logo-spin { 37 | from { 38 | transform: rotate(0deg); 39 | } 40 | to { 41 | transform: rotate(360deg); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /08-gdsfp/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /08-gdsfp/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reach/router": "^1.2.1", 7 | "react": "^16.5.0", 8 | "react-dom": "^16.5.0" 9 | }, 10 | "devDependencies": { 11 | "react-scripts": "1.0.10" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /08-gdsfp/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/08-gdsfp/lecture/public/favicon.ico -------------------------------------------------------------------------------- /08-gdsfp/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | getDerivedStateFromProps 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /08-gdsfp/lecture/src/App.final.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Router, Link } from "@reach/router"; 3 | 4 | const API = "https://contacts.now.sh"; 5 | 6 | class Fetch extends Component { 7 | state = { 8 | error: null, 9 | data: null, 10 | url: this.props.url 11 | }; 12 | 13 | static getDerivedStateFromProps(props, state) { 14 | if (props.url !== state.url) { 15 | return { 16 | error: null, 17 | data: null, 18 | url: props.url 19 | }; 20 | } 21 | return null; 22 | } 23 | 24 | componentDidMount() { 25 | this.fetch(); 26 | } 27 | 28 | componentDidUpdate(prevProps) { 29 | if (prevProps.url !== this.props.url) { 30 | this.fetch(); 31 | } 32 | } 33 | 34 | async fetch() { 35 | try { 36 | let response = await fetch(this.state.url); 37 | let data = await response.json(); 38 | this.setState({ data }); 39 | } catch (error) { 40 | this.setState({ error }); 41 | } 42 | } 43 | 44 | render() { 45 | return this.props.children(this.state); 46 | } 47 | } 48 | 49 | let Home = () => ( 50 |
    51 |

    Welcome!

    52 |

    Please select a contact.

    53 |
    54 | ); 55 | 56 | let Contact = ({ contactId }) => ( 57 | 58 | {({ error, data }) => 59 | data ? ( 60 |
    61 |

    Contact: {data.contact.first}

    62 | {`${data.contact.first} 67 |
    68 | ) : error ? ( 69 |
    ERROR! {error.message}
    70 | ) : ( 71 |
    Loading...
    72 | ) 73 | } 74 |
    75 | ); 76 | 77 | let App = ({ children, contacts }) => ( 78 |
    79 |

    Address Book

    80 |
      81 | {contacts.map(contact => ( 82 |
    • 83 | 84 | {contact.first} {contact.last} 85 | 86 |
    • 87 | ))} 88 |
    89 | {children} 90 |
    91 | ); 92 | 93 | export default () => ( 94 | 95 | {({ error, data }) => 96 | data ? ( 97 | 98 | 99 | 100 | 101 | 102 | 103 | ) : error ? ( 104 |
    ERROR! {error.message}
    105 | ) : ( 106 |
    Loading...
    107 | ) 108 | } 109 |
    110 | ); 111 | -------------------------------------------------------------------------------- /08-gdsfp/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./lib/index.css"; 4 | import App from "./App.final"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /08-gdsfp/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 4 | } 5 | -------------------------------------------------------------------------------- /09-suspense/exercise/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /09-suspense/exercise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reach/router": "^1.2.1", 7 | "react": "^16.7.0-alpha.0", 8 | "react-cache": "^2.0.0-alpha.0", 9 | "react-dom": "^16.7.0-alpha.0" 10 | }, 11 | "devDependencies": { 12 | "react-scripts": "1.0.10" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /09-suspense/exercise/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/09-suspense/exercise/public/favicon.ico -------------------------------------------------------------------------------- /09-suspense/exercise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /09-suspense/exercise/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /09-suspense/exercise/src/App.solution.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { unstable_createResource as createResource } from "react-cache"; 3 | import { Router, Link } from "@reach/router"; 4 | 5 | let ContactsResource = createResource(async path => { 6 | let response = await fetch(`https://contacts.now.sh${path}`); 7 | let json = await response.json(); 8 | return json; 9 | }); 10 | 11 | let ImageResource = createResource(src => { 12 | return new Promise(resolve => { 13 | let img = new Image(); 14 | img.src = src; 15 | img.onload = () => { 16 | setTimeout(() => { 17 | resolve(src); 18 | }, 3000); 19 | }; 20 | }); 21 | }); 22 | 23 | function Img({ src, ...props }) { 24 | // eslint-disable-next-line jsx-a11y/alt-text 25 | return ; 26 | } 27 | 28 | function Home() { 29 | let { contacts } = ContactsResource.read("/contacts"); 30 | return ( 31 |
    32 |

    Contacts

    33 |
      34 | {contacts.map(contact => ( 35 |
    • 36 | 37 | {contact.first} {contact.last} 38 | 39 |
    • 40 | ))} 41 |
    42 |
    43 | ); 44 | } 45 | 46 | function Contact({ id }) { 47 | let { contact } = ContactsResource.read(`/contacts/${id}`); 48 | return ( 49 |
    50 |

    51 | {contact.first} {contact.last} 52 |

    53 |

    54 | Loading...}> 55 | {`${contact.first} 60 | 61 |

    62 |

    63 | Home 64 |

    65 |
    66 | ); 67 | } 68 | 69 | export default () => ( 70 | Loading...
    }> 71 | 72 | 73 | 74 | 75 | 76 | ); 77 | -------------------------------------------------------------------------------- /09-suspense/exercise/src/App.start.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 1. Fill in `Contact` to use `ContactsResource` to fetch the contact, use the 4 | path: `/contacts/${id}` like this: 5 | 6 | 2. Notice how we transition before the image loads and then the page jumps 7 | around? We're waiting for the contact data but not the image. Create an 8 | `` component and a new `ImgResource` to delay transitioning until 9 | the image is loaded 10 | 11 | Remember, `createResource` needs to return a promise 12 | 13 | Tips: 14 | 15 | ``` 16 | // you can load images programatically 17 | let image = new Image(); 18 | image.src = someUrl 19 | image.onload = () => {} 20 | 21 | // You can create promises out of anything, here we'll use 22 | // setTimeout to make a "sleep" promise: 23 | function sleep(ms=1000) { 24 | return new Promise((resolve, reject) => { 25 | setTimeout(resolve, ms) 26 | }) 27 | } 28 | 29 | // combine those two things to create your `ImageResource` 30 | ``` 31 | 32 | 3. Finally, render a `Placeholder` (you'll need to import it from react) around 33 | `Img` with a `2000` delayMs, and then artificially delay your ImageResource by 34 | 3000ms with setTimeout. What happens when you click the links now? 35 | 36 | */ 37 | 38 | import React, { Suspense } from "react"; 39 | import { unstable_createResource as createResource } from "react-cache"; 40 | import { Router, Link } from "@reach/router"; 41 | 42 | let ContactsResource = createResource(async path => { 43 | let response = await fetch(`https://contacts.now.sh${path}`); 44 | let json = await response.json(); 45 | return json; 46 | }); 47 | 48 | function Home() { 49 | let { contacts } = ContactsResource.read("/contacts"); 50 | return ( 51 |
    52 |

    Contacts

    53 |
      54 | {contacts.map(contact => ( 55 |
    • 56 | 57 | {contact.first} {contact.last} 58 | 59 |
    • 60 | ))} 61 |
    62 |
    63 | ); 64 | } 65 | 66 | let FAKE_DATA = { 67 | contact: { 68 | first: "Ryan", 69 | last: "Florence", 70 | avatar: "https://placekitten.com/510/510" 71 | } 72 | }; 73 | 74 | function Contact({ id }) { 75 | // don't use FAKE_DATA, use `ContactsResource` 76 | let { contact } = FAKE_DATA; 77 | return ( 78 |
    79 |

    80 | {contact.first} {contact.last} 81 |

    82 |

    83 | {`${contact.first} 88 |

    89 |

    90 | Home 91 |

    92 |
    93 | ); 94 | } 95 | 96 | export default () => ( 97 | Loading...
    }> 98 | 99 | 100 | 101 | 102 | 103 | ); 104 | -------------------------------------------------------------------------------- /09-suspense/exercise/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./lib/index.css"; 3 | import { createRoot } from "react-dom"; 4 | import App from "./App.start"; 5 | 6 | createRoot(document.getElementById("root")).render(); 7 | -------------------------------------------------------------------------------- /09-suspense/exercise/src/lib/cache.js: -------------------------------------------------------------------------------- 1 | import {createCache} from 'simple-cache-provider'; 2 | 3 | export let cache; 4 | 5 | function initCache() { 6 | cache = createCache(initCache); 7 | } 8 | 9 | initCache(); 10 | 11 | -------------------------------------------------------------------------------- /09-suspense/exercise/src/lib/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /09-suspense/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /09-suspense/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reach/component-component": "^0.1.1", 7 | "@reach/router": "^1.2.1", 8 | "glamor": "^2.20.40", 9 | "react": "16.6", 10 | "react-cache": "^2.0.0-alpha.0", 11 | "react-dom": "16.6", 12 | "react-svg-spinner": "^1.0.1" 13 | }, 14 | "devDependencies": { 15 | "react-scripts": "1.0.10" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /09-suspense/lecture/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reach/advanced-react-workshop/c78c67a2202eb9481217d14345ecfa3a25837115/09-suspense/lecture/public/favicon.ico -------------------------------------------------------------------------------- /09-suspense/lecture/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Advanced React.js Exercise 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /09-suspense/lecture/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /09-suspense/lecture/src/index.js: -------------------------------------------------------------------------------- 1 | import "./lib/index.css"; 2 | import React from "react"; 3 | import { unstable_createRoot } from "react-dom"; 4 | import App from "./App.Refactor"; 5 | 6 | unstable_createRoot(document.getElementById("root")).render(); 7 | -------------------------------------------------------------------------------- /09-suspense/lecture/src/lib/Competitions.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export default () =>
    Competitions
    ; 3 | -------------------------------------------------------------------------------- /09-suspense/lecture/src/lib/ManageScroll.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Location } from "@reach/router"; 3 | 4 | window.history.scrollRestoration = "manual"; 5 | 6 | let scrollPositions = {}; 7 | 8 | class ManageScrollImpl extends React.Component { 9 | componentDidMount() { 10 | try { 11 | // session storage will throw for a few reasons 12 | // - user settings 13 | // - in-cognito/private browsing 14 | // - who knows... 15 | let storage = JSON.parse(sessionStorage.getItem("scrollPositions")); 16 | if (storage) { 17 | scrollPositions = JSON.parse(storage) || {}; 18 | let { key } = this.props.location; 19 | if (scrollPositions[key]) { 20 | window.scrollTo(0, scrollPositions[key]); 21 | } 22 | } 23 | } catch (e) {} 24 | 25 | window.addEventListener("scroll", this.listener); 26 | } 27 | 28 | componentWillUnmount() { 29 | window.removeEventListener("scroll", this.listener); 30 | } 31 | 32 | componentDidUpdate() { 33 | const { key } = this.props.location; 34 | if (!scrollPositions[key]) { 35 | // never seen this location before 36 | window.scrollTo(0, 0); 37 | } else { 38 | // seen it 39 | window.scrollTo(0, scrollPositions[key]); 40 | } 41 | } 42 | 43 | listener = () => { 44 | scrollPositions[this.props.location.key] = window.scrollY; 45 | try { 46 | sessionStorage.setItem( 47 | "scrollPositions", 48 | JSON.stringify(scrollPositions) 49 | ); 50 | } catch (e) {} 51 | }; 52 | 53 | render() { 54 | return null; 55 | } 56 | } 57 | 58 | export default () => ( 59 | 60 | {({ location }) => } 61 | 62 | ); 63 | -------------------------------------------------------------------------------- /09-suspense/lecture/src/lib/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, 7 | Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 8 | } 9 | -------------------------------------------------------------------------------- /10-carousel/.eslintrc: -------------------------------------------------------------------------------- 1 | { "extends": "react-app" } 2 | -------------------------------------------------------------------------------- /10-carousel/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /10-carousel/config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebookincubator/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /10-carousel/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /10-carousel/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /10-carousel/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right