diff --git a/public/fonts/iosevka-bold.ttf b/public/fonts/iosevka-bold.ttf new file mode 100755 index 0000000..f25a020 Binary files /dev/null and b/public/fonts/iosevka-bold.ttf differ diff --git a/public/fonts/iosevka-bold.woff b/public/fonts/iosevka-bold.woff new file mode 100755 index 0000000..235cab7 Binary files /dev/null and b/public/fonts/iosevka-bold.woff differ diff --git a/public/fonts/iosevka-bold.woff2 b/public/fonts/iosevka-bold.woff2 new file mode 100755 index 0000000..3dff58d Binary files /dev/null and b/public/fonts/iosevka-bold.woff2 differ diff --git a/public/fonts/iosevka-regular.ttf b/public/fonts/iosevka-regular.ttf new file mode 100755 index 0000000..8ccb849 Binary files /dev/null and b/public/fonts/iosevka-regular.ttf differ diff --git a/public/fonts/iosevka-regular.woff b/public/fonts/iosevka-regular.woff new file mode 100755 index 0000000..b051ca6 Binary files /dev/null and b/public/fonts/iosevka-regular.woff differ diff --git a/public/fonts/iosevka-regular.woff2 b/public/fonts/iosevka-regular.woff2 new file mode 100755 index 0000000..ec0f0a3 Binary files /dev/null and b/public/fonts/iosevka-regular.woff2 differ diff --git a/src/components/BubbleQuotes.tsx b/src/components/BubbleQuotes.tsx index a5566bb..81cd20c 100644 --- a/src/components/BubbleQuotes.tsx +++ b/src/components/BubbleQuotes.tsx @@ -2,10 +2,12 @@ import { css, jsx } from '@emotion/core' import Emoji, { emojiToComponent } from 'src/components/Emoji' import useTheme from 'src/hooks/useTheme' +import { allColors } from 'src/lib/theme/colors' interface BubbleQuoteProps { type: keyof typeof emojiToComponent children: React.ReactNode + backgroundColor?: keyof typeof allColors } type Size = 'md' | 'lg' @@ -25,48 +27,50 @@ const BubbleQuotes = ({ margin-bottom: ${spaces(1.75, true)}; ${ns} { font-size: ${size === 'lg' ? fontSizes(1.2) : fontSizes(1)}; - line-height: ${size === 'lg' ? lineHeights(1.5) : lineHeights(1.6)}; + line-height: ${size === 'lg' ? lineHeights(1.2) : lineHeights(1)}; } `} > - {quotes.map(({ type, children }, index) => ( -
+ {quotes.map( + ({ type, children, backgroundColor = 'lightYellow1' }, index) => (
- -
-
+ +
+
- {children} + border-radius: ${radii(0.5)}; + + ${ns} { + padding: ${spaces(1, true)} ${spaces(1, true)} 0; + } + `} + > + {children} +
- - ))} + ) + )} ) } diff --git a/src/components/Caption.tsx b/src/components/Caption.tsx new file mode 100644 index 0000000..a1fd9ab --- /dev/null +++ b/src/components/Caption.tsx @@ -0,0 +1,24 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import useTheme from 'src/hooks/useTheme' + +const Caption = ({ children }: { children: React.ReactNode }) => { + const { spaces, fontSizes, colors } = useTheme() + return ( +
+ {children} +
+ ) +} + +export default Caption diff --git a/src/components/Card.tsx b/src/components/Card.tsx new file mode 100644 index 0000000..ffcc54b --- /dev/null +++ b/src/components/Card.tsx @@ -0,0 +1,181 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import React from 'react' +import { allColors } from 'src/lib/theme/colors' +import useTheme from 'src/hooks/useTheme' +import { H3 } from 'src/components/ContentTags' + +export interface CardProps { + children: React.ReactNode + color?: 'default' + slideNumber?: number + slideCount?: number + isLast?: boolean + title?: React.ReactNode + footer?: { + content: React.ReactNode + color?: CardProps['color'] + } +} + +export interface CardState { + overrideColor?: CardProps['color'] +} + +export const backgroundColor = ( + color: NonNullable +): keyof typeof allColors => + ({ + default: 'lightYellow1' as const + }[color]) + +const slideLabelBgColor = ( + color: NonNullable +): keyof typeof allColors => + ({ + default: 'brown' as const + }[color]) + +const Card = ({ + title, + color = 'default', + children, + slideNumber, + slideCount, + isLast, + footer +}: CardProps) => { + const { ns, colors, fontSizes, spaces, radii } = useTheme() + return ( + <> +
+ {slideNumber && slideCount && ( +
+ <> + + Slide{' '} + + + {slideNumber} + {' '} + + / {slideCount} + + +
+ )} +
+
+ {title && ( +

+ {title} +

+ )} + {children} +
+ {footer && ( +
+ {footer.content} +
+ )} +
+
+ {!isLast && ( +
+ )} + + ) +} + +export default Card diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx new file mode 100644 index 0000000..8c65d1d --- /dev/null +++ b/src/components/CodeBlock.tsx @@ -0,0 +1,263 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import { useState } from 'react' +import useTheme from 'src/hooks/useTheme' +import Caption from 'src/components/Caption' +import PrismHighlight, { defaultProps } from 'prism-react-renderer' +import theme from 'src/lib/prismTheme' + +const CodeBlock = ({ + children, + shouldHighlight, + result, + // pointToRunButton, + defaultResultVisible, + caption, + noHighlight +}: { + children: string + shouldHighlight?: (lineNumber: number, tokenNumber: number) => boolean + result?: string + // pointToRunButton?: boolean + defaultResultVisible?: boolean + caption?: React.ReactNode + noHighlight?: boolean +}) => { + const [resultVisible] = useState(defaultResultVisible) + const { radii, colors, ns, maxWidths, spaces, fontSizes } = useTheme() + // const buttonOnClick = () => setResultVisible(true) + return ( +
+ {caption && ( +
+ {caption} +
+ )} + + {({ tokens, getLineProps, getTokenProps }) => ( +
+            
+ {tokens.map((line, i) => { + const { key: divKey, ...lineProps } = getLineProps({ + line, + key: i + }) + return ( +
+ {line.map((token, key) => { + const { key: spanKey, ...tokenProps } = getTokenProps({ + token, + key + }) + return ( + + ) + })} +
+ ) + })} +
+
+ )} +
+ {/* result && ( + <> +
+ {resultVisible ? ( +
+ + Result:{' '} + + + {result} + +
+ ) : ( + <> + + Run + + {pointToRunButton && ( + + ← Press this button! + + )} + + )} +
+ + )*/} +
+ ) +} + +export default CodeBlock diff --git a/src/components/ContentTags/H3.tsx b/src/components/ContentTags/H3.tsx new file mode 100644 index 0000000..410d2d4 --- /dev/null +++ b/src/components/ContentTags/H3.tsx @@ -0,0 +1,26 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import useTheme from 'src/hooks/useTheme' + +const H3 = (props: JSX.IntrinsicElements['h3']) => { + const { fontSizes, lineHeights, ns, spaces } = useTheme() + return ( +

+ ) +} + +export default H3 diff --git a/src/components/ContentTags/P.tsx b/src/components/ContentTags/P.tsx index 1266d41..0bd0469 100644 --- a/src/components/ContentTags/P.tsx +++ b/src/components/ContentTags/P.tsx @@ -2,7 +2,7 @@ import { css, jsx } from '@emotion/core' import useTheme from 'src/hooks/useTheme' -export const P = (props: JSX.IntrinsicElements['p']) => { +const P = (props: JSX.IntrinsicElements['p']) => { const { spaces } = useTheme() return (

{ /> ) } + +export default P diff --git a/src/components/ContentTags/index.tsx b/src/components/ContentTags/index.tsx index 207a07f..5352edc 100644 --- a/src/components/ContentTags/index.tsx +++ b/src/components/ContentTags/index.tsx @@ -1 +1,2 @@ -export { P } from 'src/components/ContentTags/P' +export { default as P } from 'src/components/ContentTags/P' +export { default as H3 } from 'src/components/ContentTags/H3' diff --git a/src/components/Emoji/CryingCat.tsx b/src/components/Emoji/CryingCat.tsx new file mode 100644 index 0000000..0a89310 --- /dev/null +++ b/src/components/Emoji/CryingCat.tsx @@ -0,0 +1,41 @@ +import React from 'react' + +const SvgCryingCat = (props: React.SVGProps) => ( + + + + + + + + + + + + + + +) + +export default SvgCryingCat diff --git a/src/components/Emoji/Question.tsx b/src/components/Emoji/Question.tsx new file mode 100644 index 0000000..0f39341 --- /dev/null +++ b/src/components/Emoji/Question.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const SvgQuestion = (props: React.SVGProps) => ( + + + + +) + +export default SvgQuestion diff --git a/src/components/Emoji/Run.tsx b/src/components/Emoji/Run.tsx new file mode 100644 index 0000000..fcf0c89 --- /dev/null +++ b/src/components/Emoji/Run.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const SvgRun = (props: React.SVGProps) => ( + + + + +) + +export default SvgRun diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index aadf7cd..22a37e5 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -1,9 +1,15 @@ /** @jsx jsx */ import { css, jsx } from '@emotion/core' import Bird from 'src/components/Emoji/Bird' +import CryingCat from 'src/components/Emoji/CryingCat' +import Question from 'src/components/Emoji/Question' +import Run from 'src/components/Emoji/Run' export const emojiToComponent = { - bird: Bird + bird: Bird, + cryingCat: CryingCat, + question: Question, + run: Run } const Emoji = ({ diff --git a/src/components/EmojiSeparator.tsx b/src/components/EmojiSeparator.tsx new file mode 100644 index 0000000..e370d1e --- /dev/null +++ b/src/components/EmojiSeparator.tsx @@ -0,0 +1,83 @@ +/** @jsx jsx */ +import { css, jsx, SerializedStyles } from '@emotion/core' +import React from 'react' +import Emoji, { emojiToComponent } from 'src/components/Emoji' +import { allFontSizes } from 'src/lib/theme/fontSizes' +import { allSpaces } from 'src/lib/theme/spaces' +import useTheme from 'src/hooks/useTheme' +import Caption from 'src/components/Caption' + +interface EmojiSeparatorProps { + emojis: ReadonlyArray + size?: 'md' + cssOverrides?: SerializedStyles + description?: React.ReactNode +} + +const fontSize = ( + size: NonNullable +): ReadonlyArray => + ({ + md: [2, 2.5] as const + }[size]) + +const margins = ( + size: NonNullable +): ReadonlyArray => + ({ + md: [1.25, 1.5] as const + }[size]) + +const SideSpace = ({ children }: { children: React.ReactNode }) => ( + + {children} + +) + +const EmojiSeparator = ({ + emojis, + size = 'md', + cssOverrides, + description +}: EmojiSeparatorProps) => { + const { spaces, ns, fontSizes } = useTheme() + return ( +

+ <> + + {emojis.map((emoji, index) => ( + + + + ))} + + {description && {description}} + +
+ ) +} + +export default EmojiSeparator diff --git a/src/components/GlobalStyles.tsx b/src/components/GlobalStyles.tsx index fd91edf..7477490 100644 --- a/src/components/GlobalStyles.tsx +++ b/src/components/GlobalStyles.tsx @@ -16,7 +16,7 @@ const GlobalStyles = ({ children }: { children: React.ReactNode }) => { font-size: 18px; color: ${colors('black')}; background: ${colors('lightYellow2')}; - line-height: ${lineHeights(1.6)}; + line-height: ${lineHeights(1)}; } body { @@ -57,6 +57,32 @@ const GlobalStyles = ({ children }: { children: React.ReactNode }) => { font-size: inherit; line-height: inherit; } + + @font-face { + font-family: 'Iosevka Web'; + font-weight: 400; + font-style: normal; + font-display: fallback; + src: url('/fonts/iosevka-regular.woff2') format('woff2'), + url('/fonts/iosevka-regular.woff') format('woff'), + url('/fonts/iosevka-regular.ttf') format('truetype'); + } + + @font-face { + font-family: 'Iosevka Web'; + font-weight: 700; + font-style: oblique; + font-display: fallback; + src: url('/fonts/iosevka-boldoblique.woff2') format('woff2'), + url('/fonts/iosevka-boldoblique.woff') format('woff'), + url('/fonts/iosevka-boldoblique.ttf') format('truetype'); + } + + code, + pre { + font-family: 'Iosevka Web', SFMono-Regular, Consolas, + Liberation Mono, Menlo, Courier, monospace; + } ` ]} /> diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 83dacbe..3f6e05c 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -6,14 +6,18 @@ import { siteTitle } from 'src/lib/meta' import InternalLink from 'src/components/InternalLink' const LogoLink = ({ children, ...props }: JSX.IntrinsicElements['h2']) => { - const { colors } = useTheme() + const { colors, spaces, radii } = useTheme() return (

{ { const url = `${baseUrl}/${articleKey}` const description = articlesData[articleKey]['description'] const title = articlesData[articleKey]['title'] const ogImage = `${baseUrl}/images/og-${articlesData[articleKey]['ogImage']}.png` + const { + colors, + fontSizes, + ns, + lineHeights, + letterSpacings, + spaces + } = useTheme() return ( @@ -33,7 +53,61 @@ const PostPage = ({ content={dateSchemaString(articlesData[articleKey]['date'])} /> -

Sorry, this page is under construction.

+
+ +
+

+ {title} +

+ + + + ) + } + ]} + > + {cards.map(({ title, content, footer }, index) => ( + + {content} + + ))}
) } diff --git a/src/lib/prismTheme.ts b/src/lib/prismTheme.ts new file mode 100644 index 0000000..d5bf56c --- /dev/null +++ b/src/lib/prismTheme.ts @@ -0,0 +1,75 @@ +// Converted from Yi light theme +// https://github.com/FormidableLabs/prism-react-renderer/tree/master/tools/themeFromVsCode +import { PrismTheme } from 'prism-react-renderer' + +const theme: PrismTheme = { + plain: { + color: '#383a42', + backgroundColor: '#fafafa' + }, + styles: [ + { + types: ['comment'], + style: { + color: 'rgb(160, 161, 167)', + fontStyle: 'italic' + } + }, + { + types: ['keyword', 'selector', 'changed'], + style: { + color: 'rgb(166, 38, 164)' + } + }, + { + types: ['operator'], + style: { + color: 'rgb(56, 58, 66)' + } + }, + { + types: ['constant', 'builtin', 'attr-name'], + style: { + color: 'rgb(152, 104, 1)' + } + }, + { + types: ['char', 'symbol'], + style: { + color: 'rgb(1, 132, 188)' + } + }, + { + types: ['variable', 'tag', 'deleted'], + style: { + color: 'rgb(228, 86, 73)' + } + }, + { + types: ['string', 'inserted'], + style: { + color: 'rgb(80, 161, 79)' + } + }, + { + types: ['punctuation'], + style: { + color: 'rgb(160, 161, 167)' + } + }, + { + types: ['function'], + style: { + color: 'rgb(64, 120, 242)' + } + }, + { + types: ['class-name'], + style: { + color: 'rgb(193, 132, 1)' + } + } + ] +} + +export default theme diff --git a/src/lib/theme/colors.ts b/src/lib/theme/colors.ts index 54512e5..90e0a85 100644 --- a/src/lib/theme/colors.ts +++ b/src/lib/theme/colors.ts @@ -2,7 +2,14 @@ export const allColors = { black: '#260808', lightYellow1: '#FEF5DD', lightYellow2: '#FFE8BF', - brown: '#917340' + lightPink1: '#FFF2E4', + lightPink2: '#FFDFC7', + brown: '#806538', + pink: '#FCCFC0', + white: '#FFFFFF', + white75: 'rgba(255, 255, 255, 0.75)', + paleGreen: '#C8DCC7', + red: '#DB4003' } const colors = (x: keyof typeof allColors) => allColors[x] diff --git a/src/lib/theme/fontSizes.ts b/src/lib/theme/fontSizes.ts index 2419720..11dbf9e 100644 --- a/src/lib/theme/fontSizes.ts +++ b/src/lib/theme/fontSizes.ts @@ -1,5 +1,6 @@ export const allFontSizes = { 0.75: 0.75, + 0.8: 0.8, 0.85: 0.85, 1: 1, 1.2: 1.2, diff --git a/src/lib/theme/letterSpacings.ts b/src/lib/theme/letterSpacings.ts index 438fcf1..1fd5750 100644 --- a/src/lib/theme/letterSpacings.ts +++ b/src/lib/theme/letterSpacings.ts @@ -1,6 +1,6 @@ export const allLetterSpacings = { - title: '-0.025em', - wide: '0.2em' + title: '-0.02em', + wide: '0.15em' } const letterSpacings = (x: keyof typeof allLetterSpacings) => diff --git a/src/lib/theme/lineHeights.ts b/src/lib/theme/lineHeights.ts index 63ff333..7e72e63 100644 --- a/src/lib/theme/lineHeights.ts +++ b/src/lib/theme/lineHeights.ts @@ -1,11 +1,20 @@ +import { allFontSizes } from 'src/lib/theme/fontSizes' + export const allLineHeights = { - 1: 1, - 1.1: 1.1, - 1.3: 1.3, - 1.5: 1.5, - 1.6: 1.6 + 0.75: 1.6, + 0.8: 1.6, + 0.85: 1.6, + 1: 1.6, + 1.2: 1.5, + 1.4: 1.4, + 1.6: 1.3, + 2: 1.25, + 2.5: 1.2, + 3: 1.1, + 4: 1, + 5: 1 } -const lineHeights = (x: keyof typeof allLineHeights) => allLineHeights[x] +const lineHeights = (x: keyof typeof allFontSizes) => allLineHeights[x] export default lineHeights diff --git a/src/lib/underConstructionCard.tsx b/src/lib/underConstructionCard.tsx new file mode 100644 index 0000000..f38e6fb --- /dev/null +++ b/src/lib/underConstructionCard.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { P } from 'src/components/ContentTags' + +const underConstructionCard = { + title: 'Under Construction', + content: ( + <> +

This article is currently under construction.

+ + ) +} + +export default underConstructionCard diff --git a/src/pages/generics.tsx b/src/pages/generics.tsx index 1c014c3..e696219 100644 --- a/src/pages/generics.tsx +++ b/src/pages/generics.tsx @@ -1,6 +1,63 @@ import React from 'react' import PostPage from 'src/components/PostPage' +import { P } from 'src/components/ContentTags' +import EmojiSeparator from 'src/components/EmojiSeparator' +import CodeBlock from 'src/components/CodeBlock' +import underConstructionCard from 'src/lib/underConstructionCard' -const Page = () => +const Page = () => ( + + {`// Confused by generics code like this? +function getProperty( + obj: T, + key: K +) { /* ... */ }`} +

+ If you’re (1) new to TypeScript, (2) new to{' '} + generics, and (3) struggling to + understand generics, then you’re exactly like me from 13 years + ago. +

+

+ Back then, I was studying the Java programming language in + college. Generics were a relatively new feature for Java at the + time. I was a beginner programmer then, and{' '} + generics felt very difficult. So I gave up on actually + understanding generics and used them without knowing what I was + doing. I never really understood generics until I had to use them + a lot for my job. +

+ + I gave up on understanding generics when I was learning Java + 13 years ago + + } + /> +

+ Similarly, if you feel that TypeScript generics are too + difficult—maybe you’ve only done frontend engineering in JS and + React/Vue, or you’re simply new to programming—this tutorial is + for you! I’ll try to help you actually understand generics. +

+

+ (If you didn’t find TypeScript generics to be very difficult, this + tutorial might be too easy for you.) +

+ + ) + }, + underConstructionCard + ]} + /> +) export default Page diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c8544bb..f90619c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -9,6 +9,7 @@ import { articlesList, articlesData } from 'src/lib/articles' import { dateString } from 'src/lib/date' import { siteTitle, baseUrl, siteDescription, siteOgImage } from 'src/lib/meta' import Head from 'next/head' +import { useState } from 'react' const ArticleLink = ({ title, @@ -19,7 +20,14 @@ const ArticleLink = ({ href: string date: string }) => { - const { colors, ns, fontSizes, spaces, lineHeights } = useTheme() + const { + colors, + ns, + fontSizes, + spaces, + lineHeights, + letterSpacings + } = useTheme() return (
  • { + const [visible, setVisible] = useState(defaultVisible) + const { colors } = useTheme() + return ( + <> +

    + Hello! I write tutorials to help{' '} + beginner programmers learn TypeScript. My tutorials might NOT + be as useful for experienced programmers learning TypeScript. + {!visible && ( + <> + {' '} + ( + setVisible(true)} + > + Read more… + + ) + + )} +

    + {visible && ( +

    + Why target beginner programmers? As TypeScript is + becoming popular, I believe that more beginner programmers (people + with only a few months of coding experience) will be learning it,{' '} + possibly as one of their first languages. So I wanted to + create tutorials specifically targeting beginner programmers. +

    + )} + + ) +} + const Index = () => { const { ns, spaces, fontSizes, letterSpacings, colors } = useTheme() return ( @@ -86,21 +145,7 @@ const Index = () => { type: 'bird', children: ( <> -

    - Hello! I write tutorials that might help{' '} - beginner programmers learn TypeScript. They might NOT - be as useful for experienced programmers who are new to - TypeScript. -

    -

    - Why targeting beginner programmers? As - TypeScript is becoming popular, I believe that more beginner - programmers (people with only a few months of coding - experience) will be learning it,{' '} - possibly as one of their first languages. So I wanted - to create tutorials specifically targeting beginner - programmers. -

    + ) }