├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public └── logo.webp ├── src ├── App.tsx ├── Components │ ├── CodeBlock │ │ └── CodeBlock.tsx │ ├── CommandBar │ │ └── index.tsx │ ├── CommandButton │ │ ├── index.tsx │ │ └── styles.ts │ ├── SkeletonLesson │ │ ├── SkeletonLesson.tsx │ │ └── index.ts │ └── VideoBlock │ │ ├── VideoBlock.tsx │ │ └── styles.ts ├── Utils │ ├── useLocalStorage.ts │ └── useUpdateLocalStorage.ts ├── assets │ └── logo.webp ├── data │ └── lessons.ts ├── layouts │ ├── Header │ │ ├── index.tsx │ │ └── styles.ts │ ├── Main │ │ ├── index.tsx │ │ └── styles.ts │ └── Sidebar │ │ ├── index.tsx │ │ └── styles.ts ├── main.tsx ├── markdown │ ├── uma-nova-maneira-de-pensar-em-javascript.md │ └── uma-viagem-pelo-universo-chamado-javascript.md ├── pages │ ├── MarkdownLessons.tsx │ └── styles.ts ├── styles │ ├── MediaQueries │ │ └── queries.ts │ ├── global.ts │ ├── styled.d.ts │ └── themes │ │ ├── dark.ts │ │ └── light.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json ├── vite.config.ts └── vite.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | # src/markdown 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # javascript-adventures -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JavaScript Adventures - Treinamento 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-adventures", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@types/react": "^18.0.17", 13 | "@types/styled-components": "^5.1.25", 14 | "kbar": "^0.1.0-beta.36", 15 | "polished": "^4.2.2", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-icons": "^4.4.0", 19 | "react-loading-skeleton": "^3.1.0", 20 | "react-markdown": "^8.0.3", 21 | "react-router-dom": "^6.3.0", 22 | "react-switch": "^7.0.0", 23 | "react-syntax-highlighter": "^15.5.0", 24 | "rehype-raw": "^6.1.1", 25 | "styled-breakpoints": "^11.1.1", 26 | "styled-components": "^5.3.5" 27 | }, 28 | "devDependencies": { 29 | "@types/prismjs": "^1.26.0", 30 | "@types/react-dom": "^18.0.6", 31 | "@types/react-syntax-highlighter": "^15.5.4", 32 | "@vitejs/plugin-react": "^2.0.0", 33 | "typescript": "^4.6.4", 34 | "vite": "^3.0.0", 35 | "vite-plugin-markdown": "^2.1.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsrocha-dev/javascript-adventures/9450f45ac9e110d9c3ab5793e1bcbfc44f0c8dcd/public/logo.webp -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, useEffect } from "react"; 2 | import { 3 | Route, Routes 4 | } from "react-router-dom"; 5 | 6 | import { ThemeProvider } from "styled-components"; 7 | import useLocalStorage from "./Utils/useLocalStorage"; 8 | import useUpdateLocalStorage from "./Utils/useUpdateLocalStorage"; 9 | 10 | import dark from "./styles/themes/dark"; 11 | import light from "./styles/themes/light"; 12 | 13 | import Header from "./layouts/Header"; 14 | import MainContainer from './layouts/Main'; 15 | import { Content } from "./layouts/Main/styles"; 16 | import GlobalStyle from "./styles/global"; 17 | 18 | import CommandBar from "./Components/CommandBar"; 19 | import SkeletonLesson from "./Components/SkeletonLesson/SkeletonLesson"; 20 | import Sidebar from "./layouts/Sidebar"; 21 | 22 | //Lazy components import 23 | const MarkdownLessons = lazy(() => import("./pages/MarkdownLessons")); 24 | 25 | function App() { 26 | const [theme, setTheme] = useLocalStorage('theme', light) 27 | 28 | useEffect(() => { 29 | useUpdateLocalStorage('theme', theme) 30 | }, [theme]) 31 | 32 | const toggleTheme = () => { 33 | const storageValue = localStorage.getItem('theme') 34 | const currentTheme = JSON.parse(storageValue || '{}').title; 35 | setTheme(currentTheme === 'light' ? dark : light) 36 | } 37 | 38 | return ( 39 | 40 | 41 | 42 | <> 43 | 44 | 45 | 46 |
47 | 48 | 49 | }> 51 | 52 | 53 | }/> 54 | 55 | 56 | 57 | 58 | 59 | ) 60 | } 61 | 62 | export default App 63 | -------------------------------------------------------------------------------- /src/Components/CodeBlock/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | 3 | import { CodeProps } from 'react-markdown/lib/ast-to-react'; 4 | import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; 5 | import js from "react-syntax-highlighter/dist/esm/languages/prism/javascript"; 6 | import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; 7 | 8 | const CodeBlock: FC = function ({ 9 | node, 10 | inline, 11 | className, 12 | children, 13 | ...props 14 | }) { 15 | const match = /language-(\w+)/.exec(className || ''); 16 | const language = match?.[1]; 17 | SyntaxHighlighter.registerLanguage('javascript', js); 18 | 19 | return !inline && match ? ( 20 | 26 | {String(children).replace(/\n$/, '')} 27 | 28 | ) : ( 29 | 30 | {children} 31 | 32 | ); 33 | }; 34 | 35 | export default CodeBlock; -------------------------------------------------------------------------------- /src/Components/CommandBar/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | KBarAnimator, KBarPortal, KBarPositioner, KBarProvider, KBarResults, KBarSearch, useMatches 3 | } from 'kbar'; 4 | import React from 'react'; 5 | import { useNavigate } from 'react-router-dom'; 6 | import { lessons } from '../../data/lessons'; 7 | 8 | import { transparentize } from 'polished'; 9 | 10 | import { TbBrandGithub, TbBrandLinkedin, TbExchange, TbFile, TbMenu2 } from 'react-icons/tb'; 11 | import styled from 'styled-components'; 12 | 13 | type Props = { 14 | children: JSX.Element, 15 | toggleTheme: React.Dispatch>, 16 | currentTheme: string 17 | } 18 | 19 | function CommandBar({ children, toggleTheme, currentTheme }: Props) { 20 | const navigateTo = useNavigate(); 21 | 22 | const actions = [ 23 | { 24 | id: 'licoes', 25 | name: 'Procurar nas lições...', 26 | shortcut: ['l'], 27 | keywords: 'lições licoes indice índice topicos tópicos', 28 | section: 'Lições', 29 | icon: 30 | }, 31 | ...lessons.map(({ title, tags, slug }) => ({ 32 | id: slug, 33 | name: title, 34 | keywords: tags, 35 | parent: 'licoes', 36 | perform: () => navigateTo(`/lessons/${slug}`), 37 | icon: 38 | })), 39 | { 40 | id: 'linkedin', 41 | name: 'LinkedIn', 42 | shortcut: ['f', 'l'], 43 | keywords: 'go-linkedin', 44 | section: 'Follow', 45 | perform: () => 46 | window.open('https://www.linkedin.com/in/frankrochadev/', '_blank'), 47 | icon: 48 | }, 49 | { 50 | id: 'github', 51 | name: 'Este projeto no github', 52 | shortcut: ['f', 'g'], 53 | keywords: 'github projeto', 54 | section: 'Follow', 55 | perform: () => 56 | window.open('https://github.com/fsrocha-dev/javascript-adventures', '_blank'), 57 | icon: 58 | }, 59 | { 60 | id: 'theme', 61 | name: 'Alternar tema', 62 | shortcut: ['t'], 63 | keywords: 'tema theme toggle', 64 | section: 'Preferências', 65 | perform: () => toggleTheme(''), 66 | icon: 67 | }, 68 | ]; 69 | 70 | return ( 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {children} 81 | 82 | ) 83 | } 84 | 85 | const Item = styled.div<{ active: boolean }>` 86 | `; 87 | 88 | type ItemProps = { 89 | active: boolean; 90 | item: string | { 91 | name: string; 92 | shortcut: any; 93 | icon: any; 94 | } 95 | }; 96 | 97 | 98 | const RenderResults = function () { 99 | const { results } = useMatches(); 100 | 101 | return ; 102 | }; 103 | 104 | const ResultItem = ({ item, active }: ItemProps) => { 105 | if (typeof item === 'string') return {item}; 106 | return ( 107 | 108 |
109 | <> 110 | {item.icon && item.icon} 111 | 112 | {item.name} 113 | 114 | 115 |
116 | {item.shortcut?.length ? ( 117 |
118 | {item.shortcut.map((shortcut: string) => ( 119 | 120 | {shortcut} 121 | 122 | ))} 123 |
124 | ) : null} 125 |
126 | ); 127 | }; 128 | 129 | const StyledKBarPositioner = styled(KBarPositioner)` 130 | position: fixed; 131 | display: flex; 132 | align-items: flex-start; 133 | justify-content: center; 134 | width: 100%; 135 | inset: 0px; 136 | padding: 14vh 16px 16px; 137 | background: ${props => transparentize(0.5, 'rgb(41, 41, 53)')}; 138 | box-sizing: border-box; 139 | `; 140 | 141 | const StyledKBarSearch = styled(KBarSearch)` 142 | padding: 12px 16px; 143 | font-size: 16px; 144 | width: 100%; 145 | box-sizing: border-box; 146 | outline: none; 147 | border: none; 148 | margin: 0; 149 | background: ${props => transparentize(0.1, props.theme.colors.switchBg)}; 150 | color: #f2f2f2; 151 | `; 152 | 153 | const StyledKBarGroupName = styled.div` 154 | padding: 8px 16px; 155 | font-size: 11px; 156 | font-weight: bold; 157 | color: ${props => props.theme.colors.purple}; 158 | text-transform: uppercase; 159 | letter-spacing: 1px; 160 | background: ${props => transparentize(0.1, props.theme.colors.switchBg)}; 161 | ` 162 | 163 | const StyledKBarResults = styled.div<{ setActive: boolean }>` 164 | padding: 12px 16px; 165 | background: ${(props) => props.setActive ? props.theme.colors.purple : transparentize(0.1, props.theme.colors.switchBg)}; 166 | display: flex; 167 | align-items: center; 168 | justify-content: space-between; 169 | margin: 0; 170 | cursor: pointer; 171 | color: ${(props) => props.setActive ? '#f2f2f2' : transparentize(0.2, props.theme.colors.text)}; 172 | ` 173 | 174 | const StyledKbd = styled.kbd` 175 | padding: 4px 8px; 176 | text-transform: uppercase; 177 | color: #8f9ba8; 178 | background: rgba(255, 255, 255, .1); 179 | `; 180 | 181 | const StyledActionRow = styled.div` 182 | display: flex; 183 | flex-direction: column; 184 | `; 185 | 186 | const animatorStyle = { 187 | maxWidth: '600px', 188 | width: '100%', 189 | color: '#f2f2f2', 190 | borderRadius: '8px', 191 | overflow: 'hidden', 192 | border: '1px solid #646cff', 193 | backdropFilter: 'blur(2px)' 194 | } 195 | 196 | const groupNameStyle = { 197 | padding: '8px 16px', 198 | fontSize: '10px', 199 | textTransform: 'uppercase', 200 | letterSpacing: '1px', 201 | background: 'rgba(255, 255, 255, 0.05)' 202 | } 203 | 204 | const iconStyle = { 205 | fontSize: '20px', 206 | position: 'relative', 207 | top: '-2px' 208 | } 209 | 210 | const shortcutStyle = { 211 | display: 'grid', 212 | gridAutoFlow: 'column', 213 | gap: '4px' 214 | } 215 | 216 | const actionStyle = { 217 | display: 'flex', 218 | gap: '8px', 219 | alignItems: 'center' 220 | } 221 | 222 | export default CommandBar -------------------------------------------------------------------------------- /src/Components/CommandButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { useKBar } from 'kbar' 2 | import { CButton } from './styles' 3 | import { TbSearch, TbCommand } from 'react-icons/tb' 4 | 5 | interface Props { 6 | currentTheme: string 7 | } 8 | 9 | function CommandButton({ currentTheme }: Props) { 10 | return ( 11 | 12 | 13 | 14 | Buscar 15 | 16 | 17 | 18 | K 19 | 20 | ) 21 | } 22 | 23 | export default CommandButton -------------------------------------------------------------------------------- /src/Components/CommandButton/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { shade, lighten } from 'polished' 3 | 4 | type Props = { 5 | activeTheme: string 6 | } 7 | 8 | export const CButton = styled.button` 9 | display: flex; 10 | align-items: center; 11 | background: ${props => props.theme.colors.primary}; 12 | border-radius: 8px; 13 | border: none; 14 | border: 1px solid transparent; 15 | color: ${props => props.theme.colors.secundary}; 16 | margin: 0 1em; 17 | padding: .70em 1em; 18 | cursor: pointer; 19 | transition: .25s; 20 | &:hover { 21 | border: 1px solid ${props => props.theme.colors.purple}; 22 | } 23 | .command_search { 24 | justify-content: center; 25 | align-items: center; 26 | display: flex; 27 | } 28 | &:hover .command_search{ 29 | color: ${({ theme, activeTheme }) => activeTheme == 'light' ? shade(0.3, theme.colors.secundary) : lighten(0.3, theme.colors.secundary)} 30 | } 31 | .command_search_text { 32 | margin-left: 6px; 33 | } 34 | .command_icon { 35 | margin-left: 12px; 36 | padding: 0.2em 0.4em; 37 | border-radius: 4px; 38 | display: flex; 39 | justify-content: space-between; 40 | align-items: center; 41 | border: 1px solid ${props => props.theme.colors.border} 42 | } 43 | .command_icon span{ 44 | font-size: 12px; 45 | } 46 | `; -------------------------------------------------------------------------------- /src/Components/SkeletonLesson/SkeletonLesson.tsx: -------------------------------------------------------------------------------- 1 | import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; 2 | import 'react-loading-skeleton/dist/skeleton.css'; 3 | import { useTheme } from 'styled-components'; 4 | import { SkeletonContainer } from './index'; 5 | 6 | function SkeletonLesson() { 7 | const theme = useTheme(); 8 | 9 | return ( 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default SkeletonLesson -------------------------------------------------------------------------------- /src/Components/SkeletonLesson/index.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const SkeletonContainer = styled.div` 4 | display: flex; 5 | width: 100%; 6 | flex-direction: column; 7 | padding: 0 10%; 8 | gap: 40px; 9 | `; -------------------------------------------------------------------------------- /src/Components/VideoBlock/VideoBlock.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { CodeProps } from 'react-markdown/lib/ast-to-react'; 3 | import { StyledIframeVideo, StyledVideoContainer } from './styles'; 4 | 5 | const VideoBlock: FC = function ({ 6 | node, 7 | inline, 8 | className, 9 | children, 10 | ...props 11 | }) { 12 | return ( 13 | 14 | 15 | 16 | ) 17 | }; 18 | 19 | export default VideoBlock; -------------------------------------------------------------------------------- /src/Components/VideoBlock/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const StyledVideoContainer = styled.div` 4 | margin: 50px 0; 5 | min-height: 480px; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | overflow: hidden; 10 | width: 100%; 11 | ` 12 | 13 | export const StyledIframeVideo = styled.iframe` 14 | border-radius: 8px; 15 | width: 100%; 16 | max-width: 840px; 17 | height: 100%; 18 | `; -------------------------------------------------------------------------------- /src/Utils/useLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, Dispatch, SetStateAction } from 'react'; 2 | 3 | type Response = [ 4 | T, 5 | Dispatch>, 6 | ]; 7 | 8 | function useLocalStorage(key: string, initialState: T): Response { 9 | const [state, setState] = useState(() => { 10 | const storageValue = localStorage.getItem(key); 11 | 12 | if (storageValue) { 13 | return JSON.parse(storageValue); 14 | } else { 15 | return initialState; 16 | } 17 | }); 18 | 19 | useEffect(() => { 20 | localStorage.setItem(key, JSON.stringify(state)); 21 | }, [key, state]); 22 | 23 | return [state, setState]; 24 | } 25 | 26 | export default useLocalStorage; -------------------------------------------------------------------------------- /src/Utils/useUpdateLocalStorage.ts: -------------------------------------------------------------------------------- 1 | function useUpdateLocalStorage (key: string, item: object) { 2 | const storageValue = localStorage.getItem(key); 3 | const newStorageValue = JSON.stringify(item) 4 | 5 | if(storageValue != newStorageValue) { 6 | localStorage.setItem(key, JSON.stringify(newStorageValue)); 7 | } 8 | } 9 | 10 | export default useUpdateLocalStorage; -------------------------------------------------------------------------------- /src/assets/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsrocha-dev/javascript-adventures/9450f45ac9e110d9c3ab5793e1bcbfc44f0c8dcd/src/assets/logo.webp -------------------------------------------------------------------------------- /src/data/lessons.ts: -------------------------------------------------------------------------------- 1 | const lessons = [ 2 | { 3 | title: 'Uma nova maneira de pensar em javascript', 4 | slug: 'uma-nova-maneira-de-pensar-em-javascript', 5 | tags: 'pensar modelo mental', 6 | }, 7 | { 8 | title: 'Uma viagem pelo universo chamado JavaScript', 9 | slug: 'uma-viagem-pelo-universo-chamado-javascript', 10 | tags: 'universo typeof variaveis', 11 | } 12 | ] 13 | 14 | export { lessons } 15 | -------------------------------------------------------------------------------- /src/layouts/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react'; 2 | import { TbMoon, TbSun } from 'react-icons/tb'; 3 | import Switch from 'react-switch'; 4 | import { ThemeContext } from 'styled-components'; 5 | import CommandButton from '../../Components/CommandButton'; 6 | import { Container } from './styles'; 7 | 8 | interface Props { 9 | toggleTheme(): void 10 | } 11 | 12 | const Header = ({ toggleTheme }: Props) => { 13 | const { colors, title } = useContext(ThemeContext) 14 | const [headerScrolled, setHeaderScrolled ] = useState(false) 15 | 16 | useEffect(() => { 17 | window.addEventListener('scroll', handleScroll) 18 | }, []) 19 | 20 | function handleScroll() { 21 | const scrollTop = window.pageYOffset 22 | console.log(scrollTop) 23 | if (scrollTop > 50) { 24 | setHeaderScrolled(true) 25 | } else { 26 | setHeaderScrolled(false) 27 | } 28 | } 29 | 30 | return ( 31 | 32 | 33 | 47 | 48 | } 49 | uncheckedHandleIcon={ 50 |
58 | 59 |
} 60 | height={22} 61 | width={40} 62 | handleDiameter={19} 63 | offColor={colors.switchBg} 64 | offHandleColor={colors.primary} 65 | onColor={colors.switchBg} 66 | onHandleColor={colors.primary} 67 | /> 68 |
69 | ) 70 | } 71 | 72 | export default Header -------------------------------------------------------------------------------- /src/layouts/Header/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | type Props = { 4 | hasScrolled: boolean, 5 | currentTheme: string 6 | } 7 | 8 | 9 | function checkTheme(theme: string) { 10 | return theme === 'light' ? 'rgba(255, 255, 255, 0.4)' : 'rgba(35, 35, 35, 0.4)' 11 | } 12 | 13 | export const Container = styled.div` 14 | width: 100%; 15 | height: 75px; 16 | color: ${props => props.theme.colors.secundary}; 17 | display: flex; 18 | align-items: center; 19 | justify-content: space-between; 20 | padding: 0 30px; 21 | /* z-index: 100; */ 22 | transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); 23 | background: ${ 24 | ({ theme, hasScrolled, currentTheme }) => hasScrolled 25 | ? checkTheme(currentTheme) 26 | : theme.colors.background 27 | }; 28 | position: ${({ theme, hasScrolled }) => hasScrolled ? 'fixed' : 'initial'}; 29 | backdrop-filter: blur(20px); 30 | `; -------------------------------------------------------------------------------- /src/layouts/Main/index.tsx: -------------------------------------------------------------------------------- 1 | import { MainContainer } from './styles' 2 | 3 | interface Props { 4 | children: JSX.Element, 5 | } 6 | 7 | const Main = ({ children }: Props) => { 8 | return ( 9 | 10 | {children} 11 | 12 | ) 13 | } 14 | 15 | export default Main -------------------------------------------------------------------------------- /src/layouts/Main/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const MainContainer = styled.div` 4 | display: flex; 5 | flex-direction: row; 6 | align-items: stretch; 7 | height: 100%; 8 | max-width: 100%; 9 | `; 10 | 11 | export const Content = styled.div` 12 | display: flex; 13 | width: 100%; 14 | flex-direction: column; 15 | gap: 40px; 16 | ` -------------------------------------------------------------------------------- /src/layouts/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useLocation } from 'react-router-dom'; 2 | import logoUrl from '../../assets/logo.webp'; 3 | import { SidebarContainer, SidebarGroup, SidebarItems, SidebarTitle } from './styles'; 4 | 5 | import { useEffect, useState } from 'react'; 6 | import { lessons } from '../../data/lessons'; 7 | 8 | type MenuItens = { 9 | slug: string; 10 | title: string; 11 | tags: string; 12 | active: boolean 13 | } 14 | 15 | const Sidebar = () => { 16 | const [menu, updateMenu] = useState( 17 | [], 18 | ); 19 | const location = useLocation() 20 | const currentSlug = location.pathname.split('/')[2] 21 | 22 | useEffect(() => { 23 | updateMenu(() => { return lessons.map(l => ({ slug: l.slug, title: l.title, tags: l.tags, active: currentSlug === l.slug ? true : false }))}) 24 | }, [location]) 25 | 26 | return ( 27 | 28 | 29 | 30 | JavaScript Adventures 31 | 32 | 33 |

Índice

34 | 35 | { 36 | menu.map((l) => { l.title }) 37 | } 38 | 39 |
40 |
41 | ) 42 | } 43 | 44 | export default Sidebar -------------------------------------------------------------------------------- /src/layouts/Sidebar/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { down } from '../../styles/MediaQueries/queries'; 3 | 4 | export const SidebarContainer = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | background: ${props => props.theme.colors.primary}; 8 | order: -1; 9 | flex: 0 0 270px; 10 | padding: 20px 30px; 11 | min-height: 100vh; 12 | transition: 0.25s ease-out; 13 | transform: translateX(0); 14 | ${down('lg')} { 15 | transition-timing-function: ease-out; 16 | transform: translateX(-100%); 17 | position: fixed; 18 | } 19 | `; 20 | 21 | export const SidebarTitle = styled.div` 22 | height: 40px; 23 | display: flex; 24 | align-items: center; 25 | font-size: 15px; 26 | /* font-weight: 600; */ 27 | transition: opacity .25s; 28 | img { 29 | width: 30px; 30 | margin-right: 10px; 31 | } 32 | `; 33 | 34 | export const SidebarGroup = styled.div` 35 | border-top: 1px solid ${props => props.theme.colors.border}; 36 | height: 100%; 37 | margin-top: 20px; 38 | padding-top: 25px; 39 | h2 { 40 | font-weight: bold; 41 | font-size: 14px; 42 | } 43 | ` 44 | 45 | export const SidebarItems = styled.div` 46 | margin: 15px 0; 47 | a { 48 | display: block; 49 | margin: 15px 0; 50 | color: ${props => props.theme.colors.secundary}; 51 | text-decoration: none; 52 | transition: .25s; 53 | } 54 | a:hover { 55 | color: ${props => props.theme.colors.text}; 56 | } 57 | .active { 58 | color: ${props => props.theme.colors.purple}; 59 | } 60 | ` 61 | 62 | 63 | // #6970ff 64 | // #9297fb 65 | // rgb(146,151,251) -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import App from './App'; 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | 9 | 10 | 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /src/markdown/uma-nova-maneira-de-pensar-em-javascript.md: -------------------------------------------------------------------------------- 1 | # Uma nova maneira de pensar em JavaScript 2 | 3 | 4 | 5 | ## 📓 Transcrição da aula 6 | 7 | Você já parou para pensar no porquê de você pensar da forma como pensa quando está codificando? 8 | 9 | Já considerou que pode ter aprendido algo da maneira errada e estar levando isso para a sua vida como uma verdade? 10 | 11 | Perguntas como essas são meio pretensiosas, eu reconheço! Contudo, temos que considerar que essa é uma possibilidade real e que, identificando essa possível realidade, podemos melhorar a forma que pensamos ao construir nosso código. 12 | 13 | Vamos seguindo tópico por tópico. Abra somente conforme você progredir no vídeo! 14 | 15 | ### Dando a partida 16 | 17 | Sem recorrer a ferramentas, leia o seguinte código e tente identificar os valores de `n1` e `n2` 18 | 19 | ```javascript 20 | let n1 = 6; 21 | let n2 = n1; 22 | n1 = 0; 23 | ``` 24 | 25 | Relaxe: a intenção aqui não é te ensinar sobre variáveis no JavaScript. Eu sei que você já manja disso! Quero somente que perceba e reflita sobre como você _pensa no código._ 26 | Como segundo exercício de pensamento, quero que você releia o código acima. Contudo, desta vez com a intenção de realmente ter a certeza do resultado. 27 | Preste bastante atenção! A intenção disso tudo é muito importante. E preste atenção ao que acontece na sua mente ao pensar! 28 | Talvez você tenha construído um monólogo como este: 29 | 30 | - `let n1 = 6;` 31 | - Declarar a variável chamada `n1` e definir como 6 32 | - `let n2 = n1;` 33 | - Declarar a variável chamada n2 como igual a n1 34 | - Então, se `n1` é igual a 6, logo `n2` também é igual a 6 35 | - `n1 = 0;` 36 | - Mudar o valor da variável n1 para 0 37 | - Então `n1` agora é 0, e `n2` é 6 38 | Enfim, talvez a sua conversa interior tenha sido diferente, ou talvez você tenha usado termos como atribuir valor tal em vez de definir, ou até algo diferente e até em um resultado diferente. Quem sabe? 39 | O que temos que observar é que essa conversa interior não captura realmente o que acontece! Se você falou `definir/atribuir n2 como n1`, somos levados a pensar seguinte: o que significa definir uma variável? 40 | Descobriremos que, em cada conceito fundamental da programação, tais como variáveis ou mesmo operações sobre variáveis (como é o caso de definição de valores), há conjuntos de analogias enraizadas que podemos ter associado em nossas mentes. 41 | Um tipo de analogia muito comum é aquela de que variáveis são como caixas que guardam as coisas. Mesmo que você não imagine mais caixas quando vê uma variável, elas podem estar se comportando como tal em sua imaginação, e é esse tipo de coisa que consideramos como modelos mentais. 42 | Infelizmente, às vezes, nossos modelos mentais estão errados, por talvez termos acompanhado algum tutorial que lemos no início da carreira, o qual pode ter sacrificado a precisão conceitual ao explicar algo em prol de torná-lo mais facilmente explicável naquele momento. 43 | Pode ainda acontecer de trazermos comportamentos e pensamentos vindos de outras linguagens que aprendemos anteriormente ao JavaScript, e esse é um problema que iremos juntos corrigir nesse mini-treinamento. 44 | 45 | ### Aquecendo os motores 46 | 47 | Existe um livro chamado ["Rápido e devagar: Duas formas de pensar”](https://amzn.to/3JeCZEE) que explora dois sistemas diferentes que nós usamos quando pensamos. 48 | Geralmente e quase sempre usamos o sistema "rápido”, que é muito bom em padrões e reações institivas, que, alías, são necessárias para nossa sobreviência. Um bom exemplo disso é a nossa capacidade de andar sem cair. Contudo, é preciso reconhecer: ele não é muito bom em planejamento. 49 | O nosso sistema "lento”, por outro lado é responsável pelo raciocínio estilo passo a passo, mais complexo. Ele é o que nos permite planejar coisas futuras, argumentar e resolver, por exemplo, problemas matemáticos. 50 | Contudo, usar esse sistema "lento” é desgastante mentalmente, e é por isso que tendemos a sempre optar pelo sistema "rápido”, inclusive quando lidamos com tarefas intelectivas como programar. 51 | Vamos ao seguinte exercício: 52 | Imagine que você está atolado em trabalho e quer identificar rapidamente o que essa função faz. Dê uma olhada nela abaixo: 53 | 54 | ```javascript 55 | function duplicateSpreadsheet(original) { 56 | if (original.hasPendingChanges) { 57 | throw new Error('Salve o arquivo antes de duplicar.'); 58 | } 59 | let copy = { 60 | created: Date.now(), 61 | author: original.author, 62 | cells: original.cells, 63 | metadata: original.metadata 64 | }; 65 | copy.metadata.title = 'Copia de' + original.metadata.title; 66 | return copy; 67 | } 68 | ``` 69 | 70 | Você provavelmente percebeu o seguinte: 71 | 72 | - Esta função duplica uma planilha. 73 | - Ela lança um erro se a planilha original não estiver salva. 74 | - Ela acrescenta "Cópia de” ao título da nova planilha. 75 | - Porém… 76 | O que você pode não ter notado (e parabéns se notou), é que essa função altera de maneira acidental o título da planilha original. Acredite, esse tipo de bug acontece todos sempre durante o dia a dia da pessoa que programa. 77 | Quando usamos o sistema "rápido”, tentamos adivinhar o que o código faz com base em sua estrutura geral, convenções, nomenclaturas e comentários. Contudo, ao usar o sistema "lento”, refazemos mentalmente o passo a passo do que o código faz, o que é mais cansativo e demorado. 78 | É por isso que ter um modelo mental preciso é tão importante. Simular um computador em sua cabeça é difícil e, quando você precisa usar o sistema de pensamento "lento”, seu modelo mental é tudo em que você pode confiar. 79 | Se, porém, o seu modelo mental estiver errado, você entenderá fundamentalmente mal o que esperar do seu código e todo o seu esforço terá sido em vão. 80 | Não se preocupe se você não conseguir encontrar o bug - isso significa apenas que você aproveitará ao máximo este curso! 81 | 82 | ### Trocando de marcha 83 | 84 | Espero que você tenha percebido o quão importante é o modo como pensamos quando vamos codar! Na próxima aula, começaremos a construir nossa nova maneira de pensar. Começaremos com alguns conceitos dos mais fundamentais do JavaScript, que são _valores_ e _expressões._ 85 | 86 | Estaremos juntos nesta jornada, Aventureiros e Aventureiras! 🧗 87 | 88 | Créditos: [Dan Abramov](https://overreacted.io/) | Adaptação: [Frank Rocha](https://www.frankrocha.dev/) 89 | -------------------------------------------------------------------------------- /src/markdown/uma-viagem-pelo-universo-chamado-javascript.md: -------------------------------------------------------------------------------- 1 | # Segunda lição 2 | -------------------------------------------------------------------------------- /src/pages/MarkdownLessons.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import rehypeRaw from "rehype-raw"; 4 | import CodeBlock from '../Components/CodeBlock/CodeBlock'; 5 | import VideoBlock from '../Components/VideoBlock/VideoBlock'; 6 | 7 | import { StyledMarkdown } from './styles'; 8 | 9 | const MarkdownLessons = () => { 10 | const location = useLocation() 11 | const currentSlug = location.pathname.split('/')[2] 12 | const [lesson, setLesson] = useState('') 13 | 14 | useEffect(() => { 15 | import(`../markdown/${currentSlug}.md`).then((res) => { 16 | setLesson(res.html) 17 | }).catch((e) => alert('nao tem essa licao')) 18 | }, [location]); 19 | 20 | return ( 21 | 28 | {lesson} 29 | 30 | ) 31 | } 32 | 33 | export default MarkdownLessons -------------------------------------------------------------------------------- /src/pages/styles.ts: -------------------------------------------------------------------------------- 1 | import ReactMarkdown from 'react-markdown'; 2 | import { Link } from 'react-router-dom'; 3 | import styled from 'styled-components'; 4 | 5 | export const StyledMarkdown = styled(ReactMarkdown)` 6 | display: flex; 7 | width: 100%; 8 | flex-direction: column; 9 | padding: 0 10%; 10 | font-size: 16px; 11 | color: ${props => props.theme.colors.text}; 12 | line-height: 30px; 13 | p { 14 | text-align: justify; 15 | width: 100%; 16 | } 17 | table, 18 | tr, 19 | td, 20 | th { 21 | border: 1px solid #555; 22 | } 23 | h1, 24 | h2, 25 | h3 { 26 | margin-top: 25px; 27 | } 28 | pre { 29 | margin-top: 5px; 30 | } 31 | h2, 32 | h3 { 33 | color: ${props => props.theme.colors.purple}; 34 | } 35 | summary { 36 | font-size: 16px; 37 | } 38 | details { 39 | font-size: 15px; 40 | line-height: 20px; 41 | } 42 | blockquote { 43 | padding: 0 1em; 44 | color: #6a737d; 45 | border-left: 0.25em solid #dfe2e5; 46 | } 47 | ul { 48 | margin-left: 30px; 49 | } 50 | ul li { 51 | /* padding: 5px 0; */ 52 | } 53 | a { 54 | text-decoration: none; 55 | color: ${props => props.theme.colors.secundary}; 56 | } 57 | a:hover { 58 | text-decoration: none; 59 | color: #fff; 60 | } 61 | `; 62 | -------------------------------------------------------------------------------- /src/styles/MediaQueries/queries.ts: -------------------------------------------------------------------------------- 1 | import { up, down, between, only, createTheme } from 'styled-breakpoints'; 2 | 3 | const queries = createTheme({ 4 | sm: '576px', 5 | md: '768px', 6 | lg: '992px', 7 | xl: '1200px', 8 | }); 9 | 10 | export { queries, up, down, between, only } -------------------------------------------------------------------------------- /src/styles/global.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | 3 | export default createGlobalStyle` 4 | * { 5 | margin: 0; 6 | padding: 0; 7 | outline: 0; 8 | box-sizing: border-box; 9 | -webkit-box-sizing: border-box; 10 | -moz-box-sizing: border-box; 11 | } 12 | body { 13 | background: ${props => props.theme.colors.background}; 14 | font-size: 14px; 15 | color: ${props => props.theme.colors.text}; 16 | font-family: sans-serif; 17 | } 18 | 19 | body html #root { 20 | height: 100%; 21 | } 22 | `; -------------------------------------------------------------------------------- /src/styles/styled.d.ts: -------------------------------------------------------------------------------- 1 | import 'styled-components' 2 | declare module 'styled-components' { 3 | export interface DefaultTheme { 4 | title: string, 5 | 6 | colors: { 7 | primary: string, 8 | secundary: string, 9 | 10 | purple: string, 11 | 12 | background: string, 13 | text: string, 14 | 15 | border: string, 16 | 17 | switchBg: string 18 | 19 | skeletonBase: string, 20 | skeletonHighlightColor: string 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/styles/themes/dark.ts: -------------------------------------------------------------------------------- 1 | import { shade } from 'polished' 2 | 3 | export default { 4 | title: 'dark', 5 | 6 | colors: { 7 | primary: '#1a1a1a', 8 | secundary: '#898989', 9 | 10 | purple: '#646cff', 11 | 12 | background: '#232323', 13 | text: '#e3e3e3', 14 | 15 | border: shade(0.6, '#898989'), 16 | 17 | link: '#898989', 18 | linkHover: '#646cff', 19 | 20 | switchBg: '#3a3a3a', 21 | 22 | skeletonBase: '#202020', 23 | skeletonHighlightColor: '#444' 24 | } 25 | } -------------------------------------------------------------------------------- /src/styles/themes/light.ts: -------------------------------------------------------------------------------- 1 | import { lighten, shade } from 'polished' 2 | 3 | export default { 4 | title: 'light', 5 | 6 | colors: { 7 | primary: '#f9f9f9', 8 | secundary: '#747474', 9 | 10 | purple: '#646cff', 11 | 12 | background: '#fff', 13 | text: '#213647', 14 | 15 | border: lighten(0.45, '#747474'), 16 | 17 | link: '#747474', 18 | linkHover: '#646cff', 19 | 20 | switchBg: '#f1f1f1', 21 | 22 | skeletonBase: '#f3f3f3', 23 | skeletonHighlightColor: '#f1f1f1' 24 | } 25 | } -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "files": [ 21 | "src/styles/styled.d.ts" 22 | ], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] 3 | } 4 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import { defineConfig } from 'vite'; 3 | import { Mode, plugin } from 'vite-plugin-markdown'; 4 | 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [react(), plugin({ mode: [Mode.HTML] })] 9 | }) 10 | -------------------------------------------------------------------------------- /vite.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md' { 2 | // "unknown" would be more detailed depends on how you structure frontmatter 3 | const attributes: Record; 4 | 5 | // When "Mode.TOC" is requested 6 | const toc: { level: string, content: string }[]; 7 | 8 | // When "Mode.HTML" is requested 9 | const html: string; 10 | 11 | // When "Mode.React" is requested. VFC could take a generic like React.VFC<{ MyComponent: TypeOfMyComponent }> 12 | import React from 'react'; 13 | const ReactComponent: React.VFC; 14 | 15 | // Modify below per your usage 16 | export { attributes, toc, html, ReactComponent }; 17 | } --------------------------------------------------------------------------------