├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── components ├── BoxModelDiagram.tsx ├── Disqus.tsx ├── EditorConsole.tsx ├── FileTreeDiagram.tsx ├── SectionSlideshow.tsx ├── SpectacleSlideshow.tsx └── SyntaxDiagram.tsx ├── custom.d.ts ├── examples └── files │ └── html_and_css │ ├── blog._css │ ├── blog.html │ ├── portfolio._css │ ├── portfolio.html │ ├── todo._css │ └── todo.html ├── guidebook.d.ts ├── images └── logo.svg ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── box_model_diagram.tsx ├── config.json ├── html_and_css.mdx ├── html_and_css │ ├── accessibility.mdx │ ├── advanced_selectors.mdx │ ├── box_model.mdx │ ├── browser_dev_tools.mdx │ ├── config.json │ ├── css_variables.mdx │ ├── elements.mdx │ ├── flexbox.mdx │ ├── limitations.mdx │ ├── projects.mdx │ ├── projects │ │ ├── blog_project.mdx │ │ ├── blog_project │ │ │ └── solution.mdx │ │ ├── config.json │ │ ├── portfolio_project.mdx │ │ ├── portfolio_project │ │ │ └── solution.mdx │ │ ├── todo_project.mdx │ │ └── todo_project │ │ │ └── solution.mdx │ ├── resources.mdx │ ├── responsive_design.mdx │ ├── styling.mdx │ └── units.mdx ├── index.mdx ├── intro_slides.js ├── playgrounds.js ├── react.mdx ├── schedule.tsx ├── slides.js ├── syntax_diagram.tsx ├── typescript.mdx └── typescript │ ├── config.json │ ├── context.mdx │ ├── dom.mdx │ ├── equality.mdx │ ├── events.mdx │ ├── immutability.mdx │ ├── imports_and_exports.mdx │ ├── projects.mdx │ ├── projects │ ├── config.json │ ├── drawing.mdx │ └── todo_list.mdx │ ├── resources.mdx │ ├── tools.mdx │ ├── type_declarations.mdx │ └── type_refinement.mdx ├── public └── static │ ├── devin.jpg │ ├── favicon.ico │ ├── favicon.png │ ├── logo.png │ ├── logo@2x.png │ ├── preview.png │ └── quotes.png ├── slides ├── index.mdx └── intro.mdx ├── styles ├── slidesTheme.ts └── theme.ts ├── tsconfig.json ├── utils ├── search.js └── serialize.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [ 4 | [ 5 | "styled-components", 6 | { 7 | "ssr": true, 8 | "displayName": true, 9 | "preprocess": false 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Project dependencies 4 | node_modules 5 | yarn-error.log 6 | .next 7 | 8 | # Build directory 9 | /out 10 | 11 | # Editors 12 | .vscode 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Devin Abbott 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Development Express 2 | 3 | A guide to web development: https://www.webdev.express 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn 9 | ``` 10 | 11 | ## Running Dev Server 12 | 13 | ```bash 14 | yarn dev 15 | ``` 16 | 17 | ## Building and Running Production Server 18 | 19 | ```bash 20 | yarn build 21 | yarn start 22 | ``` 23 | 24 | ## Contributing 25 | 26 | Contributions are welcome! (It might take me some time to get around to reviewing however) 27 | 28 | ## License 29 | 30 | MIT, Copyright (c) 2021 Devin Abbott 31 | 32 | ## Author 33 | 34 | Devin Abbott, [@dvnabbott](http://twitter.com/dvnabbott) 35 | -------------------------------------------------------------------------------- /components/BoxModelDiagram.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import React, { 3 | CSSProperties, 4 | useEffect, 5 | useMemo, 6 | useRef, 7 | useState, 8 | } from 'react' 9 | import styled, { useTheme } from 'styled-components' 10 | import SyntaxDiagram, { Token } from '../components/SyntaxDiagram' 11 | import { serialize } from '../utils/serialize' 12 | 13 | const colors = { 14 | margin: '#f8cb9c', 15 | border: '#fddf86' /* '#feedbb' */, 16 | padding: '#c2ddb6', 17 | content: '#9fc4e7', 18 | } 19 | 20 | const Preview = styled.div(({ theme }) => ({ 21 | ...theme.textStyles.body, 22 | background: 'rgba(0,0,0,0.04)', 23 | height: '100%', 24 | })) 25 | 26 | const Box = styled.div<{ value: string }>` 27 | pointer-events: none; 28 | box-sizing: content-box; 29 | background-color: #ddd; 30 | ${(props) => props.value}; 31 | ` 32 | 33 | export type CSSDeclaration = [string, string] 34 | 35 | type BoxSizing = 'border-box' | 'content-box' 36 | 37 | type MeasuredBox = { 38 | marginTop: number 39 | marginRight: number 40 | marginBottom: number 41 | marginLeft: number 42 | paddingTop: number 43 | paddingRight: number 44 | paddingBottom: number 45 | paddingLeft: number 46 | borderTopWidth: number 47 | borderRightWidth: number 48 | borderBottomWidth: number 49 | borderLeftWidth: number 50 | width: number 51 | height: number 52 | boxSizing: BoxSizing 53 | } 54 | 55 | type BoxModelComponent = 'margin' | 'padding' | 'border' | 'content' 56 | type FlexComponent = 'justify-content' | 'align-items' | 'flex-direction' 57 | 58 | type Axis = 'row' | 'column' 59 | 60 | function AnnotatedOverlay({ 61 | width, 62 | height, 63 | axis, 64 | stroke, 65 | }: { 66 | width: number 67 | height: number 68 | axis: Axis 69 | stroke: string 70 | }) { 71 | const midpoint = { 72 | x: width / 2, 73 | y: height / 2, 74 | } 75 | 76 | return ( 77 |
87 | 91 | 92 | 102 | 103 | 104 | 105 | 106 | 118 | 119 |
120 | ) 121 | } 122 | 123 | type Tooltip = { 124 | content: React.ReactNode 125 | location: 'N' | 'E' | 'S' | 'W' 126 | color?: string 127 | } 128 | 129 | function MeasuredBoxDiagram({ 130 | measuredBox, 131 | highlight, 132 | onHighlight, 133 | tooltips, 134 | axis, 135 | crossAxis, 136 | }: { 137 | measuredBox: MeasuredBox 138 | highlight?: BoxModelComponent | FlexComponent 139 | onHighlight: ( 140 | component: BoxModelComponent | FlexComponent | undefined 141 | ) => void 142 | tooltips: Tooltip[] 143 | axis?: Axis 144 | crossAxis?: Axis 145 | }) { 146 | const highlightColors = { 147 | margin: highlight === 'margin' ? colors.margin : 'transparent', 148 | border: 149 | highlight === 'border' 150 | ? colors.border 151 | : highlight === 'content' && measuredBox.boxSizing === 'border-box' 152 | ? colors.content 153 | : 'transparent', 154 | padding: 155 | highlight === 'padding' 156 | ? colors.padding 157 | : highlight === 'content' && measuredBox.boxSizing === 'border-box' 158 | ? colors.content 159 | : 'transparent', 160 | content: highlight === 'content' ? colors.content : 'transparent', 161 | } 162 | 163 | const marginStyle: CSSProperties = { 164 | opacity: 0.7, 165 | width: 'fit-content', 166 | borderTop: `${measuredBox.marginTop}px solid ${highlightColors.margin}`, 167 | borderRight: `${measuredBox.marginRight}px solid ${highlightColors.margin}`, 168 | borderBottom: `${measuredBox.marginBottom}px solid ${highlightColors.margin}`, 169 | borderLeft: `${measuredBox.marginLeft}px solid ${highlightColors.margin}`, 170 | } 171 | 172 | const borderStyle: CSSProperties = { 173 | width: 'fit-content', 174 | borderTop: `${measuredBox.borderTopWidth}px solid ${highlightColors.border}`, 175 | borderRight: `${measuredBox.borderRightWidth}px solid ${highlightColors.border}`, 176 | borderBottom: `${measuredBox.borderBottomWidth}px solid ${highlightColors.border}`, 177 | borderLeft: `${measuredBox.borderLeftWidth}px solid ${highlightColors.border}`, 178 | } 179 | 180 | const paddingStyle: CSSProperties = { 181 | width: 'fit-content', 182 | borderTop: `${measuredBox.paddingTop}px solid ${highlightColors.padding}`, 183 | borderRight: `${measuredBox.paddingRight}px solid ${highlightColors.padding}`, 184 | borderBottom: `${measuredBox.paddingBottom}px solid ${highlightColors.padding}`, 185 | borderLeft: `${measuredBox.paddingLeft}px solid ${highlightColors.padding}`, 186 | } 187 | 188 | const fullHeight = 189 | measuredBox.marginTop + 190 | measuredBox.marginBottom + 191 | measuredBox.borderTopWidth + 192 | measuredBox.borderBottomWidth + 193 | measuredBox.paddingTop + 194 | measuredBox.paddingBottom + 195 | measuredBox.height 196 | 197 | const fullWidth = 198 | measuredBox.marginRight + 199 | measuredBox.marginLeft + 200 | measuredBox.borderRightWidth + 201 | measuredBox.borderLeftWidth + 202 | measuredBox.paddingRight + 203 | measuredBox.paddingLeft + 204 | measuredBox.width 205 | 206 | const contentStyle: CSSProperties = { 207 | width: 208 | measuredBox.boxSizing === 'content-box' 209 | ? `${measuredBox.width}px` 210 | : `${ 211 | measuredBox.width - 212 | (measuredBox.paddingLeft + 213 | measuredBox.paddingRight + 214 | measuredBox.borderLeftWidth + 215 | measuredBox.borderRightWidth) 216 | }px`, 217 | height: 218 | measuredBox.boxSizing === 'content-box' 219 | ? `${measuredBox.height}px` 220 | : `${ 221 | measuredBox.height - 222 | (measuredBox.paddingTop + 223 | measuredBox.paddingBottom + 224 | measuredBox.borderTopWidth + 225 | measuredBox.borderBottomWidth) 226 | }px`, 227 | background: highlightColors.content, 228 | } 229 | 230 | function handleHighlight( 231 | component: BoxModelComponent | undefined, 232 | event: React.MouseEvent 233 | ) { 234 | event.stopPropagation() 235 | onHighlight(component) 236 | } 237 | 238 | const theme = useTheme() 239 | 240 | const tooltipElements = tooltips.map((tooltip, i) => { 241 | return ( 242 |
260 |
267 |
279 | {tooltip.content} 280 |
281 |
282 |
283 | ) 284 | }) 285 | 286 | return ( 287 |
296 |
301 |
305 |
309 |
313 |
314 |
315 |
316 | {crossAxis && ( 317 | 323 | )} 324 | {axis && ( 325 | 331 | )} 332 | {tooltipElements} 333 |
334 | ) 335 | } 336 | 337 | interface Props { 338 | declarations: CSSDeclaration[] 339 | popOut?: boolean 340 | above?: React.ReactNode 341 | below?: React.ReactNode 342 | content?: React.ReactNode 343 | selector?: string 344 | } 345 | 346 | export default function BoxModelDiagram({ 347 | declarations, 348 | popOut, 349 | selector = '#my-box', 350 | above, 351 | below, 352 | content, 353 | }: Props) { 354 | const tokens: Token[] = useMemo( 355 | () => [ 356 | { 357 | id: 'Rule', 358 | value: [ 359 | { 360 | id: 'Selector', 361 | style: { color: '#2e9f74' }, 362 | value: [selector], 363 | }, 364 | ' {\n', 365 | ...declarations.flatMap(([key, value]) => [ 366 | ' ', 367 | { 368 | id: key, 369 | value: [ 370 | { editable: true, id: `property-${key}`, value: [key] }, 371 | ': ', 372 | { 373 | editable: true, 374 | id: `value-${key}`, 375 | value: [value], 376 | style: { color: '#c92c2c' }, 377 | }, 378 | ';', 379 | ], 380 | }, 381 | '\n', 382 | ]), 383 | '}', 384 | ], 385 | }, 386 | ], 387 | [declarations] 388 | ) 389 | 390 | const [selectedId, setSelectedId] = useState() 391 | const [css, setCss] = useState('') 392 | const targetRef = useRef(null) 393 | const [highlight, setHighlight] = useState< 394 | BoxModelComponent | FlexComponent | undefined 395 | >(undefined) 396 | 397 | useEffect(() => { 398 | const target = targetRef.current 399 | 400 | if (!target) return 401 | 402 | target.setAttribute('style', css) 403 | }, [css]) 404 | 405 | const boxRef = useRef(null) 406 | const [computedStyle, setComputedStyle] = useState< 407 | CSSStyleDeclaration | undefined 408 | >() 409 | 410 | function updateMeasurements() { 411 | if (!boxRef.current) return 412 | 413 | const style = window.getComputedStyle(boxRef.current) 414 | 415 | setComputedStyle(style) 416 | } 417 | 418 | const measuredBox: MeasuredBox | undefined = useMemo(() => { 419 | const style = computedStyle 420 | 421 | if (!style) return 422 | 423 | return { 424 | marginTop: parseFloat(style.marginTop), 425 | marginRight: parseFloat(style.marginRight), 426 | marginBottom: parseFloat(style.marginBottom), 427 | marginLeft: parseFloat(style.marginLeft), 428 | borderTopWidth: parseFloat(style.borderTopWidth), 429 | borderRightWidth: parseFloat(style.borderRightWidth), 430 | borderBottomWidth: parseFloat(style.borderBottomWidth), 431 | borderLeftWidth: parseFloat(style.borderLeftWidth), 432 | paddingTop: parseFloat(style.paddingTop), 433 | paddingRight: parseFloat(style.paddingRight), 434 | paddingBottom: parseFloat(style.paddingBottom), 435 | paddingLeft: parseFloat(style.paddingLeft), 436 | width: parseFloat(style.width), 437 | height: parseFloat(style.height), 438 | boxSizing: 439 | style.boxSizing === 'border-box' ? 'border-box' : 'content-box', 440 | } 441 | }, [computedStyle]) 442 | 443 | useEffect(() => { 444 | updateMeasurements() 445 | }, [css]) 446 | 447 | useEffect(() => { 448 | if (!boxRef.current) return 449 | 450 | let resizeObserver = new ResizeObserver(() => { 451 | updateMeasurements() 452 | }) 453 | 454 | resizeObserver.observe(boxRef.current) 455 | 456 | return () => { 457 | if (!boxRef.current) return 458 | 459 | resizeObserver.unobserve(boxRef.current) 460 | } 461 | }, []) 462 | 463 | const theme = useTheme() 464 | 465 | const tooltips: Tooltip[] = useMemo(() => { 466 | if (!computedStyle) return [] 467 | 468 | const direction = computedStyle.flexDirection 469 | 470 | if (highlight === 'flex-direction') { 471 | return [ 472 | { location: direction === 'row' ? 'W' : 'N', content: 'Main Axis' }, 473 | { 474 | location: direction === 'row' ? 'N' : 'W', 475 | content: 'Cross Axis', 476 | color: 'gray', 477 | }, 478 | ] 479 | } else if (highlight === 'justify-content') { 480 | return [ 481 | { location: direction === 'row' ? 'W' : 'N', content: 'Start' }, 482 | { location: direction === 'row' ? 'E' : 'S', content: 'End' }, 483 | ] 484 | } else if (highlight === 'align-items') { 485 | return [ 486 | { 487 | location: direction === 'row' ? 'N' : 'W', 488 | content: 'Start', 489 | color: 'gray', 490 | }, 491 | { 492 | location: direction === 'row' ? 'S' : 'E', 493 | content: 'End', 494 | color: 'gray', 495 | }, 496 | ] 497 | } else { 498 | return [] 499 | } 500 | }, [computedStyle, highlight]) 501 | 502 | // const isFlexComponent = 503 | // highlight === 'flex-direction' || 504 | // highlight === 'justify-content' || 505 | // highlight === 'align-items' 506 | 507 | return ( 508 | { 525 | setCss(text.slice(`${selector} {`.length + 2, -1)) 526 | }} 527 | onChangeActiveToken={(id) => { 528 | setSelectedId(id) 529 | 530 | if (id?.includes('margin')) { 531 | setHighlight('margin') 532 | } else if (id?.includes('padding')) { 533 | setHighlight('padding') 534 | } else if (id?.includes('border')) { 535 | setHighlight('border') 536 | } else if (id?.includes('width') || id?.includes('height')) { 537 | setHighlight('content') 538 | } else if (id?.includes('align-items')) { 539 | setHighlight('align-items') 540 | } else if (id?.includes('justify-content')) { 541 | setHighlight('justify-content') 542 | } else if (id?.includes('flex-direction')) { 543 | setHighlight('flex-direction') 544 | } else { 545 | setHighlight(undefined) 546 | } 547 | }} 548 | > 549 | 550 | {above || 'Some content above'} 551 |
557 | {measuredBox && ( 558 | { 580 | setHighlight(highlight) 581 | setSelectedId(highlight) 582 | }} 583 | /> 584 | )} 585 | 586 | {content || ( 587 | <> 588 | A div with{' '} 589 | id="my-box" 590 | 591 | )} 592 | 593 |
594 | {below || 'Some content below'} 595 |
596 |
597 | ) 598 | } 599 | -------------------------------------------------------------------------------- /components/Disqus.tsx: -------------------------------------------------------------------------------- 1 | import { DiscussionEmbed } from 'disqus-react' 2 | import React from 'react' 3 | import { mediaQuery, PageLayout } from 'react-guidebook' 4 | import styled from 'styled-components' 5 | 6 | const Container = styled.div({ 7 | backgroundColor: 'rgb(250,250,250)', 8 | }) 9 | 10 | const isDev = process.env.NODE_ENV === 'development' 11 | 12 | interface Props { 13 | identifier: string 14 | title: string 15 | shortname: string 16 | stagingShortname?: string 17 | } 18 | 19 | export default function Disqus({ 20 | identifier, 21 | title, 22 | shortname, 23 | stagingShortname, 24 | }: Props) { 25 | if (typeof window === 'undefined') return null 26 | 27 | const name = isDev ? stagingShortname : shortname 28 | 29 | if (!name) return null 30 | 31 | const url = window.location.href 32 | 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /components/EditorConsole.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { WebPlayer, WebPlayerProps } from 'react-guidebook' 3 | import { useTheme } from 'styled-components' 4 | 5 | const paneNames = { 6 | editor: 'Code', 7 | player: 'Live Preview', 8 | transpiler: 'Babel Output', 9 | workspaces: 'Walkthrough', 10 | console: 'Console Output', 11 | } 12 | 13 | function getPaneType(pane: any): keyof typeof paneNames { 14 | return typeof pane === 'string' ? pane : pane.type 15 | } 16 | 17 | function countPlaygroundWidgets(code: string): number { 18 | return (code.match(/console\.log/g) || []).length 19 | } 20 | 21 | function codeHeight(code: string): number { 22 | const headerHeight = 40 23 | const footerHeight = 40 24 | const lineHeight = 20 25 | const padding = 4 26 | const visualSpacer = 20 // To make things look nicer 27 | const widgetHeight = 30 28 | const widgetsHeight = countPlaygroundWidgets(code) * widgetHeight 29 | const codeHeight = code.split('\n').length * lineHeight 30 | 31 | return ( 32 | headerHeight + 33 | padding + 34 | codeHeight + 35 | widgetsHeight + 36 | visualSpacer + 37 | padding + 38 | footerHeight 39 | ) 40 | } 41 | 42 | interface Props { 43 | variant?: 'slides' 44 | width?: number 45 | height?: number | string 46 | code?: string 47 | prelude?: string 48 | modules?: WebPlayerProps['modules'] 49 | panes?: WebPlayerProps['panes'] 50 | workspaces?: WebPlayerProps['workspaces'] 51 | preset?: string 52 | } 53 | 54 | export default memo( 55 | function EditorConsole({ 56 | variant, 57 | width, 58 | prelude, 59 | modules, 60 | panes = ['editor', 'player'], 61 | height, 62 | ...rest 63 | }: Props) { 64 | const theme = useTheme() 65 | 66 | const style = { 67 | minWidth: '0', 68 | minHeight: '0', 69 | ...(variant === 'slides' 70 | ? { 71 | flex: '1', 72 | height: '100%', 73 | } 74 | : { 75 | flex: '1 1 0%', 76 | height: height 77 | ? typeof height === 'number' 78 | ? `${height}px` 79 | : height 80 | : rest.code 81 | ? codeHeight(rest.code) 82 | : 700, 83 | }), 84 | } 85 | 86 | // Set up normal pane styles 87 | panes = panes.map((pane) => 88 | pane === 'player' 89 | ? { 90 | id: 'player', 91 | type: 'player', 92 | title: 'Live Preview', 93 | platform: 'web', 94 | prelude, 95 | modules, 96 | reloadable: true, 97 | style: { 98 | paddingLeft: '0', 99 | paddingRight: '0', 100 | // backgroundColor: 'white', 101 | ...(panes.length === 1 && { flex: '1 1 auto' }), 102 | }, 103 | css: 104 | `#app > iframe { width: 100%; height: 100%; } ` + 105 | (rest.preset === 'typescript' 106 | ? '#player-root { display: none !important; }' 107 | : ''), 108 | } 109 | : pane 110 | ) 111 | 112 | if (rest.workspaces && rest.workspaces.length > 0) { 113 | panes = ['workspaces', ...panes] 114 | } 115 | 116 | const baseStyles = { 117 | workspacesButtonWrapper: { 118 | backgroundColor: theme.colors.primary, 119 | }, 120 | workspacesRowActive: { 121 | backgroundColor: theme.colors.primary, 122 | borderLeftColor: theme.colors.primary, 123 | }, 124 | workspacesDescription: { 125 | backgroundColor: theme.colors.primary, 126 | }, 127 | workspacesPane: { 128 | flex: '0 0 25%', 129 | }, 130 | tabTextActive: { 131 | color: '#333', 132 | borderBottomColor: theme.colors.primary, 133 | }, 134 | } 135 | 136 | const responsivePaneSets: WebPlayerProps['responsivePaneSets'] = 137 | panes.length > 1 && width !== 0 138 | ? [ 139 | { 140 | maxWidth: 890, 141 | panes: [ 142 | { 143 | id: 'stack', 144 | type: 'stack', 145 | children: panes.map((pane, index) => { 146 | const type = getPaneType(pane) 147 | 148 | return { 149 | id: `${type}-${index}`, 150 | title: paneNames[type] || type, 151 | ...(typeof pane === 'string' ? { type } : pane), 152 | ...(type === 'workspaces' && { 153 | style: { 154 | flex: '1 1 0%', 155 | width: 'inherit', 156 | }, 157 | }), 158 | } as any // Improve JavaScript Playgrounds exported types to make this easier to type 159 | }), 160 | }, 161 | ], 162 | }, 163 | ] 164 | : [] 165 | 166 | return ( 167 | 206 | ) 207 | }, 208 | () => true 209 | ) 210 | 211 | const slidesCSS = ` 212 | .CodeMirror { 213 | background-color: rgb(250,250,250); 214 | } 215 | 216 | .CodeMirror-lines { 217 | padding-top: 20px; 218 | padding-bottom: 20px; 219 | } 220 | 221 | .cm-s-react { 222 | font-size: 18px; 223 | line-height: 26px; 224 | } 225 | 226 | .cm-s-react .CodeMirror-linenumber { 227 | display: none; 228 | } 229 | 230 | .cm-s-react .CodeMirror-gutters { 231 | background: rgb(250,250,250); 232 | padding-left: 12px; 233 | padding-right: 0px; 234 | border-left: 0px; 235 | border-right: 0px; 236 | } 237 | ` 238 | -------------------------------------------------------------------------------- /components/FileTreeDiagram.tsx: -------------------------------------------------------------------------------- 1 | import { Code, Pre } from 'react-guidebook' 2 | import { diagram } from 'tree-visit/lib/diagram' 3 | 4 | type Node = { 5 | name: string 6 | children?: Node[] 7 | } 8 | 9 | const normalizeNode = (node: Node) => 10 | typeof node === 'string' ? { name: node } : node 11 | 12 | interface Props { 13 | children: () => Node 14 | } 15 | 16 | export default function FileTreeDiagram({ children }: Props) { 17 | const root = children() 18 | 19 | const output = diagram(root, { 20 | getChildren: (node) => { 21 | const normalized = normalizeNode(node) 22 | return normalized.children ?? [] 23 | }, 24 | getLabel: (node) => { 25 | const normalized = normalizeNode(node) 26 | // return `${'children' in normalized ? '📁' : '📄'} ${normalized.name}` 27 | return normalized.name 28 | }, 29 | type: 'directory', 30 | flattenSingleChildNodes: false, 31 | }) 32 | 33 | return ( 34 |
40 |       
46 |         {output}
47 |       
48 |     
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /components/SectionSlideshow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { TreeNode } from 'generate-guidebook' 3 | import { findNodeBySlug, requireSlides } from 'react-guidebook' 4 | import SpectacleSlideshow from './SpectacleSlideshow' 5 | 6 | const guidebook: TreeNode = require('../guidebook') 7 | 8 | function requireModule(id: string) { 9 | return require('!!babel-loader!spectacle-mdx-loader!../pages/' + id + '.mdx') 10 | } 11 | 12 | export default ({ sectionName }: { sectionName: string }) => { 13 | const root = findNodeBySlug(guidebook, sectionName)! 14 | const slides = requireSlides(root, requireModule) 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /components/SpectacleSlideshow.tsx: -------------------------------------------------------------------------------- 1 | import { MDXProvider } from '@mdx-js/react' 2 | import dynamic from 'next/dynamic' 3 | import React from 'react' 4 | import { Slideshow } from 'react-guidebook' 5 | import { Slide } from 'react-guidebook/lib/utils/requireSlides' 6 | 7 | import EditorConsole from './EditorConsole' 8 | 9 | const Example = (props: React.ComponentProps) => ( 10 | 11 | ) 12 | 13 | interface Props { 14 | slides: Slide[] 15 | } 16 | 17 | // Load spectacle dynamically, since it doesn't work with SSR 18 | export default dynamic( 19 | () => 20 | import('spectacle').then((spectacle) => { 21 | return ({ slides }) => ( 22 | 29 | ) 30 | }), 31 | { 32 | ssr: false, 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /components/SyntaxDiagram.tsx: -------------------------------------------------------------------------------- 1 | import { ExternalLinkIcon } from '@modulz/radix-icons' 2 | import { 3 | createContext, 4 | ReactNode, 5 | useCallback, 6 | useContext, 7 | useEffect, 8 | useMemo, 9 | useRef, 10 | useState, 11 | } from 'react' 12 | import { Caret, HorizontalSpacer, mediaQuery } from 'react-guidebook' 13 | import styled, { CSSProperties, useTheme } from 'styled-components' 14 | 15 | type ContextValue = { 16 | selectedId?: string 17 | activeTokenStyle?: CSSProperties 18 | showToolTips: boolean 19 | onMouseEnter: (id: string) => void 20 | onInput: () => void 21 | } 22 | 23 | const SyntaxTokenContext = createContext({ 24 | onMouseEnter: () => {}, 25 | onInput: () => {}, 26 | showToolTips: true, 27 | }) 28 | 29 | interface StyledToken { 30 | id: string 31 | label?: React.ReactNode 32 | style?: CSSProperties 33 | activeStyle?: CSSProperties 34 | value: Token[] 35 | editable?: boolean 36 | } 37 | 38 | export type Token = StyledToken | string 39 | 40 | function Tooltip({ children }: { children?: React.ReactNode }) { 41 | const theme = useTheme() 42 | 43 | return ( 44 | 54 | 63 | 70 | 79 | 80 | 92 | {children} 93 | 94 | 95 | 96 | ) 97 | } 98 | 99 | function getTokenId(token: Token) { 100 | return typeof token === 'string' ? undefined : token.id 101 | } 102 | 103 | function SyntaxToken({ token, editable }: { token: Token; editable: boolean }) { 104 | const { 105 | selectedId, 106 | onMouseEnter, 107 | onInput, 108 | activeTokenStyle, 109 | showToolTips, 110 | } = useContext(SyntaxTokenContext) 111 | 112 | const content = 113 | typeof token === 'string' 114 | ? token 115 | : token.value.map((value, index) => ( 116 | 121 | )) 122 | 123 | const id = getTokenId(token) 124 | 125 | const componentStyle = useMemo((): CSSProperties | undefined => { 126 | return typeof token === 'string' 127 | ? undefined 128 | : { 129 | padding: '3px 0', 130 | position: 'relative', 131 | ...token.style, 132 | ...(id === selectedId && activeTokenStyle), 133 | ...(id === selectedId && token.activeStyle), 134 | } 135 | }, [token, selectedId, activeTokenStyle]) 136 | 137 | const editableStyle = useMemo( 138 | () => 139 | editable 140 | ? { 141 | // Add padding so the caret always shows up 142 | padding: '0 2px 0 1px', 143 | caretColor: 'black', 144 | display: 'inline-block', 145 | } 146 | : undefined, 147 | [editable] 148 | ) 149 | 150 | const handleMove = useCallback( 151 | ( 152 | event: 153 | | React.MouseEvent 154 | | React.TouchEvent 155 | ) => { 156 | if (!id) return 157 | 158 | onMouseEnter(id) 159 | 160 | event.stopPropagation() 161 | event.preventDefault() 162 | }, 163 | [id] 164 | ) 165 | 166 | if (typeof token === 'string') { 167 | // Input elements can't automatically resize to fit the content, 168 | // so we use contentEditable here 169 | return ( 170 | 177 | {content} 178 | 179 | ) 180 | } 181 | 182 | return ( 183 | 189 | {content} 190 | {showToolTips && token.id === selectedId && ( 191 | {token.label || token.id} 192 | )} 193 | 194 | ) 195 | } 196 | 197 | const Container = styled.div(({ theme }) => ({ 198 | ...theme.textStyles.code, 199 | fontSize: '1.2rem', 200 | backgroundColor: theme.colors.selectedBackground, 201 | display: 'flex', 202 | userSelect: 'none', 203 | marginBottom: `${theme.sizes.spacing.small}px`, 204 | })) 205 | 206 | const Menu = styled.div<{ layoutType: LayoutType }>( 207 | ({ theme, layoutType }) => ({ 208 | ...(layoutType === 'split' && { 209 | width: '50%', 210 | }), 211 | minWidth: '180px', 212 | margin: '10px', 213 | borderRadius: '8px', 214 | background: 'white', 215 | padding: `8px ${theme.sizes.spacing.small}px`, 216 | // borderLeft: `1px solid ${theme.colors.divider}`, 217 | 218 | [mediaQuery.small]: { 219 | display: 'none', 220 | }, 221 | }) 222 | ) 223 | 224 | const IconContainer = styled.a(({ theme }) => ({ 225 | color: theme.colors.text, 226 | border: 'none', 227 | margin: '0', 228 | borderRadius: '8px', 229 | background: 'white', 230 | padding: `8px`, 231 | lineHeight: '0', 232 | position: 'absolute', 233 | right: 0, 234 | top: 10, 235 | cursor: 'pointer', 236 | 237 | [mediaQuery.small]: { 238 | display: 'none', 239 | }, 240 | })) 241 | 242 | const MenuRow = styled.div<{ depth: number; active: boolean }>( 243 | ({ theme, depth, active }) => ({ 244 | ...theme.textStyles.body, 245 | display: 'flex', 246 | flexDirection: 'row', 247 | alignItems: 'center', 248 | paddingLeft: `${4 + depth * 20}px`, 249 | paddingRight: `${8}px`, 250 | ...(active && { 251 | borderRadius: '4px', 252 | backgroundColor: '#c5f5c5', 253 | // backgroundColor: 'rgba(0,0,0,0.1)', 254 | // textDecoration: 'underline', 255 | }), 256 | }) 257 | ) 258 | 259 | const Diagram = styled.div<{ layoutType: LayoutType }>( 260 | ({ theme, layoutType }) => ({ 261 | padding: `40px`, 262 | flex: '1 1 auto', 263 | display: 'flex', 264 | alignItems: 'center', 265 | justifyContent: layoutType === 'split' ? 'flex-start' : 'center', 266 | position: 'relative', 267 | whiteSpace: 'pre-wrap', 268 | cursor: 'crosshair', 269 | }) 270 | ) 271 | 272 | type LayoutType = 'main-syntax' | 'split' 273 | 274 | interface Props { 275 | tokens: Token[] 276 | popOut?: boolean | string 277 | showToolTips?: boolean 278 | showSyntaxTree?: boolean 279 | activeTokenStyle?: CSSProperties 280 | onChangeActiveToken?: (id?: string) => void 281 | onChangeText?: (text: string) => void 282 | children?: ReactNode 283 | selectedId?: string | undefined 284 | onChangeSelectedId?: (id: string | undefined) => void 285 | layoutType?: LayoutType 286 | } 287 | 288 | export default function SyntaxDiagram(props: Props) { 289 | const { 290 | tokens = [], 291 | popOut = true, 292 | showToolTips = true, 293 | showSyntaxTree = true, 294 | activeTokenStyle = { 295 | // backgroundColor: 'rgb(181,215,255)', 296 | backgroundColor: '#c5f5c5', 297 | }, 298 | children, 299 | onChangeActiveToken = () => {}, 300 | onChangeText = () => {}, 301 | selectedId: externalSelectedId, 302 | layoutType = 'main-syntax', 303 | } = props 304 | 305 | const isControlled = 'selectedId' in props 306 | 307 | const [internalSelectedId, setInternalSelectedId] = useState< 308 | string | undefined 309 | >(undefined) 310 | const selectedId = isControlled ? externalSelectedId : internalSelectedId 311 | 312 | const rootSpanRef = useRef(null) 313 | 314 | const onMouseEnter = useCallback((id) => { 315 | setInternalSelectedId(id) 316 | onChangeActiveToken(id) 317 | }, []) 318 | 319 | const handleLeave = useCallback(() => { 320 | setInternalSelectedId(undefined) 321 | onChangeActiveToken(undefined) 322 | }, []) 323 | 324 | const onInput = useCallback(() => { 325 | const rootSpan = rootSpanRef.current 326 | 327 | if (!rootSpan) return 328 | 329 | onChangeText(rootSpan.textContent || '') 330 | }, []) 331 | 332 | useEffect(() => { 333 | onInput() 334 | }, []) 335 | 336 | const contextValue: ContextValue = useMemo( 337 | () => ({ 338 | selectedId, 339 | onMouseEnter, 340 | activeTokenStyle, 341 | showToolTips, 342 | onInput, 343 | }), 344 | [selectedId] 345 | ) 346 | 347 | const menuItems = useMemo(() => { 348 | return createMenuItems(tokens) 349 | }, [tokens]) 350 | 351 | const theme = useTheme() 352 | 353 | const popOutElement = useMemo(() => { 354 | return ( 355 | popOut && ( 356 | 366 | 367 | 368 | ) 369 | ) 370 | }, [tokens, popOut]) 371 | 372 | return ( 373 | 374 | 379 | 380 | 381 | {tokens.map((component, index) => ( 382 | 383 | ))} 384 | 385 | {popOutElement} 386 | 387 | {showSyntaxTree && ( 388 | 389 | {menuItems.map((item, index) => ( 390 | { 395 | setInternalSelectedId(item.id) 396 | onChangeActiveToken(item.id) // should this be here? why wasn't it before? 397 | }} 398 | > 399 | {item.hasChildren ? ( 400 | 401 | ) : ( 402 | 403 | )} 404 | 405 | {item.title} 406 | 407 | ))} 408 | 409 | )} 410 | {!showSyntaxTree && children && ( 411 | {children} 412 | )} 413 | 414 | 415 | ) 416 | } 417 | 418 | type MenuItem = { 419 | id: string 420 | title: React.ReactNode 421 | depth: number 422 | hasChildren: boolean 423 | } 424 | 425 | function createMenuItems(tokens: Token[]) { 426 | function flatten(tokens: Token[], depth: number): MenuItem[] { 427 | return tokens.flatMap((value) => { 428 | if (typeof value === 'string') { 429 | return [] 430 | } 431 | 432 | const children = flatten(value.value, depth + 1) 433 | 434 | const menuItem: MenuItem = { 435 | id: value.id, 436 | title: value.label || value.id, 437 | depth, 438 | hasChildren: children.length > 0, 439 | } 440 | 441 | return [menuItem, ...children] 442 | }) 443 | } 444 | 445 | return flatten(tokens, 0) 446 | } 447 | -------------------------------------------------------------------------------- /custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' 2 | declare module '*.svg' 3 | -------------------------------------------------------------------------------- /examples/files/html_and_css/blog._css: -------------------------------------------------------------------------------- 1 | body, 2 | h1, 3 | h2 { 4 | margin: 0; 5 | } 6 | 7 | body { 8 | font-family: sans-serif; 9 | background-color: rgb(243, 238, 255); 10 | color: rgb(26, 1, 114); 11 | } 12 | 13 | main, 14 | header, 15 | footer { 16 | padding: 40px 120px; 17 | } 18 | 19 | /* Reduce the padding on small screens */ 20 | @media (max-width: 500px) { 21 | main, 22 | header, 23 | footer { 24 | padding: 40px; 25 | } 26 | } 27 | 28 | header, 29 | footer { 30 | background-color: aliceblue; 31 | } 32 | 33 | footer { 34 | text-align: center; 35 | } 36 | 37 | section { 38 | background-color: white; 39 | padding: 40px; 40 | margin-bottom: 40px; 41 | } 42 | 43 | .last-section { 44 | margin-bottom: 0; 45 | } 46 | 47 | h2 { 48 | margin: 0 0 20px 0; 49 | } 50 | 51 | img { 52 | width: 300px; 53 | height: 200px; 54 | max-width: 100%; 55 | background-color: rgba(0, 0, 0, 0.05); 56 | } 57 | -------------------------------------------------------------------------------- /examples/files/html_and_css/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | My Blog 7 | 8 | 9 | 10 |
11 |

Welcome to my blog!

12 |
13 |
14 |
15 |

My latest article

16 |

17 | This is my latest article. Here is some bolded text 18 | and some italic text. 19 |

20 |

21 | Here we have a random photo from 22 | Picsum Photos. 23 |

24 | Randomized photo from Picsum Photos 28 |
29 |
30 |

My first article

31 |

This is my first article.

32 |
33 |
34 |
35 | © 2020 Me 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/files/html_and_css/portfolio._css: -------------------------------------------------------------------------------- 1 | @import url('http://fonts.cdnfonts.com/css/gordita'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | /* Reset */ 8 | body, 9 | ul, 10 | ol, 11 | h1, 12 | h2, 13 | h3, 14 | h4, 15 | h5, 16 | h6, 17 | label, 18 | blockquote { 19 | padding: 0; 20 | margin: 0; 21 | border: 0; 22 | } 23 | 24 | html { 25 | background-color: #222; 26 | font-size: 62.5%; 27 | } 28 | 29 | body { 30 | color: rgb(255, 247, 247); 31 | font-family: 'Gordita'; 32 | font-size: 1.8rem; 33 | } 34 | 35 | h1 { 36 | font-size: 6.4rem; 37 | font-weight: 400; 38 | margin-bottom: 24px; 39 | } 40 | 41 | main { 42 | max-width: 1120px; 43 | margin: 0 auto; 44 | padding: 128px 96px; 45 | } 46 | 47 | .container { 48 | display: flex; 49 | } 50 | 51 | /* @media (max-width: 600px) { 52 | .container { 53 | flex-direction: column; 54 | } 55 | } */ 56 | 57 | .left-container { 58 | flex: 0 0 200px; 59 | margin-top: 116px; 60 | } 61 | 62 | .right-container { 63 | flex: 1; 64 | } 65 | 66 | .navigation-items { 67 | display: flex; 68 | flex-direction: column; 69 | list-style-type: none; 70 | } 71 | 72 | .navigation-list-item { 73 | margin-bottom: 1rem; 74 | margin-right: 1rem; 75 | } 76 | 77 | a { 78 | color: white; 79 | } 80 | 81 | section { 82 | margin-top: 20px; 83 | margin-bottom: 40px; 84 | } 85 | 86 | #devin-image { 87 | width: 250px; 88 | height: 250px; 89 | border-radius: 50%; 90 | object-fit: cover; 91 | float: right; 92 | border: 2px solid white; 93 | margin-left: 40px; 94 | margin-bottom: 40px; 95 | } 96 | 97 | ul { 98 | list-style-position: inside; 99 | } 100 | 101 | ul > li { 102 | margin-bottom: 1rem; 103 | } 104 | 105 | form { 106 | display: flex; 107 | flex-direction: column; 108 | padding: 20px; 109 | background-color: rgba(255, 255, 255, 0.05); 110 | border: 1px solid rgba(0, 0, 0, 0.5); 111 | } 112 | 113 | .form-row { 114 | display: flex; 115 | flex-direction: row; 116 | align-items: center; 117 | margin-bottom: 20px; 118 | } 119 | 120 | .form-row:last-child { 121 | margin-bottom: 0; 122 | } 123 | 124 | .form-row > label { 125 | flex: 0 0 100px; 126 | display: inline-block; 127 | text-align: right; 128 | margin-right: 20px; 129 | } 130 | 131 | .form-row > input, 132 | .form-row > select { 133 | padding: 0.8rem; 134 | border: none; 135 | font-size: 1.8rem; 136 | background-color: #444; 137 | border: 1px solid #aaa; 138 | border-radius: 2px; 139 | color: white; 140 | min-width: 0; 141 | } 142 | 143 | .form-row > select > option { 144 | background-color: #444; 145 | } 146 | 147 | .form-row > input, 148 | .form-row > select { 149 | flex: 1 1 0%; 150 | } 151 | 152 | .form-row > select { 153 | padding-right: 20px; 154 | } 155 | 156 | .form-row > input::placeholder { 157 | color: rgba(255, 255, 255, 0.4); 158 | } 159 | 160 | .submit-form-row { 161 | justify-content: flex-end; 162 | } 163 | 164 | .youtube-embed { 165 | width: 100%; 166 | height: 500px; 167 | } 168 | 169 | :root { 170 | --color-magenta: #ff4daf; 171 | --color-violet: #ba8ef2; 172 | --color-teal: #5dd1c6; 173 | --color-aqua: #46c6ff; 174 | } 175 | 176 | .magenta { 177 | color: var(--color-magenta); 178 | } 179 | .violet { 180 | color: var(--color-violet); 181 | } 182 | .teal { 183 | color: var(--color-teal); 184 | } 185 | .aqua { 186 | color: var(--color-aqua); 187 | } 188 | 189 | button { 190 | background-color: var(--color-violet); 191 | padding: 1.2rem 2.4rem; 192 | border: none; 193 | font-size: 1.8rem; 194 | border: 1px solid white; 195 | border-radius: 2px; 196 | color: white; 197 | font-weight: bold; 198 | box-shadow: 0 1px 4px black; 199 | transition: 0.5s; 200 | } 201 | 202 | button:hover { 203 | background-color: #a071dd; 204 | border-color: transparent; 205 | } 206 | 207 | button:active { 208 | background-color: #6b38ad; 209 | border-color: transparent; 210 | } 211 | 212 | blockquote { 213 | margin: 20px; 214 | font-style: italic; 215 | } 216 | 217 | blockquote::before { 218 | content: ''; 219 | font-style: normal; 220 | display: inline-block; 221 | width: 48px; 222 | height: 48px; 223 | border-radius: 4px; 224 | background-color: #795548; 225 | vertical-align: middle; 226 | margin-right: 20px; 227 | margin-bottom: 10px; 228 | background-image: url('https://webdev.express/static/quotes.png'); 229 | } 230 | 231 | @media screen and (max-width: 1200px) { 232 | main { 233 | padding: 64px; 234 | } 235 | } 236 | 237 | @media (max-width: 600px) { 238 | main { 239 | padding: 48px; 240 | } 241 | 242 | .container { 243 | flex-direction: column; 244 | } 245 | 246 | .left-container { 247 | margin-top: 0; 248 | flex: 0 0 80px; 249 | } 250 | 251 | .navigation-items { 252 | flex-direction: row; 253 | justify-content: space-between; 254 | } 255 | 256 | #devin-image { 257 | width: 150px; 258 | height: 150px; 259 | } 260 | 261 | h1 { 262 | font-size: 48px; 263 | } 264 | } 265 | 266 | @media screen and (max-width: 480px) { 267 | main { 268 | padding: 24px; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /examples/files/html_and_css/portfolio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | My Portfolio 7 | 8 | 9 | 10 |
11 |
12 | 28 |
29 |

Welcome

30 |
31 | Devin in front of shrubbery 36 |

About

37 |

38 | My name is Devin Abbott, and I'm a web & mobile app developer. 39 |

40 |

41 | I primarily work with React and 42 | React Native. Sometimes I write 43 | Swift — but mainly for macOS, not iOS. Many of my 44 | projects are open source. 45 |

46 |
Open source is fun, sometimes.
47 |
48 |
49 |

Projects

50 |

Here are a few open source projects I've worked on:

51 | 58 |
59 |
60 |

Videos

61 |

Sometimes I talk at conferences.

62 |

Here's a talk I gave at React Europe in 2017:

63 | 67 |
68 |
69 |

Contact

70 |

Want to say hi?

71 |
75 |
76 | 83 |
84 |
85 | 92 |
93 |
94 | 100 |
101 |
102 | 103 |
104 |
105 |
106 |
107 |
108 |
109 | 110 | 111 | -------------------------------------------------------------------------------- /examples/files/html_and_css/todo._css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body, 6 | h1, 7 | label, 8 | form, 9 | ul, 10 | li, 11 | hr { 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | body { 17 | font-family: sans-serif; 18 | padding: 20px; 19 | color: #333; 20 | } 21 | 22 | h1 { 23 | margin: 20px 0; 24 | } 25 | 26 | input { 27 | width: 100%; 28 | padding: 10px; 29 | background-color: #eee; 30 | border: none; 31 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); 32 | } 33 | 34 | ul { 35 | list-style: none; 36 | margin: 10px 0; 37 | border-top: 1px solid #ddd; 38 | background: lightyellow; 39 | } 40 | 41 | li { 42 | padding: 10px; 43 | border-bottom: 1px solid #ddd; 44 | user-select: none; 45 | cursor: pointer; 46 | } 47 | 48 | hr { 49 | height: 1px; 50 | border: none; 51 | background: #ddd; 52 | margin: 10px 0; 53 | } 54 | 55 | label { 56 | margin-right: 10px; 57 | } 58 | 59 | button { 60 | border: 1px solid #ddd; 61 | padding: 8px; 62 | background: #eee; 63 | } 64 | 65 | select { 66 | border: none; 67 | border: 1px solid #ddd; 68 | padding: 2px; 69 | background: #eee; 70 | } 71 | -------------------------------------------------------------------------------- /examples/files/html_and_css/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TODO List 7 | 8 | 9 | 10 |

todos

11 |
12 | 19 |
20 |
21 |
22 | 23 | 28 |
29 |
    30 |
  • Task A
  • 31 |
  • Task B
  • 32 |
  • Task C
  • 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /guidebook.d.ts: -------------------------------------------------------------------------------- 1 | import type { TreeNode } from 'generate-guidebook' 2 | 3 | const value: TreeNode 4 | 5 | export default value 6 | -------------------------------------------------------------------------------- /images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | logo 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 46 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withImages = require('next-images') 2 | const slug = require('rehype-slug') 3 | 4 | const withGuidebook = require('generate-guidebook/next')({ 5 | guidebookDirectory: './pages', 6 | guidebookModulePath: './guidebook.js', 7 | }) 8 | 9 | const withMDX = require('next-mdx-frontmatter')({ 10 | extension: /\.mdx?$/, 11 | MDXOptions: { 12 | rehypePlugins: [slug], 13 | }, 14 | }) 15 | 16 | const withRawExampleLoader = (nextConfig) => ({ 17 | ...nextConfig, 18 | webpack(config, options) { 19 | config.module.rules.push({ 20 | test: /examples(\/|\\)files(\/|\\).*$/, 21 | use: 'raw-loader', 22 | }) 23 | 24 | if (typeof nextConfig.webpack === 'function') { 25 | return nextConfig.webpack(config, options) 26 | } 27 | 28 | return config 29 | }, 30 | }) 31 | 32 | module.exports = withRawExampleLoader( 33 | withGuidebook( 34 | withImages( 35 | withMDX({ 36 | pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], 37 | }) 38 | ) 39 | ) 40 | ) 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webdev-express", 3 | "description": "A guide to web development", 4 | "version": "1.0.0", 5 | "author": "Devin Abbott (http://github.com/dabbott)", 6 | "dependencies": { 7 | "@babel/core": "^7.10.2", 8 | "@mdx-js/mdx": "^1.6.22", 9 | "@mdx-js/react": "^1.6.22", 10 | "@mdx-js/tag": "^0.20.3", 11 | "@modulz/radix-icons": "^3.3.0", 12 | "@next/mdx": "^8.1.0", 13 | "disqus-react": "^1.0.10", 14 | "flexsearch": "^0.6.32", 15 | "next": "^10.0.3", 16 | "next-images": "^1.6.2", 17 | "react": "^17.0.1", 18 | "react-dom": "^17.0.1", 19 | "react-ga": "^3.3.0", 20 | "react-guidebook": "^0.10.5", 21 | "spectacle": "^6.2.0", 22 | "styled-components": "^5.1.1", 23 | "tree-visit": "^0.0.7" 24 | }, 25 | "keywords": [], 26 | "license": "MIT", 27 | "scripts": { 28 | "dev": "next", 29 | "build": "next build && next export", 30 | "start": "next start -p $PORT", 31 | "format": "prettier --write '**/*.js'", 32 | "test": "echo \"Error: no test specified\" && exit 1" 33 | }, 34 | "devDependencies": { 35 | "@mdx-js/loader": "^1.6.22", 36 | "@types/mdx-js__react": "^1.5.3", 37 | "@types/node": "^14.14.11", 38 | "@types/react": "^17.0.0", 39 | "@types/resize-observer-browser": "^0.1.5", 40 | "@types/styled-components": "^5.1.4", 41 | "babel-loader": "^8.1.0", 42 | "babel-plugin-styled-components": "^1.11.1", 43 | "generate-guidebook": "^1.3.4", 44 | "next-mdx-frontmatter": "^0.0.3", 45 | "prettier": "^2.0.5", 46 | "raw-loader": "^3.0.0", 47 | "rehype-slug": "^4.0.1", 48 | "spectacle-mdx-loader": "^0.1.1", 49 | "typescript": "^4.1.2" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "https://github.com/dabbott/webdev-express" 54 | }, 55 | "prettier": { 56 | "semi": false, 57 | "singleQuote": true, 58 | "trailingComma": "es5" 59 | }, 60 | "guidebook": { 61 | "title": "Web Development Express", 62 | "description": "A guide to web development", 63 | "location": { 64 | "host": "www.webdev.express" 65 | }, 66 | "author": { 67 | "twitter": "dvnabbott" 68 | }, 69 | "favicons": [ 70 | { 71 | "type": "image/x-icon", 72 | "path": "/static/favicon.ico" 73 | }, 74 | { 75 | "type": "image/png", 76 | "path": "/static/favicon.png" 77 | } 78 | ], 79 | "previewImage": { 80 | "type": "image/png", 81 | "width": "1200", 82 | "height": "630", 83 | "alt": "Website preview", 84 | "path": "/static/preview.png" 85 | }, 86 | "github": { 87 | "user": "dabbott", 88 | "repo": "webdev-express" 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Link from 'next/link' 3 | import ReactGA from 'react-ga' 4 | import React, { useMemo } from 'react' 5 | import { MDXProvider } from '@mdx-js/react' 6 | import { ThemeProvider } from 'styled-components' 7 | import type { AppProps } from 'next/app' 8 | import Router from 'next/router' 9 | import { 10 | Page, 11 | Author, 12 | PageComponents, 13 | NotFound, 14 | findNodeBySlug, 15 | trackPageView, 16 | initializeAnalytics, 17 | LinkProvider, 18 | Anchor, 19 | RouterProvider, 20 | Banner, 21 | LinkProps, 22 | HeadTags, 23 | GuidebookConfig, 24 | Styles, 25 | } from 'react-guidebook' 26 | import defaultTheme from '../styles/theme' 27 | import slidesTheme from '../styles/slidesTheme' 28 | import EditorConsole from '../components/EditorConsole' 29 | import Disqus from '../components/Disqus' 30 | import FileTreeDiagram from '../components/FileTreeDiagram' 31 | import SyntaxDiagram from '../components/SyntaxDiagram' 32 | import BoxModelDiagram from '../components/BoxModelDiagram' 33 | import logo from '../images/logo.svg' 34 | import guidebook from '../guidebook' 35 | import { searchPages, searchTextMatch } from '../utils/search' 36 | import pkg from '../package.json' 37 | 38 | const config: GuidebookConfig = pkg.guidebook ?? {} 39 | 40 | const Components = { 41 | ...PageComponents, 42 | Example: EditorConsole, 43 | Author, 44 | FileTreeDiagram, 45 | SyntaxDiagram, 46 | BoxModelDiagram, 47 | Details: ({ children }: { children: React.ReactNode }) => children, 48 | } 49 | 50 | const LinkComponent = ({ href, children, style }: LinkProps) => ( 51 | 52 | {children} 53 | 54 | ) 55 | 56 | export default function App({ Component, pageProps, router }: AppProps) { 57 | const slug = router.pathname.slice(1) 58 | const theme = slug.endsWith('slides') ? slidesTheme : defaultTheme 59 | 60 | let content 61 | let title: string 62 | let description: string | undefined 63 | 64 | // Serve these pages "bare", without the Page component wrapper 65 | if (slug.endsWith('slides')) { 66 | title = 'Slides' 67 | content = 68 | } else if (slug.endsWith('playgrounds')) { 69 | title = 'Playgrounds' 70 | content = 71 | } else if (slug.includes('syntax_diagram')) { 72 | title = 'Syntax Diagram' 73 | content = 74 | } else if (slug.includes('box_model_diagram')) { 75 | title = 'Box Model Diagram' 76 | content = 77 | } else if (slug.includes('schedule')) { 78 | title = 'Class Schedule' 79 | content = 80 | } else { 81 | const node = findNodeBySlug(guidebook, slug) 82 | 83 | if (!node) { 84 | title = 'Not found' 85 | content = 86 | } else { 87 | const isIntroduction = node.slug === '' 88 | 89 | title = node.title 90 | description = node.subtitle 91 | content = ( 92 | 93 | 98 | // ) : undefined 99 | // } 100 | footer={ 101 | isIntroduction ? undefined : config.disqus ? ( 102 | 108 | ) : undefined 109 | } 110 | searchPages={searchPages} 111 | searchTextMatch={searchTextMatch} 112 | > 113 | 114 | 115 | 116 | ) 117 | } 118 | } 119 | 120 | const { pathname, asPath: clientPath } = router 121 | 122 | const customRouter = useMemo( 123 | () => ({ 124 | pathname, 125 | clientPath, 126 | push: (pathname: string) => { 127 | router.push(pathname) 128 | }, 129 | }), 130 | [pathname, clientPath] 131 | ) 132 | 133 | return ( 134 | <> 135 | 136 | {HeadTags({ 137 | config: pkg.guidebook ?? {}, 138 | pageTitle: title, 139 | pageDescription: description, 140 | })} 141 | 142 | 143 | 144 | 145 | 146 | 147 | {/* A single child is required here for React.Children.only */} 148 | {content} 149 | 150 | 151 | 152 | 153 | ) 154 | } 155 | 156 | if (typeof document !== 'undefined' && config.googleAnalytics) { 157 | const pageView = () => trackPageView(ReactGA) 158 | 159 | initializeAnalytics(ReactGA, config.googleAnalytics.trackingId) 160 | pageView() 161 | ;(Router as any).onRouteChangeComplete = pageView 162 | } 163 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | // https://github.com/vercel/next.js/blob/ce5d9c858b70f647b532ec2f62ddde506af32b6a/examples/with-styled-components/pages/_document.js 2 | import Document, { DocumentContext } from 'next/document' 3 | import { ServerStyleSheet } from 'styled-components' 4 | 5 | export default class MyDocument extends Document { 6 | static async getInitialProps(ctx: DocumentContext) { 7 | const sheet = new ServerStyleSheet() 8 | const originalRenderPage = ctx.renderPage 9 | 10 | try { 11 | ctx.renderPage = () => 12 | originalRenderPage({ 13 | enhanceApp: (App) => (props) => 14 | sheet.collectStyles(), 15 | }) 16 | 17 | const initialProps = await Document.getInitialProps(ctx) 18 | return { 19 | ...initialProps, 20 | styles: ( 21 | <> 22 | {initialProps.styles} 23 | {sheet.getStyleElement()} 24 | 25 | ), 26 | } 27 | } finally { 28 | sheet.seal() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pages/box_model_diagram.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { deserialize } from '../utils/serialize' 3 | import styled from 'styled-components' 4 | import BoxModelDiagram, { CSSDeclaration } from '../components/BoxModelDiagram' 5 | import { parseQueryParameters, parseUrl, useRouter } from 'react-guidebook' 6 | 7 | const Container = styled.div({ 8 | height: '100vh', 9 | display: 'flex', 10 | alignItems: 'center', 11 | justifyContent: 'center', 12 | overflow: 'hidden', 13 | }) 14 | 15 | const Inner = styled.div({ 16 | flex: '1 1 auto', 17 | maxWidth: '900px', 18 | display: 'flex', 19 | flexDirection: 'column', 20 | justifyContent: 'center', 21 | }) 22 | 23 | function getProps( 24 | fragment: string 25 | ): 26 | | { 27 | declarations: CSSDeclaration[] 28 | content?: React.ReactNode 29 | } 30 | | undefined { 31 | const data = parseQueryParameters(fragment).data 32 | 33 | if (!data) return 34 | 35 | const parsed = JSON.parse(data) 36 | 37 | return { 38 | declarations: parsed.declarations, 39 | content: parsed.content ? deserialize(parsed.content) : undefined, 40 | } 41 | } 42 | 43 | export default function BoxModelDiagramPage() { 44 | const router = useRouter() 45 | const { fragment } = parseUrl(router.clientPath) 46 | const { declarations = [], content } = getProps(fragment) || {} 47 | 48 | return ( 49 | 50 | 51 | 56 | 57 | 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /pages/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": ["html_and_css", "typescript", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /pages/html_and_css.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: HTML & CSS 3 | --- 4 | 5 | ## Overview 6 | 7 | HTML code describes the UI of a webpage. Here's an example HTML file with a live preview: 8 | 9 | 15 | 16 | 17 | 18 | My page 19 | 20 | 21 |

Welcome!

22 | 23 | 24 | `, 25 | }} 26 | /> 27 | 28 | HTML includes a large set of built-in UI elements, such as `

` (paragraph) and ` 75 |

76 |
77 | 78 | Span: Wikipedia 79 |
80 | 87 | 88 | `, 89 | }} 90 | /> 91 | 92 | ## Keyboard Navigation 93 | 94 | The first place to start when supporting keyboard navigation is making sure we're using semantic elements. Elements like `button` support keyboard navigation out-of-the-box. 95 | 96 | If we want to implement custom navigation, we'll usually start with the `tabindex` HTML attribute, which lets us specify that an element is be focusable. 97 | 98 | Beyond that, we'll often need to use JavaScript to handle key press events. 99 | -------------------------------------------------------------------------------- /pages/html_and_css/advanced_selectors.mdx: -------------------------------------------------------------------------------- 1 | ## Combinators 2 | 3 | We can combine CSS selectors using **combinators**. These are analogous to operators, like `+` or `-`, in other programming languages. 4 | 5 | ### Descendant combinator 6 | 7 | 13 | 14 | 15 | 16 | My page 17 | 22 | 23 | 24 |

Welcome!

25 |
26 |

I'm in main

27 |
28 |

I'm nested in a section in main

29 |
30 |
31 | 32 | `, 33 | }} 34 | /> 35 | 36 | ### Child combinator 37 | 38 | 44 | 45 | 46 | 47 | My page 48 | 53 | 54 | 55 |

Welcome!

56 |
57 |

I'm in main

58 |
59 |

I'm nested in a section in main

60 |
61 |
62 | 63 | `, 64 | }} 65 | /> 66 | 67 | ## Pseudo-classes 68 | 69 | These represent either element state or location in the document. 70 | 71 | 77 | 78 | 79 | 80 | My page 81 | 89 | 90 | 91 |

Try clicking me!

92 | 93 | `, 94 | }} 95 | /> 96 | 97 | If we use margins for spacing in lists, it's common to remove the margin from the last element. 98 | 99 | 105 | 106 | 107 | 108 | My page 109 | 122 | 123 | 124 |
    125 |
  1. Apples
  2. 126 |
  3. Cherries
  4. 127 |
  5. Oranges
  6. 128 |
  7. Peaches
  8. 129 |
  9. Pears
  10. 130 |
131 | 132 | `, 133 | }} 134 | /> 135 | 136 | ## Pseudo-elements 137 | 138 | We can insert a new HTML element in our UI using the CSS `::before` and `::after` pseudo-elements. These are often used for adding decorations around other elements. 139 | 140 | 146 | 147 | 148 | 149 | My page 150 | 168 | 169 | 170 |
A quote!
171 | 172 | `, 173 | }} 174 | /> 175 | 176 | 177 | -------------------------------------------------------------------------------- /pages/html_and_css/box_model.mdx: -------------------------------------------------------------------------------- 1 | ## What is the Box Model? 2 | 3 | Every HTML is rendered within a rectangular "box". The box model describes the layout for this box. 4 | 5 | ## Width and height 6 | 7 | The most direct way to specify the size of a box is through the `width` and `height` CSS styles. 8 | 9 | We can use hardcoded values, e.g. `300px`, or a variety of other values like percents, e.g. `50%`. 10 | 11 | If we don't specify a width/height, these styles default to `auto`. Using `auto` sets the width or height to the "intrinsic content size" of the box (i.e. big enough to fit its content). 12 | 13 | 19 | 20 | ## Padding, margin, and border 21 | 22 | Each element/box can have a **border**. We can add spacing _outside_ the border with **margin**, or inside the border with **padding**. 23 | 24 | 31 | 32 | ### Specifying a different size on each side 33 | 34 | We can use separate CSS styles to specify different margins/borders/paddings for each side. 35 | 36 | For example: 37 | 38 | 46 | 47 | ### Shorthand syntax 48 | 49 | There's a shorthand syntax for combining different margin and padding sizes in the same line. We can specify up to 4 values, going clockwise from the top: top, right, bottom, and left. If we only specify 2 values, then they will represent: vertical, horizonal. 50 | 51 | 57 | 58 | ## Content box vs. border box 59 | 60 | We can use two different algorithms for calculating box dimensions. We specify which algorithm to use via the `box-sizing` style. 61 | 62 | The two options are: 63 | 64 | - `content-box` - The width/height don't include padding or border width 65 | - `border-box` - The width/height include padding and border width 66 | 67 | 76 | 77 | > Try changing `content-box` to `border-box` and see what happens! 78 | 79 | Generally, `border-box` is a little easier to use, and most developers set it on every element (we can use this CSS to do so: `* { box-sizing: border-box; }`). 80 | 81 | ## Position 82 | 83 | We can use `position: relative` along with `top/right/bottom/left` to move a box without affect the layout of anything around it. 84 | 85 | 92 | 93 | We `position: absolute`, on the other hand, removes the box from the regular page layout and sets the position relative to the box's nearest ancestor that also sets a `position`. 94 | 95 | 104 | -------------------------------------------------------------------------------- /pages/html_and_css/browser_dev_tools.mdx: -------------------------------------------------------------------------------- 1 | The browser's built-in developer tools let us inspect the HTML of _any_ webpage. This is extremely useful for debugging our layout and styles. 2 | 3 | ## Debugging styles 4 | 5 | A few common techniques: 6 | 7 | - We often add temporary styles to individual elements to see what they would look like 8 | - We check the computed box model diagram to see why the dimensions of an element look a certain way 9 | - We can look up a computed style for an element and then jump to the source CSS file or style tag where it's defined 10 | -------------------------------------------------------------------------------- /pages/html_and_css/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "elements", 4 | "styling", 5 | "box_model", 6 | "browser_dev_tools", 7 | "flexbox", 8 | "advanced_selectors", 9 | "units", 10 | "responsive_design", 11 | "accessibility", 12 | "css_variables", 13 | "limitations", 14 | "projects", 15 | "resources" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /pages/html_and_css/css_variables.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS Variables 3 | --- 4 | 5 | All modern browsers support CSS **custom properties**, also known as CSS variables. 6 | 7 | ## Declaring variables 8 | 9 | All variables must be declared in a CSS rule, i.e. within curly braces. 10 | 11 | To declare a top-level variable that can be used anywhere, we typically use the `:root` pseudo-class. In an HTML page, this refers to the HTML element. 12 | 13 | Any CSS value can be stored in a variable: sizes (with units), colors, shorthand properties (e.g. `1px solid blue` for a border), etc. 14 | 15 | 21 | 22 | 23 | 24 | My page 25 | 39 | 40 | 41 |

Hello, world!

42 | 43 | 44 | `, 45 | }} 46 | /> 47 | 48 | ## Shadowing variables 49 | 50 | Any CSS rule can redeclare a variable to a different value. An HTML element will use the variable declared on the nearest ancestor. 51 | 52 | 58 | 59 | 60 | 61 | My page 62 | 79 | 80 | 81 |

Hello, world!

82 | 83 |
84 |

Hello, world!

85 | 86 |
87 | 88 | `, 89 | }} 90 | /> 91 | 92 | ## Fallback values 93 | 94 | We can also provide a fallback value when using `var`, in case our variable isn't defined. 95 | 96 | In this example, we don't declare a `--primary-color` in the root. This means that our `h1` and `button` outside of the `section` will use the fallback value, since `--primary-color` isn't defined. 97 | 98 | 104 | 105 | 106 | 107 | My page 108 | 122 | 123 | 124 |

