diff --git a/src/SidebarProvider.tsx b/src/SidebarProvider.tsx new file mode 100644 index 0000000..c30a2dd --- /dev/null +++ b/src/SidebarProvider.tsx @@ -0,0 +1,35 @@ +import React, { useState } from "react" + +interface SidebarProviderProps { + children: React.ReactNode +} + +export interface SidebarContextInterface { + current: number + setCurrent: (index: number) => void +} + +export const SidebarContext = React.createContext( + null +) + +export function SidebarProvider({ + children, +}: SidebarProviderProps): JSX.Element { + const [current, setCurrent] = useState(0) + + function selectFirstLevel(index: number) { + setCurrent(index) + } + + return ( + + {children} + + ) +} diff --git a/src/components/Markdown/styles.tsx b/src/components/Markdown/styles.tsx index 7a62343..caef2c2 100644 --- a/src/components/Markdown/styles.tsx +++ b/src/components/Markdown/styles.tsx @@ -2,21 +2,12 @@ import styled, { css } from "styled-components" import { BASE_FONT_SIZE, BASE_LINE_HEIGHT, - MODULAR_SCALE, fontFamily, + modularScale, } from "../../design/typography" -function modularFontSize(power: number) { - return BASE_FONT_SIZE * Math.pow(MODULAR_SCALE, power) -} - -function modularScale(power: number) { - const fontSize = modularFontSize(power) - - // attempt to fit line-height a little larger than font-size - const paddedLineHeight = fontSize * 1.1 - const lineHeight = - paddedLineHeight - (paddedLineHeight % BASE_LINE_HEIGHT) + BASE_LINE_HEIGHT +function modularScaleCSS(power: number) { + const { fontSize, lineHeight } = modularScale(power) return css` font-size: ${fontSize}px; @@ -28,6 +19,14 @@ export const MarkdownWrapper = styled.div` font-size: ${BASE_FONT_SIZE}px; line-height: ${BASE_LINE_HEIGHT}px; + & > :first-child { + margin-top: 0; + } + + & > :first-child { + margin-bottom: 0; + } + p { margin: ${BASE_LINE_HEIGHT}px 0; } @@ -45,26 +44,26 @@ export const MarkdownWrapper = styled.div` } h1 { - ${modularScale(6)}; + ${modularScaleCSS(6)}; } h2 { - ${modularScale(5)}; + ${modularScaleCSS(5)}; } h3 { - ${modularScale(4)}; + ${modularScaleCSS(4)}; } h4 { - ${modularScale(3)}; + ${modularScaleCSS(3)}; } h5 { - ${modularScale(2)}; + ${modularScaleCSS(2)}; } h6 { - ${modularScale(1)}; + ${modularScaleCSS(1)}; } ` diff --git a/src/components/ResourceHeader/index.tsx b/src/components/ResourceHeader/index.tsx new file mode 100644 index 0000000..6735ed0 --- /dev/null +++ b/src/components/ResourceHeader/index.tsx @@ -0,0 +1,60 @@ +import React from "react" + +import ChevronUp from "../../icons/chevron-up.svg" +import * as SC from "./styles" + +interface ResourceHeaderProps { + title: any + authors: any + createdAt: any + timeToRead: any + recommendedReading: any +} + +export function ResourceHeader({ + title, + authors, + createdAt, + timeToRead, + recommendedReading, +}: ResourceHeaderProps) { + const date = new Date(createdAt) + const month = date.toLocaleString("default", { month: "long" }) + const day = date.getDate() + const year = date.getUTCFullYear() + const dateToHuman = `${month} ${day}, ${year}` + + return ( + + {title} + + + + {authors.map(author => ( + + ))} + + + {authors.length} contributor{authors.lenght > 1 && "s"} + + {dateToHuman} + + {timeToRead} minute{timeToRead !== 1 && "s"} read time + + + + {recommendedReading && ( + + Recommended reading + {recommendedReading.map(item => { + return ( + + {item} + + ) + })} + + )} + + ) +} diff --git a/src/components/ResourceHeader/styles.tsx b/src/components/ResourceHeader/styles.tsx new file mode 100644 index 0000000..71162d0 --- /dev/null +++ b/src/components/ResourceHeader/styles.tsx @@ -0,0 +1,69 @@ +import styled from "styled-components" +import { fontFamily, modularScale } from "../../design/typography" + +export const ResourceHeaderWrapper = styled.div` + border-bottom: 1px solid #dbdbdb; + padding-bottom: 32px; + margin-bottom: 32px; +` + +export const Top = styled.div` + display: flex; + align-items: center; +` + +export const Title = styled.h1` + font-family: ${fontFamily.header}; + font-size: ${modularScale(6).fontSize}px; + line-height: ${modularScale(6).lineHeight}px; + letter-spacing: -1.75px; +` + +export const AuthorAvatars = styled.div` + margin-right: 16px; +` + +export const AuthorAvatar = styled.img` + width: 32px; + height: 32px; + border: 2px solid #fff; + border-radius: 50%; +` + +export const Meta = styled.div` + & + &::before { + content: "•"; + margin: 0 8px; + } +` + +export const RecommendedReading = styled.div` + margin-top: 16px; +` + +export const ReadLink = styled.div` + display: flex; + align-items: center; + color: #04b0a6; + margin-top: 4px; + margin-left: 20px; + cursor: pointer; + + &:hover { + color: #045551; + } + + svg { + width: 14px; + transform: rotate(90deg); + margin-right: 6px; + } + + svg path { + fill: #04b0a6; + } +` + +export const ReadLinkText = styled.div` + text-decoration: underline; +` diff --git a/src/components/ResourcesLayout/index.tsx b/src/components/ResourcesLayout/index.tsx index 97298bb..0ffc020 100644 --- a/src/components/ResourcesLayout/index.tsx +++ b/src/components/ResourcesLayout/index.tsx @@ -6,35 +6,22 @@ */ import React, { PropsWithChildren } from "react" -import { useStaticQuery, graphql } from "gatsby" import { GlobalStyles } from "../../globalStyles" import { ResourcesSidebar } from "../ResourcesSidebar" -import { Footer } from "../Footer" import * as SC from "./styles" +import { SidebarProvider } from "../../SidebarProvider" export function ResourcesLayout({ children }: PropsWithChildren<{}>) { - const data = useStaticQuery(graphql` - query { - site { - siteMetadata { - title - } - } - } - `) - return ( -
+ -

{data.site.siteMetadata.title}

- {children} + {children}
-
-
+ ) } diff --git a/src/components/ResourcesLayout/styles.tsx b/src/components/ResourcesLayout/styles.tsx index cc0bb79..232913d 100644 --- a/src/components/ResourcesLayout/styles.tsx +++ b/src/components/ResourcesLayout/styles.tsx @@ -2,12 +2,13 @@ import styled from "styled-components" export const Main = styled.div` display: flex; - margin: 128px auto 64px !important; width: 100%; + min-height: 100vh; ` export const MainContent = styled.main` flex: 1 1 auto; + margin-top: 128px; & > :first-child { margin-top: 0; @@ -17,3 +18,8 @@ export const MainContent = styled.main` margin-bottom: 0; } ` + +export const Container = styled.div` + width: 650px; + margin: 0 auto; +` diff --git a/src/components/ResourcesSidebar/index.tsx b/src/components/ResourcesSidebar/index.tsx index 822b8b9..6c68493 100644 --- a/src/components/ResourcesSidebar/index.tsx +++ b/src/components/ResourcesSidebar/index.tsx @@ -2,6 +2,8 @@ import React, { useState } from "react" import Tree from "react-treeview" import { useStaticQuery, graphql } from "gatsby" import useBuildTree from "./useBuildTree" +import useSidebar from "./../../hooks/useSidebar" +import banner from "../../images/tph-banner.png" import * as SC from "./styles" export interface IFile { @@ -56,32 +58,63 @@ const ALL_RESOURCES = graphql` } ` -function plantTree(item: IFileOrFolder) { +function plantTree(item: IFileOrFolder, index?: number, firstLevel?: boolean) { if (item.type === "file") { return ( - + {item.title} ) } - return + if (firstLevel && index !== undefined) { + return + } + + return } function Folder({ item }: { item: IFolder }) { - const [collapsed, setCollapse] = useState(false) + const [collapsed, setCollapse] = useState(true) function toggleCollapse() { setCollapse(prevState => !prevState) } return ( - {item.title}} - > - {item.children.map(plantTree)} - + + {item.title}} + > + {item.children.map(node => plantTree(node))} + + + ) +} + +function FirstLevelFolder({ item, index }: { item: IFolder; index: number }) { + const { current, setCurrent } = useSidebar() + const collapsed = current !== index + + return ( + + setCurrent(index)} + > + {item.title} + + + } + > + {item.children.map(node => plantTree(node))} + + ) } @@ -91,7 +124,10 @@ export function ResourcesSidebar() { return ( - {tree.map(node => plantTree(node))} + + + {tree.map((node, index) => plantTree(node, index, true))} + ) } diff --git a/src/components/ResourcesSidebar/styles.tsx b/src/components/ResourcesSidebar/styles.tsx index b3f0716..0bd74f8 100644 --- a/src/components/ResourcesSidebar/styles.tsx +++ b/src/components/ResourcesSidebar/styles.tsx @@ -1,10 +1,111 @@ import styled from "styled-components" import { Link } from "gatsby" +import ChevronUp from "../../icons/chevron-up.svg" export const ResourcesSidebarWrapper = styled.div` box-sizing: border-box; - padding: 0 16px; flex: 0 0 300px; + background: #f9f9f9; +` + +export const Banner = styled.img` + display: block; + width: 100%; +` + +export const TreeWrapper = styled.div` + width: 100%; + + & + & { + border-top: 1px solid #dbdbdb; + } +` + +export const Label = styled.div` + padding: 4px 0; +` + +export const FirstLabel = styled.div` + display: flex; + align-items: center; + width: 100%; + padding: 12px 15px 12px 0; + font-weight: 700; + color: #a9a9a9; + + &.active { + color: #000; + } + + svg { + margin-left: auto; + transform: rotate(90deg); + transition: transform 0.3s; + + path { + fill: #a9a9a9; + } + } + + &.active svg { + transform: rotate(0); + + path { + fill: #000; + } + } +` + +export const CollapseToggler = styled(ChevronUp)`` + +export const Inner = styled.div` + padding-top: 30px; + padding-left: 20px; + + .tree-view { + overflow-y: hidden; + } + + .tree-view_item { + display: flex; + cursor: pointer; + } + + .tree-view_children { + margin-left: 16px; + display: flex; + flex-direction: column; + align-items: flex-start; + } + + .tree-view_children-collapsed { + height: 0px; + } + + .tree-view_arrow { + cursor: pointer; + margin-right: 6px; + display: inline-block; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .tree-view_arrow:after { + content: "▾"; + } + + .tree-view_arrow.firstLevel { + display: none; + } + + .tree-view_arrow-collapsed { + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + } ` export const PageLink = styled(Link)` diff --git a/src/content/resources/javascript/callbacks/intro.md b/src/content/resources/javascript/callbacks/intro.md index c490815..3d8826d 100644 --- a/src/content/resources/javascript/callbacks/intro.md +++ b/src/content/resources/javascript/callbacks/intro.md @@ -5,6 +5,6 @@ created_at: 2019/07/27 title: Callbacks --- -# Callbacks are everywhere +## Callbacks are everywhere If you've been doing javascript for any amount of time you'll have noticed that callbacks appear in just about every single piece of code. diff --git a/src/content/resources/javascript/iterative-vs-functional.md b/src/content/resources/javascript/iterative-vs-functional.md index a515781..2e9bd98 100644 --- a/src/content/resources/javascript/iterative-vs-functional.md +++ b/src/content/resources/javascript/iterative-vs-functional.md @@ -2,10 +2,9 @@ authors: - "veksen#1060" created_at: "2019/07/27" +title: Iterative vs Functional array helpers --- -# Iterative vs Functional array helpers - -- placeholder -- ## find diff --git a/src/content/resources/javascript/promises/intro.md b/src/content/resources/javascript/promises/intro.md index a57e13e..aefcd68 100644 --- a/src/content/resources/javascript/promises/intro.md +++ b/src/content/resources/javascript/promises/intro.md @@ -9,14 +9,14 @@ recommended_reading: - javascript/es6/arrow-functions --- -# A Promise to Keep +## A Promise to Keep A `Promise` in Javascript represents an action that has already started, but one that will be completed at a later time. Much like in real life, when you create a promise, you are expected to fulfill that promise. However, sometimes things go wrong where you can no longer fulfill a promise you made. This is essentially the main idea behind how promises work in javascript. -## Basics +### Basics When you create a Promise or call a function that returns a Promise in Javascript, you're left with an object that can either resolve into the actual value that you were promised, or it @@ -24,7 +24,7 @@ can reject and leave you with an error for why that promise failed. We can access these values using the `.then` and `.catch` methods on the `Promise` object respectively. -## A Simple Example +### A Simple Example First, let's explore a bit of a made-up example. Imagine we have a promise-returning function called `getMembers` that retrieves all the members in a discord server. When we execute this @@ -50,7 +50,7 @@ getMembers("The Programmers Hangout").then(members => { This way we are able to make sure that we only try to `console.log` when the `getMembers` function has resolved and ready to be used. -## Beginner Mistakes +### Beginner Mistakes Promises are possibly the #1 most common source of confusion for beginners. In order to avoid falling in pitfalls yourself, you have to remember 2 things about Javascript when @@ -85,7 +85,7 @@ getWeather("Los Angeles").then(weather => { }) ``` -## Real World Example +### Real World Example Here is a real function that gets character information from the Rick and Morty API. You can try it in your browser if you want to test it out. diff --git a/src/content/resources/javascript/promises/simplifying-promises.md b/src/content/resources/javascript/promises/simplifying-promises.md index ce4eff4..6d60e13 100644 --- a/src/content/resources/javascript/promises/simplifying-promises.md +++ b/src/content/resources/javascript/promises/simplifying-promises.md @@ -2,10 +2,9 @@ authors: - "Xetera#0001" created_at: "2019/07/26" +title: Simplifying Promises --- -# Simplifying Promises - -- placeholder -- The first naive attempt, using new Promise for something that already returns a promise. diff --git a/src/content/resources/php/design-patterns/singleton.md b/src/content/resources/php/design-patterns/singleton.md index 1ee26a0..cb7f3fa 100644 --- a/src/content/resources/php/design-patterns/singleton.md +++ b/src/content/resources/php/design-patterns/singleton.md @@ -2,10 +2,9 @@ authors: - "supergrecko#3434" created_at: "2019/07/27" +title: Singletons --- -# Singletons - A singleton is a class which is only instantiated once during runtime. This is done by keeping a static property containing its instance on the singleton class. There are multiple benefits to using a singleton class @@ -16,7 +15,7 @@ There are multiple benefits to using a singleton class By using a singleton instead of a static class we expose a cleaner class to use and we can use regular instance properties instead of static properties. -# Creating a Singleton in PHP +## Creating a Singleton in PHP Creating a class which can be used as a singleton is very simple. @@ -50,7 +49,7 @@ class Singleton } ``` -# Testing our Singleton +## Testing our Singleton To give a little functionality to our freshly baked Singleton we add these three members to the class @@ -93,7 +92,7 @@ $first->setWord("Banana"); var_dump($first->getWord() === $second->getWord()); // bool(true) ``` -# Further Research +## Further Research Here's a couple links which will help you understand Singletons better. diff --git a/src/content/resources/python/basics.md b/src/content/resources/python/basics.md index 4a9fb27..41ee2b2 100644 --- a/src/content/resources/python/basics.md +++ b/src/content/resources/python/basics.md @@ -2,9 +2,10 @@ authors: - "Xetera#0001" date: 2019/06/26 +title: Some Python Article --- -# Python +## Python This is a placeholder post diff --git a/src/design/typography.ts b/src/design/typography.ts index f91a1d6..3d6924d 100644 --- a/src/design/typography.ts +++ b/src/design/typography.ts @@ -5,6 +5,24 @@ export const BASE_FONT_SIZE = 18 export const BASE_LINE_HEIGHT = 25 export const MODULAR_SCALE = 1.1487 +function modularFontSize(power: number) { + return BASE_FONT_SIZE * Math.pow(MODULAR_SCALE, power) +} + +export function modularScale(power: number) { + const fontSize = modularFontSize(power) + + // attempt to fit line-height a little larger than font-size + const paddedLineHeight = fontSize * 1.1 + const lineHeight = + paddedLineHeight - (paddedLineHeight % BASE_LINE_HEIGHT) + BASE_LINE_HEIGHT + + return { + fontSize, + lineHeight, + } +} + export const fontFamily = { header: FONT_MONTSERRAT, body: FONT_OXYGEN, diff --git a/src/globalStyles.tsx b/src/globalStyles.tsx index be4b604..074f345 100644 --- a/src/globalStyles.tsx +++ b/src/globalStyles.tsx @@ -11,6 +11,7 @@ export const GlobalStyles = createGlobalStyle` } body { + margin: 0; font-size: ${BASE_FONT_SIZE}px; line-height: ${BASE_LINE_HEIGHT}px; } @@ -33,51 +34,4 @@ export const GlobalStyles = createGlobalStyle` padding-left: 2.8em; overflow: initial; } - - /* TODO: probably extract this */ - .tree-view { - overflow-y: hidden; - } - - .tree-view_item { - display: flex; - cursor: pointer; - } - - .tree-view_item, - .tree-view_children > div { - padding: 4px 0; - } - - .tree-view_children { - margin-left: 16px; - display: flex; - flex-direction: column; - align-items: flex-start; - } - - .tree-view_children-collapsed { - height: 0px; - } - - .tree-view_arrow { - cursor: pointer; - margin-right: 6px; - display: inline-block; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - } - - .tree-view_arrow:after { - content: "▾"; - } - - .tree-view_arrow-collapsed { - -webkit-transform: rotate(-90deg); - -moz-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - transform: rotate(-90deg); - } ` diff --git a/src/hooks/useSidebar.tsx b/src/hooks/useSidebar.tsx new file mode 100644 index 0000000..ab3a4b4 --- /dev/null +++ b/src/hooks/useSidebar.tsx @@ -0,0 +1,12 @@ +import { useContext } from "react" +import { SidebarContext, SidebarContextInterface } from "../SidebarProvider" + +export default function useSidebar(): SidebarContextInterface { + const sidebarContext = useContext(SidebarContext) + + if (!sidebarContext) { + throw Error("Need context") + } + + return sidebarContext +} diff --git a/src/icons/chevron-up.svg b/src/icons/chevron-up.svg new file mode 100644 index 0000000..0eb6bd8 --- /dev/null +++ b/src/icons/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/tph-banner.png b/src/images/tph-banner.png new file mode 100644 index 0000000..945bd65 Binary files /dev/null and b/src/images/tph-banner.png differ diff --git a/src/templates/languagePost.tsx b/src/templates/languagePost.tsx index 0f0ef2c..8d3b15e 100644 --- a/src/templates/languagePost.tsx +++ b/src/templates/languagePost.tsx @@ -3,14 +3,22 @@ import { graphql } from "gatsby" import { Markdown } from "../components/Markdown" import { SEO } from "../components/SEO" import { ResourcesLayout } from "../components/ResourcesLayout" +import { ResourceHeader } from "../components/ResourceHeader" // @todo maybe find alternative type for data function LanguagePost({ data }: any) { - const { html, frontmatter } = data.file.post + const { html, fields, frontmatter, timeToRead } = data.file.post console.log(data) return ( + ) @@ -31,9 +39,11 @@ export const query = graphql` } } frontmatter { - date + created_at title + recommended_reading } + timeToRead } } }