diff --git a/docs/blog/2018-11-07-gatsby-for-apps/index.md b/docs/blog/2018-11-07-gatsby-for-apps/index.md index 8c12eb78195cb..8f62eda6ad9f2 100644 --- a/docs/blog/2018-11-07-gatsby-for-apps/index.md +++ b/docs/blog/2018-11-07-gatsby-for-apps/index.md @@ -8,6 +8,7 @@ tags: - applications - beyond static excerpt: Gatsby is great for not only static sites but also traditional web applications. Using Gatsby enables the benefits of both static and web applications so you don't have to sacrifice the advantages of one approach to reap the benefits of the other. +cover: images/what-if-i-told-you.jpg --- Gatsby is great for static sites. You probably know this! It’s equally great for web applications. You may not know this. Gatsby is great for building web experiences that leverage the benefits of both so called static sites and web applications -- simultaneously. You don't have to sacrifice the advantages of one approach to reap the benefits of the other. diff --git a/www/src/assets/newsletter-form-ornament.svg b/www/src/assets/newsletter-form-ornament.svg new file mode 100644 index 0000000000000..321fd2a746e68 --- /dev/null +++ b/www/src/assets/newsletter-form-ornament.svg @@ -0,0 +1,14 @@ + + + + + diff --git a/www/src/assets/ornaments.js b/www/src/assets/ornaments.js new file mode 100644 index 0000000000000..134a580bd5987 --- /dev/null +++ b/www/src/assets/ornaments.js @@ -0,0 +1,3 @@ +import NewsletterFormOrnament from "!raw-loader!./newsletter-form-ornament.svg" + +export { NewsletterFormOrnament } diff --git a/www/src/components/ecosystem/ecosystem-featured-item.js b/www/src/components/ecosystem/ecosystem-featured-item.js index 535837e77b2fb..2a55fd7c49e6d 100644 --- a/www/src/components/ecosystem/ecosystem-featured-item.js +++ b/www/src/components/ecosystem/ecosystem-featured-item.js @@ -4,6 +4,8 @@ import styled from "react-emotion" import { Link } from "gatsby" import Img from "gatsby-image" +import { HorizontalScrollerItem } from "../shared/horizontal-scroller" + import StarIcon from "react-icons/lib/md/star" import ArrowDownwardIcon from "react-icons/lib/md/arrow-downward" @@ -12,17 +14,12 @@ import presets, { colors } from "../../utils/presets" const MAX_DESCRIPTION_LENGTH = 100 -const EcosystemFeaturedItemRoot = styled(`li`)` - width: 85vw; - margin: 0 2px 0 0; - padding: 5px; - - :last-child { - margin-right: 0; - } +const EcosystemFeaturedItemRoot = styled(HorizontalScrollerItem)` + margin-right: ${rhythm(options.blockMarginBottom)}; ${presets.Tablet} { border-bottom: 1px solid ${colors.gray.superLight}; + box-shadow: none; margin: 0; padding: 0; width: auto; diff --git a/www/src/components/ecosystem/ecosystem-featured-items.js b/www/src/components/ecosystem/ecosystem-featured-items.js index aa8c822090737..46502614d339d 100644 --- a/www/src/components/ecosystem/ecosystem-featured-items.js +++ b/www/src/components/ecosystem/ecosystem-featured-items.js @@ -2,18 +2,19 @@ import React from "react" import PropTypes from "prop-types" import styled from "react-emotion" +import { + HorizontalScroller, + HorizontalScrollerContent, +} from "../shared/horizontal-scroller" + import presets, { colors } from "../../utils/presets" import { rhythm, options } from "../../utils/typography" import { scrollbarStyles } from "../../utils/styles" import { SCROLLER_CLASSNAME } from "../../utils/scrollers-observer" -export const EcosystemFeaturedItemsRootBase = styled(`div`)` - overflow-x: scroll; +const EcosystemFeaturedItemsRoot = styled(HorizontalScroller)` margin: ${rhythm(0.1)} -${rhythm(options.blockMarginBottom)}; - -webkit-overflow-scrolling: touch; -` -const EcosystemFeaturedItemsRoot = styled(EcosystemFeaturedItemsRootBase)` ${presets.Tablet} { border-top: 1px solid ${colors.gray.superLight}; margin-top: ${rhythm(0.4)}; @@ -31,7 +32,10 @@ export const ListBase = styled(`ul`)` padding: 0 calc(${rhythm(options.blockMarginBottom)} - 5px) 4px; ` -const List = styled(ListBase)` +const List = styled(HorizontalScrollerContent)` + padding-left: ${rhythm(options.blockMarginBottom)}; + padding-right: ${rhythm(options.blockMarginBottom)}; + ${presets.Tablet} { flex-direction: column; padding: 0; diff --git a/www/src/components/email-capture-form.js b/www/src/components/email-capture-form.js index 8111661cfcbe6..b8797074999a2 100644 --- a/www/src/components/email-capture-form.js +++ b/www/src/components/email-capture-form.js @@ -1,55 +1,81 @@ import React from "react" +import styled from "react-emotion" + +import SendIcon from "react-icons/lib/md/send" + import { rhythm, options } from "../utils/typography" import presets, { colors } from "../utils/presets" import hex2rgba from "hex2rgba" import { formInput } from "../utils/form-styles" import { buttonStyles } from "../utils/styles" -const Label = props => ( - -) - -const SingleLineInput = React.forwardRef((props, ref) => ( - -)) - -const ErrorMessage = ({ children }) => ( -
- {children} -
-) +const StyledForm = styled(`form`)` + margin: 0; + + ${presets.Desktop} { + display: flex; + } +` + +const Label = styled(`label`)` + :after { + content: ${props => (props.isRequired ? `'*'` : ``)}; + color: ${colors.warning}; + } +` + +const SingleLineInput = styled(`input`)` + ${formInput}; + width: 100%; + + :focus { + border-color: ${colors.gatsby}; + outline: 0; + box-shadow: 0 0 0 0.2rem ${hex2rgba(colors.lilac, 0.25)}; + } +` + +const SingleLineInputOnHomepage = styled(SingleLineInput)` + font-family: ${options.systemFontFamily.join(`,`)}; + font-size: 1rem; + padding: 0.6rem; +` + +const ErrorMessage = styled(`div`)` + color: ${colors.warning}; + font-family: ${options.systemFontFamily.join(`,`)}; + font-size: 0.875rem; + margin: calc(1.05rem / 2) 0; +` + +const SuccesMessage = styled(`div`)` + font-family: ${options.systemFontFamily.join(`,`)}; +` + +const Submit = styled(`input`)` + ${buttonStyles.default}; + margin-top: 20px; +` + +const SubmitOnHomepage = styled(`button`)` + ${buttonStyles.default}; + font-size: 1.125rem; + width: 100%; + margin-top: 10px; + + span { + align-items: center; + display: flex; + width: 100%; + justify-content: space-between; + } + + ${presets.Desktop} { + width: auto; + margin-top: 0; + margin-left: 0.5rem; + } +` class Form extends React.Component { constructor(props) { @@ -58,7 +84,7 @@ class Form extends React.Component { this.onSubmit = this.onSubmit.bind(this) // let's use uncontrolled components https://reactjs.org/docs/uncontrolled-components.html - this.email = React.createRef() + this.email = null } state = { @@ -78,7 +104,7 @@ class Form extends React.Component { fields: [ { name: `email`, - value: this.email.current.value, + value: this.email.value, }, ], context: { @@ -133,33 +159,49 @@ class Form extends React.Component { } render() { + const { isHomepage } = this.props + + const SingleLineInputComponent = isHomepage + ? SingleLineInputOnHomepage + : SingleLineInput + return ( -
-
-
+ )} + { + this.email = input + }} + aria-label={isHomepage ? `Email` : ``} + placeholder={isHomepage ? `your.email@example.com` : ``} + /> + {this.state.fieldErrors.email && ( + {this.state.fieldErrors.email} + )} {this.state.errorMessage && ( {this.state.errorMessage} )} - - + + {isHomepage ? ( + + + Subscribe + + + + ) : ( + + )} + ) } } @@ -179,44 +221,65 @@ class EmailCaptureForm extends React.Component { } render() { - const { signupMessage, overrideCSS } = this.props + const { signupMessage, overrideCSS, isHomepage, className } = this.props + + const FormComponent = props => ( +
+ ) return ( -
-
-

{signupMessage}

+ + {isHomepage ? ( +
+ {this.state.successMessage ? ( + + ) : ( + + )} +
+ ) : (
- {this.state.successMessage ? ( +
+

{signupMessage}

- ) : ( - - )} + css={{ + backgroundColor: colors.ui.light, + borderRadius: presets.radius, + color: colors.gatsby, + fontFamily: options.headerFontFamily.join(`,`), + padding: `15px`, + }} + > + {this.state.successMessage ? ( +
+ ) : ( + + )} +
+
-
-
+ )} + ) } } @@ -225,6 +288,8 @@ EmailCaptureForm.defaultProps = { signupMessage: `Enjoyed this post? Receive the next one in your inbox!`, confirmMessage: `Thank you! Youʼll receive your first email shortly.`, overrideCSS: {}, + isHomepage: false, + className: ``, } export default EmailCaptureForm diff --git a/www/src/components/homepage/homepage-blog-post.js b/www/src/components/homepage/homepage-blog-post.js new file mode 100644 index 0000000000000..f5a3a8a04011c --- /dev/null +++ b/www/src/components/homepage/homepage-blog-post.js @@ -0,0 +1,294 @@ +import React from "react" +import PropTypes from "prop-types" +import styled from "react-emotion" +import { Link, graphql } from "gatsby" +import Img from "gatsby-image" + +import ArrowForwardIcon from "react-icons/lib/md/arrow-forward" + +import { HorizontalScrollerItem } from "../shared/horizontal-scroller" + +import presets, { colors } from "../../utils/presets" +import { rhythm, options } from "../../utils/typography" + +const HomepageBlogPostRoot = styled( + HorizontalScrollerItem.withComponent(`article`) +)` + display: flex; + flex-direction: column; + font-family: ${options.systemFontFamily.join(`,`)}; + padding-bottom: ${rhythm(2.5)}; + position: relative; + + .main-body & a { + border: none; + box-shadow: none; + font-family: inherit; + + :hover { + background: transparent; + } + } + + ${presets.Tablet} { + width: 320px; + } + + ${presets.Desktop} { + flex-shrink: 0; + margin-right: 0; + margin-bottom: ${rhythm(presets.gutters.default)}; + padding-bottom: ${rhythm(3.5)}; + width: ${props => (props.fullWidth ? `100%` : `80%`)}; + + :hover { + background: ${colors.ui.whisper}; + } + } +` + +const Cover = styled(Img)` + border-radius: ${presets.radiusLg}px ${presets.radiusLg}px 0 0; + display: block; + margin-bottom: -${rhythm(0.5)}; +` + +const Header = styled(`h1`)` + color: ${colors.gatsbyDarker}; + font-size: 1.25rem; + font-weight: bold; + line-height: 1.2; + margin: 0; + padding: ${rhythm(4 / 5)}; + padding-bottom: 0; + + ${presets.Desktop} { + font-size: ${props => (props.first ? `1.75rem` : `1.5rem`)}; + padding: ${rhythm(1.5)}; + padding-bottom: 0; + } +` + +const Meta = styled(`div`)` + align-items: center; + color: ${colors.gray.calm}; + display: flex; + flex-wrap: wrap; + font-size: 0.875rem; + margin-top: 1rem; + padding: 0 ${rhythm(4 / 5)}; + + & > * { + flex-shrink: 0; + } + + ${presets.Desktop} { + margin-top: 1.5rem; + padding: 0 ${rhythm(1.5)}; + } +` + +const Author = styled(Link)` + align-items: center; + display: flex; + z-index: 1; + + img { + border-radius: 50%; + height: 28px; + width: 28px; + } + + span { + color: ${colors.gatsby}; + border-bottom: 1px solid ${colors.ui.bright}; + box-shadow: inset 0 -2px 0px 0px ${colors.ui.bright}; + margin-left: 0.5rem; + } + + a& { + font-weight: normal; + } + + ${presets.Desktop} { + :hover { + span { + background: ${colors.ui.bright}; + } + } + } +` + +const Excerpt = styled(`p`)` + color: ${colors.gray.copy}; + font-size: 0.875rem; + line-height: 1.5; + padding: 0 ${rhythm(4 / 5)}; + + ${presets.Desktop} { + margin: 0; + margin-top: 1.5rem; + padding: 0 ${rhythm(1.5)}; + } +` + +const ReadMore = styled(Link)` + align-items: flex-end; + background: transparent; + bottom: 0; + color: ${colors.gatsby}; + display: flex; + flex-grow: 1; + font-size: 0.875rem; + left: 0; + padding: ${rhythm(4 / 5)}; + position: absolute; + right: 0; + top: 0; + z-index: 0; + + &:hover { + background: yellow; + } + + svg { + height: 18px; + width: 18px; + } + + span { + color: ${colors.gatsby}; + border-bottom: 1px solid ${colors.ui.bright}; + box-shadow: inset 0 -2px 0px 0px ${colors.ui.bright}; + font-weight: bold; + margin-right: 0.2rem; + } + + ${presets.Desktop} { + padding: ${rhythm(1.5)}; + + span { + :hover { + background: ${colors.ui.bright}; + } + } + } +` + +const formatDate = (dateString, desktopViewport = false) => { + const date = new Date(dateString) + + var options = { + month: `long`, + day: `numeric`, + year: `numeric`, + } + + return date.toLocaleDateString(`en-EN`, desktopViewport ? options : {}) +} + +const HomepageBlogPost = ({ + post, + first = false, + fullWidth = false, + desktopViewport = false, +}) => { + const { + excerpt: automaticExcerpt, + fields: { slug }, + frontmatter: { + excerpt: handwrittenExcerpt, + author: { + id: authorName, + avatar: { + childImageSharp: { fixed: authorFixed }, + }, + fields: { slug: authorSlug }, + }, + date, + title, + cover, + }, + } = post + + const excerpt = handwrittenExcerpt ? handwrittenExcerpt : automaticExcerpt + + return ( + + {desktopViewport && + cover && } + + +
+ {title} +
+ + + + + {authorName} + {authorName} + +  on  + {formatDate(date, desktopViewport)} + + {first && {excerpt}} + + Read more + + +
+ ) +} + +HomepageBlogPost.propTypes = { + post: PropTypes.object.isRequired, + first: PropTypes.bool, + fullWidth: PropTypes.bool, + desktopViewport: PropTypes.bool, +} + +export const homepageBlogPostFragment = graphql` + fragment HomepageBlogPostData on MarkdownRemark { + excerpt + fields { + slug + } + frontmatter { + excerpt + title + date + author { + id + fields { + slug + } + avatar { + childImageSharp { + fixed( + width: 30 + height: 30 + quality: 80 + traceSVG: { + turdSize: 10 + background: "#f6f2f8" + color: "#e0d6eb" + } + ) { + ...GatsbyImageSharpFixed_tracedSVG + } + } + } + } + cover { + childImageSharp { + fluid(maxWidth: 700, quality: 80) { + ...GatsbyImageSharpFluid_withWebp + } + } + } + } + } +` + +export default HomepageBlogPost diff --git a/www/src/components/homepage/homepage-blog-posts.js b/www/src/components/homepage/homepage-blog-posts.js new file mode 100644 index 0000000000000..e3da4041f034c --- /dev/null +++ b/www/src/components/homepage/homepage-blog-posts.js @@ -0,0 +1,222 @@ +import React, { Component } from "react" +import PropTypes from "prop-types" +import styled from "react-emotion" +import { Link } from "gatsby" + +import ArrowForwardIcon from "react-icons/lib/md/arrow-forward" + +import HomepageBlogPost from "./homepage-blog-post" +import { + HorizontalScroller, + HorizontalScrollerContent, + HorizontalScrollerItem, +} from "../shared/horizontal-scroller" + +import presets, { colors } from "../../utils/presets" +import { rhythm, options } from "../../utils/typography" +import { SCROLLER_CLASSNAME } from "../../utils/scrollers-observer" + +const HomepageBlogPostsRootMobile = styled(HorizontalScroller)` + margin: -6px -${rhythm(presets.gutters.default / 2)}; +` + +const HorizontalScrollerContentAsDiv = HorizontalScrollerContent.withComponent( + `div` +) + +const HomepageBlogPostsRootDesktop = styled(`div`)` + display: flex; +` + +const PostsColumn = styled(`div`)` + align-items: flex-end; + display: flex; + flex-direction: column; + flex-basis: 45%; + margin-right: ${rhythm(presets.gutters.default)}; + position: relative; + + :last-child { + align-items: flex-start; + margin-right: 0; + top: 30px; + } +` + +const ViewAllStyle = styled(HorizontalScrollerItem.withComponent(`div`))` + display: flex; + font-family: ${options.headerFontFamily.join(`,`)}; + overflow: hidden; + width: auto; + + a { + box-shadow: none; + border: 0; + display: flex; + flex-direction: column; + font-weight: bold; + font-size: 1.25rem; + justify-content: center; + line-height: 1.2; + padding: ${rhythm(1.5)}; + width: 100%; + + span { + align-items: center; + display: flex; + } + + svg { + height: 18px; + margin-left: 0.2rem; + width: 18px; + } + } + + ${presets.Desktop} { + background: ${colors.gatsby}; + color: white; + flex-shrink: 0; + height: 160px; + + margin-left: ${rhythm(presets.gutters.default)}; + width: 125px; + + a { + padding: ${rhythm(1)}; + justify-content: flex-start; + + &:hover { + color: ${colors.gatsby}; + background: ${colors.ui.whisper}; + } + } + } + + ${presets.Hd} { + width: 160px; + } +` + +const LastPost = styled(`div`)` + display: flex; + width: 100%; +` + +const ViewAll = () => ( + + + View all + + posts + + + + +) + +class HomepageBlogPosts extends Component { + desktopMediaQuery + + state = { + desktopViewport: false, + } + + componentDidMount = () => { + this.desktopMediaQuery = window.matchMedia(presets.desktop) + this.desktopMediaQuery.addListener(this.updateViewPortState) + this.setState({ desktopViewport: this.desktopMediaQuery.matches }) + } + + componentWillUnmount = () => { + this.desktopMediaQuery.removeListener(this.updateViewPortState) + } + + updateViewPortState = e => { + this.setState({ desktopViewport: this.desktopMediaQuery.matches }) + } + + splitPostsToColumns = posts => + posts.reduce( + (merge, post, idx) => { + if (idx % 2) { + merge[1].push(post) + } else { + merge[0].push(post) + } + + return merge + }, + [[], []] + ) + + render() { + const { posts } = this.props + const postsInColumns = this.splitPostsToColumns(posts) + const { desktopViewport } = this.state + + return ( + + {desktopViewport ? ( + + {postsInColumns.map((column, colIdx) => ( + + {column.map((post, postIdx) => { + const { + fields: { slug }, + } = post + + const firstPost = !colIdx && !postIdx + const lastPost = colIdx & postIdx + + if (lastPost) { + { + /* add 'View all posts' link as a sibling of the last post card */ + } + return ( + + + + + ) + } + + return ( + + ) + })} + + ))} + + ) : ( + + + {posts.map((post, idx) => { + const { + fields: { slug }, + } = post + return + })} + + + + )} + + ) + } +} + +HomepageBlogPosts.propTypes = { + posts: PropTypes.array.isRequired, +} + +export default HomepageBlogPosts diff --git a/www/src/components/homepage/homepage-blog.js b/www/src/components/homepage/homepage-blog.js new file mode 100644 index 0000000000000..607a50da28ff3 --- /dev/null +++ b/www/src/components/homepage/homepage-blog.js @@ -0,0 +1,29 @@ +import React from "react" +import PropTypes from "prop-types" + +import HomepageSection from "./homepage-section" +import HomepageBlogPosts from "./homepage-blog-posts" + +import { BlogIcon } from "../../assets/mobile-nav-icons" + +const HomepageBlog = ({ posts }) => ( + + + +) + +HomepageBlog.propTypes = { + posts: PropTypes.array.isRequired, +} + +export default HomepageBlog diff --git a/www/src/components/homepage/homepage-ecosystem.js b/www/src/components/homepage/homepage-ecosystem.js index 5e5656e434a15..651b88c2edca9 100644 --- a/www/src/components/homepage/homepage-ecosystem.js +++ b/www/src/components/homepage/homepage-ecosystem.js @@ -6,14 +6,15 @@ import ArrowForwardIcon from "react-icons/lib/md/arrow-forward" import HomepageSection from "./homepage-section" import EcosystemSection from "../ecosystem/ecosystem-section" -import { - EcosystemFeaturedItemsRootBase, - ListBase as EcosystemFeaturedItemsListBase, -} from "../ecosystem/ecosystem-featured-items" import EcosystemFeaturedItem, { BlockLink as FeaturedItemBlockLink, } from "../ecosystem/ecosystem-featured-item" +import { + HorizontalScroller, + HorizontalScrollerContent, +} from "../shared/horizontal-scroller" + import { EcosystemIcon } from "../../assets/mobile-nav-icons" import { PluginsIcon, StartersIcon } from "../../assets/ecosystem-icons" @@ -30,10 +31,6 @@ const Sections = styled(`div`)` flex-direction: row; margin: 0 -8px; } - - ${presets.Desktop} { - margin: 0 1.5rem 0 2.5rem; - } ` const Section = styled(EcosystemSection)` @@ -55,54 +52,46 @@ const Section = styled(EcosystemSection)` const SubTitle = styled(`h3`)` color: ${colors.lemon}; font-size: 1.2rem; + margin-bottom: 0.25rem; margin-top: 2rem; - ${presets.Tablet} { - margin-left: 3rem; - } - ${presets.Desktop} { - margin-left: 6rem; + margin-left: 3rem; + margin-bottom: 1rem; } ` -const FeaturedItems = styled(EcosystemFeaturedItemsRootBase)` +const FeaturedItems = styled(HorizontalScroller)` margin: 0 -${rhythm(presets.gutters.default / 2)}; ${presets.Desktop} { margin: 0; - margin-left: calc(3rem - (${rhythm(options.blockMarginBottom)})); - margin-right: 1rem; - overflow-x: auto; - } - - ${presets.Hd} { - margin-right: 3rem; + overflow-x: visible; } ` -const FeaturedItemsList = styled(EcosystemFeaturedItemsListBase)` - padding: 0 calc(${rhythm(options.blockMarginBottom)} - 7px) 0; - +const FeaturedItemsList = styled(HorizontalScrollerContent)` ${presets.Desktop} { flex-wrap: wrap; margin: 0; + padding: 0; width: 100%; } ` const FeaturedItem = styled(EcosystemFeaturedItem)` - margin: 0 6px 6px 0; + margin-right: ${rhythm(presets.gutters.default / 2)}; ${presets.Tablet} { border-bottom: none; - margin: 0 6px 6px 0; - padding: 5px; + margin: ${rhythm(presets.gutters.default / 2)}; + margin-top: 0; + margin-left: 0; width: 320px; } ${presets.Desktop} { - flex-basis: 30%; + flex-basis: 28%; :nth-child(4) { margin-left: 8%; diff --git a/www/src/components/homepage/homepage-newsletter.js b/www/src/components/homepage/homepage-newsletter.js new file mode 100644 index 0000000000000..06c8ae27f756c --- /dev/null +++ b/www/src/components/homepage/homepage-newsletter.js @@ -0,0 +1,105 @@ +import React from "react" +import styled from "react-emotion" + +import HomepageSection from "./homepage-section" +import EmailCaptureForm from "../../components/email-capture-form" + +import { NewsletterFormOrnament } from "../../assets/ornaments" + +import { rhythm, options } from "../../utils/typography" +import presets, { colors } from "../../utils/presets" + +const stripedBorderHeight = `8px` + +const Container = styled(`div`)` + border: 1px solid ${colors.ui.light}; + border-radius: ${presets.radiusLg}px; + display: flex; + flex-direction: column; + margin-bottom: ${rhythm(presets.gutters.default)}; + padding: ${rhythm(presets.gutters.default * 1.2)}; + padding-bottom: calc( + ${rhythm(presets.gutters.default * 1.2)} + ${stripedBorderHeight} + ); + position: relative; + + :after { + border-radius: 0 0 ${presets.radiusLg}px ${presets.radiusLg}px; + background: ${colors.ui.whisper} + repeating-linear-gradient( + 135deg, + ${colors.lemon}, + ${colors.lemon} 20px, + transparent 20px, + transparent 40px, + ${colors.mint} 40px, + ${colors.mint} 60px, + transparent 60px, + transparent 80px + ); + bottom: 0; + content: ""; + height: ${stripedBorderHeight}; + left: 0; + right: 0; + position: absolute; + } + + ${presets.Desktop} { + flex-direction: row; + justify-content: space-between; + + > * { + flex-basis: 50%; + } + } +` + +const Ornament = styled(`span`)` + left: -4px; + position: absolute; + top: -8px; +` + +const Name = styled(`h3`)` + color: ${colors.lilac}; + font-fa1mily: ${options.headerFontFamily.join(`,`)}; + font-size: 0.875rem; + font-weight: normal; + margin: 0; + text-transform: uppercase; +` + +const Title = styled(`h1`)` + color: ${colors.gatsby}; + font-size: 1.25rem; + line-height: 1.3; + margin: 0; + margin-top: 0.2rem; +` + +const Form = styled(EmailCaptureForm)` + margin-top: 1.25rem; + + ${presets.Desktop} { + margin-top: 0; + } +` + +const HomepageNewsletter = () => ( + + + +
+ The Gatsby Newsletter + Keep up with the latest things Gatsby! +
+ +
+
+) + +export default HomepageNewsletter diff --git a/www/src/components/homepage/homepage-section.js b/www/src/components/homepage/homepage-section.js index 04f02b86be6e2..0d6fac03a646e 100644 --- a/www/src/components/homepage/homepage-section.js +++ b/www/src/components/homepage/homepage-section.js @@ -14,30 +14,30 @@ const HomepageSectionRoot = styled(`section`)` background: ${props => (props.inverse ? colors.gatsby : `#fff`)}; color: ${props => (props.inverse ? colors.ui.light : colors.gatsbyDark)}; margin: 0 -${rhythm(presets.gutters.default / 2)}; - padding: ${rhythm(2)} ${rhythm(presets.gutters.default / 2)}; + padding: ${rhythm(1)} ${rhythm(presets.gutters.default / 2)}; width: calc(100% + ${rhythm(presets.gutters.default)}); ${presets.Hd} { - margin: 0 -${vP}; + margin: -1px -${vP}; + padding: ${rhythm(1)} 5%; width: calc(100% + (${vP} * 2)); } ${presets.VHd} { - padding: ${rhythm(2)} 5%; + padding: ${rhythm(1.5)} 8%; } ` -const Header = styled(`header`)` +export const Header = styled(`header`)` ${presets.Tablet} { - margin-left: 3rem; max-width: 30rem; } ${presets.Desktop} { - margin-left: 6rem; + margin-left: 3rem; } ` -const Name = styled(`h3`)` +export const Name = styled(`h3`)` align-items: center; color: ${props => (props.inverse ? colors.ui.light : colors.lilac)}; display: flex; @@ -60,33 +60,38 @@ const Icon = styled(`span`)` } svg { + fill: transparent; height: ${ICON_SIZE}; stroke: ${props => (props.inverse ? colors.ui.light : colors.lilac)}; width: ${ICON_SIZE}; } ` -const Title = styled(`h1`)` +export const Title = styled(`h1`)` color: ${props => (props.inverse ? colors.lemon : colors.gatsby)}; font-size: 1.75rem; margin: 0; - margin-bottom: 0.5em; ` const Introduction = styled(`p`)` color: ${props => (props.inverse ? colors.ui.light : colors.gatsbyDark)}; font-size: 1.125rem; font-family: ${options.headerFontFamily.join(`,`)}; - margin-bottom: 0; + margin: 0; + margin-top: ${rhythm(4 / 5)}; ` const Actions = styled(`div`)` display: flex; flex-wrap: wrap; - margin-top: -${rhythm(1 / 4)}; + margin: 1rem 0 1.5rem; > a { - margin: ${rhythm(1.2)} 0 ${rhythm(1.5)}; + margin-right: ${rhythm(0.2)}; + } + + ${presets.Desktop} { + margin: 1rem 0 2.5rem; } ` @@ -98,10 +103,11 @@ const HomepageSection = ({ introduction, inverseStyle, links, + className, }) => ( - -
- {sectionName && ( + + {sectionName && ( +
{sectionIcon && ( - )} - {title && {title}} - {introduction && ( - {introduction} - )} - - {links.map((item, idx) => { - const { to, label, icon: Icon } = item - - return ( - - ) - })} - -
+ {title && {title}} + {introduction && ( + {introduction} + )} + {links && ( + + {links.map((item, idx) => { + const { to, label, icon: Icon, secondary } = item + + return ( + + ) + })} + + )} +
+ )} {children}
) @@ -140,6 +154,7 @@ HomepageSection.propTypes = { introduction: PropTypes.string, links: PropTypes.array, inverseStyle: PropTypes.bool, + className: PropTypes.string, } export default HomepageSection diff --git a/www/src/components/shared/horizontal-scroller.js b/www/src/components/shared/horizontal-scroller.js new file mode 100644 index 0000000000000..746f12ed7598b --- /dev/null +++ b/www/src/components/shared/horizontal-scroller.js @@ -0,0 +1,32 @@ +import styled from "react-emotion" + +import { rhythm } from "../../utils/typography" +import presets from "../../utils/presets" + +const BOX_SHADOW_BLUR = `8px` + +export const HorizontalScroller = styled(`div`)` + overflow-x: scroll; + -webkit-overflow-scrolling: touch; +` + +export const HorizontalScrollerContent = styled(`ul`)` + display: inline-flex; + list-style: none; + padding: ${BOX_SHADOW_BLUR} ${rhythm(presets.gutters.default / 2)} + calc(${BOX_SHADOW_BLUR} * 1.5); + margin: 0; +` + +export const HorizontalScrollerItem = styled(`li`)` + background: #fff; + border-radius: ${presets.radiusLg}px; + box-shadow: 0 0 ${BOX_SHADOW_BLUR} rgba(0, 0, 0, 0.2); + margin: 0; + margin-right: ${rhythm(presets.gutters.default / 2)}; + width: 77vw; + + :last-child { + margin-right: 0; + } +` diff --git a/www/src/pages/index.js b/www/src/pages/index.js index ef14655156029..5520242f02e65 100644 --- a/www/src/pages/index.js +++ b/www/src/pages/index.js @@ -3,7 +3,7 @@ import { graphql } from "gatsby" import Helmet from "react-helmet" import Layout from "../components/layout" import presets, { colors } from "../utils/presets" -import { rhythm, options } from "../utils/typography" +import { rhythm } from "../utils/typography" import { WebpackIcon, ReactJSIcon, GraphQLIcon } from "../assets/logos" import { vP } from "../components/gutters" import Container from "../components/container" @@ -14,12 +14,12 @@ import Card from "../components/card" import UsedBy from "../components/used-by" import CardHeadline from "../components/card-headline" import Diagram from "../components/diagram" -import BlogPostPreviewItem from "../components/blog-post-preview-item" import FuturaParagraph from "../components/futura-paragraph" import Button from "../components/button" import TechWithIcon from "../components/tech-with-icon" -import EmailCaptureForm from "../components/email-capture-form" import HomepageEcosystem from "../components/homepage/homepage-ecosystem" +import HomepageBlog from "../components/homepage/homepage-blog" +import HomepageNewsletter from "../components/homepage/homepage-newsletter" import { setupScrollersObserver, unobserveScrollers, @@ -45,7 +45,7 @@ class IndexRoute extends React.Component { render() { const { data: { - allMarkdownRemark: blogPosts, + allMarkdownRemark: { edges: postsData }, allStartersYaml: { edges: startersData }, allNpmPackage: { edges: pluginsData }, }, @@ -86,6 +86,8 @@ class IndexRoute extends React.Component { starters, }) + const posts = postsData.map(item => item.node) + return ( @@ -109,9 +111,10 @@ class IndexRoute extends React.Component {
- -
- - -

- Latest from the Gatsby blog -

- {blogPosts.edges.map(({ node }) => ( - - ))} - -
-
-
+ + +
@@ -302,7 +256,7 @@ export const pageQuery = graphql` } allMarkdownRemark( sort: { order: DESC, fields: [frontmatter___date, fields___slug] } - limit: 3 + limit: 4 filter: { frontmatter: { draft: { ne: true } } fileAbsolutePath: { regex: "/docs.blog/" } @@ -311,7 +265,7 @@ export const pageQuery = graphql` ) { edges { node { - ...BlogPostPreview_item + ...HomepageBlogPostData } } } diff --git a/www/src/utils/colors.js b/www/src/utils/colors.js index e814a01573a84..6e78f906e8c3f 100644 --- a/www/src/utils/colors.js +++ b/www/src/utils/colors.js @@ -5,7 +5,9 @@ const colors = { // @see https://www.figma.com/file/J6IYJEtdRmwJQOrcZ2DfvxDD/Gatsby gatsby: `#663399`, // was #744c9e gatsbyDark: `#442266`, + gatsbyDarker: `#221133`, lemon: `#ffdf37`, + mint: `#73fff7`, lilac: `#8c65b3`, lavender: `#b190d5`, wisteria: `#ccb2e5`,