Hello, world!

125 | 126 |
127 |

Hello, world!

128 | 129 |
130 | 131 | `, 132 | }} 133 | /> 134 | -------------------------------------------------------------------------------- /pages/html_and_css/elements.mdx: -------------------------------------------------------------------------------- 1 | ## Element Syntax 2 | 3 | HTML describes a hierarchy of **elements** that make up a webpage. Each element can be configured using **attributes**, and may contain nested **content** (text or other elements) within. 4 | 5 | ### Regular elements 6 | 7 | Suppose we wanted to use the `main` element, and set its `class` attribute to the value `"my-content"`. Here's how we would write it: 8 | 9 | ', 41 | ], 42 | }, 43 | //'\n', 44 | { id: 'Content', value: ['Hello, world!'] }, 45 | //'\n', 46 | { 47 | id: 'Closing tag', 48 | value: [ 49 | '', 57 | ], 58 | }, 59 | ], 60 | }, 61 | ]} 62 | /> 63 | 64 | > Hover or tap to highlight the different parts of the diagram above. 65 | 66 | ### Void elements 67 | 68 | Some elements must not contain any content - these are called **void elements**. In this case, a closing tag is not allowed. The opening tag may end in either `>`, or `/>` like XML. 69 | 70 | ', 102 | ], 103 | }, 104 | ], 105 | }, 106 | ]} 107 | /> 108 | 109 | > An up-to-date list of all void elements can be found here: https://github.com/wooorm/html-void-elements 110 | 111 | 112 | 113 | ### Entity references 114 | 115 | The characters `<`, `>`,`"`,`'` and `&` are reserved, since they're used by the HTML syntax to define elements and attributes. We can use special escape sequences starting with `&` to insert these as text within an element's content. 116 | 117 | For example, if we want to write a `<` character, we could use the following HTML: 118 | 119 | 125 | 126 | 127 | 128 | My page 129 | 130 | 131 |

