├── README.md
├── src
├── vite-env.d.ts
├── markdown
│ ├── uma-viagem-pelo-universo-chamado-javascript.md
│ └── uma-nova-maneira-de-pensar-em-javascript.md
├── assets
│ └── logo.webp
├── Components
│ ├── SkeletonLesson
│ │ ├── index.ts
│ │ └── SkeletonLesson.tsx
│ ├── VideoBlock
│ │ ├── styles.ts
│ │ └── VideoBlock.tsx
│ ├── CommandButton
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── CodeBlock
│ │ └── CodeBlock.tsx
│ └── CommandBar
│ │ └── index.tsx
├── styles
│ ├── MediaQueries
│ │ └── queries.ts
│ ├── styled.d.ts
│ ├── themes
│ │ ├── dark.ts
│ │ └── light.ts
│ └── global.ts
├── layouts
│ ├── Main
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Header
│ │ ├── styles.ts
│ │ └── index.tsx
│ └── Sidebar
│ │ ├── index.tsx
│ │ └── styles.ts
├── Utils
│ ├── useUpdateLocalStorage.ts
│ └── useLocalStorage.ts
├── main.tsx
├── data
│ └── lessons.ts
├── pages
│ ├── MarkdownLessons.tsx
│ └── styles.ts
└── App.tsx
├── vercel.json
├── public
└── logo.webp
├── tsconfig.node.json
├── vite.config.ts
├── .gitignore
├── index.html
├── vite.d.ts
├── tsconfig.json
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | # javascript-adventures
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
--------------------------------------------------------------------------------
/src/markdown/uma-viagem-pelo-universo-chamado-javascript.md:
--------------------------------------------------------------------------------
1 | # Segunda lição
2 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]
3 | }
4 |
--------------------------------------------------------------------------------
/public/logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsrocha-dev/javascript-adventures/HEAD/public/logo.webp
--------------------------------------------------------------------------------
/src/assets/logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsrocha-dev/javascript-adventures/HEAD/src/assets/logo.webp
--------------------------------------------------------------------------------
/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 | `;
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 | `
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | JavaScript Adventures - Treinamento
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/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/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/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/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/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 | `;
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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;
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/markdown/uma-nova-maneira-de-pensar-em-javascript.md:
--------------------------------------------------------------------------------
1 | # Uma nova maneira de pensar em JavaScript
2 |
3 | VIDEO
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/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
--------------------------------------------------------------------------------