diff --git a/src/components/templates/blog/BlogPost.jsx b/src/components/templates/blog/BlogPost.jsx new file mode 100644 index 00000000..a948d446 --- /dev/null +++ b/src/components/templates/blog/BlogPost.jsx @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018-present Arctic Ice Studio + * Copyright (C) 2018-present Sven Greb + * + * Project: Nord Docs + * Repository: https://github.com/arcticicestudio/nord-docs + * License: MIT + */ + +import React from "react"; +import MDXRenderer from "gatsby-mdx/mdx-renderer"; +import { MDXProvider } from "@mdx-js/tag"; +import { graphql } from "gatsby"; +import camelCase from "camelcase"; + +import BaseLayout from "layouts/core/BaseLayout"; +import Section, { Content } from "containers/core/Section"; + +import PageTypoHead from "../shared/PageTypoHead"; +import mappedHtmlElementComponents from "../shared/mappedHtmlElementComponents"; +import { blogPostTemplatePropTypes } from "../shared/propTypes"; + +/** + * A template for blog posts with custom HTML components and injected props that can be used in the MDX document. + * + * @author Arctic Ice Studio + * @author Sven Greb + * @since 0.10.0 + * @see https://mdxjs.com + * @see https://github.com/ChristopherBiscardi/gatsby-mdx + * + */ +const BlogPost = ({ data: { mdx, images }, location: { pathname }, ...passProps }) => { + /* + * Make each image available as image prop in camelcase format consisting of the name and extension of the file. + * Example: "snow-mountain.png" -> "snowMountainPng" + */ + const blogPostImages = {}; + images?.edges.forEach(({ node }) => { + const { extension, name } = node; + const imageName = camelCase(`${name}.${extension}`); + blogPostImages[imageName] = node.childImageSharp; + }); + + /* + * Shorten the object key path for content image props by deconstructing the "childImageSharp" property. + * The images can then be used by their index: `props.contentImages[0].fluid` + */ + const blogPostContentImages = []; + if (mdx.frontmatter?.contentImages) { + mdx.frontmatter.contentImages.forEach(({ childImageSharp }) => blogPostContentImages.push(childImageSharp)); + } + + return ( + +
+ + + + + {mdx.code.body} + + + +
+
+ ); +}; + +BlogPost.propTypes = blogPostTemplatePropTypes; + +export const pageQuery = graphql` + query($id: String!, $relativeDirectory: String!) { + images: allFile( + filter: { relativeDirectory: { eq: $relativeDirectory }, extension: { regex: "/(png|jpe?g)/" } } + sort: { fields: [name], order: ASC } + ) { + edges { + node { + childImageSharp { + ...contentMdxDocumentImageFluid + } + extension + name + } + } + } + mdx(id: { eq: $id }) { + code { + body + } + id + ...contentBlogPostFrontmatter + } + } +`; + +export default BlogPost; diff --git a/src/components/templates/docs/DocsPage.jsx b/src/components/templates/docs/DocsPage.jsx new file mode 100644 index 00000000..aaff5ded --- /dev/null +++ b/src/components/templates/docs/DocsPage.jsx @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018-present Arctic Ice Studio + * Copyright (C) 2018-present Sven Greb + * + * Project: Nord Docs + * Repository: https://github.com/arcticicestudio/nord-docs + * License: MIT + */ + +import React from "react"; +import MDXRenderer from "gatsby-mdx/mdx-renderer"; +import { MDXProvider } from "@mdx-js/tag"; +import { graphql } from "gatsby"; +import camelCase from "camelcase"; + +import { WaveFooter } from "atoms/core/vectors/divider"; +import BaseLayout from "layouts/core/BaseLayout"; +import Section, { Content } from "containers/core/Section"; + +import mappedHtmlElementComponents from "../shared/mappedHtmlElementComponents"; +import { docsPageTemplatePropTypes } from "../shared/propTypes"; +import PaperSheet from "./styled/PaperSheet"; +import PageTypoHead from "../shared/PageTypoHead"; + +/** + * A template for docs pages with custom HTML components and injected props that can be used in the MDX document. + * + * @author Arctic Ice Studio + * @author Sven Greb + * @since 0.10.0 + * @see https://mdxjs.com + * @see https://github.com/ChristopherBiscardi/gatsby-mdx + * + */ +const DocsPage = ({ data: { mdx, images }, location: { pathname }, ...passProps }) => { + /* + * Make each image available as image prop in camelcase format consisting of the name and extension of the file. + * Example: "snow-mountain.png" -> "snowMountainPng" + */ + const blogPostImages = {}; + images?.edges.forEach(({ node }) => { + const { extension, name } = node; + const imageName = camelCase(`${name}.${extension}`); + blogPostImages[imageName] = node.childImageSharp; + }); + + /* Shorten the object key path for content image props by deconstructing the "childImageSharp" property. */ + const blogPostContentImages = []; + if (mdx.frontmatter?.contentImages) { + mdx.frontmatter.contentImages.forEach(({ childImageSharp }) => blogPostContentImages.push(childImageSharp)); + } + + return ( + +
+ + + + + + {mdx.code.body} + + + + + +
+
+ ); +}; + +DocsPage.propTypes = docsPageTemplatePropTypes; + +export const pageQuery = graphql` + query($id: String!, $relativeDirectory: String!) { + images: allFile( + filter: { relativeDirectory: { eq: $relativeDirectory }, extension: { regex: "/(png|jpe?g)/" } } + sort: { fields: [name], order: ASC } + ) { + edges { + node { + childImageSharp { + ...contentMdxDocumentImageFluid + } + extension + name + } + } + } + mdx(id: { eq: $id }) { + code { + body + } + id + ...contentDocsPageFrontmatter + } + } +`; + +export default DocsPage; diff --git a/src/components/templates/docs/styled/PaperSheet.jsx b/src/components/templates/docs/styled/PaperSheet.jsx new file mode 100644 index 00000000..c1f91a96 --- /dev/null +++ b/src/components/templates/docs/styled/PaperSheet.jsx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018-present Arctic Ice Studio + * Copyright (C) 2018-present Sven Greb + * + * Project: Nord Docs + * Repository: https://github.com/arcticicestudio/nord-docs + * License: MIT + */ + +import styled from "styled-components"; +import { rgba } from "polished"; + +import { colors, media, motion, themedMode, MODE_BRIGHT_SNOW_FLURRY, MODE_DARK_NIGHT_FROST } from "styles/theme"; +import { baseBackgroundColor } from "containers/core/shared/styles"; + +const dropShadowColorAmbientLight = themedMode({ + [MODE_BRIGHT_SNOW_FLURRY]: rgba(colors.shadow.base[MODE_BRIGHT_SNOW_FLURRY], 0.1), + [MODE_DARK_NIGHT_FROST]: rgba(colors.shadow.base[MODE_DARK_NIGHT_FROST], 0.1) +}); + +const dropShadowColorDirectLight = themedMode({ + [MODE_BRIGHT_SNOW_FLURRY]: rgba(colors.shadow.base[MODE_BRIGHT_SNOW_FLURRY], 0.25), + [MODE_DARK_NIGHT_FROST]: rgba(colors.shadow.base[MODE_DARK_NIGHT_FROST], 0.25) +}); + +/** + * A styled paper sheet component. + * + * @author Arctic Ice Studio + * @author Sven Greb + * @since 0.10.0 + */ +const PaperSheet = styled.div` + background-color: ${baseBackgroundColor}; + overflow: hidden; + width: 100%; + margin: 0 auto; + padding: 0 1em; + border-radius: 8px; + box-shadow: 0 4px 6px ${dropShadowColorDirectLight}, 0 5px 7px ${dropShadowColorAmbientLight}; + transition: box-shadow ${motion.speed.duration.transition.base.themeModeSwitch}ms ease-in-out, + background-color ${motion.speed.duration.transition.base.themeModeSwitch}ms ease-in-out; + + ${media.phoneLandscape` + max-width: 95%; + padding: 2em 4em; + `}; +`; + +export default PaperSheet; diff --git a/src/components/templates/shared/PageTypoHead.jsx b/src/components/templates/shared/PageTypoHead.jsx new file mode 100644 index 00000000..bd60abca --- /dev/null +++ b/src/components/templates/shared/PageTypoHead.jsx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018-present Arctic Ice Studio + * Copyright (C) 2018-present Sven Greb + * + * Project: Nord Docs + * Repository: https://github.com/arcticicestudio/nord-docs + * License: MIT + */ + +import React from "react"; +import PropTypes from "prop-types"; +import styled from "styled-components"; + +import { H1, P } from "atoms/core/html-elements"; +import { colors, media, motion, ms, themedMode, MODE_BRIGHT_SNOW_FLURRY, MODE_DARK_NIGHT_FROST } from "styles/theme"; + +const fontColor = themedMode({ + [MODE_BRIGHT_SNOW_FLURRY]: colors.nord2, + [MODE_DARK_NIGHT_FROST]: colors.font.base[MODE_DARK_NIGHT_FROST] +}); + +const Headline = styled(H1)` + font-size: ${({ fontScale }) => ms(fontScale)}; + color: ${fontColor}; + margin-bottom: 0.8em; + font-weight: 500; + transition: color ${motion.speed.duration.transition.base.themeModeSwitch}ms ease-in-out; + + ${media.tabletLandscape` + font-size: ${({ fontScale }) => ms(fontScale + 1)}; + `}; + + ${media.desktop` + font-size: ${({ fontScale }) => ms(fontScale + 2)}; + `}; +`; + +const Subline = styled(P)` + max-width: 80%; + margin: 1em auto; + font-size: ${({ fontScale }) => ms(fontScale / 3.2)}; + + ${media.tabletLandscape` + max-width: 70%; + `}; + + ${media.desktop` + max-width: 60%; + `}; +`; + +const Wrapper = styled.div` + max-width: 90%; + text-align: center; + margin: 0 auto 4em auto; +`; + +/** + * A typography header consisting of a large headline and a subline. + * + * @author Arctic Ice Studio + * @author Sven Greb + * @since 0.10.0 + */ +const PageTypoHead = ({ headline, fontScale, subline, ...passProps }) => + headline || subline ? ( + + {headline && {headline}} + {subline && {subline}} + + ) : null; + +PageTypoHead.propTypes = { + fontScale: PropTypes.number.isRequired, + headline: PropTypes.string, + subline: PropTypes.string +}; + +PageTypoHead.defaultProps = { + headline: "", + subline: "" +}; + +export default PageTypoHead; diff --git a/src/components/templates/shared/Wrapper.jsx b/src/components/templates/shared/Wrapper.jsx new file mode 100644 index 00000000..2e13e2fa --- /dev/null +++ b/src/components/templates/shared/Wrapper.jsx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018-present Arctic Ice Studio + * Copyright (C) 2018-present Sven Greb + * + * Project: Nord Docs + * Repository: https://github.com/arcticicestudio/nord-docs + * License: MIT + */ + +import styled from "styled-components"; + +import { typography } from "styles/theme"; + +/** + * A styled component for the `wrapper` mapping of the supported `MDXProvider` tags. + * It is used to override global/inherited styles for docs pages and blog posts. + * + * @author Arctic Ice Studio + * @author Sven Greb + * @since 0.10.0 + */ +const Wrapper = styled.div` + font-family: ${typography.typefaces.straight}; +`; + +export default Wrapper; diff --git a/src/components/templates/shared/mappedHtmlElementComponents.jsx b/src/components/templates/shared/mappedHtmlElementComponents.jsx new file mode 100644 index 00000000..5d8a80fe --- /dev/null +++ b/src/components/templates/shared/mappedHtmlElementComponents.jsx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018-present Arctic Ice Studio + * Copyright (C) 2018-present Sven Greb + * + * Project: Nord Docs + * Repository: https://github.com/arcticicestudio/nord-docs + * License: MIT + */ + +import React from "react"; + +import Link from "atoms/core/Link"; +import { + Blockquote, + Code, + H1, + H2, + H3, + H4, + H5, + H6, + Hr, + Li, + Ol, + P, + Pre, + Table, + Td, + Th, + Tr, + Ul +} from "atoms/core/html-elements"; + +import Wrapper from "./Wrapper"; + +/** + * A mapping of custom HTML components to their corresponding HTML tag for usage with the `MDXProvider`. + * + * @author Arctic Ice Studio + * @author Sven Greb + * @since 0.10.0 + * @see https://mdxjs.com + * @see https://github.com/ChristopherBiscardi/gatsby-mdx + * + */ +const mappedHtmlElementComponents = { + a: Link, + blockquote: Blockquote, + code: Code, + h1: props =>