A less-than symbol: <

132 | 133 | `, 134 | }} 135 | /> 136 | 137 | Here are the characters we _have to_ use entity references to write. 138 | 139 | | Character | Entity reference | 140 | | --------- | ---------------- | 141 | | `<` | `<` | 142 | | `>` | `>` | 143 | | `"` | `"` | 144 | | `'` | `'` | 145 | | `&` | `&` | 146 | 147 | There are many other entity references that exist mainly for historic reasons: UTF-8 wasn't always widely supported, so there had to be a way to represent other characters within ASCII. 148 | 149 | ## `` elements 150 | 151 | The following are a few of the most common elements used in the `head` of a document. 152 | 153 | ### `style` 154 | 155 | We can use a `style` element to style our page with CSS: 156 | 157 | 163 | 164 | 165 | 166 | My page 167 | 172 | 173 | 174 | Hello, world! 175 | 176 | `, 177 | }} 178 | /> 179 | 180 | ### `link` 181 | 182 | We can also link to external CSS files using the `link` element, e.g.: ``. 183 | 184 | > Note that `link` is a void element, so we must _not_ include a closing tag. 185 | 186 | ### `script` 187 | 188 | Script elements let us run JavaScript to add interactivity to our page. 189 | 190 | 196 | 197 | 198 | 199 | My page 200 | 207 | 208 | 209 | Click me! 210 | 211 | `, 212 | }} 213 | /> 214 | 215 | We can link to external JavaScript files using the `src` attribute of a `script` element, e.g. ``. 216 | 217 | > The `script` tag is _not_ a void element, so we _must_ include a closing tag. 218 | 219 | Note that script tags may also be used in the `body`, and that the location of the script tag _does matter_. A script in the `head` will run before the `body` is constructed and the UI is presented to the user - so some parts of the document may not be accessible via JavaScript yet. 220 | 221 | ## `` elements 222 | 223 | Now, let's look at some of the most common `body` elements. 224 | 225 | ### Block vs. inline 226 | 227 | Most elements are either **block** or **inline** elements. Block elements are rendered on their own line, while inline elements render on the same line, so long as they fit within their parent element (think a paragraph with word-wrapping). 228 | 229 | The most general-purpose block element is the `div` and the most general-purpose inline element is the `span`. 230 | 231 | 237 | 238 | 239 | 240 | My page 241 | 242 | 243 |
Block
244 |
Block
245 |
Block
246 | Inline 247 | Inline 248 | Inline 249 |
Block
250 | 251 | `, 252 | }} 253 | /> 254 | 255 | Note that block elements cannot be nested inside inline elements. 256 | 257 | We can change an element from block to inline or vice versa using the CSS declaration `display: block` or `display: inline`. 258 | 259 | > Technically, as of HTML5, elements are now grouped into [content categories](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories) rather than "block" or "inline", which are purely presentational. However, most developers still refer to elements as "block" or "inline", and the mental model is essentially the same. 260 | 261 | > Here's a complete list of [block elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements#elements) and [inline elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements#list_of_inline_elements) 262 | 263 | ### Semantic HTML 264 | 265 | The elements `div` and `span` do not have any semantic meaning or built-in styles. However, many elements do, and we should try to use these whenever possible. 266 | 267 | Here are a handful of common elements: 268 | 269 | - Sections: `main`, `nav`, `header`, `footer`, `section` 270 | - Headings: `h1`, `h2`, `h3`, `h4`, `h5`, `h6` 271 | - Inline styles: `strong`, `em` 272 | - Navigation: `a` 273 | - Embedded content: `img`, `video`, `svg`, `audio` 274 | - Lists: `ul`, `ol`, `li` 275 | - Forms: `form`, `label`, `input`, `select`, `option`, `textarea`, `button` 276 | 277 | Although we could make many webpages using just `div` and `span` elements, we should try to use semantic elements - semantic elements often come with built-in styles or behaviors that users expect, and assistive technologies like screenreaders use them to navigate the page. 278 | 279 | ## Validating HTML 280 | 281 | Browsers almost never throw errors, however, it's still a good idea to write valid HTML so that all browsers display your page the same way. 282 | 283 | You can check that your HTML is valid here: https://validator.w3.org/#validate_by_input (or programatically here: https://validator.w3.org/docs/api.html) 284 | 285 | Your text editor will usually guide you in the right direction through autocomplete. 286 | -------------------------------------------------------------------------------- /pages/html_and_css/flexbox.mdx: -------------------------------------------------------------------------------- 1 | Flexbox is an alternate layout algorithm we can use. 2 | 3 | ## CSS Properties 4 | 5 | The first property we need to consider is `flex-direction`, which defines the **main axis** and **cross axis** of our layout. After this, we can use `justify-content` to distribute elements along the main axis, and `align-items` to align elements along the cross axis. 6 | 7 | 29 | Child 1 30 | , 31 | 42 | Child 2 43 | , 44 | 55 | Child 3 56 | , 57 | ]} 58 | /> 59 | 60 | ## Comparisons with other layout systems 61 | 62 | - **UIStackView on iOS**: https://dabbott.github.io/webdev-projects/cheatsheets/uistackview-flexbox.html 63 | -------------------------------------------------------------------------------- /pages/html_and_css/limitations.mdx: -------------------------------------------------------------------------------- 1 | ## HTML 2 | 3 | A few challenges we typically encounter when using HTML directly: 4 | 5 | - **Reusability**: while there are some ways we can reuse HTML snippets, such as template tags and web components, they typically rely heavily on JavaScript, and even then are somewhat cumbersome to use directly. 6 | - **Browser differences**: since different browsers may implement the web standards at different times or have different bugs, we need to double check whether we can use newer features (see https://caniuse.com/) and test on multiple platforms 7 | - **Screen reader differences**: just like how browsers have differences, so do screen readers. We similarly need to test with multiple screenreaders (see https://webaim.org/projects/screenreadersurvey8/ for which to test with) 8 | 9 | ## CSS 10 | 11 | A few challenges we typically encounter when using CSS directly: 12 | 13 | - **Encapsulation**: Since CSS is global, we need to enforce naming conventions across our team (e.g. http://getbem.com/introduction/). Otherwise, our CSS files can quickly grow in size and complexity until it's practically impossible to make changes without breaking something. 14 | - **Vendor prefixing**: If we want to use newer CSS features, we need to add browser-specific features to property names. This is a hassle to do manually (fortunately there are tools that can do this automatically) 15 | - **CSS Resets**: Often we want our website to look the same on different browsers. This means we'll want to "reset" each browser's default styles to something standard. A few common options: https://meyerweb.com/eric/tools/css/reset/, https://necolas.github.io/normalize.css/. 16 | -------------------------------------------------------------------------------- /pages/html_and_css/projects.mdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabbott/webdev-express/1a394c10d1bb8afafd2b712400f1f489f9871393/pages/html_and_css/projects.mdx -------------------------------------------------------------------------------- /pages/html_and_css/projects/blog_project.mdx: -------------------------------------------------------------------------------- 1 | import html from '../../../examples/files/html_and_css/blog.html' 2 | import css from '../../../examples/files/html_and_css/blog._css' 3 | 4 | ## Finished Project 5 | 6 | 15 | 16 | ## Steps 17 | 18 | 1. Create or open an HTML file. Add a ``, ``, ``, and ``. 19 | 20 | 1. Add a ` 38 | 39 | 40 | dog 41 | 42 | `, 43 | }} 44 | /> 45 | 46 | Here's an example where we use a media query to change the root font size - and since all headings have rem-based font sizes by default, they all resize! 47 | 48 | 54 | 55 | 56 | 57 | My page 58 | 73 | 74 | 75 |

Heading 1

76 |

Heading 2

77 |

Heading 3

78 | 79 | `, 80 | }} 81 | /> 82 | -------------------------------------------------------------------------------- /pages/html_and_css/styling.mdx: -------------------------------------------------------------------------------- 1 | ## CSS 2 | 3 | CSS, or Cascading Style Sheets, is a language for customizing the styles of HTML elements. CSS lets us "select" which element we want to style, and then apply any number of style properties, such as `font-size` or `background-color`. There are _hundreds_ of properties, but only around 30 or so that we use enough to memorize. Usually a quick Google search for what you're trying to do will bring up the CSS property you need. 4 | 5 | To use CSS, we embed a CSS **stylesheet** inside a ` 26 | 27 | 28 |

