├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── ElasticScroll.js │ ├── Footer.js │ ├── Framework.js │ ├── Header.js │ ├── Image.js │ ├── Layout.js │ ├── Main.js │ ├── Nav.js │ ├── NavButtons.js │ ├── SEO.js │ └── Showhide.js ├── css │ ├── base.js │ ├── index.js │ └── prism.js ├── favicon.png ├── images │ ├── diagrama.png │ ├── gatsby-icon.png │ ├── props.jpg │ ├── react.png │ ├── state.jpg │ └── top.png ├── monkey-patch.js ├── pages │ ├── 404.js │ ├── index.mdx │ ├── intro.mdx │ ├── paso-01.mdx │ ├── paso-02.mdx │ ├── paso-03.mdx │ └── props-y-state.mdx ├── prism-extensions.js └── utils.js ├── templates ├── template.css └── template.js └── traduccion ├── Componentes.md └── JSX.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "proseWrap": "always" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workshop de React 2 | 3 | En este workshop, vamos a aprender y entender las bases de React haciendo una app que nos muestre el clima en alguna ciudad. 😀 4 | 5 | [**Vamos al Workshop nomás ⇨**](https://agustinmulet.github.io/reactworkshop/) 6 | 7 | --- 8 | 9 | ## Mejoras a hacer 10 | 11 | - [x] Diseño responsive 12 | - [ ] Crear sandbox para explicar de forma más gráfica Props y State 13 | - [ ] Controlled y Uncontrolled Inputs 14 | - [ ] Explicar Forms 15 | - [ ] Hooks 16 | 17 | --- 18 | 19 | ## Créditos 20 | 21 | Fuentes usadas para hacer este workshop: 22 | 23 | - [Tutorial de React de Flavio Copes](https://flaviocopes.com/react/) 24 | - [Documentación de React](https://reactjs.org/docs/getting-started.html) 25 | - [MDN web docs](https://developer.mozilla.org/es/docs/Web) 26 | - [Stateful vs. Stateless Functional Components in React (envatotuts+)](https://code.tutsplus.com/tutorials/stateful-vs-stateless-functional-components-in-react--cms-29541) 27 | - [Create-React-App (Repo)](https://github.com/facebook/create-react-app) 28 | - [Documentación de Tippy.js usada como plantilla de Gatsby+MDX](https://github.com/atomiks/tippyjs/tree/master/website) -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | * **These should be gatsby plugins, but some weren't working.** 6 | * **For now I'm monkey-patching them...** 7 | */ 8 | import elasticScroll from 'elastic-scroll-polyfill' 9 | import { toKebabCase } from './src/utils' 10 | 11 | function addDataLabelToTdElements() { 12 | const labels = Array.from(document.querySelectorAll('th')).map( 13 | th => th.textContent, 14 | ) 15 | const rowTds = Array.from(document.querySelectorAll('tbody tr')).map(tr => 16 | Array.from(tr.querySelectorAll('td')), 17 | ) 18 | rowTds.forEach(tds => { 19 | tds.forEach((td, index) => { 20 | td.setAttribute('data-label', labels[index]) 21 | }) 22 | }) 23 | } 24 | 25 | function addElasticScrollingToCodeBlocks() { 26 | if (/Mac/.test(navigator.platform)) { 27 | elasticScroll({ targets: 'pre[class*="language"]' }) 28 | } 29 | } 30 | 31 | function autoLinkHeaders() { 32 | const headers = Array.from(document.querySelectorAll('h3,h4,h5,h6')) 33 | headers.forEach(header => { 34 | const a = document.createElement('a') 35 | const href = toKebabCase(header.textContent) 36 | a.id = href 37 | a.href = `#${href}` 38 | a.innerHTML = header.innerHTML 39 | header.innerHTML = '' 40 | header.appendChild(a) 41 | }) 42 | } 43 | 44 | export function onRouteUpdate() { 45 | addDataLabelToTdElements() 46 | addElasticScrollingToCodeBlocks() 47 | autoLinkHeaders() 48 | } 49 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pathPrefix: '/reactworkshop', 3 | siteMetadata: { 4 | title: `React Workshop`, 5 | description: `Workshop de React para aprender las bases realizando una app del clima.`, 6 | author: `@agustinmulet`, 7 | }, 8 | plugins: [ 9 | `gatsby-plugin-react-helmet`, 10 | { 11 | resolve: `gatsby-source-filesystem`, 12 | options: { 13 | name: `images`, 14 | path: `${__dirname}/src/images`, 15 | }, 16 | }, 17 | { 18 | resolve: `gatsby-source-filesystem`, 19 | options: { 20 | name: `pages`, 21 | path: `${__dirname}/src/pages/`, 22 | }, 23 | }, 24 | `gatsby-plugin-image`, 25 | `gatsby-transformer-sharp`, 26 | `gatsby-plugin-sharp`, 27 | { 28 | resolve: `gatsby-plugin-manifest`, 29 | options: { 30 | name: `gatsby-starter-default`, 31 | short_name: `starter`, 32 | start_url: `/`, 33 | background_color: `#663399`, 34 | theme_color: `#663399`, 35 | display: `minimal-ui`, 36 | icon: `src/favicon.png`, 37 | }, 38 | }, 39 | { 40 | resolve: `gatsby-plugin-google-analytics`, 41 | options: { 42 | trackingId: 'UA-132529584-1', 43 | head: false, 44 | }, 45 | }, 46 | `gatsby-plugin-catch-links`, 47 | `gatsby-plugin-styled-components`, 48 | { 49 | resolve: `gatsby-plugin-mdx`, 50 | options: { 51 | defaultLayouts: { 52 | default: require.resolve('./src/components/Layout.js'), 53 | }, 54 | gatsbyRemarkPlugins: [{ 55 | resolve: `gatsby-remark-prismjs`, 56 | options: { 57 | classPrefix: 'language-', 58 | inlineCodeMarker: null, 59 | aliases: {}, 60 | }, 61 | }], 62 | }, 63 | }, 64 | { 65 | resolve: `gatsby-plugin-google-fonts`, 66 | options: { 67 | fonts: [`nunito sans\:400,600,700`], 68 | }, 69 | }, 70 | ], 71 | } -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Node APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/node-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-default", 3 | "private": true, 4 | "description": "A simple starter to get up and developing quickly with Gatsby", 5 | "version": "0.1.0", 6 | "author": "Kyle Mathews ", 7 | "dependencies": { 8 | "@mdx-js/mdx": "^1.6.22", 9 | "@mdx-js/react": "^1.6.22", 10 | "@mdx-js/tag": "^0.20.3", 11 | "@reach/tabs": "^0.16.4", 12 | "babel-plugin-styled-components": "^2.0.2", 13 | "elastic-scroll-polyfill": "^2.1.0", 14 | "gatsby": "^4.4.0", 15 | "gatsby-image": "^3.11.0", 16 | "gatsby-plugin-catch-links": "^4.4.0", 17 | "gatsby-plugin-google-analytics": "^4.4.0", 18 | "gatsby-plugin-google-fonts": "1.0.1", 19 | "gatsby-plugin-image": "^2.4.0", 20 | "gatsby-plugin-manifest": "^4.4.0", 21 | "gatsby-plugin-mdx": "^3.4.0", 22 | "gatsby-plugin-offline": "^5.4.0", 23 | "gatsby-plugin-react-helmet": "^5.4.0", 24 | "gatsby-plugin-sharp": "^4.4.0", 25 | "gatsby-plugin-styled-components": "^5.4.0", 26 | "gatsby-remark-autolink-headers": "^5.4.0", 27 | "gatsby-remark-images": "^6.4.0", 28 | "gatsby-remark-prismjs": "^6.4.0", 29 | "gatsby-source-filesystem": "^4.4.0", 30 | "gatsby-transformer-remark": "^5.4.0", 31 | "gatsby-transformer-sharp": "^4.4.0", 32 | "prismjs": "^1.25.0", 33 | "prop-types": "^15.8.0", 34 | "react": "^17.0.2", 35 | "react-dom": "^17.0.2", 36 | "react-feather": "^2.0.9", 37 | "react-helmet": "^6.1.0", 38 | "rehype-react": "^7.0.3", 39 | "styled-components": "^5.3.3" 40 | }, 41 | "keywords": [ 42 | "gatsby" 43 | ], 44 | "license": "MIT", 45 | "scripts": { 46 | "build": "gatsby build", 47 | "develop": "gatsby develop", 48 | "start": "node src/monkey-patch.js && npm run develop", 49 | "format": "prettier --write \"src/**/*.js\"", 50 | "deploy": "gatsby build --prefix-paths && gh-pages -d public", 51 | "test": "echo \"Write tests! -> https://gatsby.app/unit-testing\"" 52 | }, 53 | "devDependencies": { 54 | "@mdx-js/loader": "^1.6.22", 55 | "gh-pages": "^3.2.3", 56 | "loader-utils": "^3.2.0", 57 | "prettier": "^2.5.1" 58 | }, 59 | "repository": { 60 | "type": "git", 61 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 62 | }, 63 | "bugs": { 64 | "url": "https://github.com/gatsbyjs/gatsby/issues" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/ElasticScroll.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Children, cloneElement, createRef } from 'react' 2 | import elasticScroll from 'elastic-scroll-polyfill' 3 | 4 | class ElasticScroll extends Component { 5 | scroller = createRef() 6 | 7 | componentDidMount() { 8 | this.instance = elasticScroll({ 9 | targets: this.scroller, 10 | ...this.props, 11 | }) 12 | } 13 | 14 | componentWillUnmount() { 15 | this.instance.disable() 16 | this.instance = null 17 | } 18 | 19 | render() { 20 | return Children.map(this.props.children, (child) => 21 | cloneElement(child, { 22 | children:
{child.props.children}
, 23 | ref: (node) => { 24 | this.scroller = node 25 | const { ref } = child 26 | if (ref) { 27 | if (typeof ref === 'function') { 28 | ref(node) 29 | } else if (ref.hasOwnProperty('current')) { 30 | ref.current = node 31 | } 32 | } 33 | }, 34 | }), 35 | ) 36 | } 37 | } 38 | 39 | export default ElasticScroll 40 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Footer = styled.footer` 4 | text-align: center; 5 | padding: 25px 0; 6 | border-top: 1px solid rgba(0, 16, 64, 0.08); 7 | margin-top: 50px; 8 | color: #9dacb6; 9 | font-size: 85%; 10 | background: white; 11 | ` 12 | 13 | export default Footer 14 | -------------------------------------------------------------------------------- /src/components/Framework.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { css } from 'styled-components' 3 | import { Link as GatsbyLink } from 'gatsby' 4 | 5 | export const MEDIA_SIZES = { 6 | xs: 360, 7 | sm: 576, 8 | md: 768, 9 | lg: 992, 10 | xl: 1200, 11 | } 12 | 13 | export const MEDIA = Object.keys(MEDIA_SIZES).reduce((acc, mediaSize) => { 14 | acc[mediaSize] = `@media (min-width: ${MEDIA_SIZES[mediaSize]}px)` 15 | return acc 16 | }, {}) 17 | 18 | export const Center = styled.div` 19 | text-align: center; 20 | ` 21 | 22 | export const Container = styled.div` 23 | position: relative; 24 | max-width: 940px; 25 | padding: 0 ${(props) => props.mobilePadding}%; 26 | margin: 0 auto; 27 | 28 | ${MEDIA.sm} { 29 | padding: 0 25px; 30 | } 31 | ${MEDIA.md} { 32 | padding: 0 40px; 33 | } 34 | ${MEDIA.lg} { 35 | padding: 0 50px; 36 | } 37 | ` 38 | Container.defaultProps = { 39 | mobilePadding: 5, 40 | } 41 | 42 | export const Row = styled(({ children, spacing, ...rest }) => ( 43 |
{children}
44 | ))` 45 | display: flex; 46 | flex-wrap: wrap; 47 | justify-content: space-between; 48 | margin: 0 -${(props) => props.spacing}px; 49 | ` 50 | Row.defaultProps = { 51 | spacing: 15, 52 | } 53 | 54 | export const Col = styled( 55 | ({ children, base, xs, sm, md, lg, xl, spacing, ...rest }) => ( 56 |
{children}
57 | ), 58 | )` 59 | flex: 1; 60 | padding: 0 ${(props) => props.spacing}px; 61 | ${(props) => 62 | props.base && 63 | css` 64 | flex-basis: ${(props) => (100 * props.base) / 12}%; 65 | `} 66 | ${(props) => 67 | ['xs', 'sm', 'md', 'lg', 'xl'] 68 | .filter((size) => props[size]) 69 | .map( 70 | (size) => css` 71 | ${MEDIA[size]} { 72 | flex-basis: ${(props) => (100 * props[size]) / 12}%; 73 | } 74 | `, 75 | )}; 76 | ` 77 | Col.defaultProps = { 78 | spacing: 15, 79 | } 80 | 81 | export const Link = styled(GatsbyLink).attrs((props) => ({ 82 | activeStyle: { 83 | fontWeight: 'bold', 84 | background: 'linear-gradient(90deg, #9dc6ff , #346CA4)', 85 | color: '#324B6D', 86 | }, 87 | }))` 88 | color: inherit; 89 | text-decoration: none; 90 | transition: color 0.15s; 91 | ` 92 | 93 | export const ExternalLink = styled.a.attrs((props) => ({ 94 | target: '_blank', 95 | rel: 'noopener noreferrer', 96 | }))` 97 | color: inherit; 98 | text-decoration: none; 99 | transition: color 0.15s; 100 | 101 | &:hover { 102 | color: #2263e5; 103 | } 104 | ` 105 | 106 | export const Flex = styled.div` 107 | display: flex; 108 | flex-wrap: wrap; 109 | justify-content: ${(props) => props.justify}; 110 | 111 | > div { 112 | margin-right: 15px; 113 | margin-bottom: 15px; 114 | flex: ${(props) => props.type === 'even' && 1}; 115 | } 116 | ` 117 | Flex.defaultProps = { 118 | justify: 'space-between', 119 | } 120 | 121 | export const Button = styled.button` 122 | border: none; 123 | background: linear-gradient(135deg, #56c8ff, #6f99fc) no-repeat; 124 | color: white; 125 | will-change: opacity; 126 | box-shadow: 0 4px 8px -1px rgba(25, 80, 137, 0.08), 127 | 0 8px 24px -2px rgba(0, 128, 255, 0.06); 128 | font-size: 17px; 129 | font-weight: 600; 130 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); 131 | padding: 8px 16px; 132 | border-radius: 4px; 133 | margin-right: 8px; 134 | margin-bottom: 8px; 135 | transition: opacity 0.2s; 136 | 137 | &:hover { 138 | opacity: 0.8; 139 | } 140 | ` 141 | 142 | export const Demo = styled.div` 143 | margin: 15px 0; 144 | background: #eeeefa; 145 | padding: 15px 15px 6px; 146 | border-radius: 8px; 147 | ` 148 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import ReactLogo from '../images/react.png' 4 | import { MEDIA, Container, Flex, ExternalLink } from './Framework' 5 | import GitHub from 'react-feather/dist/icons/github' 6 | import Menu from 'react-feather/dist/icons/menu' 7 | 8 | const HeaderRoot = styled.header` 9 | position: relative; 10 | background: linear-gradient(91deg, #f1eefc, #9dc6ff 70%, #a5bcff); 11 | padding: 25px 0 5px 0; 12 | text-align: center; 13 | margin-bottom: 50px; 14 | ` 15 | 16 | const Logo = styled.img` 17 | height: 60px; 18 | margin: 0px; 19 | ` 20 | 21 | const Title = styled.h1` 22 | font-size: 48px; 23 | font-weight: 600; 24 | margin-top: 0; 25 | margin-bottom: 8px; 26 | ` 27 | 28 | const ButtonLink = styled(ExternalLink)` 29 | background: rgba(255, 255, 255, 0.2); 30 | border: 1px solid rgba(255, 255, 255, 0.4); 31 | padding: 12px 24px; 32 | border-radius: 4px; 33 | transition: all 0.15s; 34 | color: #4574c0; 35 | margin: 0 10px 10px; 36 | font-weight: 500; 37 | position: absolute; 38 | right: 0px; 39 | top: 0px; 40 | 41 | &:hover { 42 | background: white; 43 | border-bottom-color: white; 44 | box-shadow: 0 8px 16px -2px rgba(0, 32, 128, 0.25); 45 | } 46 | ` 47 | 48 | const MenuButton = styled.button` 49 | position: absolute; 50 | top: -10px; 51 | left: 15px; 52 | color: inherit; 53 | font-weight: bold; 54 | border: none; 55 | background: none; 56 | text-transform: uppercase; 57 | border-radius: 4px; 58 | padding: 0; 59 | 60 | ${MEDIA.lg} { 61 | display: none; 62 | } 63 | ` 64 | 65 | const iconStyles = { 66 | verticalAlign: -6, 67 | marginRight: 10, 68 | } 69 | 70 | const githubStyles = { 71 | ...iconStyles, 72 | color: '#333', 73 | } 74 | 75 | const menuStyles = { 76 | width: 36, 77 | height: 36, 78 | } 79 | 80 | function Header({ openNav }) { 81 | return ( 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | React Workshop 90 | 91 | 92 | 93 | Github 94 | 95 | 96 | 97 | 98 | ) 99 | } 100 | 101 | export default Header 102 | -------------------------------------------------------------------------------- /src/components/Image.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StaticImage } from "gatsby-plugin-image" 3 | 4 | const Diagrama = () => { 5 | return 10 | } 11 | 12 | const Props = () => { 13 | return 18 | } 19 | 20 | const State = () => { 21 | return 26 | } 27 | 28 | const Top = () => { 29 | return 34 | } 35 | 36 | export { 37 | Diagrama, 38 | Props, 39 | State, 40 | Top 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Container } from './Framework' 3 | import Nav from './Nav' 4 | import NavButtons from './NavButtons' 5 | import Header from './Header' 6 | import Main from './Main' 7 | import Footer from './Footer' 8 | import SEO from './SEO' 9 | import CSS from '../css' 10 | 11 | class Layout extends Component { 12 | state = { 13 | isNavOpen: false, 14 | } 15 | 16 | openNav = () => { 17 | this.setState({ isNavOpen: true }) 18 | } 19 | 20 | closeNav = () => { 21 | this.setState({ isNavOpen: false }) 22 | } 23 | 24 | render() { 25 | const { isNavOpen } = this.state 26 | const { children, pageContext } = this.props 27 | return ( 28 | <> 29 | 30 | 31 |