, + h2: props =>

, + h3: props =>

, + h4: props =>

, + h5: props =>

, + h6: props =>
, + hr: Hr, + inlineCode: Code, + li: props =>
  • , + ol: Ol, + p: props =>

    , + pre: Pre, + table: Table, + td: Td, + th: Th, + tr: Tr, + ul: Ul, + wrapper: Wrapper +}; + +export default mappedHtmlElementComponents; diff --git a/src/components/templates/shared/propTypes.js b/src/components/templates/shared/propTypes.js new file mode 100644 index 00000000..baf41443 --- /dev/null +++ b/src/components/templates/shared/propTypes.js @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018-present Arctic Ice Studio + * Copyright (C) 2018-present Sven Greb + * + * Project: Nord Docs + * Repository: https://github.com/arcticicestudio/nord-docs + * License: MIT + */ + +/** + * @file Provides shared prop types. + * + * @author Arctic Ice Studio + * @author Sven Greb + * @since 0.10.0 + */ + +import PropTypes from "prop-types"; + +import { + contentBlogPostFrontmatterPropTypes, + contentDocsPageFrontmatterPropTypes, + contentMdxImageFluidPropTypes +} from "data/graphql/fragmentPropTypes"; + +const dataImagesPropTypes = { + images: PropTypes.shape({ + edges: PropTypes.arrayOf( + PropTypes.shape({ + childImageSharp: PropTypes.shape({ + ...contentMdxImageFluidPropTypes + }), + extension: PropTypes.string, + name: PropTypes.string + }) + ) + }) +}; + +const dataMDXPropTypes = { + code: PropTypes.shape({ + body: PropTypes.string + }), + id: PropTypes.string +}; + +const blogPostTemplatePropTypes = { + data: PropTypes.shape({ + ...dataImagesPropTypes, + mdx: PropTypes.shape({ + ...dataMDXPropTypes, + ...contentBlogPostFrontmatterPropTypes + }) + }).isRequired +}; + +const docsPageTemplatePropTypes = { + data: PropTypes.shape({ + ...dataImagesPropTypes, + mdx: PropTypes.shape({ + ...dataMDXPropTypes, + ...contentDocsPageFrontmatterPropTypes + }) + }).isRequired +}; + +export { blogPostTemplatePropTypes, docsPageTemplatePropTypes };