Welcome!

29 | 30 | `, 31 | }} 32 | /> 33 | 34 | ## Syntax 35 | 36 | A stylesheet may contain any number of rules. Each rule has the following syntax: 37 | 38 | 86 | 87 | ## Where to put styles 88 | 89 | Stylesheets can be embedded, external, or inline. So far, we've seen embedded styles (they're _embedded_ within an HTML page). 90 | 91 | ### External styles 92 | 93 | To use an external stylesheet, we link to it using a `` element in the `head`: e.g. ``. This is useful since stylesheets can be reused between HTML files this way. 94 | 95 | ### Inline styles 96 | 97 | With both embedded and external styles, all rules are _global_! Inline styles, on the other hand, are applied to a single HTML element. This can be useful for prototyping and for passing variables to CSS. However, this tends to be verbose and hard to maintain, so we tend not to do it often in production code. 98 | 99 | 105 | 106 | 107 | 108 | My page 109 | 110 | 111 |

Inline styles!

112 | 113 | `, 114 | }} 115 | /> 116 | 117 | ## Selectors 118 | 119 | The selector is the part of the rule that lets us choose which HTML element(s) our rule should target. 120 | 121 | The 3 most common selectors are: tag, id, and class. 122 | 123 | ### Tag selector 124 | 125 | We use the tag selector to target _every element_ with any given tag name. 126 | 127 | In this example, we target every `p` element. 128 | 129 | 135 | 136 | 137 | 138 | My page 139 | 144 | 145 | 146 |

