├── .gitignore
├── README.md
├── gatsby-config.js
├── gatsby-node.js
├── index.js
├── package.json
├── src
├── components
│ ├── common
│ │ ├── HtmlContent.js
│ │ ├── Layout.js
│ │ └── index.js
│ ├── index.js
│ └── legal
│ │ ├── LegalPageBody.js
│ │ ├── LegalPageHero.js
│ │ ├── LegalPageNavigation.js
│ │ ├── LegalPageSection.js
│ │ ├── MobileNavigationButton.js
│ │ ├── MobileNavigationIcon.js
│ │ ├── NavigationItem.js
│ │ └── index.js
├── gatsby-plugin-theme-ui
│ └── index.js
├── schemas
│ └── legal.json
├── styles
│ ├── global.js
│ ├── htmlContentStyle.js
│ └── theme.js
├── templates
│ └── legal.js
└── util
│ └── helpers.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node ###
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 | *.pid.lock
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 | .idea
35 |
36 | # Optional npm cache directory
37 | .npm
38 |
39 | # Optional eslint cache
40 | .eslintcache
41 |
42 | # Optional REPL history
43 | .node_repl_history
44 |
45 | # Output of 'npm pack'
46 | *.tgz
47 |
48 | # Yarn Integrity file
49 | .yarn-integrity
50 |
51 |
52 | # Build Files
53 | public/
54 | .cache/
55 |
56 | # Gatsby context
57 | .gatsby-context.js
58 |
59 | # Bundle stats
60 | bundle-stats.json
61 |
62 | netlify.toml
63 | .env.development
64 | .env.production
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Gatsby Theme Legals Prismic
4 |
5 | - [Gatsby Theme](https://www.gatsbyjs.org/docs/themes/what-are-gatsby-themes/) for adding polished legal pages 💅out-of-the-box.
6 | - Responsive across Mobiles 📱, Tablets 💊 and Desktops 🖥️
7 | - Customisable to your brand using [Theme UI](https://theme-ui.com/) 🎨
8 | - Builds legal pages sourced from content in [Prismic](https://prismic.io/)
9 | - Demo at [https://gatsby-theme-legals.netlify.com/](https://gatsby-theme-legals.netlify.com/)
10 |
11 | ## Why?
12 |
13 | Legal pages are probably the most unexciting part of your site, and the last place you want to expend your creative energy.
14 |
15 | The purpose of `gatsby-theme-legals` is to do the heavy lifting for you. Super polished, responsive legal pages that you can just plug onto your existing project.
16 |
17 | ## Installation
18 |
19 | ```
20 | yarn add @littleplusbig/gatsby-theme-legals-prismic
21 | ```
22 |
23 | ## Configuration
24 |
25 | In your `gatsby-config.js`, under `plugins` add:
26 |
27 | ```
28 | {
29 | resolve: "gatsby-theme-legals-prismic",
30 | options: {
31 | prismicRepositoryName: PRISMIC_REPO_NAME,
32 | prismicAccessToken: PRISMIC_API_KEY,
33 | siteName: YOUR_SITE_NAME, // (Optional)
34 | homePath: HOME_PATH // (Optional) Defaults to '/'
35 | },
36 | },
37 | ```
38 |
39 | Replacing `PRISMIC_REPO_NAME`, `PRISMIC_API_KEY`, `YOUR_SITE_NAME` and `HOME_PATH` with their respective values.
40 |
41 | ## Prismic Configuration
42 |
43 | 1. Create a new custom type in your Prismic repository.
44 | 2. Make sure that it is repeatable and name it `Legal`.
45 | 3. Using the JSON Editor paste in the following content structure:
46 |
47 | ```
48 | {
49 | "Main": {
50 | "page_name": {
51 | "type": "StructuredText",
52 | "config": {
53 | "single": "heading1",
54 | "label": "Page Name",
55 | "placeholder": "Privacy Policy"
56 | }
57 | },
58 | "uid": {
59 | "type": "UID",
60 | "config": {
61 | "label": "Slug",
62 | "placeholder": "privacy-policy"
63 | }
64 | },
65 | "hero_subtitle": {
66 | "type": "StructuredText",
67 | "config": {
68 | "single": "paragraph",
69 | "label": "Hero Subtitle",
70 | "placeholder": "How we manage your data"
71 | }
72 | },
73 | "sections": {
74 | "type": "Group",
75 | "config": {
76 | "fields": {
77 | "section_heading": {
78 | "type": "StructuredText",
79 | "config": {
80 | "single": "heading2",
81 | "label": "Section Heading",
82 | "placeholder": "General information"
83 | }
84 | },
85 | "content": {
86 | "type": "StructuredText",
87 | "config": {
88 | "multi": "paragraph, preformatted, heading3, strong, em, hyperlink, list-item, o-list-item, o-list-item",
89 | "allowTargetBlank": true,
90 | "label": "Content",
91 | "placeholder": "Information on this website is of a general nature. Our company has ..."
92 | }
93 | }
94 | },
95 | "label": "Sections"
96 | }
97 | }
98 | },
99 | "SEO": {
100 | "meta_title": {
101 | "type": "StructuredText",
102 | "config": {
103 | "single": "heading1",
104 | "label": "Meta Title",
105 | "placeholder": "Enter meta title"
106 | }
107 | },
108 | "meta_description": {
109 | "type": "StructuredText",
110 | "config": {
111 | "single": "paragraph",
112 | "label": "Meta Description",
113 | "placeholder": "Enter meta description"
114 | }
115 | },
116 | "open_graph_image": {
117 | "type": "Image",
118 | "config": {
119 | "constraint": {
120 | "width": 1200,
121 | "height": 630
122 | },
123 | "thumbnails": [],
124 | "label": "Open Graph Image"
125 | }
126 | }
127 | }
128 | }
129 | ```
130 |
131 | 4. Create one or more `Legal` Content pages, each with 1 or more sections. Don't forget to populate each page's `SEO` tab!
132 |
133 | ## Laying Down the Law
134 |
135 | If you don't already have a Privacy Policy or Terms and Conditions document, you can generate one at [Iubenda](https://www.iubenda.com/).
136 |
137 | ## Overriding the Theme
138 |
139 | ### Colors and Styles
140 |
141 | This project uses [theme-ui](https://theme-ui.com/), allowing some of the styling to be customised to your project's brand.
142 |
143 | In order to override the styles, in the `src` directory of your project, add a folder titled `gatsby-plugin-theme-ui`, and within that folder a file named `index.js`.
144 |
145 | Inside of this file (`your-gatsby-project/src/gatsby-plugin-theme-ui/index.js`) add the following:
146 |
147 | ```
148 | import baseTheme from '@littleplusbig/gatsby-theme-legals-prismic/src/gatsby-plugin-theme-ui';
149 |
150 | export default {
151 | ...baseTheme,
152 | fonts: {
153 | ...baseTheme.fonts,
154 | body: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
155 | heading: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
156 | },
157 | colors: {
158 | ...baseTheme.colors,
159 | text: '#333333',
160 | background: '#FFFFFF',
161 | primary: '#6F2B9F',
162 | primaryDark: '#5B2589',
163 | primaryLight: '#BB75D1',
164 | white: '#FFFFFF',
165 | offWhite: '#FCFAFF',
166 | black: '#000000',
167 | offBlack: '#333333',
168 | grey: '#F3F3F3',
169 | },
170 | };
171 |
172 | ```
173 |
174 | Above are the default values for the theme, which you can change depending on your project.
175 |
176 | In particular, the colours accenting each legal page are controlled by `primary`, `primaryLight` and `primaryDark`.
177 |
178 | For example, here is how I might change the theme colours from shades of purple, to a snazzy blue:
179 |
180 | ```
181 | import baseTheme from '@littleplusbig/gatsby-theme-legals-prismic/src/gatsby-plugin-theme-ui';
182 |
183 | export default {
184 | ...baseTheme,
185 | colors: {
186 | ...baseTheme.colors,
187 | primary: '#7ed6df',
188 | primaryDark: '#22a6b3',
189 | primaryLight: '#c7ecee',
190 | },
191 | };
192 |
193 | ```
194 |
195 | 
196 |
197 | The complete set of customisable theme values can be explored in [gatsby-theme-legals-prismic/src/styles/theme.js](https://github.com/littleplusbig/gatsby-theme-legals-prismic/blob/master/src/styles/theme.js)
198 |
199 | More information about `gatsby-plugin-theme-ui` [here](https://www.npmjs.com/package/gatsby-plugin-theme-ui).
200 |
201 | ### Components
202 |
203 | The components that make up the legal pages can be some what customised too. This can be done through concept new to Gatsby Themes called '[Component Shadowing](https://www.gatsbyjs.org/blog/2019-04-29-component-shadowing/)'.
204 |
205 | If you wish to override a component, in the `src` directory of your project, create the following directory structure: `@littleplusbig/gatsby-theme-legals-prismic/components`.
206 |
207 | There are several components that a legal page, they can all be viewed here: [gatsby-theme-legals-prismic/src/components](https://github.com/littleplusbig/gatsby-theme-legals-prismic/tree/master/src/components)
208 |
209 | An example of how these components might be customised is adding your project's `` and `` components to the layout.
210 |
211 | In order to do this I create a shadowing `layout.js` in the directory we've just created (`your-gatsby-project/src/@littleplusbig/gatsby-theme-legals-prismic/components/layout.js`):
212 |
213 | ```
214 | import React from 'react';
215 | import { Header, Footer } from '../../somewhere-in-your-project'
216 |
217 | export default ({ children }) => (
218 | <>
219 |
220 | {children}
221 |
222 | >
223 | );
224 | ```
225 |
226 | ## Markdown? Contentful? WordPress?
227 |
228 | Soon my friend, soon.
229 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = ({
2 | prismicRepositoryName,
3 | prismicAccessToken,
4 | siteName = null,
5 | homePath = '/',
6 | }) => ({
7 | siteMetadata: {
8 | homePath,
9 | siteName,
10 | },
11 | plugins: [
12 | 'gatsby-plugin-theme-ui',
13 | {
14 | resolve: 'gatsby-source-prismic',
15 | options: {
16 | repositoryName: prismicRepositoryName,
17 | accessToken: prismicAccessToken,
18 | schemas: {
19 | legal: require('./src/schemas/legal.json'),
20 | },
21 | },
22 | },
23 | ],
24 | })
25 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | // graphql function doesn't throw an error so we have to check to check for the result.errors to throw manually
2 | const wrapper = promise => promise.then((result) => {
3 | if (result.errors) throw result.errors;
4 | return result;
5 | });
6 |
7 |
8 | exports.createPages = async ({ graphql, actions }) => {
9 | const { createPage } = actions;
10 |
11 | const legalPageTemplate = require.resolve('./src/templates/legal.js');
12 |
13 | const legalPages = await wrapper(
14 | graphql(`
15 | {
16 | allPrismicLegal {
17 | edges {
18 | node {
19 | id
20 | uid
21 | }
22 | }
23 | }
24 | }
25 | `),
26 | );
27 |
28 | const legalPageList = legalPages.data.allPrismicLegal.edges;
29 |
30 | /* ---------------------------------------------
31 | = Create an individual page for each Information page =
32 | ----------------------------------------------- */
33 |
34 | legalPageList.forEach((edge) => {
35 | // The uid you assigned in Prismic is the slug!
36 | createPage({
37 | path: `/${edge.node.uid}/`,
38 | component: legalPageTemplate,
39 | context: {
40 | // Pass the unique ID (uid) through context so the template can filter by it
41 | uid: edge.node.uid,
42 | },
43 | });
44 | });
45 | };
46 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | // Left blank on purpose
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@littleplusbig/gatsby-theme-legals-prismic",
3 | "author": "Allan Pooley",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/littleplusbig/gatsby-theme-legals-prismic"
7 | },
8 | "bugs": {
9 | "url": "https://github.com/littleplusbig/gatsby-theme-legals-prismic/issues"
10 | },
11 | "keywords": [
12 | "gatsby",
13 | "gatsby-theme",
14 | "gatsby-plugin",
15 | "prismic",
16 | "terms and conditions",
17 | "privacy policy",
18 | "legal"
19 | ],
20 | "version": "1.0.18",
21 | "license": "MIT",
22 | "main": "index.js",
23 | "scripts": {
24 | "build": "gatsby build",
25 | "develop": "gatsby develop",
26 | "clean": "gatsby clean"
27 | },
28 | "peerDependencies": {
29 | "gatsby": "^2.13.41",
30 | "react": "^16.8.6",
31 | "react-dom": "^16.8.6"
32 | },
33 | "devDependencies": {
34 | "gatsby": "^2.13.41",
35 | "react": "^16.8.6",
36 | "react-dom": "^16.8.6"
37 | },
38 | "dependencies": {
39 | "@emotion/core": "^10.0.14",
40 | "@emotion/styled": "^10.0.14",
41 | "@mdx-js/react": "^1.0.27",
42 | "gatsby-plugin-theme-ui": "^0.2.25",
43 | "gatsby-source-prismic": "^2.3.0-alpha.3",
44 | "react-helmet": "^5.2.1",
45 | "react-intersection-observer": "^8.24.1",
46 | "styled-components": "^4.3.2",
47 | "theme-ui": "^0.2.25"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/common/HtmlContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HtmlContentStyle from '../../styles/htmlContentStyle';
3 |
4 | const HtmlContent = ({ content }) => (
5 | <>
6 |
10 |
11 | >
12 | );
13 |
14 | export default HtmlContent;
15 |
--------------------------------------------------------------------------------
/src/components/common/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Helmet } from 'react-helmet';
3 | import { Layout as ThemeLayout, Main } from 'theme-ui';
4 | import GlobalStyle from '../../styles/global';
5 |
6 | const Layout = (props) => {
7 | const {
8 | children,
9 | // location,
10 | seoData,
11 | } = props;
12 | const {
13 | metaTitle = null,
14 | metaDescription = null,
15 | openGraphImage = null,
16 | } = seoData;
17 | return (
18 |
19 |
20 | {metaTitle && metaTitle.text && (
21 |
22 | { metaDescription && metaDescription.text && (
23 |
24 | )}
25 | { openGraphImage && openGraphImage.url && (
26 |
27 | )}
28 |
29 | )}
30 |
31 | {children}
32 |
33 |
34 | );
35 | };
36 |
37 | export default Layout;
38 |
--------------------------------------------------------------------------------
/src/components/common/index.js:
--------------------------------------------------------------------------------
1 | import HtmlContent from './HtmlContent';
2 | import Layout from './Layout';
3 |
4 | export {
5 | HtmlContent,
6 | Layout,
7 | };
8 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AllanPooley/gatsby-theme-legals-prismic/d5b10e540ed01589deca9356fa478d756e1c68f0/src/components/index.js
--------------------------------------------------------------------------------
/src/components/legal/LegalPageBody.js:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx } from 'theme-ui';
3 | import { LegalPageNavigation, LegalPageSection } from '.';
4 |
5 | const LegalPageBody = ({ activeSection, sectionInViewHandler, sections }) => (
6 |
12 |
23 |
33 |
37 |
38 |
46 | { sections && sections.map((section, index) => (
47 |
53 | ))}
54 |
55 |
56 |
57 | )
58 |
59 | export default LegalPageBody
60 |
--------------------------------------------------------------------------------
/src/components/legal/LegalPageHero.js:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx, Styled } from 'theme-ui';
3 | import { Link } from 'gatsby';
4 |
5 |
6 | const LegalPageHero = ({ title, siteName, homePath }) => (
7 |
15 |
24 |
41 | {siteName && (
42 |
51 | {siteName}
52 |
53 | )}
54 |
61 | {title}
62 |
63 |
70 |
77 | Back to home
78 |
79 |
80 |
81 |
99 |
117 |
118 |
119 | )
120 |
121 | export default LegalPageHero
122 |
--------------------------------------------------------------------------------
/src/components/legal/LegalPageNavigation.js:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx } from 'theme-ui'
3 | import { useState, useEffect } from 'react'
4 | import {
5 | isClient,
6 | } from '../../util/helpers'
7 | import {
8 | MobileNavigationButton,
9 | NavigationItem,
10 | } from '.';
11 |
12 | const scrollToPageSection = (event, sectionId) => {
13 | if (event) event.preventDefault()
14 | const targetEl = document.getElementById(sectionId);
15 | if (targetEl) targetEl.scrollIntoView({ behavior: 'smooth', block: 'start' })
16 | }
17 |
18 | const LegalPageNavigation = (props) => {
19 | const {
20 | activeSection,
21 | sections,
22 | } = props
23 | const [navOpen, setNavOpen] = useState(false)
24 | const sectionTitles = sections && sections.map(section => section.sectionHeading.text)
25 | useEffect(() => {
26 | const navChecker = async () => {
27 | if (navOpen) setNavOpen(false)
28 | }
29 | if (isClient) window.addEventListener('scroll', navChecker)
30 | return () => {
31 | if (isClient) window.addEventListener('scroll', navChecker)
32 | }
33 | })
34 | return (
35 |
98 | )
99 | }
100 |
101 | export default LegalPageNavigation;
102 |
--------------------------------------------------------------------------------
/src/components/legal/LegalPageSection.js:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx } from 'theme-ui'
3 | import { HtmlContent } from '../common'
4 | import { getSectionId } from '../../util/helpers'
5 | import { InView } from 'react-intersection-observer'
6 | import { Styled } from 'theme-ui'
7 |
8 | const LegalPageSection = ({ index, section, sectionInViewHandler }) => {
9 | const {
10 | sectionHeading,
11 | content,
12 | } = section
13 | const sectionId = getSectionId(index, sectionHeading.text)
14 | return (
15 | sectionInViewHandler(index, inView)}
25 | >
26 |
32 | {sectionHeading.text}
33 |
34 |
37 |
38 | )
39 | };
40 |
41 | export default LegalPageSection;
42 |
--------------------------------------------------------------------------------
/src/components/legal/MobileNavigationButton.js:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx } from 'theme-ui';
3 | import {
4 | MobileNavigationIcon
5 | } from '.';
6 | import {
7 | getSectionAffix,
8 | } from '../../util/helpers';
9 |
10 | const MobileNavigationButton = ({
11 | navOpen,
12 | activeSection,
13 | sectionTitles,
14 | setNavOpenHandler
15 | }) => (
16 |
50 | );
51 |
52 | export default MobileNavigationButton;
53 |
--------------------------------------------------------------------------------
/src/components/legal/MobileNavigationIcon.js:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx } from 'theme-ui';
3 | import theme from '../../styles/theme';
4 |
5 | const MobileNavigationIcon = () => (
6 |
17 | );
18 |
19 | export default MobileNavigationIcon;
20 |
--------------------------------------------------------------------------------
/src/components/legal/NavigationItem.js:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx } from 'theme-ui'
3 | import {
4 | getSectionAffix,
5 | getSectionId,
6 | } from '../../util/helpers'
7 |
8 | const NavigationItem = ({
9 | index,
10 | isActive,
11 | scrollToHandler,
12 | sectionTitle,
13 | }) => (
14 |
20 |
55 |
56 | );
57 |
58 | export default NavigationItem;
59 |
--------------------------------------------------------------------------------
/src/components/legal/index.js:
--------------------------------------------------------------------------------
1 | import LegalPageBody from './LegalPageBody';
2 | import LegalPageHero from './LegalPageHero';
3 | import LegalPageSection from './LegalPageSection';
4 | import LegalPageNavigation from './LegalPageNavigation';
5 | import MobileNavigationButton from './MobileNavigationButton';
6 | import MobileNavigationIcon from './MobileNavigationIcon';
7 | import NavigationItem from './NavigationItem';
8 |
9 | export {
10 | LegalPageBody,
11 | LegalPageHero,
12 | LegalPageSection,
13 | LegalPageNavigation,
14 | MobileNavigationButton,
15 | MobileNavigationIcon,
16 | NavigationItem,
17 | };
18 |
--------------------------------------------------------------------------------
/src/gatsby-plugin-theme-ui/index.js:
--------------------------------------------------------------------------------
1 | import { theme } from "../styles/theme"
2 | export default theme
3 |
--------------------------------------------------------------------------------
/src/schemas/legal.json:
--------------------------------------------------------------------------------
1 | {
2 | "Main": {
3 | "page_name": {
4 | "type": "StructuredText",
5 | "config": {
6 | "single": "heading1",
7 | "label": "Page Name",
8 | "placeholder": "Privacy Policy"
9 | }
10 | },
11 | "uid": {
12 | "type": "UID",
13 | "config": {
14 | "label": "Slug",
15 | "placeholder": "privacy-policy"
16 | }
17 | },
18 | "hero_subtitle": {
19 | "type": "StructuredText",
20 | "config": {
21 | "single": "paragraph",
22 | "label": "Hero Subtitle",
23 | "placeholder": "How we manage your data"
24 | }
25 | },
26 | "sections": {
27 | "type": "Group",
28 | "config": {
29 | "fields": {
30 | "section_heading": {
31 | "type": "StructuredText",
32 | "config": {
33 | "single": "heading2",
34 | "label": "Section Heading",
35 | "placeholder": "General information"
36 | }
37 | },
38 | "content": {
39 | "type": "StructuredText",
40 | "config": {
41 | "multi": "paragraph, preformatted, heading3, strong, em, hyperlink, list-item, o-list-item, o-list-item",
42 | "allowTargetBlank": true,
43 | "label": "Content",
44 | "placeholder": "Information on this website is of a general nature. Our company has ..."
45 | }
46 | }
47 | },
48 | "label": "Sections"
49 | }
50 | }
51 | },
52 | "SEO": {
53 | "meta_title": {
54 | "type": "StructuredText",
55 | "config": {
56 | "single": "heading1",
57 | "label": "Meta Title",
58 | "placeholder": "Enter meta title"
59 | }
60 | },
61 | "meta_description": {
62 | "type": "StructuredText",
63 | "config": {
64 | "single": "paragraph",
65 | "label": "Meta Description",
66 | "placeholder": "Enter meta description"
67 | }
68 | },
69 | "open_graph_image": {
70 | "type": "Image",
71 | "config": {
72 | "constraint": {
73 | "width": 1200,
74 | "height": 630
75 | },
76 | "thumbnails": [],
77 | "label": "Open Graph Image"
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import theme from './theme';
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | /* http://meyerweb.com/eric/tools/css/reset/
6 | v2.0 | 20110126
7 | License: none (public domain)
8 | */
9 | html, body, div, span, applet, object, iframe,
10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
11 | a, abbr, acronym, address, big, cite, code,
12 | del, dfn, em, img, ins, kbd, q, s, samp,
13 | small, strike, strong, sub, sup, tt, var,
14 | b, u, i, center,
15 | dl, dt, dd, ol, ul, li,
16 | fieldset, form, label, legend,
17 | article, aside, canvas, details, embed,
18 | figure, figcaption, footer, header, hgroup,
19 | menu, nav, output, ruby, section, summary,
20 | time, mark, audio, video {
21 | margin: 0;
22 | padding: 0;
23 | border: 0;
24 | font-size: 100%;
25 | font: inherit;
26 | vertical-align: baseline;
27 | }
28 | article, aside, details, figcaption, figure,
29 | footer, header, hgroup, menu, nav, section {
30 | display: block;
31 | }
32 | @media screen and (min-width: 35em) {
33 | html {
34 | margin-right: calc(-100vw + 100%);
35 | overflow-x: hidden;
36 | }
37 | }
38 | ol, ul, li {
39 | list-style: none;
40 | }
41 | blockquote, q {
42 | quotes: none;
43 | }
44 | blockquote::before, blockquote::after,
45 | q::before, q::after {
46 | content: '';
47 | content: none;
48 | }
49 | table {
50 | border-collapse: collapse;
51 | border-spacing: 0;
52 | }
53 | * {
54 | box-sizing: border-box;
55 | }
56 | body {
57 | font-size: 100%;
58 | font-variant-ligatures: none;
59 | text-rendering: optimizeLegibility;
60 | text-shadow: rgba(0, 0, 0, .01) 0 0 1px;
61 | }
62 | h1,
63 | h2,
64 | h3,
65 | h4,
66 | h5,
67 | h6 {
68 |
69 | }
70 | img {
71 | display: block;
72 | width: 100%;
73 | height: auto;
74 | }
75 | button,
76 | input {
77 | font-family: inherit;
78 | font-size: inherit;
79 | background: none;
80 | border: none;
81 | outline: none;
82 | appearance: none;
83 | border-radius: 0;
84 | padding: 0;
85 | text-align: left;
86 | resize: none;
87 | &:focus {
88 | outline: none;
89 | }
90 | &:invalid {
91 | box-shadow: none;
92 | }
93 | }
94 | /* Added to Fix Footer to bottom of viewport */
95 | html, body {
96 | height: 100%;
97 | }
98 | .siteRoot {
99 | padding: 60px 0 0 0;
100 | margin: 0 auto;
101 | display: flex;
102 | min-height: 100vh;
103 | flex-direction: column;
104 | }
105 | .siteContent {
106 | flex: 1;
107 | }
108 | /* Added to prevent scrolling when the menu is open */
109 | .contain {
110 | overflow: hidden;
111 | }
112 | button,
113 | input,
114 | textarea,
115 | select {
116 | color: ${theme.colors.text};
117 | font-family: inherit;
118 | font-size: inherit;
119 | background: none;
120 | border: none;
121 | appearance: none;
122 | /* stylelint-disable-next-line */
123 | -webkit-appearance: none;
124 | /* stylelint-disable-next-line */
125 | -moz-appearance: none;
126 | border-radius: 0;
127 | resize: none;
128 | &:invalid {
129 | box-shadow: none;
130 | }
131 | &:focus {
132 | outline: 5px auto #5E9ED6;
133 | outline: 5px auto -webkit-focus-ring-color;
134 | }
135 | }
136 | body:not(.user-is-tabbing) button:focus,
137 | body:not(.user-is-tabbing) input:focus,
138 | body:not(.user-is-tabbing) select:focus,
139 | body:not(.user-is-tabbing) textarea:focus,
140 | body:not(.user-is-tabbing) a:focus {
141 | outline: none;
142 | }
143 | `;
144 | export default GlobalStyle;
145 |
--------------------------------------------------------------------------------
/src/styles/htmlContentStyle.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import theme from './theme';
3 |
4 | const HtmlContentStyle = createGlobalStyle`
5 | .gatsby-theme-legals-html-content {
6 | * {
7 | color: ${theme.colors.text}
8 | }
9 |
10 | em {
11 | font-style: italic;
12 | }
13 |
14 | p {
15 | margin-bottom: 20px;
16 |
17 | strong {
18 | font-weight: ${theme.fontWeights.bold};
19 | }
20 | }
21 |
22 | a {
23 | text-decoration: underline;
24 | }
25 |
26 | h2,
27 | h3,
28 | h4,
29 | h5,
30 | h6 {
31 | margin-top: 30px;
32 | margin-bottom: 20px;
33 |
34 | &:first-child {
35 | margin-top: 0;
36 | }
37 | }
38 |
39 | h2 {
40 | margin-top: 60px;
41 |
42 | &:first-child {
43 | margin-top: 0;
44 | }
45 | }
46 |
47 | h3 {
48 | margin-top: 40px;
49 |
50 | &:first-child {
51 | margin-top: 0;
52 | }
53 | }
54 |
55 | a {
56 | text-decoration: underline;
57 |
58 | &:hover {
59 | text-decoration: none;
60 | }
61 | }
62 |
63 | ul, ol {
64 | width: 90%;
65 | padding-left: 25px;
66 | margin-bottom: 30px;
67 |
68 | li {
69 | margin-left: 15px;
70 | margin-bottom: 20px;
71 | }
72 | }
73 |
74 | ul {
75 | list-style-type: disc;
76 |
77 | li {
78 | list-style-type: disc;
79 | position: relative;
80 | }
81 | }
82 |
83 | ol {
84 | list-style-type: decimal;
85 |
86 | li {
87 | list-style-type: decimal;
88 | margin-left: 10px;
89 | text-indent: 0.5em;
90 | }
91 | }
92 | }
93 | `;
94 | export default HtmlContentStyle;
95 |
--------------------------------------------------------------------------------
/src/styles/theme.js:
--------------------------------------------------------------------------------
1 | export const theme = {
2 | space: [0, 4, 8, 16, 32],
3 | breakpoints: [
4 | '500px', '800px', '1080px',
5 | ],
6 | fonts: {
7 | body: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
8 | heading: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
9 | },
10 | fontSizes: [14, 16, 20, 24, 36, 44, 64, 80],
11 | fontWeights: {
12 | body: 400,
13 | regular: 400,
14 | medium: 500,
15 | subheading: 500,
16 | heading: 700,
17 | bold: 700,
18 | },
19 | lineHeights: {
20 | body: 1.5,
21 | heading: 1.2,
22 | },
23 | letterSpacings: {
24 | body: 'normal',
25 | caps: '0.2em',
26 | },
27 | colors: {
28 | text: '#333333',
29 | background: '#FFFFFF',
30 | primary: '#6F2B9F',
31 | primaryDark: '#5B2589',
32 | primaryLight: '#BB75D1',
33 | white: '#FFFFFF',
34 | offWhite: '#FCFAFF',
35 | black: '#000000',
36 | offBlack: '#333333',
37 | grey: '#F3F3F3',
38 | },
39 | sizes: {
40 | wrapper: '1240px',
41 | },
42 | textStyles: {
43 | heading: {
44 | fontFamily: 'heading',
45 | lineHeight: 'heading',
46 | fontWeight: 'heading',
47 | color: 'text',
48 | },
49 | label: {
50 | fontSize: [2, 2, 3, 3],
51 | fontFamily: 'heading',
52 | lineHeight: 'heading',
53 | fontWeight: 'heading',
54 | color: 'text',
55 | },
56 | controls: {
57 | fontSize: [1, 1, 1, 1],
58 | fontFamily: 'heading',
59 | fontWeight: 'regular',
60 | lineHeight: 'heading',
61 | textTransform: 'uppercase',
62 | letterSpacing: '0.05em',
63 | },
64 | },
65 | styles: {
66 | root: {
67 | fontFamily: 'body',
68 | fontWeight: 'body',
69 | lineHeight: 'body',
70 | },
71 | h1: {
72 | variant: 'textStyles.heading',
73 | fontSize: [5, 6, 6, 7],
74 | },
75 | h2: {
76 | variant: 'textStyles.heading',
77 | fontSize: [3, 3, 4, 4],
78 | },
79 | h3: {
80 | variant: 'textStyles.heading',
81 | fontSize: 3,
82 | },
83 | h4: {
84 | variant: 'textStyles.heading',
85 | fontSize: 2,
86 | },
87 | h5: {
88 | variant: 'textStyles.heading',
89 | fontSize: 1,
90 | },
91 | h6: {
92 | variant: 'textStyles.heading',
93 | fontSize: 0,
94 | },
95 | span: {
96 | variant: 'textStyles.label',
97 | },
98 | },
99 | }
100 |
101 | export default theme
102 |
--------------------------------------------------------------------------------
/src/templates/legal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { graphql } from 'gatsby';
3 | import { Layout } from '../components/common';
4 | import {
5 | LegalPageHero,
6 | LegalPageBody,
7 | } from '../components/legal';
8 | import { Styled } from 'theme-ui';
9 |
10 | const arraysEqual = (arr1, arr2) => {
11 | if (arr1.length !== arr2.length)
12 | return false
13 | for (var i = arr1.length; i--;) {
14 | if(arr1[i] !== arr2[i])
15 | return false
16 | }
17 | return true
18 | }
19 |
20 |
21 | class LegalPageTemplate extends Component {
22 | state = {
23 | sectionsInView: [],
24 | }
25 |
26 | sectionInViewHandler = ( sectionIndex, isInView ) => {
27 | const { sectionsInView } = this.state
28 | let newSectionsInView = [ ...sectionsInView ]
29 | const indexExists = newSectionsInView.includes(sectionIndex)
30 | if (isInView) {
31 | // Intersection Observer has notified us section has come into view
32 | // indexExists prevents us from adding duplicates
33 | if (!indexExists) newSectionsInView = [
34 | ...sectionsInView,
35 | sectionIndex,
36 | ]
37 | } else {
38 | // Intersection Observer has notified us section is now out of view
39 | newSectionsInView = sectionsInView.filter(index => index !== sectionIndex)
40 | }
41 | if (!arraysEqual(sectionsInView, newSectionsInView)) {
42 | // Only if a new section has come into, or an existing section has come
43 | // out of view, we update the state.
44 | this.setState({
45 | sectionsInView: newSectionsInView,
46 | })
47 | }
48 |
49 | }
50 |
51 | render() {
52 | const {
53 | sectionsInView,
54 | } = this.state
55 | // The 'active' section is the section closest to the top of the page that
56 | // are still in view (therefore, the smallest index in our array)
57 | const activeSection = sectionsInView.length > 0 ? sectionsInView.reduce((a, b) => Math.min(a, b)) : 0
58 | const {
59 | data: {
60 | site: {
61 | siteMetadata: {
62 | homePath = '/',
63 | siteName,
64 | },
65 | },
66 | page: {
67 | data: pageData,
68 | },
69 | },
70 | location,
71 | } = this.props
72 | const {
73 | pageTitle,
74 | heroSubtitle,
75 | sections,
76 | metaTitle,
77 | metaDescription,
78 | openGraphImage,
79 | } = pageData;
80 | const seoData = {
81 | metaTitle,
82 | metaDescription,
83 | openGraphImage,
84 | };
85 | const bannerTitle = pageTitle && pageTitle.text ? pageTitle.text : 'P';
86 | const bannerSubtitle = heroSubtitle && heroSubtitle.text ? heroSubtitle.text : 'You have questions, we have answers';
87 | return (
88 |
89 |
93 |
99 |
104 |
105 |
106 | )
107 | }
108 | }
109 |
110 | export default LegalPageTemplate
111 |
112 | export const pageQuery = graphql`
113 | query LegalPageBySlug($uid: String!) {
114 | site {
115 | siteMetadata {
116 | siteName,
117 | homePath,
118 | }
119 | },
120 | page: prismicLegal(uid: { eq: $uid }) {
121 | data {
122 | pageTitle: page_name {
123 | text
124 | }
125 | heroSubtitle: hero_subtitle {
126 | text
127 | }
128 | sections {
129 | content {
130 | html
131 | }
132 | sectionHeading: section_heading {
133 | text
134 | }
135 | }
136 | metaTitle: meta_title {
137 | html
138 | text
139 | },
140 | metaDescription: meta_description {
141 | html
142 | text
143 | },
144 | openGraphImage: open_graph_image {
145 | alt
146 | copyright
147 | url
148 | }
149 | }
150 | }
151 | }
152 | `
153 |
--------------------------------------------------------------------------------
/src/util/helpers.js:
--------------------------------------------------------------------------------
1 | export const getSectionId = (index, title) => `${getSectionAffix(index)}-${toKebabCase(title)}`;
2 | export const getSectionAffix = index => `${(index < 9) ? `0${index + 1}` : index + 1}`;
3 | export const isClient = typeof window !== 'undefined';
4 | export const toKebabCase = str => {
5 | return str
6 | .toLowerCase()
7 | .replace(/[^a-z0-9]+/g, "-")
8 | .replace(/(^-|-$)+/g, "")
9 | }
10 |
--------------------------------------------------------------------------------