feat: style resources page (#30)

feat: style resources sidebar
This commit is contained in:
Jean-Philippe Sirois
2019-07-30 11:34:03 -04:00
20 changed files with 401 additions and 113 deletions
+35
View File
@@ -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<SidebarContextInterface | null>(
null
)
export function SidebarProvider({
children,
}: SidebarProviderProps): JSX.Element {
const [current, setCurrent] = useState(0)
function selectFirstLevel(index: number) {
setCurrent(index)
}
return (
<SidebarContext.Provider
value={{
current,
setCurrent: selectFirstLevel,
}}
>
{children}
</SidebarContext.Provider>
)
}
+17 -18
View File
@@ -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)};
}
`
+60
View File
@@ -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 (
<SC.ResourceHeaderWrapper>
<SC.Title>{title}</SC.Title>
<SC.Top>
<SC.AuthorAvatars>
{authors.map(author => (
<SC.AuthorAvatar src={author.avatar} />
))}
</SC.AuthorAvatars>
<SC.Meta>
{authors.length} contributor{authors.lenght > 1 && "s"}
</SC.Meta>
<SC.Meta>{dateToHuman}</SC.Meta>
<SC.Meta>
{timeToRead} minute{timeToRead !== 1 && "s"} read time
</SC.Meta>
</SC.Top>
{recommendedReading && (
<SC.RecommendedReading>
Recommended reading
{recommendedReading.map(item => {
return (
<SC.ReadLink>
<ChevronUp /> <SC.ReadLinkText>{item}</SC.ReadLinkText>
</SC.ReadLink>
)
})}
</SC.RecommendedReading>
)}
</SC.ResourceHeaderWrapper>
)
}
+69
View File
@@ -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;
`
+4 -17
View File
@@ -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 (
<div>
<SidebarProvider>
<GlobalStyles />
<SC.Main>
<ResourcesSidebar />
<SC.MainContent>
<h1>{data.site.siteMetadata.title}</h1>
{children}
<SC.Container>{children}</SC.Container>
</SC.MainContent>
</SC.Main>
<Footer />
</div>
</SidebarProvider>
)
}
+7 -1
View File
@@ -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;
`
+47 -11
View File
@@ -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 (
<SC.PageLink to={item.path} activeClassName="active">
<SC.PageLink key={item.title} to={item.path} activeClassName="active">
{item.title}
</SC.PageLink>
)
}
return <Folder item={item} />
if (firstLevel && index !== undefined) {
return <FirstLevelFolder key={item.title} item={item} index={index} />
}
return <Folder key={item.title} item={item} />
}
function Folder({ item }: { item: IFolder }) {
const [collapsed, setCollapse] = useState(false)
const [collapsed, setCollapse] = useState(true)
function toggleCollapse() {
setCollapse(prevState => !prevState)
}
return (
<Tree
collapsed={collapsed}
nodeLabel={<div onClick={toggleCollapse}>{item.title}</div>}
>
{item.children.map(plantTree)}
</Tree>
<SC.TreeWrapper>
<Tree
collapsed={collapsed}
nodeLabel={<SC.Label onClick={toggleCollapse}>{item.title}</SC.Label>}
>
{item.children.map(node => plantTree(node))}
</Tree>
</SC.TreeWrapper>
)
}
function FirstLevelFolder({ item, index }: { item: IFolder; index: number }) {
const { current, setCurrent } = useSidebar()
const collapsed = current !== index
return (
<SC.TreeWrapper>
<Tree
className="firstLevel"
collapsed={collapsed}
nodeLabel={
<SC.FirstLabel
className={!collapsed ? "active" : undefined}
onClick={() => setCurrent(index)}
>
{item.title}
<SC.CollapseToggler />
</SC.FirstLabel>
}
>
{item.children.map(node => plantTree(node))}
</Tree>
</SC.TreeWrapper>
)
}
@@ -91,7 +124,10 @@ export function ResourcesSidebar() {
return (
<SC.ResourcesSidebarWrapper>
{tree.map(node => plantTree(node))}
<SC.Banner src={banner} />
<SC.Inner>
{tree.map((node, index) => plantTree(node, index, true))}
</SC.Inner>
</SC.ResourcesSidebarWrapper>
)
}
+102 -1
View File
@@ -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)`
@@ -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.
@@ -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
@@ -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.
@@ -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.
@@ -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.
+2 -1
View File
@@ -2,9 +2,10 @@
authors:
- "Xetera#0001"
date: 2019/06/26
title: Some Python Article
---
# Python
## Python
This is a placeholder post
+18
View File
@@ -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,
+1 -47
View File
@@ -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);
}
`
+12
View File
@@ -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
}
+3
View File
@@ -0,0 +1,3 @@
<svg width="15" height="8" viewBox="0 0 15 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.72578 0.21176L0.213835 6.72587C-0.0712782 7.01171 -0.0712782 7.47481 0.213835 7.76136C0.498947 8.0472 0.962045 8.0472 1.24716 7.76136L7.24241 1.76394L13.2377 7.76064C13.5228 8.04648 13.9859 8.04648 14.2717 7.76064C14.5568 7.47481 14.5568 7.01099 14.2717 6.72515L7.75976 0.211038C7.47759 -0.0704669 7.00722 -0.070466 6.72578 0.21176Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+12 -2
View File
@@ -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 (
<ResourcesLayout>
<SEO title={frontmatter.title} />
<ResourceHeader
title={frontmatter.title}
authors={fields.authors}
createdAt={frontmatter.created_at}
timeToRead={timeToRead}
recommendedReading={frontmatter.recommended_reading}
/>
<Markdown content={html} />
</ResourcesLayout>
)
@@ -31,9 +39,11 @@ export const query = graphql`
}
}
frontmatter {
date
created_at
title
recommended_reading
}
timeToRead
}
}
}