Hello

147 |

World

148 |

!

149 | 150 | `, 151 | }} 152 | /> 153 | 154 | ### Class selector 155 | 156 | We use the class selector to target every element with a specific `class` attribute. This gives us more granular control than the tag selector, since we can decide which elements the CSS rule should apply to. We can also apply the same class to elements with different tag names. 157 | 158 | The class selector has a `.` prefix followed by value of the `class` attribute we want to target. 159 | 160 | 166 | 167 | 168 | 169 | My page 170 | 175 | 176 | 177 |

Hello

178 |

World

179 |

!

180 | 181 | `, 182 | }} 183 | /> 184 | 185 | ### Id selector 186 | 187 | We use the id selector to target one element with a specific `id` attribute. This gives us even more granular control than the class selector. We often want to use this when we know we'll only have one instance of a given element. 188 | 189 | The id selector has a `#` prefix followed by the value of the `id` attribute we want to target. 190 | 191 | 197 | 198 | 199 | 200 | My page 201 | 206 | 207 | 208 |

Hello

209 |

World

210 |

!

211 | 212 | `, 213 | }} 214 | /> 215 | 216 | ## Cascading 217 | 218 | The **cascade** is the algorithm that browsers use to determine the precedence of declarations when multiple declarations target the same element. 219 | 220 | We're allowed to have any number of CSS rules target the same element. For example, consider the element `

Welcome!

`. We might have a selector for `#my-example`, `.my-paragraph`, _and_ `p` all at the same time. We could even have multiple rules with the selector `p`. How will these rules applied to the `p`? 221 | 222 | First, if two rules with the same selector exist, whichever comes later in the stylesheet wins. In the following example, we have two rules with a `color` declaration that both use the `p` selector - in this case, the one that comes later in the style tag wins, so the paragraph is green. 223 | 224 | 230 | 231 | 232 | 233 | My page 234 | 242 | 243 | 244 |

Welcome!

245 | 246 | `, 247 | }} 248 | /> 249 | 250 | ## Specificity 251 | 252 | In general, more specific rules take precedence over more generic rules. 253 | 254 | For ids, classes, and tags: the `#id` selector is the most specific, `.class` is less specific, and `tag` is the most generic. Even if a `.class` element comes later in the file, an `#id` will tag precedence. 255 | 256 | 262 | 263 | 264 | 265 | My page 266 | 277 | 278 | 279 |

Welcome!

280 | 281 | `, 282 | }} 283 | /> 284 | 285 | ## Targeting multiple elements 286 | 287 | If we want to reuse the same declarations for multiple selectors, there's shorter syntax to do so. This is common for applying styles to all elements of a certain category, e.g. headings, or for resetting browser-default styles. 288 | 289 | 295 | 296 | 297 | 298 | My page 299 | 304 | 305 | 306 |

Heading 1

307 |

Heading 2

308 |

Heading 3

309 | 310 | `, 311 | }} 312 | /> 313 | 314 | ## Using multiple classes 315 | 316 | We can apply multiple classes to the same element. This gives us more flexibility to mix-and-match different styles. 317 | 318 | 324 | 325 | 326 | 327 | My page 328 | 336 | 337 | 338 |

Heading 1

339 |

Heading 2

340 |

Heading 3

341 | 342 | `, 343 | }} 344 | /> 345 | -------------------------------------------------------------------------------- /pages/html_and_css/units.mdx: -------------------------------------------------------------------------------- 1 | ## Pixels 2 | 3 | Pixels are convenient because they're always consistent: no matter where a `px` is used, it always means the same thing. However, this means that we can't easily scale _all_ of our font sizes at once in certain contexts (e.g. the user wants to increase the font size) - we have to go through and set a new `px` value anywhere we use `px`. 4 | 5 | 11 | 12 | 13 | 14 | My page 15 | 26 | 27 | 28 |

Heading 1

29 |

Heading 2

30 |

Heading 3

31 | 32 | `, 33 | }} 34 | /> 35 | 36 | ## Ems & Rems 37 | 38 | The units `em` are relative to the parent's font size, while `rem` are relatively to the root font size (the size set on the `html` element). 39 | 40 | 46 | 47 | 48 | 49 | My page 50 | 64 | 65 | 66 |

Heading 1

67 |

Heading 2

68 |

Heading 3

69 | 70 | `, 71 | }} 72 | /> 73 | 74 | ### When to use Ems and Rems? 75 | 76 | These units are most commonly used for fonts and spacing. We should use them anywhere we may want a large portion of our site to scale up or down. 77 | 78 | ## Supporting system font size 79 | 80 | It's common to set the root font size to a percent, which then is based on the browser-provided default font size. The user may increase or decrease their font size across all their webpages through their system or browser settings, and we can use this approach to respect their preference. 81 | 82 | Many websites use `62.5%` as the root font size - since the default browser font size is typically `16px`, this means the root font size is `10px`, which can make the math easier when converting from design mockups (e.g. a `40px` font size or spacing value in a mockup from a designer would be `4rem`). 83 | 84 | 90 | 91 | 92 | 93 | My page 94 | 108 | 109 | 110 |

Heading 1

111 |

Heading 2

112 |

Heading 3

113 | 114 | `, 115 | }} 116 | /> 117 | -------------------------------------------------------------------------------- /pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Web Development Express 3 | subtitle: A guide to web development 4 | --- 5 | 6 | ## About this guide 7 | 8 | This guide accompanies my 8-day web development class. The class schedule is roughly: 9 | 10 | - Day 1-2: HTML & CSS 11 | - Day 3-4: TypeScript 12 | - Day 5-8: React 13 | 14 | Feel free to play with the examples in this guide if you want another look at what I went over during the class. 15 | -------------------------------------------------------------------------------- /pages/intro_slides.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SpectacleSlideshow from '../components/SpectacleSlideshow' 3 | 4 | const introSlides = require('!!babel-loader!spectacle-mdx-loader!../slides/intro.mdx') 5 | 6 | const slides = introSlides.default.map((slide, index, list) => { 7 | return { 8 | sectionName: `Intro`, 9 | SlideComponent: slide, 10 | NotesComponent: introSlides.notes[index], 11 | } 12 | }) 13 | 14 | export default () => 15 | -------------------------------------------------------------------------------- /pages/playgrounds.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import EditorConsole from '../components/EditorConsole' 3 | 4 | export default function Playgrounds() { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /pages/react.mdx: -------------------------------------------------------------------------------- 1 | For React, I'll be referring to my React guide, https://www.react.express/ 2 | -------------------------------------------------------------------------------- /pages/schedule.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Heading1, 4 | Heading3, 5 | List, 6 | VerticalSpacer, 7 | UnorderedList, 8 | HorizontalSpacer, 9 | } from 'react-guidebook' 10 | import styled from 'styled-components' 11 | 12 | const Container = styled.div({ 13 | height: '100vh', 14 | display: 'flex', 15 | alignItems: 'stretch', 16 | justifyContent: 'center', 17 | overflow: 'hidden', 18 | padding: '100px 40px', 19 | background: `linear-gradient(135deg, #607d8b, #251542)`, 20 | }) 21 | 22 | const Inner = styled.div({ 23 | flex: '1 1 0', 24 | maxWidth: '1200px', 25 | margin: '0 auto', 26 | display: 'flex', 27 | alignItems: 'stretch', 28 | justifyContent: 'center', 29 | flexDirection: 'column', 30 | }) 31 | 32 | const Title = styled.h1(({ theme }) => ({ 33 | ...theme.textStyles.title, 34 | display: 'inline-block', // So the gradient doesn't extend beyond the text 35 | backgroundColor: theme.colors.title.left, // Fallback 36 | backgroundImage: `linear-gradient(45deg, ${theme.colors.title.left}, ${theme.colors.title.right})`, 37 | backgroundSize: '100%', 38 | backgroundRepeat: 'repeat', 39 | WebkitBackgroundClip: 'text', 40 | WebkitTextFillColor: 'transparent', 41 | MozBackgroundClip: 'text', 42 | MozTextFillColor: 'transparent', 43 | backgroundClip: 'text', 44 | textFillColor: 'transparent', 45 | whiteSpace: 'pre', 46 | })) 47 | 48 | const Section = styled.section(({ theme }) => ({ 49 | flex: '1 1 0', 50 | padding: '30px 40px', 51 | background: 'white', 52 | borderRadius: '4px', 53 | // border: `1px solid ${theme.colors.textDecorativeLight}`, 54 | boxShadow: '0 2px 8px rgba(0,0,0,0.2), 0 4px 20px rgba(0,0,0,0.1)', 55 | // width: '33%', 56 | })) 57 | 58 | const StyledUnorderedList = styled(UnorderedList)({ 59 | paddingLeft: '30px', 60 | }) 61 | 62 | const StyledHeading = styled(Heading3)({ 63 | marginBottom: '6px', 64 | marginTop: '16px', 65 | background: 'rgba(0,0,0,0.05)', 66 | display: 'inline-block', 67 | padding: '0 10px', 68 | borderRadius: '2px', 69 | }) 70 | 71 | const ListItem = styled(List)({ 72 | fontSize: '1.1rem', 73 | }) 74 | 75 | const Row = styled.div({ 76 | flex: '1 1 0', 77 | display: 'flex', 78 | flexDirection: 'row', 79 | }) 80 | 81 | const Chunk = styled.div({ 82 | flex: '1 1 0', 83 | }) 84 | 85 | export default function BoxModelDiagramPage() { 86 | return ( 87 | 88 | 89 |
96 |
97 | {'Day 1-2: HTML & CSS'} 98 | 99 | 100 | Day 1 101 | 102 | Elements 103 | Styling 104 | Box Model 105 | Browser Dev Tools 106 | 107 | 108 | 109 | Day 2 110 | 111 | Flexbox 112 | Advanced Selectors 113 | Units 114 | Responsive Design 115 | Accessibility 116 | Limitations 117 | 118 | 119 | 120 |
121 | 122 |
123 | 128 | {'Day 3-4: TypeScript'} 129 | 130 | 131 | 132 | Day 3 133 | 134 | TypeScript Overview 135 | Type Declarations 136 | Type Refinement 137 | DOM 138 | 139 | 140 | 141 | Day 4 142 | 143 | Tools 144 | Imports and Exports 145 | Events 146 | Equality 147 | 148 | 149 | 150 |
151 |
152 | 153 |
154 | 159 | {'Day 5-8: React'} 160 | 161 | 162 | 163 | Day 5 - Fundamentals 164 | 165 | {'JSX, Elements & Components'} 166 | {'Props & Children'} 167 | Styling 168 | Conditional Rendering 169 | {'Lists & Keys'} 170 | 171 | 172 | 173 | Day 6 - Hooks 174 | 175 | Hooks 176 | Rules of hooks 177 | Built-in hooks 178 | Custom hooks 179 | 180 | 181 | 182 | Day 7 - Data 183 | 184 | XHR 185 | Promises 186 | Async/Await 187 | Suspense 188 | Working with Contexts 189 | 190 | 191 | 192 | Day 8 - Production-readiness 193 | 194 | Performance 195 | Devtools 196 | Lifecycle 197 | Patterns 198 | Testing 199 | 200 | 201 | 202 |
203 |
204 |
205 | ) 206 | } 207 | -------------------------------------------------------------------------------- /pages/slides.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SpectacleSlideshow from '../components/SpectacleSlideshow' 3 | 4 | const introSlides = require('!!babel-loader!spectacle-mdx-loader!../slides/index.mdx') 5 | 6 | const slides = introSlides.default.map((slide, index) => { 7 | return { 8 | sectionName: ``, 9 | SlideComponent: slide, 10 | NotesComponent: introSlides.notes[index], 11 | } 12 | }) 13 | 14 | export default () => 15 | -------------------------------------------------------------------------------- /pages/syntax_diagram.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { parseQueryParameters, parseUrl, useRouter } from 'react-guidebook' 3 | import styled from 'styled-components' 4 | import SyntaxDiagram, { Token } from '../components/SyntaxDiagram' 5 | 6 | const Container = styled.div({ 7 | height: '100vh', 8 | display: 'flex', 9 | alignItems: 'center', 10 | justifyContent: 'center', 11 | overflow: 'hidden', 12 | }) 13 | 14 | function getTokens(fragment: string): Token[] | undefined { 15 | const data = parseQueryParameters(fragment).data 16 | 17 | if (!data) return 18 | 19 | return JSON.parse(data).tokens 20 | } 21 | 22 | export default function SyntaxDiagramPage() { 23 | const router = useRouter() 24 | const { fragment } = parseUrl(router.clientPath) 25 | const tokens = getTokens(fragment) || [] 26 | 27 | return ( 28 | 29 | 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /pages/typescript.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TypeScript 3 | --- 4 | 5 | ## Web browsers run JavaScript 6 | 7 | JavaScript is the only language that web browsers can run natively. 8 | 9 | JavaScript is weakly-typed, so the following code is valid: 10 | 11 | 20 | 21 | ## TypeScript to the rescue 22 | 23 | TypeScript is a superset of JavaScript that supports type annotations. 24 | 25 | The above code is no longer valid: 26 | 27 | 36 | 37 | ## How TypeScript works 38 | 39 | Type annotations are stripped out during a build step. 40 | 41 | Because of this, there may be no way to tell the type of your variables at runtime. E.g. in the example below, there's no way to tell that the `rect` object is a `Rectangle` at runtime, since `Rectangle` doesn't exist in the compiled code. 42 | 43 | 62 | 63 | TODO: Mention sourcemaps 64 | 65 | ## Comparisons with other languages 66 | 67 | - **Swift**: https://dabbott.github.io/webdev-projects/cheatsheets/swift-typescript.html 68 | 69 | > Go through cheatsheet up to anonymous functions 70 | 71 | ## TypeScript Playground 72 | 73 | You can play with TypeScript in the playground on the TypeScript docs here: https://www.typescriptlang.org/play 74 | -------------------------------------------------------------------------------- /pages/typescript/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "type_declarations", 4 | "type_refinement", 5 | "dom", 6 | "imports_and_exports", 7 | "events", 8 | "equality", 9 | "immutability", 10 | "context", 11 | "tools" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /pages/typescript/context.mdx: -------------------------------------------------------------------------------- 1 | See https://www.typescript.express/functions/context 2 | -------------------------------------------------------------------------------- /pages/typescript/dom.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DOM 3 | --- 4 | 5 | The DOM or "Document Object Model" is an in-memory representation of an HTML page. 6 | 7 | We can use TypeScript to read and modify the UI of a webpage using the DOM. 8 | 9 | ## Accessing elements 10 | 11 | We typically use `document.querySelector` or `document.querySelectorAll` to grab a reference to existing elements in our HTML document. We can then modify them using DOM APIs. 12 | 13 | 20 | 21 | 22 | 23 | My page 24 | 25 | 26 | 27 | 28 | 34 | `, 35 | }} 36 | /> 37 | 38 | ## Creating elements 39 | 40 | 50 | -------------------------------------------------------------------------------- /pages/typescript/equality.mdx: -------------------------------------------------------------------------------- 1 | See https://www.typescript.express/syntax/equality 2 | 3 | See https://www.typescript.express/syntax/logical_operators 4 | -------------------------------------------------------------------------------- /pages/typescript/events.mdx: -------------------------------------------------------------------------------- 1 | ## Event loop 2 | 3 | The event loop is queue of functions. When a program starts, the engine evaluates all JavaScript code. Then the engine goes "idle" until new "events" (functions) are added to the event loop. New events may be inserted into the event loop at any time, either by our code or the JavaScript environment (usually in response to I/O), which are then executed in sequence. We use callback functions to perform actions as a result of events. 4 | 5 | ## setTimeout 6 | 7 | To set a one-off timer, we use `setTimeout`. 8 | 9 | 16 | 17 | 18 | 19 | My page 20 | 21 | Waiting... 22 | 27 | `, 28 | }} 29 | /> 30 | 31 | ## setInterval 32 | 33 | To set a repeating timer, we use `setInterval`. 34 | 35 | 42 | 43 | 44 | 45 | My page 46 | 47 | Waiting... 48 | 53 | `, 54 | }} 55 | /> 56 | 57 | ## Click events 58 | 59 | Events bubble up from inner to outer elements. We can prevent this by calling `event.stopPropagation()`. 60 | 61 | If we want to add multiple events, we can use `element.addEventListener` instead of assigning directly to the event handler, e.g. `outer.addEventListener('click', () => { ... })`. 62 | 63 | Most global events can be assigned to the `document` object. 64 | 65 | 72 | 73 | 74 | 75 | My page 76 | 86 | 87 | 88 |
89 |
90 |
91 | 92 | 108 | `, 109 | }} 110 | /> 111 | 112 | ## Capture and bubble phases 113 | 114 | ![Event phase diagram](https://d259t2jj6zp7qm.cloudfront.net/images/c_scale%2Cw_300-capture-bubble_nbgmry.png) 115 | 116 | > From: https://developer.salesforce.com/blogs/developer-relations/2017/08/depth-look-lightning-component-events.html 117 | 118 | The third parameter to `addEventListener` can bind events to the capture phase, e.g. `outer.addEventListener('click', () => { ... }, true)`. 119 | -------------------------------------------------------------------------------- /pages/typescript/immutability.mdx: -------------------------------------------------------------------------------- 1 | Most of the time, we use mutable objects and arrays, but we often want to treat them as if they were immutable - instead of mutating them, we make a clone and modify the clone. 2 | 3 | We typically do this through "spread" syntax: 4 | 5 | See: https://www.typescript.express/syntax/spread 6 | 7 | We sometimes do this for deeply nested objects, cloning each intermediate object/array. 8 | -------------------------------------------------------------------------------- /pages/typescript/imports_and_exports.mdx: -------------------------------------------------------------------------------- 1 | See https://www.typescript.express/modules/imports_and_exports 2 | -------------------------------------------------------------------------------- /pages/typescript/projects.mdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabbott/webdev-express/1a394c10d1bb8afafd2b712400f1f489f9871393/pages/typescript/projects.mdx -------------------------------------------------------------------------------- /pages/typescript/projects/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": ["todo_list", "drawing"] 3 | } 4 | -------------------------------------------------------------------------------- /pages/typescript/projects/drawing.mdx: -------------------------------------------------------------------------------- 1 | ## Completed Example 2 | 3 |