├── .eslintrc.js
├── .gitignore
├── .idea
└── modules.xml
├── .prettierrc
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── LICENSE
├── README.md
├── babel.config.js
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.esm.js
├── gatsby-node.js
├── gatsby-ssr.js
├── jsconfig.json
├── package-lock.json
├── package.json
├── path-mappings.json
├── src
├── components
│ ├── animated-character
│ │ ├── animated-character.js
│ │ ├── animated-character.module.scss
│ │ ├── animated-character.stories.js
│ │ └── index.js
│ ├── author-block
│ │ ├── author-block.js
│ │ ├── author-block.module.scss
│ │ ├── author-block.stories.js
│ │ └── index.js
│ ├── avatar
│ │ ├── avatar.js
│ │ ├── avatar.module.scss
│ │ ├── avatar.stories.js
│ │ └── index.js
│ ├── blog-link
│ │ ├── blog-link.js
│ │ ├── blog-link.module.scss
│ │ ├── blog-link.stories.js
│ │ └── index.js
│ ├── blogs-listing-block
│ │ ├── blogs.js
│ │ ├── blogs.module.scss
│ │ ├── blogs.stories.js
│ │ └── index.js
│ ├── bustout
│ │ ├── bustout.js
│ │ ├── bustout.module.scss
│ │ ├── bustout.stories.js
│ │ └── index.js
│ ├── case-studies-block
│ │ ├── case-studies-block.js
│ │ ├── case-studies-block.module.scss
│ │ ├── case-studies-block.stories.js
│ │ └── index.js
│ ├── code-block
│ │ ├── code-block.js
│ │ └── index.js
│ ├── contact-detailed
│ │ ├── contact.js
│ │ ├── contact.module.scss
│ │ ├── contact.stories.js
│ │ └── index.js
│ ├── contact
│ │ ├── contact.js
│ │ ├── contact.module.scss
│ │ ├── contact.stories.js
│ │ └── index.js
│ ├── filter-tags
│ │ ├── filter-tags.js
│ │ ├── filter-tags.module.scss
│ │ ├── filter-tags.stories.js
│ │ └── index.js
│ ├── footer
│ │ ├── footer.js
│ │ ├── footer.module.scss
│ │ ├── footer.stories.js
│ │ └── index.js
│ ├── header
│ │ ├── header.js
│ │ ├── header.module.scss
│ │ ├── header.stories.js
│ │ └── index.js
│ ├── help-block
│ │ ├── help-block.js
│ │ ├── help-block.module.scss
│ │ ├── help-block.stories.js
│ │ └── index.js
│ ├── hero
│ │ ├── hero.js
│ │ ├── hero.module.scss
│ │ ├── hero.stories.js
│ │ └── index.js
│ ├── jobs-listing-block
│ │ ├── index.js
│ │ ├── jobs-block.js
│ │ ├── jobs-block.module.scss
│ │ └── jobs-block.stories.js
│ ├── layout
│ │ ├── index.js
│ │ ├── layout.js
│ │ └── layout.module.scss
│ ├── markdown
│ │ ├── index.js
│ │ └── markdown.js
│ ├── menu-button
│ │ ├── index.js
│ │ ├── menu-button.js
│ │ └── menu-button.module.scss
│ ├── nav-link
│ │ ├── index.js
│ │ ├── nav-link.js
│ │ └── nav-link.module.scss
│ ├── parent-page-link
│ │ ├── index.js
│ │ ├── parent-page-link.js
│ │ ├── parent-page-link.module.scss
│ │ └── parent-page-link.stories.js
│ ├── process-block
│ │ ├── index.js
│ │ ├── process-block.js
│ │ ├── process-block.module.scss
│ │ └── process-block.stories.js
│ ├── process-image-block
│ │ ├── index.js
│ │ ├── process-image-block.js
│ │ ├── process-image-block.module.scss
│ │ └── process-image-block.stories.js
│ ├── quote-slider
│ │ ├── index.js
│ │ ├── quote-slider.js
│ │ ├── quote-slider.module.scss
│ │ └── quote-slider.stories.js
│ ├── rich-text
│ │ ├── index.js
│ │ └── rich-text.js
│ ├── seo.js
│ ├── services-listing-block
│ │ ├── index.js
│ │ ├── services-block.js
│ │ ├── services-block.module.scss
│ │ └── services-block.stories.js
│ ├── streamfield-block
│ │ ├── index.js
│ │ ├── streamfield-block.js
│ │ ├── streamfield-block.module.scss
│ │ └── streamfield-block.stories.js
│ ├── tag
│ │ ├── index.js
│ │ ├── tag.js
│ │ ├── tag.module.scss
│ │ └── tag.stories.js
│ ├── team-listing-block
│ │ ├── index.js
│ │ ├── team-listing-block.js
│ │ ├── team-listing-block.module.scss
│ │ └── team-listing-block.stories.js
│ ├── teaser-block
│ │ ├── index.js
│ │ ├── teaser-block.js
│ │ ├── teaser-block.module.scss
│ │ ├── teaser-block.stories.js
│ │ └── teaser.js
│ ├── testimonials-block
│ │ ├── index.js
│ │ ├── testimonials-block.js
│ │ ├── testimonials-block.module.scss
│ │ └── testimonials-block.stories.js
│ ├── theme-provider
│ │ ├── index.js
│ │ ├── theme-provider.js
│ │ └── themes.js
│ └── title-block
│ │ ├── index.js
│ │ ├── title-block.js
│ │ ├── title-block.module.scss
│ │ └── title-block.stories.js
├── context
│ └── theme-context.js
├── fonts
│ └── apercu
│ │ ├── apercu-black-pro.eot
│ │ ├── apercu-black-pro.ttf
│ │ ├── apercu-black-pro.woff
│ │ ├── apercu-black-pro.woff2
│ │ ├── apercu-bold-pro.eot
│ │ ├── apercu-bold-pro.ttf
│ │ ├── apercu-bold-pro.woff
│ │ ├── apercu-bold-pro.woff2
│ │ ├── apercu-light-pro.eot
│ │ ├── apercu-light-pro.ttf
│ │ ├── apercu-light-pro.woff
│ │ ├── apercu-light-pro.woff2
│ │ ├── apercu-regular-pro.eot
│ │ ├── apercu-regular-pro.ttf
│ │ ├── apercu-regular-pro.woff
│ │ └── apercu-regular-pro.woff2
├── fragments
│ └── index.js
├── html.js
├── images
│ ├── 1162px-Firefox_Logo,_2017.png
│ ├── 404.jpg
│ ├── data-greeting.svg
│ ├── default-avatar.png
│ ├── default-featured.png
│ ├── default-search-image.jpg
│ ├── favicon.png
│ ├── firefox.png
│ ├── frag-cluster1.svg
│ ├── frag-cluster2.svg
│ ├── frag-cluster3.svg
│ ├── help-character.png
│ ├── help-character.svg
│ ├── icons
│ │ ├── chevron.svg
│ │ ├── cluster.svg
│ │ ├── diamond.svg
│ │ ├── frag.png
│ │ ├── quote.svg
│ │ ├── tick.png
│ │ └── tick.svg
│ ├── logo.svg
│ ├── man-coffee.svg
│ ├── man-fruit.svg
│ ├── man-right.svg
│ ├── processes-desktop.png
│ ├── processes-mobile.png
│ ├── tbx-flame.svg
│ ├── toolkit.svg
│ ├── wagtail-bird.svg
│ ├── wagtail-greeting.svg
│ ├── wagtail.svg
│ └── will.jpg
├── pages
│ ├── 404
│ │ ├── 404.js
│ │ ├── 404.module.scss
│ │ └── index.js
│ └── preview.js
├── styles
│ ├── _animations.scss
│ ├── _base-page.scss
│ ├── _fonts.scss
│ ├── _global.scss
│ ├── _grid.scss
│ ├── _mixins.scss
│ ├── _typography.scss
│ ├── _vars.scss
│ └── app.module.scss
├── templates
│ ├── blog-post
│ │ ├── blog-post.js
│ │ ├── blog-post.module.scss
│ │ ├── blog-post.stories.js
│ │ └── index.js
│ ├── blogs
│ │ ├── blog-listing.js
│ │ ├── blog-listing.module.scss
│ │ ├── blog-listing.stories.js
│ │ └── index.js
│ ├── case-studies
│ │ ├── case-study-listing.js
│ │ ├── case-study-listing.module.scss
│ │ ├── case-study-listing.stories.js
│ │ └── index.js
│ ├── case-study
│ │ ├── case-study.js
│ │ ├── case-study.module.scss
│ │ ├── case-study.stories.js
│ │ └── index.js
│ ├── culture
│ │ ├── culture-page.js
│ │ ├── culture-page.module.scss
│ │ ├── culture-page.stories.js
│ │ └── index.js
│ ├── index.js
│ ├── jobs
│ │ ├── index.js
│ │ ├── jobs-listing.js
│ │ ├── jobs-listing.module.scss
│ │ └── jobs-listing.stories.js
│ ├── person
│ │ ├── index.js
│ │ ├── person.js
│ │ ├── person.module.scss
│ │ └── person.stories.js
│ ├── service
│ │ ├── index.js
│ │ ├── service-page.js
│ │ ├── service-page.module.scss
│ │ └── service-page.stories.js
│ ├── standard
│ │ ├── index.js
│ │ ├── standard.js
│ │ ├── standard.module.scss
│ │ └── standard.stories.js
│ ├── sub-service
│ │ └── index.js
│ └── team
│ │ ├── index.js
│ │ ├── team-listing.js
│ │ ├── team-listing.module.scss
│ │ └── team-listing.stories.js
└── utils
│ ├── css-vars-polyfill.js
│ ├── safeget.js
│ ├── selectors.js
│ ├── tags.js
│ ├── torchup.js
│ ├── urls.js
│ └── wagtail-preview.js
└── static
├── BingSiteAuth.xml
├── _redirects
├── images
└── default-search-image.jpg
└── robots.txt
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const paths = require('./path-mappings.json')
2 |
3 | module.exports = {
4 | "parser": "babel-eslint",
5 | "extends": [
6 | "plugin:react/recommended",
7 | "prettier",
8 | "prettier/react"
9 | ],
10 | "plugins": [
11 | "organize-imports"
12 | ],
13 | "rules": {
14 | "organize-imports/organize-imports": ["error", {
15 | "orderRules": [{
16 | "moduleType": "nodeModule",
17 | "comment": "Vendor Modules"
18 | }, {
19 | "moduleType": "componentModules",
20 | "comment": "Components",
21 | "include": [
22 | "src/components/",
23 | "src/templates/"
24 | ],
25 | "exclude": [
26 | "src/**/*.scss"
27 | ]
28 | }, {
29 | "moduleType": "utilityModule",
30 | "comment": "Utilities",
31 | "include": [
32 | "src/utils/",
33 | "src/context/",
34 | "src/fragments/",
35 | "src/images/"
36 | ]
37 | }, {
38 | "moduleType": "styleModules",
39 | "comment": "Styles",
40 | "include": [
41 | "src/**/*.scss"
42 | ]
43 | }],
44 | "pathAliases": Object.keys(paths).map(prefix => {
45 | return {
46 | "prefix": prefix,
47 | "resolvesTo": paths[prefix]
48 | }
49 | })
50 | }]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # dotenv environment variables file
55 | .env
56 |
57 | # gatsby files
58 | .cache/
59 | public
60 |
61 | # Mac files
62 | .DS_Store
63 |
64 | # Yarn
65 | yarn-error.log
66 | .pnp/
67 | .pnp.js
68 | # Yarn Integrity file
69 | .yarn-integrity
70 | /.idea/vcs.xml
71 | /.idea/misc.xml
72 | /.idea/codeStyles/Project.xml
73 | /.idea/codeStyles/codeStyleConfig.xml
74 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 | import '@storybook/addon-viewport/register';
4 | import '@storybook/addon-backgrounds/register';
5 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { addDecorator } from '@storybook/react'; // <- or your storybook framework
2 | import { withBackgrounds } from '@storybook/addon-backgrounds';
3 | import { configure } from '@storybook/react';
4 | import 'reset-css'
5 | import '../src/styles/_fonts.scss'
6 |
7 | // automatically import all files ending in *.stories.js
8 | const req = require.context('../src', true, /.stories.js$/);
9 | function loadStories() {
10 | req.keys().forEach(filename => req(filename));
11 | }
12 |
13 | addDecorator(
14 | withBackgrounds([
15 | { name: 'tbx-accent', value: '#fd5765', default: true },
16 | { name: 'tbx-purple', value: '#231749' },
17 | { name: 'white', value: '#FFF' },
18 | ])
19 | )
20 |
21 | // Gatsby's Link overrides:
22 | // Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here
23 | global.___loader = {
24 | enqueue: () => {},
25 | hovering: () => {},
26 | }
27 | // Gatsby internal mocking to prevent unnecessary errors in storybook testing environment
28 | global.__PATH_PREFIX__ = ""
29 | // This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to if it wasn't inside a storybook
30 | window.___navigate = pathname => {
31 | action("NavigateTo:")(pathname)
32 | }
33 | configure(loadStories, module)
34 |
35 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = (baseConfig, env, defaultConfig) => {
4 | // Transpile Gatsby module because Gatsby includes un-transpiled ES6 code.
5 | defaultConfig.module.rules[0].exclude = [/node_modules\/(?!(gatsby)\/)/]
6 |
7 | // use installed babel-loader which is v8.0-beta (which is meant to work with @babel/core@7)
8 | defaultConfig.module.rules[0].use[0].loader = require.resolve("babel-loader")
9 |
10 | // use @babel/plugin-proposal-class-properties for class arrow functions
11 | defaultConfig.module.rules[0].use[0].options.plugins = [
12 | require.resolve("@babel/plugin-proposal-class-properties"),
13 | // require.resolve("@babel/plugin-syntax-export-default-from")
14 | ]
15 |
16 | // SCSS Support
17 | defaultConfig.module.rules.push({
18 | test: /\.scss$/,
19 | loaders: [
20 | "style-loader",
21 | {
22 | loader: 'css-loader',
23 | options: {
24 | modules: true,
25 | importLoaders: 1,
26 | },
27 | },
28 | "sass-loader"
29 | ],
30 | include: path.resolve(__dirname, "../src/")
31 | })
32 |
33 | // use @babel/preset-react for JSX and env (instead of staged presets)
34 | defaultConfig.module.rules[0].use[0].options.presets = [
35 | require.resolve("@babel/preset-react"),
36 | require.resolve("@babel/preset-env"),
37 | ]
38 |
39 | defaultConfig.resolve.mainFields = ["browser", "module", "main"]
40 |
41 | return defaultConfig
42 | }
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 gatsbyjs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const pathMappings = require('./path-mappings.json')
2 | var browserslist = require('browserslist')
3 |
4 | module.exports = function (api) {
5 | api.cache(true)
6 |
7 | return {
8 | "presets": [
9 | [
10 | "babel-preset-gatsby",
11 | {
12 | "targets": {
13 | "browsers": browserslist()
14 | }
15 | }
16 | ]
17 | ],
18 | "plugins": [
19 | ["module-resolver", {
20 | "root": ["./"],
21 | "alias": pathMappings
22 | }]
23 | ]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import 'reset-css'
2 | import 'babel-polyfill'
3 | import smartquotes from 'smartquotes'
4 |
5 | // Smartquotes (at Tom D's request!)
6 | smartquotes().listen()
7 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | // Use esm to allow importing ES6 modules in gatsby-node.esm.js
2 | require = require('esm')(module)
3 | module.exports = require('./gatsby-node.esm.js')
4 |
--------------------------------------------------------------------------------
/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/
5 | */
6 |
7 | // You can delete this file if you're not using it
8 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "allowSyntheticDefaultImports": false,
5 | "baseUrl": "./",
6 | "paths": {
7 | "@root": ["./"],
8 | "@components": ["./src/components"],
9 | "@images": ["./src/images"],
10 | "@utils": ["./src/utils"],
11 | "@context": ["./src/context"],
12 | "@templates": ["./src/templates"],
13 | }
14 | },
15 | "exclude": ["node_modules", "public"]
16 | }
--------------------------------------------------------------------------------
/path-mappings.json:
--------------------------------------------------------------------------------
1 | {
2 | "@root": "./",
3 | "@components": "./src/components",
4 | "@images": "./src/images",
5 | "@utils": "./src/utils",
6 | "@context": "./src/context",
7 | "@templates": "./src/templates",
8 | "@styles": "./src/styles"
9 | }
--------------------------------------------------------------------------------
/src/components/animated-character/animated-character.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Utilities
5 | import { ReactComponent as WomanChar } from '@images/help-character.svg'
6 | import { ReactComponent as WagtailChar } from '@images/wagtail.svg'
7 | import { ReactComponent as ManChar } from '@images/data-greeting.svg'
8 | // Styles
9 | import './animated-character.module.scss'
10 | import styles from './animated-character.module.scss'
11 |
12 | class AnimatedCharacter extends React.Component {
13 | constructor(props) {
14 | super(props)
15 | this.state = { active: false }
16 | this.containerRef = React.createRef()
17 | }
18 |
19 | animate = () => {
20 | if (this.containerRef.current) {
21 | setTimeout(() => {
22 | this.setState({ active: true })
23 | }, 500)
24 | }
25 | }
26 |
27 | componentDidMount() {
28 | if (this.containerRef.current && window) {
29 | this.animate()
30 | }
31 | }
32 |
33 | render() {
34 | const { containerClassName, character } = this.props
35 | return (
36 |
44 | {this.renderCharacter()}
45 |
46 | )
47 | }
48 |
49 | renderCharacter = () => {
50 | const { active } = this.state
51 | switch (this.props.character) {
52 | case 'woman-left':
53 | return (
54 |
57 | )
58 |
59 | case 'wagtail':
60 | return (
61 |
64 | )
65 |
66 | case 'man-left':
67 | return (
68 |
71 | )
72 |
73 | default:
74 | return
75 | }
76 | }
77 | }
78 |
79 | AnimatedCharacter.propTypes = {
80 | character: PropTypes.string,
81 | containerClassName: PropTypes.string,
82 | }
83 |
84 | AnimatedCharacter.defaultProps = {
85 | className: '',
86 | containerClassName: '',
87 | }
88 |
89 | export default AnimatedCharacter
90 |
--------------------------------------------------------------------------------
/src/components/animated-character/animated-character.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import AnimatedCharacter from './animated-character'
6 |
7 | storiesOf('Components/Shared Components', module).add(
8 | 'Animated Character',
9 | () => {
10 | return (
11 |
14 | )
15 | }
16 | )
17 |
--------------------------------------------------------------------------------
/src/components/animated-character/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import AnimatedCharacter from './animated-character'
3 | export default AnimatedCharacter
4 |
--------------------------------------------------------------------------------
/src/components/author-block/author-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import dayjs from 'dayjs'
5 | import { Link } from 'gatsby'
6 | // Components
7 | import Avatar from '@components/avatar'
8 | import Tag from '@components/tag'
9 | // Styles
10 | import styles from './author-block.module.scss'
11 |
12 | const AuthorBlock = ({ author, datePublished, tags, readTime, className }) => (
13 |
14 |
15 |
20 |
21 |
22 | {author.name || ''}
23 |
24 |
25 |
26 | {author.role || ''}
27 |
28 | {datePublished ? (
29 |
30 | {dayjs(datePublished).format(`DD MMM 'YY`)}
31 |
32 | ) : null}
33 | {readTime ? (
34 |
35 | {readTime} min read
36 |
37 | ) : null}
38 |
39 | {tags.length ? (
40 |
41 | {tags.map((tag, index) => (
42 |
43 | ))}
44 |
45 | ) : null}
46 |
47 |
48 |
49 | )
50 |
51 | AuthorBlock.propTypes = {
52 | className: PropTypes.string,
53 | author: PropTypes.object.isRequired,
54 | datePublished: PropTypes.string,
55 | tags: PropTypes.array,
56 | readTime: PropTypes.number,
57 | }
58 |
59 | AuthorBlock.defaultProps = {
60 | author: {
61 | avatar: require('@images/default-avatar.png'),
62 | },
63 | }
64 |
65 | export default AuthorBlock
66 |
--------------------------------------------------------------------------------
/src/components/author-block/author-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .authorBlock {
4 |
5 | &Container {
6 | @include container();
7 | display: flex;
8 | align-items: flex-start;
9 | }
10 |
11 | &Image {
12 | width: 55px;
13 | height: 55px;
14 | margin-right: 20px;
15 |
16 | @include for-desktop-up {
17 | width: 80px;
18 | height: 80px;
19 | }
20 | }
21 |
22 | &Details {
23 | display: flex;
24 | flex-direction: column;
25 | }
26 |
27 | &Name {
28 | color: $primary;
29 | font-size: 20px;
30 | margin-bottom: 2px;
31 | border-bottom: none;
32 | }
33 |
34 | &Meta {
35 | font-size: 12px;
36 | line-height: 1.4;
37 | margin: 0 0 0;
38 |
39 | &Role {
40 | margin: 0 0 2px;
41 | line-height: 1.4;
42 | color: $accent-small-text;
43 | text-transform: uppercase;
44 | letter-spacing: 0.15em;
45 | font-weight: bold;
46 | font-size: 12px;
47 | }
48 |
49 | &Date {
50 | font-size: 12px;
51 | text-transform: none;
52 | letter-spacing: 0;
53 | color: #333;
54 | margin-left: 5px;
55 | padding-left: 5px;
56 | border-left: 1px solid rgba(0, 0, 0, 0.1);
57 | }
58 |
59 | &ReadTime {
60 | font-size: 12px;
61 | text-transform: none;
62 | letter-spacing: 0;
63 | color: #333;
64 | margin-left: 5px;
65 | padding-left: 5px;
66 | border-left: 1px solid rgba(0, 0, 0, 0.1);
67 | }
68 | }
69 |
70 | &Tags {
71 | margin: 10px 0 0;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/author-block/author-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import AuthorBlock from './author-block'
6 |
7 | storiesOf('Components/Shared Components', module).add('Author Block', () => {
8 | return (
9 |
32 | )
33 | })
34 |
--------------------------------------------------------------------------------
/src/components/author-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import AuthorBlock from './author-block'
3 | export default AuthorBlock
4 |
--------------------------------------------------------------------------------
/src/components/avatar/avatar.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Styles
5 | import styles from './avatar.module.scss'
6 |
7 | const Avatar = ({ src, className, containerClassName, alt }) => (
8 |
9 |

14 |
15 | )
16 |
17 | Avatar.propTypes = {
18 | src: PropTypes.string,
19 | className: PropTypes.string,
20 | containerClassName: PropTypes.string,
21 | alt: PropTypes.string,
22 | }
23 |
24 | Avatar.defaultProps = {
25 | className: '',
26 | containerClassName: '',
27 | alt: '',
28 | }
29 |
30 | export default Avatar
31 |
--------------------------------------------------------------------------------
/src/components/avatar/avatar.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .avatar {
4 | height: 100px;
5 | width: 100px;
6 | border-radius: 90px;
7 |
8 | &Image {
9 | height: 100%;
10 | min-width: 100%;
11 | border-radius: 100%;
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/avatar/avatar.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import Avatar from './avatar'
6 |
7 | storiesOf('Components/Shared Components', module).add('Avatar', () => {
8 | return (
9 |
20 | )
21 | })
22 |
--------------------------------------------------------------------------------
/src/components/avatar/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Avatar from './avatar'
3 | export default Avatar
4 |
--------------------------------------------------------------------------------
/src/components/blog-link/blog-link.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | import dayjs from 'dayjs'
6 | // Components
7 | import Avatar from '@components/avatar'
8 | // Styles
9 | import styles from './blog-link.module.scss'
10 |
11 | const BlogLink = ({
12 | href,
13 | title,
14 | featured,
15 | description,
16 | className,
17 | authorAvatar,
18 | authorName,
19 | authorRole,
20 | datePublished,
21 | }) => (
22 |
29 | {title}
30 | {description && featured ? (
31 | {description}
32 | ) : null}
33 |
34 |
39 |
40 | {authorName}
41 |
42 | {authorRole}
43 | {datePublished ? (
44 |
45 | {dayjs(datePublished).format(`DD MMM 'YY`)}
46 |
47 | ) : null}
48 |
49 |
50 |
51 |
52 | )
53 |
54 | BlogLink.propTypes = {
55 | href: PropTypes.string,
56 | title: PropTypes.string,
57 | featured: PropTypes.bool,
58 | description: PropTypes.string,
59 | className: PropTypes.string,
60 | authorAvatar: PropTypes.string,
61 | authorName: PropTypes.string,
62 | authorRole: PropTypes.string,
63 | datePublished: PropTypes.string,
64 | }
65 |
66 | BlogLink.defaultProps = {
67 | className: '',
68 | }
69 |
70 | export default BlogLink
71 |
--------------------------------------------------------------------------------
/src/components/blog-link/blog-link.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | // Theme overrides:
4 | $blog-author: var(--testimonial-name);
5 |
6 | .blogLink {
7 | display: block;
8 | padding: 0 0 50px 0;
9 | margin: 40px 0 0;
10 | text-decoration: none;
11 | border-bottom: none;
12 |
13 | &:hover {
14 | .blogLinkTitle {
15 | color: $light-blue;
16 | }
17 | }
18 |
19 | &Meta {
20 | display: flex;
21 | flex-direction: row;
22 | align-items: center;
23 | }
24 |
25 | &Title {
26 | font-size: 30px;
27 | font-weight: bold;
28 | line-height: 40px;
29 | margin: 0 0 20px;
30 | color: $primary;
31 | }
32 |
33 | &Image {
34 | width: 55px;
35 | height: 55px;
36 | margin-right: 15px;
37 | }
38 |
39 | &Author {
40 | display: flex;
41 | flex-direction: column;
42 |
43 | &Name {
44 | color: $primary;
45 | font-size: 20px;
46 | }
47 |
48 | &Role {
49 | margin: 0 0 2px;
50 | line-height: 1.4;
51 | color: $blog-author;
52 | text-transform: uppercase;
53 | letter-spacing: 0.15em;
54 | font-weight: bold;
55 | font-size: 12px;
56 | }
57 |
58 | &Date {
59 | font-size: 12px;
60 | font-weight: normal;
61 | text-transform: none;
62 | letter-spacing: 0;
63 | color: #333;
64 | margin-left: 5px;
65 | padding-left: 5px;
66 | border-left: 1px solid rgba(0, 0, 0, 0.1);
67 | }
68 | }
69 |
70 | &Featured {
71 | @extend .blogLink;
72 | border-top: none;
73 | padding-top: 0;
74 | padding-bottom: 50px !important;
75 |
76 | @include for-desktop-up {
77 | width: 58.33vw;
78 | }
79 |
80 | .blogLinkTitle {
81 | font-size: 45px;
82 | line-height: 50px;
83 | font-weight: 800;
84 |
85 | @include for-desktop-up {
86 | font-size: 65px !important;
87 | line-height: 80px;
88 | }
89 | }
90 |
91 | .blogLinkDesc {
92 | margin: 0 0 30px;
93 | color: $grey;
94 | @include for-tablet-portrait-up {
95 | line-height: 34px;
96 | font-size: 20px ;
97 | }
98 | }
99 |
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/components/blog-link/blog-link.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import BlogLink from './blog-link'
6 |
7 | storiesOf('Components/Shared Components', module).add('BlogLink', () => {
8 | return (
9 |
18 |
19 |
20 | )
21 | })
22 |
--------------------------------------------------------------------------------
/src/components/blog-link/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import BlogLink from './blog-link'
3 | export default BlogLink
4 |
--------------------------------------------------------------------------------
/src/components/blogs-listing-block/blogs.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Components
6 | import BlogLink from '@components/blog-link'
7 | // Styles
8 | import styles from './blogs.module.scss'
9 |
10 | class BlogsBlock extends React.Component {
11 | render() {
12 | const {
13 | blogs,
14 | className,
15 | sectionTitle,
16 | listingUrl,
17 | showFeatured = true,
18 | } = this.props
19 | const featuredPost = blogs[0]
20 |
21 | if (!blogs || blogs.length === 0) {
22 | return ;
23 | }
24 |
25 | return (
26 |
27 | {sectionTitle ? (
28 |
{sectionTitle}
29 | ) : null}
30 |
31 | {(showFeatured && featuredPost) ? (
32 |
43 | ) : null}
44 |
45 |
46 | {(blogs || []).slice(showFeatured ? 1 : 0).map((blog, index) => (
47 |
55 | ))}
56 |
57 |
58 | {listingUrl ? (
59 |
60 | See more blogs
61 |
62 | ) : null}
63 |
64 |
65 | )
66 | }
67 | }
68 |
69 | BlogsBlock.propTypes = {
70 | blogs: PropTypes.array,
71 | className: PropTypes.string,
72 | listingUrl: PropTypes.string,
73 | sectionTitle: PropTypes.string,
74 | showFeatured: PropTypes.bool,
75 | }
76 |
77 | BlogsBlock.defaultProps = {
78 | className: '',
79 | blogs: [],
80 | sectionTitle: 'Thinking',
81 | }
82 |
83 | export default BlogsBlock
84 |
--------------------------------------------------------------------------------
/src/components/blogs-listing-block/blogs.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .block {
4 | background-color: white;
5 | padding: 100px 0 20px;
6 | position: relative;
7 |
8 | &Content {
9 | @include container;
10 | }
11 |
12 | &Intro {
13 |
14 | &Opinion {
15 | position: relative;
16 | padding-top: 20px;
17 |
18 | @include for-tablet-landscape-up {
19 | width: 60%;
20 | }
21 |
22 | &Title {
23 | color: $accent;
24 | }
25 |
26 | &Text {
27 | line-height: 30px;
28 | margin: 0 0 40px;
29 | color: $primary;
30 | @include for-tablet-landscape-up {
31 | line-height: 42px;
32 | }
33 | }
34 |
35 | &::before {
36 | position: absolute;
37 | content: '';
38 | width: 60px;
39 | height: 8px;
40 | left: 0;
41 | top: 0;
42 | background: $accent;
43 | }
44 | }
45 | }
46 |
47 | &BlogList {
48 | display: grid;
49 | grid-template-columns: 1fr;
50 | @include for-tablet-landscape-up {
51 | grid-template-columns: 1fr 1fr;
52 | grid-column-gap: 8.33vw;
53 | }
54 | padding-bottom: 50px;
55 |
56 | @include target-ie {
57 | display: flex;
58 | flex-direction: column;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/blogs-listing-block/blogs.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import Blogs from './blogs'
6 |
7 | storiesOf('Components/Landing Page', module).add('Blog Listing block', () => {
8 | return (
9 |
46 | )
47 | })
48 |
--------------------------------------------------------------------------------
/src/components/blogs-listing-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Blogs from './blogs'
3 | export default Blogs
4 |
--------------------------------------------------------------------------------
/src/components/bustout/bustout.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Utilities
5 | import { renderTorchUp } from '@utils/torchup'
6 | // Styles
7 | import styles from './bustout.module.scss'
8 |
9 | const Bustout = ({ src, align = 'left', className, caption }) => (
10 |
11 |
21 |
27 | {caption ? (
28 |
34 | ) : null}
35 |
36 |
37 | )
38 |
39 | Bustout.propTypes = {
40 | src: PropTypes.string,
41 | caption: PropTypes.string,
42 | align: PropTypes.oneOf(['left', 'right', 'full']),
43 | className: PropTypes.string,
44 | }
45 |
46 | Bustout.defaultProps = {
47 | className: '',
48 | src: 'http://playground.torchboxapps.com/tbx-rebrand/assets/images/todd.jpg',
49 | }
50 |
51 | export default Bustout
52 |
--------------------------------------------------------------------------------
/src/components/bustout/bustout.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .bustout {
4 | display: flex;
5 | position: relative;
6 | background: $blue;
7 |
8 | flex-direction: column;
9 | @include for-tablet-landscape-up {
10 | flex-direction: row;
11 | }
12 |
13 | &Container {
14 | margin: 60px 0 80px;
15 | }
16 |
17 | &RightAligned {
18 | @extend .bustout;
19 | flex-direction: column-reverse;
20 | @include for-tablet-landscape-up {
21 | flex-direction: row-reverse;
22 | }
23 | }
24 |
25 | &Full {
26 | flex-direction: column;
27 |
28 | .bustoutImage {
29 | width: 100%;
30 | }
31 |
32 | .bustoutCaption {
33 | width: 100%;
34 | }
35 | }
36 |
37 | &Image {
38 | min-height: 300px;
39 | width: 100%;
40 | @include for-tablet-landscape-up {
41 | float: left;
42 | width: 50%;
43 | }
44 | background-size: cover;
45 | background-position: center;
46 |
47 | }
48 |
49 | &Caption {
50 | background: $light-blue;
51 | width: 100%;
52 |
53 | @include for-tablet-landscape-up {
54 | float: left;
55 | width: 50%;
56 | }
57 |
58 | &Inner {
59 | padding: 8.33vw;
60 | }
61 |
62 | h1, h2, h3, h4, h5 {
63 | color: white;
64 | margin-top: 0;
65 | }
66 |
67 | p {
68 | color: rgba(white, 0.8);
69 | }
70 |
71 | }
72 |
73 | &::after {
74 | content: "";
75 | display: table;
76 | clear: both;
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/components/bustout/bustout.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import Bustout from './bustout'
6 |
7 | storiesOf('Components/Shared Components', module).add('Bustout', () => {
8 | return (
9 |
16 |
17 |
18 | )
19 | })
20 |
--------------------------------------------------------------------------------
/src/components/bustout/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Bustout from './bustout'
3 | export default Bustout
4 |
--------------------------------------------------------------------------------
/src/components/case-studies-block/case-studies-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import CaseStudiesBlock from './case-studies-block'
6 |
7 | storiesOf('Components/Landing Page', module).add('Case Studies block', () => {
8 | return (
9 |
46 | )
47 | })
48 |
--------------------------------------------------------------------------------
/src/components/case-studies-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import CaseStudiesBlock from './case-studies-block'
3 | export default CaseStudiesBlock
4 |
--------------------------------------------------------------------------------
/src/components/code-block/code-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import HighlightJS from 'highlightjs'
5 | import 'highlightjs/styles/idea.css'
6 |
7 |
8 | class CodeBlock extends React.PureComponent {
9 | constructor(props) {
10 | super(props)
11 | this.setRef = this.setRef.bind(this)
12 | }
13 |
14 | setRef(el) {
15 | this.codeEl = el
16 | }
17 |
18 | componentDidMount() {
19 | this.highlightCode()
20 | }
21 |
22 | componentDidUpdate() {
23 | this.highlightCode()
24 | }
25 |
26 | highlightCode() {
27 | HighlightJS.highlightBlock(this.codeEl)
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
34 | {this.props.value}
35 |
36 |
37 | )
38 | }
39 | }
40 |
41 | CodeBlock.defaultProps = {
42 | language: ''
43 | }
44 |
45 | CodeBlock.propTypes = {
46 | value: PropTypes.string.isRequired,
47 | language: PropTypes.string
48 | }
49 |
50 | export default CodeBlock
51 |
--------------------------------------------------------------------------------
/src/components/code-block/index.js:
--------------------------------------------------------------------------------
1 | import CodeBlock from './code-block'
2 | export default CodeBlock
3 |
--------------------------------------------------------------------------------
/src/components/contact-detailed/contact.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import Contact from './contact'
6 |
7 | storiesOf('Components/Shared Components', module).add(
8 | 'Contact Block (Detailed)',
9 | () => {
10 | return
11 | }
12 | )
13 |
--------------------------------------------------------------------------------
/src/components/contact-detailed/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Contact from './contact'
3 | export default Contact
4 |
--------------------------------------------------------------------------------
/src/components/contact/contact.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Components
6 | import Avatar from '@components/avatar'
7 | // Styles
8 | import styles from './contact.module.scss'
9 | // Utils
10 | import safeGet from '@utils/safeget'
11 |
12 | const Contact = ({ title, emailAddress, phoneNumber, className, image }) => (
13 |
30 | )
31 |
32 | Contact.propTypes = {
33 | title: PropTypes.string,
34 | emailAddress: PropTypes.string,
35 | phoneNumber: PropTypes.string,
36 | className: PropTypes.string,
37 | image: PropTypes.object,
38 | }
39 |
40 | Contact.defaultProps = {
41 | title: 'Get in touch about your project',
42 | className: '',
43 | emailAddress: 'will@torchbox.com',
44 | name: 'Will Heinemen',
45 | phoneNumber: '+41524204242',
46 | role: 'Head of new buisness',
47 | avatar: require('@images/will.jpg'),
48 | }
49 |
50 | export default Contact
51 |
--------------------------------------------------------------------------------
/src/components/contact/contact.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | // Theme overrides:
4 | $contact-phone-number-color: var(--contact-phone-number-color);
5 | $contact-phone-number-hover-color: var(--contact-phone-number-hover-color);
6 |
7 | .contactBlock {
8 | background-color: white;
9 | padding-top: 20px;
10 | padding-bottom: 20px;
11 |
12 | &Content {
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | @include container;
17 | @include for-tablet-portrait-up {
18 | flex-direction: row;
19 | align-items: center;
20 | }
21 | }
22 |
23 | &Image {
24 | margin-bottom: 20px;
25 | @include for-tablet-portrait-up {
26 | margin-bottom: 0;
27 | }
28 | }
29 |
30 | &Details {
31 | display: flex;
32 | flex-direction: row;
33 | flex-wrap: wrap;
34 | align-items: center;
35 | @include for-phone-only {
36 | justify-content: center;
37 | }
38 |
39 | @include for-tablet-portrait-up {
40 | padding-left: 20px;
41 | }
42 | }
43 |
44 | &Title {
45 | width: 100%;
46 | color: $primary;
47 | font-size: 26px;
48 | line-height: 30px;
49 | margin: 0 0 5px;
50 | font-weight: 800;
51 |
52 | @include for-phone-only {
53 | text-align: center;
54 | }
55 | }
56 |
57 | &Email {
58 | color: $light-blue;
59 | font-size: 20px;
60 | line-height: 24px;
61 | text-decoration: none;
62 | margin: 0px 15px 0 0;
63 | font-weight: bold;
64 |
65 | @include for-phone-only {
66 | margin: 10px 15px 0 0;
67 | }
68 |
69 | &:hover,
70 | &:focus {
71 | color: $blue;
72 | }
73 | }
74 |
75 | &Number {
76 | height: 24px;
77 | line-height: 24px;
78 | text-decoration: none;
79 | font-size: 20px;
80 | font-weight: bold;
81 | color: $contact-phone-number-color;
82 | margin: 0px 15px 0 0;
83 | border-bottom: none;
84 | @include for-phone-only {
85 | margin: 10px 15px 0 0;
86 | }
87 |
88 | &:hover {
89 | color: $contact-phone-number-hover-color;
90 | }
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/contact/contact.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import Contact from './contact'
6 |
7 | storiesOf('Components/Shared Components', module).add(
8 | 'Contact Block (Minimal)',
9 | () => {
10 | return (
11 |
16 | )
17 | }
18 | )
19 |
--------------------------------------------------------------------------------
/src/components/contact/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Contact from './contact'
3 | export default Contact
4 |
--------------------------------------------------------------------------------
/src/components/filter-tags/filter-tags.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import Tag from '@components/tag'
6 | // Styles
7 | import styles from './filter-tags.module.scss'
8 |
9 | const FilterTags = ({ tags, onChange = () => null, activeTag, className }) => (
10 |
11 | {tags.map((tag, index) => (
12 | onChange(tag, index)}
18 | href={tag.href || '#'}
19 | label={tag.label || tag}
20 | />
21 | ))}
22 |
23 | )
24 |
25 | FilterTags.propTypes = {
26 | tags: PropTypes.array,
27 | className: PropTypes.string,
28 | onChange: PropTypes.func,
29 | activeTag: PropTypes.number,
30 | }
31 |
32 | FilterTags.defaultProps = {
33 | className: '',
34 | }
35 |
36 | export default FilterTags
37 |
--------------------------------------------------------------------------------
/src/components/filter-tags/filter-tags.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .filter {
4 | display: flex;
5 | align-items: center;
6 | justify-content: flex-start;
7 | flex-wrap: wrap;
8 |
9 | &Tag {
10 | box-sizing: content-box;
11 | font-size: 14px;
12 | padding: 2px 8px;
13 | margin-right: 10px;
14 |
15 | &Active {
16 | @extend .filterTag;
17 | border: 2px solid #fd5765;
18 | color: #fd5765;
19 | }
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/filter-tags/filter-tags.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 |
5 | import { State, Store } from '@sambego/storybook-state'
6 | // Components
7 | import FilterTags from './filter-tags'
8 |
9 | const store = new Store({
10 | activeOption: 0,
11 | })
12 |
13 | storiesOf('Components/Shared Components', module).add('Filter Tags', () => {
14 | return (
15 |
23 |
24 | {
28 | store.set({ activeOption: index })
29 | }}
30 | />
31 |
32 |
33 | )
34 | })
35 |
--------------------------------------------------------------------------------
/src/components/filter-tags/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import FilterTags from './filter-tags'
3 | export default FilterTags
4 |
--------------------------------------------------------------------------------
/src/components/footer/footer.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { Link } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Styles
6 | import styles from './footer.module.scss'
7 |
8 | const Footer = ({ links, className }) => (
9 |
10 |
11 |
12 | -
13 |
Glorious Oxfordshire
14 |
15 | Unit 9
16 | Southill Business Park
17 | Charlbury
18 | OX7 3EW
19 | UK
20 |
21 |
22 |
23 | -
24 |
Vibrant Bristol
25 |
26 | 3rd Floor
27 |
28 | 15 Colston Street
29 |
30 | Bristol
31 |
32 | BS1 5AP
33 |
34 | UK
35 |
36 |
37 |
38 | -
39 |
Historic Cambridge
40 |
41 | Future Business Centre
42 |
43 | Kings Hedge road
44 |
45 | Cambridge
46 |
47 | CB4 2HY
48 |
49 | UK
50 |
51 |
52 |
53 | -
54 |
Working in the US
55 |
56 | We have a special formula for working successfully with
57 | organisations in the US
58 |
59 |
60 |
61 |
62 |
63 | © Torchbox {new Date().getFullYear()}
64 | {links.map((link, index) => (
65 |
70 | {link.label}
71 |
72 | ))}
73 |
74 |
75 |
.default})
80 |
81 | )
82 |
83 | Footer.propTypes = {
84 | links: PropTypes.array,
85 | className: PropTypes.string,
86 | }
87 |
88 | Footer.defaultProps = {
89 | className: '',
90 | links: [],
91 | }
92 |
93 | export default Footer
94 |
--------------------------------------------------------------------------------
/src/components/footer/footer.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .footer {
4 | position: relative;
5 | background-color: $light-grey;
6 | padding: 100px 0 30px;
7 |
8 | &Content {
9 | @include container;
10 | }
11 |
12 | &Address {
13 | position: relative;
14 | padding: 0px 100px 20px 0px;
15 |
16 | &List {
17 | display: flex;
18 | flex-direction: row;
19 | flex-wrap: wrap;
20 | margin: 0 0 60px;
21 | }
22 |
23 | &Title {
24 | font-size: 20px;
25 | font-weight: bold;
26 | color: $primary;
27 | margin: 0 0 10px;
28 | line-height: 1;
29 |
30 | &::after {
31 | position: absolute;
32 | top: -6px;
33 | margin-left: 10px;
34 | content: '';
35 | width: 20px;
36 | height: 30px;
37 | background: url("../../images/icons/diamond.svg");
38 | transform: rotate(30deg);
39 | }
40 | }
41 |
42 | p {
43 | line-height: 22px;
44 | color: $grey;
45 | font-size: 14px;
46 | max-width: 300px;
47 | }
48 | }
49 |
50 | &Copyright {
51 | font-size: 0.7em;
52 | color: $grey;
53 | margin-right: 10px;
54 | }
55 |
56 | &Link {
57 | display: inline-block;
58 | height: 30px;
59 | font-size: 14px;
60 | margin-right: 10px;
61 | }
62 |
63 | a {
64 | font-size: 14px;
65 | height: 23px;
66 | font-weight: bold;
67 | border-bottom: 2px solid $green;
68 | color: $blue;
69 | &:hover {
70 | color: $blue;
71 | }
72 | }
73 |
74 | &Image {
75 | position: absolute;
76 | bottom: 0;
77 | right: 20px;
78 | width: 25vw;
79 | display: block;
80 | z-index: 20;
81 |
82 | @include for-phone-only {
83 | display: none ;
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/components/footer/footer.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import Footer from './footer'
6 |
7 | storiesOf('Components/Shared Components', module).add('Footer', () => {
8 | return (
9 |
33 | )
34 | })
35 |
--------------------------------------------------------------------------------
/src/components/footer/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Footer from './footer'
3 | export default Footer
4 |
--------------------------------------------------------------------------------
/src/components/header/header.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | import { State, Store } from '@sambego/storybook-state'
5 | import { action, configureActions } from '@storybook/addon-actions'
6 | // Components
7 | import Header from './index'
8 |
9 | const store = new Store({
10 | currentUrl: '#1',
11 | })
12 |
13 | storiesOf('Components/Shared Components', module).add('Header', () => {
14 | return (
15 |
16 |
81 | )
82 | })
83 |
--------------------------------------------------------------------------------
/src/components/header/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import header from './header'
3 | export default header
4 |
--------------------------------------------------------------------------------
/src/components/help-block/help-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Components
6 | import Contact from '../contact/contact'
7 | import { ReactComponent as TickIcon } from '@images/icons/tick.svg'
8 | // Utilities
9 | import { renderTorchUp } from '@utils/torchup'
10 | // Styles
11 | import styles from './help-block.module.scss'
12 |
13 | class HelpBlock extends React.Component {
14 | render() {
15 | const { title, links, sectionTitle, contact } = this.props
16 | return (
17 |
18 |
19 | {sectionTitle}
20 | {title && }
21 |
22 | {links != null ? (
23 |
24 |
25 | {links.map((link, index) => (
26 | -
30 |
31 | { link.href ? (
32 |
33 | {link.title}
34 |
35 | ) : (
36 | {link.title}
37 | )
38 | }
39 |
40 | ))}
41 |
42 |
43 | ) : null}
44 |
45 |
46 | {contact ? (
47 |
48 | ) : null}
49 |
50 | )
51 | }
52 | }
53 |
54 | HelpBlock.propTypes = {
55 | title: PropTypes.string,
56 | links: PropTypes.array,
57 | sectionTitle: PropTypes.string.isRequired,
58 | greetingImage: PropTypes.string,
59 | contact: PropTypes.object,
60 | }
61 |
62 | HelpBlock.defaultProps = {
63 | title: 'We can help you…',
64 | links: [],
65 | sectionTitle: 'Services',
66 | }
67 |
68 | export default HelpBlock
69 |
--------------------------------------------------------------------------------
/src/components/help-block/help-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import HelpBlock from './help-block'
6 | import Contact from '@components/contact/contact'
7 |
8 | storiesOf('Components/Landing Page', module).add('Help block', () => {
9 | return (
10 |
33 | )
34 | })
35 |
--------------------------------------------------------------------------------
/src/components/help-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import HelpBlock from './help-block'
3 | export default HelpBlock
4 |
--------------------------------------------------------------------------------
/src/components/hero/hero.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Components
6 | import AnimatedCharacter from '@components/animated-character'
7 | import ParentPageLink from '@components/parent-page-link'
8 | // Utilities
9 | import { renderTorchUp } from '@utils/torchup'
10 | import { pageUrl } from '@utils/urls'
11 | // Styles
12 | import styles from './hero.module.scss'
13 |
14 | class Hero extends React.Component {
15 |
16 | render() {
17 | const {
18 | title,
19 | description,
20 | links,
21 | collapsed,
22 | greetingImageType,
23 | parentLink,
24 | pageNavRef,
25 | } = this.props
26 |
27 | return (
28 |
33 |
34 | {parentLink ? (
35 |
39 | ) : null}
40 |
41 |
42 |
43 | {links != null ? (
44 |
60 | ) : null}
61 |
62 |
63 |
64 | )
65 | }
66 | }
67 |
68 | Hero.propTypes = {
69 | title: PropTypes.string,
70 | description: PropTypes.string,
71 | links: PropTypes.array,
72 | collapsed: PropTypes.bool,
73 | greetingImageType: PropTypes.string,
74 | parentLink: PropTypes.object,
75 | }
76 |
77 | Hero.defaultProps = {
78 | links: [],
79 | collapsed: false,
80 | greetingImageType: 'woman-left',
81 | }
82 |
83 | export default Hero
84 |
--------------------------------------------------------------------------------
/src/components/hero/hero.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | import { State, Store } from '@sambego/storybook-state'
5 |
6 | const store = new Store({
7 | collapsed: false,
8 | })
9 | // Components
10 | import Hero from './hero'
11 | // Styles
12 | import styles from './hero.module.scss'
13 |
14 | const HeroComp = () => {
15 | if (typeof window !== `undefined`) {
16 | window.addEventListener('scroll', () => {
17 | if (window.scrollY > 0) {
18 | if (!store.state.collapsed) {
19 | store.set({ collapsed: true })
20 | }
21 | } else {
22 | if (store.state.collapsed) {
23 | store.set({ collapsed: false })
24 | }
25 | }
26 | })
27 | }
28 |
29 | return (
30 |
31 |
43 |
53 | 😍 START SCROLLING!!
54 |
55 |
56 | )
57 | }
58 |
59 | storiesOf('Components/Landing Page', module)
60 | .add('Hero block', HeroComp)
61 | .add('Hero Block (Dark Theme)', () => (
62 |
63 |
64 |
65 | ))
66 |
--------------------------------------------------------------------------------
/src/components/hero/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Hero from './hero'
3 | export default Hero
4 |
--------------------------------------------------------------------------------
/src/components/jobs-listing-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import JobsBlock from './jobs-block'
3 | export default JobsBlock
4 |
--------------------------------------------------------------------------------
/src/components/jobs-listing-block/jobs-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Components
6 | import Avatar from '../avatar/avatar'
7 | // Styles
8 | import styles from './jobs-block.module.scss'
9 | //Utilities
10 | import { ReactComponent as Woman } from '@images/help-character.svg'
11 |
12 | class JobsBlock extends React.Component {
13 | render() {
14 | const { jobs, className, listingUrl } = this.props
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
38 |
39 | {listingUrl ? (
40 |
41 | See more jobs
42 |
43 | ) : null}
44 |
45 |
46 | )
47 | }
48 | }
49 |
50 | JobsBlock.propTypes = {
51 | jobs: PropTypes.array,
52 | className: PropTypes.string,
53 | listingUrl: PropTypes.string,
54 | }
55 |
56 | JobsBlock.defaultProps = {
57 | className: '',
58 | jobs: [],
59 | sectionTitle: 'Thinking',
60 | }
61 |
62 | export default JobsBlock
63 |
--------------------------------------------------------------------------------
/src/components/jobs-listing-block/jobs-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import JobsBlock from './jobs-block'
6 |
7 | storiesOf('Components/Listing Pages', module).add('Jobs listing block', () => {
8 | return (
9 |
49 | )
50 | })
51 |
--------------------------------------------------------------------------------
/src/components/layout/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Layout from './layout'
3 | export default Layout
4 |
--------------------------------------------------------------------------------
/src/components/layout/layout.module.scss:
--------------------------------------------------------------------------------
1 | @import "../../styles/app.module";
2 | .pageContainer {
3 | overflow: hidden;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/markdown/index.js:
--------------------------------------------------------------------------------
1 | import Markdown from './markdown'
2 |
3 | export default Markdown
4 |
--------------------------------------------------------------------------------
/src/components/markdown/markdown.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import ReactMarkdown from 'react-markdown'
5 | // Components
6 | import CodeBlock from '@components/code-block'
7 |
8 | const Markdown = ({ source }) => (
9 |
10 | )
11 |
12 | Markdown.propTypes = {
13 | source: PropTypes.string,
14 | }
15 |
16 | export default Markdown
17 |
--------------------------------------------------------------------------------
/src/components/menu-button/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import MenuButton from './menu-button'
3 | export default MenuButton
4 |
--------------------------------------------------------------------------------
/src/components/menu-button/menu-button.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Styles
5 | import styles from './menu-button.module.scss'
6 |
7 | const MenuButton = ({ onClick, isOpen, className }) => (
8 |
16 |
17 |
18 |
19 |
20 | )
21 |
22 | MenuButton.propTypes = {
23 | onClick: PropTypes.func.isRequired,
24 | isOpen: PropTypes.bool.isRequired,
25 | className: PropTypes.string,
26 | }
27 |
28 | MenuButton.defaultProps = {
29 | onClick: null,
30 | isOpen: false,
31 | }
32 |
33 | export default MenuButton
34 |
--------------------------------------------------------------------------------
/src/components/menu-button/menu-button.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | $header-icon-color: var(--header-icon-color, $primary);
4 |
5 | .menuButton {
6 | position: absolute;
7 | top: 0;
8 | right: -25px;
9 | width: 80px;
10 | height: 100%;
11 | display: block;
12 | border: none;
13 | text-align: center;
14 | z-index: 999;
15 | cursor: pointer;
16 | @include for-desktop-small-up {
17 | display: none;
18 | }
19 |
20 | span {
21 | &:nth-child(1) {
22 | transition: all 0.2s ease-in;
23 | position: absolute;
24 | width: 20px;
25 | background-color: $header-icon-color;
26 | height: 3px;
27 | left: 37%;
28 | top: 40px;
29 | transform: translateX(0);
30 | }
31 | &:nth-child(2) {
32 | transition: all 0.2s ease-in;
33 | background-color: $header-icon-color;
34 | position: absolute;
35 | width: 16px;
36 | height: 3px;
37 | left: 45%;
38 | top: 48px;
39 | }
40 | &:nth-child(3) {
41 | transition: all 0.2s ease-in;
42 | background-color: $header-icon-color;
43 | position: absolute;
44 | width: 12px;
45 | height: 3px;
46 | left: 41%;
47 | top: 55px;
48 | }
49 | }
50 | &.twist {
51 | span {
52 | &:nth-child(1) {
53 | opacity: 0;
54 | }
55 |
56 | &:nth-child(2) {
57 | background-color: white;
58 | transform: rotate(-45deg) scale(2);
59 | top: 50px;
60 | left: 40%;
61 | height: 2px;
62 | width: 12px;
63 | }
64 |
65 | &:nth-child(3) {
66 | background-color: white;
67 | transform: rotate(45deg) scale(2);
68 | top: 50px;
69 | left: 40%;
70 | height: 2px;
71 | width: 12px;
72 | }
73 |
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/nav-link/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import NavLink from './nav-link'
3 | export default NavLink
4 |
--------------------------------------------------------------------------------
/src/components/nav-link/nav-link.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { Link } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Styles
6 | import styles from './nav-link.module.scss'
7 |
8 | const NavLink = ({
9 | title,
10 | strap,
11 | badge,
12 | href,
13 | onClick,
14 | active,
15 | collapsed,
16 | dropdownLinks,
17 | }) => (
18 |
19 |
27 |
28 | {title}
29 | {badge != null ?
{badge}
: null}
30 |
31 |
32 | {dropdownLinks && dropdownLinks.length !== 0 &&
33 |
34 |
35 | {dropdownLinks.map((link, index) => (
36 | -
37 | {link.title}
38 |
39 | ))}
40 |
41 |
42 | }
43 |
44 | )
45 |
46 | NavLink.propTypes = {
47 | title: PropTypes.string,
48 | strap: PropTypes.string,
49 | badge: PropTypes.number,
50 | href: PropTypes.string,
51 | onClick: PropTypes.func,
52 | active: PropTypes.bool,
53 | collapsed: PropTypes.bool,
54 | dropdownLinks: PropTypes.array,
55 | }
56 |
57 | NavLink.defaultProps = {
58 | onClick: null,
59 | isOpen: false,
60 | dropdownLinks: [],
61 | }
62 |
63 | export default NavLink
64 |
--------------------------------------------------------------------------------
/src/components/parent-page-link/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import ParentPageLink from './parent-page-link'
3 | export default ParentPageLink
4 |
--------------------------------------------------------------------------------
/src/components/parent-page-link/parent-page-link.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { Link } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Styles
6 | import styles from './parent-page-link.module.scss'
7 |
8 | const ParentPageLink = ({ label, href, className }) => (
9 |
10 | {label}
11 |
12 | )
13 |
14 | ParentPageLink.propTypes = {
15 | label: PropTypes.string,
16 | href: PropTypes.string,
17 | className: PropTypes.string,
18 | }
19 |
20 | ParentPageLink.defaultProps = {
21 | className: '',
22 | label: '',
23 | href: '#',
24 | }
25 |
26 | export default ParentPageLink
27 |
--------------------------------------------------------------------------------
/src/components/parent-page-link/parent-page-link.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | // Component theme overrides:
4 | $hero-background: var(--hero-background, white);
5 | $hero-title-color: var(--hero-title-color, $primary);
6 | $hero-parent-page-color: var(--hero-parent-page-color);
7 | $hero-parent-page-slash-color: var(--hero-parent-page-slash-color);
8 | $hero-parent-page-hover-color: var(--hero-parent-page-hover-color);
9 |
10 | .parentLink {
11 | display: block;
12 | font-size: 18px;
13 | color: $accent;
14 | margin: 0 0 16px;
15 | position: relative;
16 | z-index: 0;
17 |
18 | a {
19 | border: none;
20 | color: $hero-parent-page-color;
21 | z-index: 2;
22 | background-color: $hero-background;
23 |
24 | &:hover {
25 | color: $hero-parent-page-hover-color;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/parent-page-link/parent-page-link.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import ParentPageLink from './parent-page-link'
6 |
7 | storiesOf('Components/Shared Components', module).add(
8 | 'Parent Page Link',
9 | () => {
10 | return (
11 |
23 | )
24 | }
25 | )
26 |
--------------------------------------------------------------------------------
/src/components/process-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import ProcessBlock from './process-block'
3 | export default ProcessBlock
4 |
--------------------------------------------------------------------------------
/src/components/process-block/process-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Utilities
6 | import { ReactComponent as ToolkitImage } from '@images/toolkit.svg'
7 | import { renderTorchUp } from '@utils/torchup'
8 | import { pageUrl } from '@utils/urls'
9 | // Components
10 | import RichText from '@components/rich-text'
11 | // Styles
12 | import styles from './process-block.module.scss'
13 |
14 | class ProcessBlock extends React.Component {
15 | render() {
16 | const { title = '', className, sectionTitle, processes } = this.props
17 | return (
18 |
19 |
{sectionTitle}
20 |
21 | {title &&
}
22 |
23 |
24 | {processes.map((process, index) => (
25 | -
26 |
{process.title}
27 |
28 |
29 |
30 | {process.pageLinkLabel && process.pageLink &&
31 |
35 | {process.pageLinkLabel}
36 |
37 | }
38 |
39 | ))}
40 |
41 |
42 |
43 | )
44 | }
45 | }
46 |
47 | ProcessBlock.propTypes = {
48 | title: PropTypes.string,
49 | className: PropTypes.string,
50 | sectionTitle: PropTypes.string,
51 | processes: PropTypes.array,
52 | }
53 |
54 | ProcessBlock.defaultProps = {
55 | className: '',
56 | sectionTitle: 'Process',
57 | processes: [],
58 | }
59 |
60 | export default ProcessBlock
61 |
--------------------------------------------------------------------------------
/src/components/process-block/process-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .process {
4 |
5 | &Title {
6 | font-weight: 800;
7 | }
8 |
9 | &Block {
10 | background-color: white;
11 | padding: 90px 0 120px;
12 | padding-bottom: 0;
13 | position: relative;
14 | }
15 |
16 | &Image {
17 | width: 66vw;
18 | height: calc(66vw * 0.6);
19 | position: absolute;
20 | left: -8.33vw;
21 | top: 20px;
22 | @include for-tablet-landscape-up {
23 | width: 19vw;
24 | height: calc(19vw * 0.6);
25 | top: 60px;
26 | }
27 |
28 | .toolkit_svg_accented {
29 | fill: red;
30 | }
31 |
32 | }
33 |
34 | &Container {
35 | @include container;
36 | }
37 |
38 | &List {
39 | display: flex;
40 | flex-wrap: wrap;
41 | flex-direction: row;
42 | margin: 0;
43 | padding-top: calc((66vw * 0.6) + 60px);
44 |
45 | @include for-tablet-landscape-up {
46 | margin: 60px 0 80px 16.66vw;
47 | padding-top: 50px;
48 | }
49 | }
50 |
51 | &Item {
52 | width: 100%;
53 | padding-bottom: 60px;
54 | @include for-tablet-landscape-up {
55 | width: 50%;
56 | padding-right: 60px;
57 | padding-bottom: 90px;
58 | }
59 |
60 | &Title {
61 | font-size: 34px;
62 | line-height: 40px;
63 | color: $light-blue;
64 | font-weight: 800;
65 | background: white;
66 | padding: 10px 0 0;
67 | margin: 0 0 0;
68 | }
69 |
70 | &Desc {
71 | font-size: 17px;
72 | line-height: 26px;
73 | margin: 10px 0 0;
74 | color: rgba(51, 51, 51, 0.9);
75 | width: calc(100% - 20px);
76 | padding-bottom: 20px;
77 | }
78 |
79 | &Link {
80 | font-size: 17px;
81 | font-weight: 700;
82 | line-height: 26px;
83 | text-decoration: none;
84 | color: $light-blue;
85 |
86 | &:hover,
87 | &:focus {
88 | color: $light-blue;
89 | border-bottom-color: $light-blue;
90 | }
91 | }
92 |
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/components/process-block/process-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import ProcessBlock from './process-block'
6 |
7 | storiesOf('Components/Landing Page', module).add(
8 | 'Process (image) block',
9 | () => {
10 | return (
11 |
41 | )
42 | }
43 | )
44 |
--------------------------------------------------------------------------------
/src/components/process-image-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import ProcessImageBlock from './process-image-block'
3 | export default ProcessImageBlock
4 |
--------------------------------------------------------------------------------
/src/components/process-image-block/process-image-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Utilities
6 | import { renderTorchUp } from '@utils/torchup'
7 | // Styles
8 | import styles from './process-image-block.module.scss'
9 |
10 | class ProcessImageBlock extends React.Component {
11 | render() {
12 | const { title = '', className, sectionTitle } = this.props
13 | return (
14 |
15 |
16 |
{sectionTitle}
17 |
18 |
19 |
20 |
})
25 |
})
30 |
31 |
32 |
33 | )
34 | }
35 | }
36 |
37 | ProcessImageBlock.propTypes = {
38 | title: PropTypes.string,
39 | className: PropTypes.string,
40 | sectionTitle: PropTypes.string,
41 | }
42 |
43 | ProcessImageBlock.defaultProps = {
44 | className: '',
45 | sectionTitle: 'Process',
46 | }
47 |
48 | export default ProcessImageBlock
49 |
--------------------------------------------------------------------------------
/src/components/process-image-block/process-image-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .process {
4 |
5 | &Block {
6 | background-color: white;
7 | padding: 90px 0 40px;
8 | }
9 |
10 | &Container {
11 | @include container;
12 | }
13 |
14 | &Title {
15 | font-size: 45px;
16 | line-height: 50px;
17 | color: $primary;
18 | font-weight: 800;
19 | }
20 |
21 | &ImageContainer {
22 | padding: 20px 0;
23 | @include for-tablet-landscape-up {
24 | padding: 60px 60px 0;
25 | }
26 | }
27 |
28 | &DesktopImage {
29 | width: 100%;
30 | display: none;
31 | @include for-tablet-landscape-up {
32 | display: block;
33 | }
34 | }
35 |
36 | &MobileImage {
37 | width: 100%;
38 | @include for-tablet-landscape-up {
39 | display: none;
40 | }
41 | }
42 |
43 | }
44 |
45 | .pageSectionTitle {
46 | left: 0;
47 | top: -45px;
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/process-image-block/process-image-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import ProcessImageBlock from './process-image-block'
6 |
7 | storiesOf('Components/Landing Page', module).add(
8 | 'Process (image) block',
9 | () => {
10 | return (
11 |
41 | )
42 | }
43 | )
44 |
--------------------------------------------------------------------------------
/src/components/quote-slider/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import QuoteSlider from './quote-slider'
3 | export default QuoteSlider
4 |
--------------------------------------------------------------------------------
/src/components/quote-slider/quote-slider.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import QuoteSlider from './quote-slider'
6 |
7 | storiesOf('Components/Shared Components', module).add('Quote Slider', () => {
8 | return (
9 |
17 |
39 |
40 | )
41 | })
42 |
--------------------------------------------------------------------------------
/src/components/rich-text/index.js:
--------------------------------------------------------------------------------
1 | import RichText from './rich-text'
2 | export default RichText
3 |
--------------------------------------------------------------------------------
/src/components/rich-text/rich-text.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 |
5 | import { pageUrl } from '@utils/urls'
6 |
7 | export default class RichText extends React.Component {
8 | static propTypes = {
9 | value: PropTypes.string.isRequired
10 | }
11 |
12 | state = {
13 | processedHTML: ''
14 | }
15 |
16 | componentDidMount() {
17 | this.parseAnchorsInHTML()
18 | }
19 |
20 | render() {
21 | const { className } = this.props
22 | const { processedHTML } = this.state
23 | return
24 | }
25 |
26 | parseAnchorsInHTML() {
27 | // This can be run during the runtime only due to use of web browser's API,
28 | // i.e. "document".
29 | const el = document.createElement( 'body' )
30 | el.innerHTML = this.props.value
31 |
32 | // Set "href" for page links.
33 | for (const link of el.querySelectorAll('a[data-page-slug][data-page-type]')) {
34 | const url = pageUrl({
35 | type: link.dataset.pageType,
36 | slug: link.dataset.pageSlug,
37 | serviceSlug: link.dataset.pageServiceSlug
38 | })
39 | link.href = url
40 | delete link.dataset.pageType
41 | delete link.dataset.pageSlug
42 | delete link.dataset.pageServiceSlug
43 | }
44 | this.setState({ processedHTML: el.innerHTML })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/services-listing-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import ServicesBlock from './services-block'
3 | export default ServicesBlock
4 |
--------------------------------------------------------------------------------
/src/components/services-listing-block/services-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Styles
6 | import styles from './services-block.module.scss'
7 |
8 | class ServicesBlock extends React.Component {
9 | render() {
10 | const { services, className, sectionTitle, greetingImage } = this.props
11 |
12 | return (
13 |
14 |

15 |
16 |
17 |
{sectionTitle}
18 |
19 | {services.slice(1).map((blog, index) => (
20 |
25 |
{blog.title}
26 |
{blog.desc}
27 |
28 | ))}
29 |
30 |
31 |
32 | )
33 | }
34 | }
35 |
36 | ServicesBlock.propTypes = {
37 | services: PropTypes.array,
38 | className: PropTypes.string,
39 | listingUrl: PropTypes.string.isRequired,
40 | sectionTitle: PropTypes.string,
41 | greetingImage: PropTypes.string,
42 | }
43 |
44 | ServicesBlock.defaultProps = {
45 | className: '',
46 | services: [],
47 | sectionTitle: 'Wagtail design, build + support',
48 | greetingImage: require('@images/toolkit.svg'),
49 | }
50 |
51 | export default ServicesBlock
52 |
--------------------------------------------------------------------------------
/src/components/services-listing-block/services-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .block {
4 | background-color: white;
5 | padding: 95px 0 20px;
6 |
7 | &Image {
8 | position: absolute;
9 | width: 40vw;
10 | margin-top: 20px;
11 | @include for-tablet-landscape-up {
12 | position: absolute;
13 | width: 19vw;
14 | top: 145px;
15 | margin-top: 0;
16 | }
17 | }
18 |
19 | &Content {
20 | @include container;
21 | padding: calc(0.7 * 40vw + 60px) 0 80px 40px;
22 | @include for-tablet-landscape-up {
23 | padding: 55px 0 80px 16.66vw;
24 | }
25 | }
26 |
27 | &ServiceList {
28 | display: grid;
29 | grid-template-columns: 1fr;
30 | @include for-tablet-landscape-up {
31 | grid-template-columns: 1fr 1fr;
32 | grid-column-gap: 8.33vw;
33 | }
34 | }
35 |
36 | &Service {
37 | padding: 0 0 60px 0;
38 | text-decoration: none;
39 | border-bottom: none;
40 |
41 | &Title {
42 | font-size: 30px;
43 | font-weight: 800;
44 | line-height: 40px;
45 | margin: 0 0 10px;
46 | color: $blue;
47 | }
48 |
49 | &Desc {
50 | font-size: 17px;
51 | line-height: 26px;
52 | margin: 10px 0 0;
53 | color: rgba(51, 51, 51, 0.9);
54 | }
55 |
56 | }
57 |
58 | }
59 |
60 | .pageSectionTitle {
61 | top: -45px;
62 | left: 0;
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/services-listing-block/services-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import ServicesBlock from './services-block'
6 | // Styles
7 | import styles from './services-block.module.scss'
8 |
9 | storiesOf('Components/Landing Page', module).add(
10 | 'Services listing block',
11 | () => {
12 | return (
13 |
14 |
49 |
50 | )
51 | }
52 | )
53 |
--------------------------------------------------------------------------------
/src/components/streamfield-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import StreamfieldBlock from './streamfield-block'
3 | export default StreamfieldBlock
4 |
--------------------------------------------------------------------------------
/src/components/tag/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import Tag from './tag'
3 | export default Tag
4 |
--------------------------------------------------------------------------------
/src/components/tag/tag.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 |
5 | import { Link } from 'gatsby'
6 | // Styles
7 | import styles from './tag.module.scss'
8 |
9 | const Tag = ({ label, className, onClick, href }) => (
10 |
15 | {label}
16 |
17 | )
18 |
19 | Tag.propTypes = {
20 | label: PropTypes.string,
21 | onClick: PropTypes.func,
22 | href: PropTypes.string,
23 | className: PropTypes.string,
24 | }
25 |
26 | Tag.defaultProps = {
27 | className: '',
28 | }
29 |
30 | export default Tag
31 |
--------------------------------------------------------------------------------
/src/components/tag/tag.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .tag {
4 | display: inline-block;
5 | transition: all 0.2s ease;
6 | border: 2px solid rgba(35, 23, 73, 0.05);
7 | border-radius: 5px;
8 | font-size: 12px;
9 | color: $light-grey-accessible;
10 | padding: 0 5px;
11 | margin: 5px 5px 5px 0;
12 | position: relative;
13 | font-weight: 500;
14 | cursor: pointer;
15 |
16 | display: inline-flex;
17 | height: 26px;
18 | align-items: center;
19 |
20 | &:hover {
21 | border: 2px solid $accent;
22 | color: $accent-small-text;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/tag/tag.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | import { action } from '@storybook/addon-actions'
5 | // Components
6 | import Tag from './tag'
7 |
8 | storiesOf('Components/Shared Components', module).add('Tag', () => {
9 | return (
10 |
18 |
19 |
20 | )
21 | })
22 |
--------------------------------------------------------------------------------
/src/components/team-listing-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import TeamListingBlock from './team-listing-block'
3 | export default TeamListingBlock
4 |
--------------------------------------------------------------------------------
/src/components/team-listing-block/team-listing-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Styles
6 | import styles from './team-listing-block.module.scss'
7 |
8 | const TeamListingBlock = ({ team, className }) => {
9 | return (
10 |
11 |
12 |
13 | {team.map((person) => (
14 |
19 |

24 |
{person.name}
25 |
{person.role}
26 |
27 | ))}
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | TeamListingBlock.propTypes = {
35 | team: PropTypes.array,
36 | className: PropTypes.string,
37 | }
38 |
39 | TeamListingBlock.defaultProps = {
40 | className: '',
41 | team: [],
42 | }
43 |
44 | export default TeamListingBlock
45 |
--------------------------------------------------------------------------------
/src/components/team-listing-block/team-listing-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .block {
4 | position: relative;
5 | background-color: white;
6 | padding: 20px 0;
7 |
8 | &Content {
9 | @include container;
10 | }
11 |
12 | &PersonList {
13 | display: flex;
14 | flex-wrap: wrap;
15 | }
16 |
17 | &PersonLink {
18 | cursor: pointer;
19 | padding-bottom: 50px;
20 | text-decoration: none;
21 | border-bottom: none;
22 | width: 50%;
23 |
24 | @include for-tablet-landscape-up {
25 | width: 33.333%;
26 | }
27 |
28 | @include for-desktop-up {
29 | width: 25%;
30 | }
31 |
32 | &Avatar {
33 | width: 100%;
34 | }
35 |
36 | &Name {
37 | display: block;
38 | margin: 10px 0 5px;
39 | line-height: 1;
40 | font-size: 18px;
41 | font-weight: 800;
42 | color: $primary;
43 |
44 | @include for-tablet-portrait-up {
45 | font-size: 22px;
46 | }
47 |
48 | &:hover {
49 | color: $blue;
50 | }
51 | }
52 |
53 | &Role {
54 | line-height: 1.4;
55 | color: $accent;
56 | text-transform: uppercase;
57 | letter-spacing: 0.15em;
58 | font-weight: bold;
59 | font-size: 9px;
60 | margin: 0;
61 |
62 | @include for-tablet-portrait-up {
63 | font-size: 11px;
64 | }
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/team-listing-block/team-listing-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import TeamListingBlock from './team-listing-block'
6 |
7 | storiesOf('Components/Listing Pages', module).add('Team listing block', () => {
8 | return (
9 |
73 | )
74 | })
75 |
--------------------------------------------------------------------------------
/src/components/teaser-block/index.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { StaticQuery, graphql } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Components
6 | import TeaserBlock from './teaser-block'
7 | // Utilities
8 | import { pageUrl } from '@utils/urls'
9 |
10 | const TeaserBlockContainer = ({ ignoreSlug }) => {
11 | return (
12 | {
29 | const teasers = data.wagtail.services
30 | .filter(service => service.slug !== ignoreSlug)
31 | .slice(0, 2)
32 | .map(service => ({
33 | ...service,
34 | href: pageUrl(service.servicePage),
35 | }))
36 | return
37 | }}
38 | />
39 | )
40 | }
41 |
42 | TeaserBlockContainer.propTypes = {
43 | ignoreSlug: PropTypes.string,
44 | }
45 |
46 | export default TeaserBlockContainer
47 |
--------------------------------------------------------------------------------
/src/components/teaser-block/teaser-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Link } from 'gatsby'
5 | // Components
6 | import TeaserLink from './teaser'
7 | // Utilities
8 | import { renderTorchUp } from '@utils/torchup'
9 | // Styles
10 | import styles from './teaser-block.module.scss'
11 |
12 | const TeaserBlock = ({ title, teasers, className }) => {
13 | return (
14 |
15 |
16 |
17 |
18 | {teasers.map((teaser, index) => (
19 |
25 | ))}
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | TeaserBlock.propTypes = {
33 | title: PropTypes.string,
34 | src: PropTypes.string,
35 | className: PropTypes.string,
36 | teasers: PropTypes.array,
37 | }
38 |
39 | TeaserBlock.defaultProps = {
40 | className: '',
41 | teasers: [],
42 | title: 'More from Torchbox...',
43 | }
44 |
45 | export default TeaserBlock
46 |
--------------------------------------------------------------------------------
/src/components/teaser-block/teaser-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .teaserBlock {
4 | background-color: $light-grey;
5 | padding: 30px 0 0;
6 |
7 | @include for-tablet-landscape-up {
8 | padding: 80px 0 0;
9 | }
10 |
11 | &Container {
12 | @include container;
13 | }
14 |
15 | &Title {
16 | margin: 0 0 30px;
17 | }
18 |
19 | &List {
20 | display: flex;
21 | flex-direction: column;
22 | justify-content: space-between;
23 |
24 | @include for-tablet-landscape-up {
25 | flex-direction: row;
26 | }
27 | }
28 |
29 | &Item {
30 | flex: 1;
31 | text-decoration: none;
32 | background-color: white;
33 | margin-bottom: 4px;
34 | border-radius: 3px;
35 | border-bottom: none;
36 | padding: 40px 30px;
37 |
38 | &:hover {
39 | background-color: rgba(255, 255, 255, 0.5);
40 | }
41 |
42 | @include for-tablet-landscape-up {
43 | margin-right: 4px;
44 | padding: 50px 50px;
45 | }
46 |
47 | &Title {
48 | color: $light-blue;
49 | font-weight: 800;
50 | font-size: 35px;
51 | line-height: 40px;
52 | margin: 0;
53 |
54 | @include for-tablet-landscape-up {
55 | font-size: 45px;
56 | line-height: 50px;
57 | }
58 |
59 | .teaserBlockItem:hover & {
60 | color: $blue;
61 | }
62 |
63 | &Icon {
64 | width: 20px;
65 | height: 30px;
66 | margin-top: 3px;
67 | margin-left: 10px;
68 | fill: $green;
69 |
70 | @include for-tablet-landscape-up {
71 | margin-top: 8px;
72 | }
73 | }
74 | }
75 |
76 | &Desc {
77 | font-size: 20px;
78 | line-height: 30px;
79 | color: $grey;
80 | display: block;
81 | margin: 10px 0;
82 | }
83 |
84 | &:last-of-type {
85 | border-bottom: none;
86 | border-right: none;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/teaser-block/teaser-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import TeaserBlock from './teaser-block'
6 |
7 | storiesOf('Components/Shared Components', module).add('Teaser block', () => {
8 | return (
9 | , 'services'],
15 | description: 'For web builds with the Wagtail open source CMS',
16 | link: '#',
17 | },
18 | {
19 | image: require('@images/tbx-flame.svg'),
20 | title: ['Data',
, 'marketing'],
21 | description: 'For our data driven digital marketing services',
22 | link: '#',
23 | },
24 | ]}
25 | />
26 | )
27 | })
28 |
--------------------------------------------------------------------------------
/src/components/teaser-block/teaser.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { Link } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Components
6 | import { ReactComponent as ChevronIcon } from '@images/icons/chevron.svg'
7 | // Styles
8 | import styles from './teaser-block.module.scss'
9 |
10 | const TeaserLink = ({ title, description, href, className }) => (
11 |
12 |
13 | {title}
14 |
15 |
16 | {description}
17 |
18 | )
19 |
20 | TeaserLink.propTypes = {
21 | title: PropTypes.string,
22 | description: PropTypes.string,
23 | href: PropTypes.string,
24 | className: PropTypes.string,
25 | }
26 |
27 | export default TeaserLink
28 |
--------------------------------------------------------------------------------
/src/components/testimonials-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import TestimonialsBlock from './testimonials-block'
3 | export default TestimonialsBlock
4 |
--------------------------------------------------------------------------------
/src/components/testimonials-block/testimonials-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import QuoteSlider from '@components/quote-slider'
6 | // Styles
7 | import styles from './testimonials-block.module.scss'
8 |
9 | class TestimonialsBlock extends React.Component {
10 | render() {
11 | const { testimonials, logos, className, sectionTitle } = this.props
12 | const spacingClass = testimonials.length ? styles.testimonialsIconsListSpaced : '';
13 | return (
14 |
15 |
{sectionTitle}
16 |
17 |
18 |
19 | {logos.map((logo, index) => (
20 | -
24 |
25 |

26 |
27 |
28 | ))}
29 |
30 |
31 | {testimonials.length ? (
32 |
33 | ): null }
34 |
35 |
36 | )
37 | }
38 | }
39 |
40 | TestimonialsBlock.propTypes = {
41 | logos: PropTypes.array,
42 | testimonials: PropTypes.array.isRequired,
43 | className: PropTypes.string,
44 | sectionTitle: PropTypes.string.isRequired,
45 | }
46 |
47 | TestimonialsBlock.defaultProps = {
48 | testimonials: [],
49 | className: '',
50 | sectionTitle: 'Clients',
51 | }
52 |
53 | export default TestimonialsBlock
54 |
--------------------------------------------------------------------------------
/src/components/testimonials-block/testimonials-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .testimonials {
4 | background-color: white;
5 | padding-bottom: 100px;
6 | position: relative;
7 |
8 | &Container {
9 | @include container;
10 | padding: 90px 0 0;
11 | }
12 |
13 | &Block {
14 | display: grid;
15 | grid-template-columns: auto 1fr;
16 | grid-column-gap: 50px;
17 | }
18 |
19 | &Icons {
20 |
21 | &List {
22 | display: flex;
23 | flex-wrap: wrap;
24 | flex-direction: row;
25 | }
26 |
27 | &ListSpaced {
28 | margin-bottom: 100px;
29 |
30 | @include for-tablet-landscape-up {
31 | margin-bottom: 200px;
32 | }
33 | }
34 |
35 | &Item {
36 | display: flex;
37 | align-items: center;
38 | justify-content: center;
39 | width: calc(100% / 2);
40 |
41 | @include for-tablet-portrait-up {
42 | width: calc(100% / 3);
43 | }
44 |
45 | @include for-tablet-landscape-up {
46 | width: calc(100% / 4);
47 | }
48 |
49 | @include for-desktop-up {
50 | width: calc(100% / 6);
51 | }
52 |
53 | div {
54 | padding: 15px;
55 |
56 | @include for-tablet-landscape-up {
57 | padding: 30px;
58 | }
59 | }
60 |
61 | img {
62 | width: 100%;
63 | height: 100%;
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/testimonials-block/testimonials-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import TestimonialsBlock from './testimonials-block'
6 |
7 | storiesOf('Components/Landing Page', module).add('Testimonials block', () => {
8 | return (
9 |
45 | )
46 | })
47 |
--------------------------------------------------------------------------------
/src/components/theme-provider/index.js:
--------------------------------------------------------------------------------
1 | import ThemeProvider from './theme-provider'
2 | export default ThemeProvider
3 |
--------------------------------------------------------------------------------
/src/components/theme-provider/theme-provider.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import propTypes from 'prop-types'
4 | // Utils
5 | import getTheme from './themes'
6 |
7 | const ThemeProvider = ({ children, theme = 'light' }) => {
8 | const themeVars = getTheme(theme)
9 | const themeStyles = Object.keys(themeVars)
10 | .map(key => `--${key}: ${themeVars[key]}; `)
11 | .reduce((style1, style2) => style1 + style2)
12 |
13 | return (
14 | <>
15 |
20 | {children}
21 | >
22 | )
23 | }
24 |
25 | ThemeProvider.propTypes = {
26 | children: propTypes.node,
27 | theme: propTypes.string,
28 | }
29 |
30 | export default ThemeProvider
31 |
--------------------------------------------------------------------------------
/src/components/title-block/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | import TitleBlock from './title-block'
3 | export default TitleBlock
4 |
--------------------------------------------------------------------------------
/src/components/title-block/title-block.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Utilities
5 | import { renderTorchUp } from '@utils/torchup'
6 | // Styles
7 | import styles from './title-block.module.scss'
8 |
9 | const TitleBlock = ({ title, className, onMouseEnter, onMouseLeave, innerPage, contentPathField }) => (
10 |
15 |
16 |
17 | )
18 |
19 | TitleBlock.propTypes = {
20 | title: PropTypes.string,
21 | className: PropTypes.string,
22 | onMouseEnter: PropTypes.func,
23 | onMouseLeave: PropTypes.func,
24 | }
25 |
26 | TitleBlock.defaultProps = {
27 | className: '',
28 | }
29 |
30 | export default TitleBlock
31 |
--------------------------------------------------------------------------------
/src/components/title-block/title-block.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .block {
4 | @include container;
5 | background: white;
6 |
7 | &Title {
8 | font-size: 50px;
9 | line-height: 55px;
10 | margin: 0 0 20px 0;
11 |
12 | @include for-tablet-landscape-up {
13 | font-size: 70px;
14 | line-height: 80px;
15 | }
16 |
17 | @include for-desktop-up {
18 | margin: 0 8.33vw 20px 16.66vw;
19 | }
20 | }
21 |
22 | &TitleInner {
23 | font-size: 45px;
24 | line-height: 50px;
25 |
26 | @include for-tablet-landscape-up {
27 | font-size: 70px;
28 | line-height: 80px;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/title-block/title-block.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | import { State, Store } from '@sambego/storybook-state'
5 | // Components
6 | import TitleBlock from './title-block'
7 | // Utilities
8 | import { parseToHtml } from '@utils/torchup'
9 |
10 | const store = new Store({
11 | title: 'Meet the team, your swell digital pals.',
12 | })
13 |
14 | storiesOf('Components/Shared Components', module).add(
15 | 'Title Block (+ Test TorchUp)',
16 | () => {
17 | const { title } = store.state
18 |
19 | const updateTitle = event => {
20 | store.set({ title: event.target.value })
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
33 |
34 |
35 |
36 | // TorchUp Syntax:
37 |
38 | *foo* => foo (in bold)
39 |
40 | {'{ foo }'} => foo (in heavy-bold/black)
41 |
42 | [ foo ] => foo (with text in accent color)
43 |
\ => Escape next special character
44 |
45 |
46 |
47 | )
48 | }
49 | )
50 |
51 | const helper = {
52 | container: {
53 | display: 'flex',
54 | flexWrap: 'wrap',
55 | alignItems: 'center',
56 | padding: 40,
57 | paddingBottom: 20,
58 | backgroundColor: '#3BECCD',
59 | },
60 |
61 | label: {
62 | fontWeight: '800',
63 | opacity: 0.7,
64 | marginBottom: 20,
65 | marginRight: 20,
66 | },
67 |
68 | input: {
69 | flexGrow: 1,
70 | minWidth: 600,
71 | backgroundColor: 'rgba(0, 0, 0, 0.15)',
72 | border: 'none',
73 | height: 40,
74 | borderRadius: 5,
75 | marginBottom: 20,
76 | color: 'white',
77 | padding: '5px 10px',
78 | fontSize: 16,
79 | fontWeight: 600,
80 | },
81 |
82 | codeContainer: {
83 | padding: 40,
84 | paddingBottom: 20,
85 | backgroundColor: '#fd5765',
86 | },
87 |
88 | code: {
89 | background: 'rgba(0, 0, 0, 0.15)',
90 | color: 'rgba(255, 255, 255, 0.9)',
91 | borderRadius: 3,
92 | padding: 10,
93 | width: '100%',
94 | },
95 | }
96 |
--------------------------------------------------------------------------------
/src/context/theme-context.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | const ThemeContext = React.createContext(null)
4 | export default ThemeContext
5 |
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-black-pro.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-black-pro.eot
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-black-pro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-black-pro.ttf
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-black-pro.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-black-pro.woff
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-black-pro.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-black-pro.woff2
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-bold-pro.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-bold-pro.eot
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-bold-pro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-bold-pro.ttf
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-bold-pro.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-bold-pro.woff
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-bold-pro.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-bold-pro.woff2
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-light-pro.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-light-pro.eot
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-light-pro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-light-pro.ttf
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-light-pro.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-light-pro.woff
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-light-pro.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-light-pro.woff2
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-regular-pro.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-regular-pro.eot
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-regular-pro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-regular-pro.ttf
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-regular-pro.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-regular-pro.woff
--------------------------------------------------------------------------------
/src/fonts/apercu/apercu-regular-pro.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/fonts/apercu/apercu-regular-pro.woff2
--------------------------------------------------------------------------------
/src/html.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 |
5 | export default function HTML(props) {
6 | return (
7 |
8 |
9 |
10 |
11 |
15 | {props.headComponents}
16 |
17 |
18 | {props.preBodyComponents}
19 |
22 |
27 | {props.postBodyComponents}
28 |
29 |
30 | )
31 | }
32 |
33 | HTML.propTypes = {
34 | htmlAttributes: PropTypes.object,
35 | headComponents: PropTypes.array,
36 | bodyAttributes: PropTypes.object,
37 | preBodyComponents: PropTypes.array,
38 | body: PropTypes.string,
39 | postBodyComponents: PropTypes.array,
40 | }
41 |
--------------------------------------------------------------------------------
/src/images/1162px-Firefox_Logo,_2017.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/1162px-Firefox_Logo,_2017.png
--------------------------------------------------------------------------------
/src/images/404.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/404.jpg
--------------------------------------------------------------------------------
/src/images/data-greeting.svg:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/images/default-avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/default-avatar.png
--------------------------------------------------------------------------------
/src/images/default-featured.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/default-featured.png
--------------------------------------------------------------------------------
/src/images/default-search-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/default-search-image.jpg
--------------------------------------------------------------------------------
/src/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/favicon.png
--------------------------------------------------------------------------------
/src/images/firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/firefox.png
--------------------------------------------------------------------------------
/src/images/frag-cluster1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/src/images/frag-cluster2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/src/images/frag-cluster3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/src/images/help-character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/help-character.png
--------------------------------------------------------------------------------
/src/images/help-character.svg:
--------------------------------------------------------------------------------
1 |
26 |
--------------------------------------------------------------------------------
/src/images/icons/chevron.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/src/images/icons/diamond.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/icons/frag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/icons/frag.png
--------------------------------------------------------------------------------
/src/images/icons/quote.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/icons/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/icons/tick.png
--------------------------------------------------------------------------------
/src/images/icons/tick.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/src/images/man-coffee.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/man-fruit.svg:
--------------------------------------------------------------------------------
1 |
26 |
--------------------------------------------------------------------------------
/src/images/man-right.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/images/processes-desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/processes-desktop.png
--------------------------------------------------------------------------------
/src/images/processes-mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/processes-mobile.png
--------------------------------------------------------------------------------
/src/images/tbx-flame.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/images/toolkit.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/wagtail-bird.svg:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/src/images/wagtail-greeting.svg:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/src/images/wagtail.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/src/images/will.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/images/will.jpg
--------------------------------------------------------------------------------
/src/pages/404/404.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | // Components
4 | import Layout from '@components/layout'
5 | import Contact from '@components/contact-detailed'
6 | // Styles
7 | import styles from './404.module.scss'
8 |
9 | const NotFoundPage = ({ contact, contactReasons }) => (
10 |
11 |
17 |
18 |
19 | )
20 |
21 | export default NotFoundPage
22 |
--------------------------------------------------------------------------------
/src/pages/404/404.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 | &Hero {
5 | position: relative;
6 | display: flex;
7 | align-items: flex-end;
8 | width: 100vw;
9 | height: calc(100vh - 50px);
10 | background-image: url('../../images/404.jpg');
11 | background-size: cover;
12 | background-position: center;
13 | padding-top: 180px;
14 | padding-bottom: 45vh;
15 | @include for-tablet-landscape-up {
16 | padding-bottom: 30vh;
17 | }
18 |
19 | &::after {
20 | content: '';
21 | background: linear-gradient(to bottom, white 1%, rgba(255, 255, 255, 0) 100%);
22 | width: 100%;
23 | height: 300px;
24 | position: absolute;
25 | top: 0;
26 | left: 0;
27 | z-index: 10;
28 | }
29 | }
30 |
31 | &HeroContent {
32 | position: absolute;
33 | margin: 0 8.33vw 20px 8.33vw;
34 |
35 | @include for-tablet-landscape-up {
36 | margin: 0 8.33vw 20px 24.99vw;
37 | }
38 | }
39 |
40 | &Strapline {
41 | font-size: 50px;
42 | line-height: 50px;
43 | font-weight: 800;
44 |
45 | @include for-tablet-landscape-up {
46 | font-size: 70px;
47 | line-height: 70px;
48 | }
49 | }
50 |
51 | &Link {
52 | font-weight: 700;
53 | font-size: 20px;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/pages/404/index.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { graphql } from 'gatsby'
4 | // Components
5 | import NotFoundPage from './404'
6 |
7 | export default ({ data }) => {
8 | return
9 | }
10 |
11 | export const query = graphql`
12 | query {
13 | wagtail {
14 | contact {
15 | ...contactSnippet
16 | }
17 | contactReasons {
18 | ...contactReasonsSnippet
19 | }
20 | }
21 | }
22 | `
23 |
--------------------------------------------------------------------------------
/src/pages/preview.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import qs from 'query-string'
4 | // Components
5 | import BlogPostContainer, {
6 | previewQuery as blogPostQuery,
7 | } from '@templates/blog-post'
8 | import BlogPostsContainer, {
9 | previewQuery as blogPostsQuery,
10 | } from '@templates/blogs'
11 | import CaseStudyContainer, {
12 | previewQuery as workPageQuery,
13 | } from '@templates/case-study'
14 | import CulturePageContainer, {
15 | previewQuery as culturePageQuery,
16 | } from '@templates/culture'
17 | import PersonPageContainer, {
18 | previewQuery as personPageQuery,
19 | } from '@templates/person'
20 | import JobsPageContainer, {
21 | previewQuery as jobsPageQuery,
22 | } from '@templates/jobs'
23 | import StandardPageContainer, {
24 | previewQuery as standardPageQuery,
25 | } from '@templates/standard'
26 | import ServicePageContainer, {
27 | previewQuery as servicePageQuery,
28 | } from '@templates/service'
29 | import SubServicePageContainer, {
30 | previewQuery as subServicePageQuery,
31 | } from '@templates/sub-service'
32 | import TeamPage, { previewQuery as teamPageQuery } from '@templates/team'
33 | import CaseStudiesPage, {
34 | previewQuery as caseStudiesPageQuery,
35 | } from '@templates/case-studies'
36 | // Utils
37 | import WagtailPreviewProvider from 'src/utils/wagtail-preview'
38 |
39 | const previewMappings = {
40 | 'blog.blogpage': {
41 | query: blogPostQuery,
42 | template: BlogPostContainer,
43 | },
44 | 'blog.blogindexpage': {
45 | query: blogPostsQuery,
46 | template: BlogPostsContainer,
47 | },
48 | 'work.workpage': {
49 | query: workPageQuery,
50 | template: CaseStudyContainer,
51 | },
52 | 'people.culturepage': {
53 | query: culturePageQuery,
54 | template: CulturePageContainer,
55 | },
56 | 'people.personpage': {
57 | query: personPageQuery,
58 | template: PersonPageContainer,
59 | },
60 | 'torchbox.jobindexpage': {
61 | query: jobsPageQuery,
62 | template: JobsPageContainer,
63 | },
64 | 'torchbox.standardpage': {
65 | query: standardPageQuery,
66 | template: StandardPageContainer,
67 | },
68 | 'services.servicepage': {
69 | query: servicePageQuery,
70 | template: ServicePageContainer,
71 | },
72 | 'services.subservicepage': {
73 | query: subServicePageQuery,
74 | template: SubServicePageContainer,
75 | },
76 | 'people.personindexpage': {
77 | query: teamPageQuery,
78 | template: TeamPage,
79 | },
80 | 'work.workindexpage': {
81 | query: caseStudiesPageQuery,
82 | template: CaseStudiesPage,
83 | },
84 | }
85 |
86 | export default WagtailPreviewProvider(previewMappings)
87 |
--------------------------------------------------------------------------------
/src/styles/_animations.scss:
--------------------------------------------------------------------------------
1 | @mixin menu-animation ($time: 350ms) {
2 | transition: all $time cubic-bezier(0.705, 0.000, 1.000, 1.130);
3 | transition-timing-function: cubic-bezier(0.705, 0.000, 1.000, 1.130);
4 | }
5 |
6 | @keyframes fadeOut {
7 | 0% {
8 | opacity: 1;
9 | display: block;
10 | }
11 |
12 | 100% {
13 | opacity: 0;
14 | display: none;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/styles/_base-page.scss:
--------------------------------------------------------------------------------
1 | .page {
2 | background-color: white;
3 | padding: 230px 0 0;
4 |
5 | &Title {
6 | margin-bottom: 20px;
7 | @include for-tablet-landscape-up {
8 | margin-bottom: 30px;
9 | }
10 | h1 {
11 | font-weight: 800;
12 | }
13 | }
14 |
15 | &Author {
16 | @include for-tablet-landscape-up {
17 | margin: 0 0 0 16.66vw;
18 | }
19 | padding-bottom: 40px;
20 | }
21 |
22 | &Streamfield {}
23 |
24 | &Showcase {
25 | margin-top: 100px;
26 | }
27 |
28 | &Contact {
29 | margin-top: 130px;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/styles/_fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Apercu Pro';
3 | // font-display: swap;
4 | src: url('../fonts/apercu/apercu-light-pro.eot');
5 | src: url('../fonts/apercu/apercu-light-pro.eot?#iefix') format('embedded-opentype'),
6 | url('../fonts/apercu/apercu-light-pro.woff') format('woff'),
7 | url('../fonts/apercu/apercu-light-pro.woff2') format('woff2'),
8 | url('../fonts/apercu/apercu-light-pro.ttf') format('truetype');
9 | font-weight: 300;
10 | font-style: normal;
11 | }
12 |
13 | @font-face {
14 | font-family: 'Apercu Pro';
15 | // font-display: swap;
16 | src: url('../fonts/apercu/apercu-regular-pro.eot');
17 | src: url('../fonts/apercu/apercu-regular-pro.eot?#iefix') format('embedded-opentype'),
18 | url('../fonts/apercu/apercu-regular-pro.woff') format('woff'),
19 | url('../fonts/apercu/apercu-regular-pro.woff2') format('woff2'),
20 | url('../fonts/apercu/apercu-regular-pro.ttf') format('truetype');
21 | font-weight: normal;
22 | font-style: normal;
23 | }
24 |
25 | @font-face {
26 | font-family: 'Apercu Pro';
27 | // font-display: swap;
28 | src: url('../fonts/apercu/apercu-bold-pro.eot');
29 | src: url('../fonts/apercu/apercu-bold-pro.eot?#iefix') format('embedded-opentype'),
30 | url('../fonts/apercu/apercu-bold-pro.woff') format('woff'),
31 | url('../fonts/apercu/apercu-bold-pro.woff2') format('woff2'),
32 | url('../fonts/apercu/apercu-bold-pro.ttf') format('truetype');
33 | font-weight: 700;
34 | font-style: normal;
35 | }
36 |
37 | @font-face {
38 | font-family: 'Apercu Pro';
39 | // font-display: swap;
40 | src: url('../fonts/apercu/apercu-black-pro.eot');
41 | src: url('../fonts/apercu/apercu-black-pro.eot?#iefix') format('embedded-opentype'),
42 | url('../fonts/apercu/apercu-black-pro.woff') format('woff'),
43 | url('../fonts/apercu/apercu-black-pro.woff2') format('woff2'),
44 | url('../fonts/apercu/apercu-black-pro.ttf') format('truetype');
45 | font-weight: 800;
46 | font-style: normal;
47 | }
48 |
--------------------------------------------------------------------------------
/src/styles/_global.scss:
--------------------------------------------------------------------------------
1 | // Theme overrides:
2 | $page-section-title-color: var(--page-section-title-color, $accent);
3 |
4 | * {
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | font-family: $mainFont;
10 | font-size: 18px;
11 | line-height: 1.4;
12 | -webkit-text-size-adjust: none;
13 |
14 | @include for-tablet-landscape-up {
15 | font-size: 18px;
16 | }
17 |
18 | @include for-desktop-up {
19 | font-size: 20px;
20 | }
21 | }
22 |
23 | .accentedText {
24 | color: $accent;
25 | }
26 |
27 | .pageSectionTitle {
28 | position: absolute;
29 | top: 45px;
30 | left: 8.33vw;
31 | color: $page-section-title-color;
32 | font-weight: bold;
33 |
34 | @include for-tablet-landscape-up {
35 | max-width: 140px;
36 | }
37 |
38 | @media (min-width: 1050px) {
39 | max-width: 150px;
40 | }
41 |
42 | @include for-desktop-up {
43 | max-width: 200px;
44 | }
45 |
46 | @include for-big-desktop-up {
47 | max-width: 300px;
48 | }
49 |
50 | &::before {
51 | position: absolute;
52 | content: '';
53 | width: 100%;
54 | height: 2px;
55 | top: -15px;
56 | background: $page-section-title-color;
57 | }
58 | }
59 |
60 | .seeMore {
61 | margin: 40px 0 80px;
62 | text-align: center;
63 |
64 | a {
65 | transition: all 0.2s ease;
66 | color: $link-color;
67 | text-decoration: none;
68 | font-weight: bold;
69 | padding: 15px;
70 | border-bottom: 2px solid var(--read-more-background-color, $accent);
71 | cursor: pointer;
72 |
73 | &:hover {
74 | background: var(--read-more-background-color, $accent);
75 | border-radius: 4px;
76 | border-color: var(--read-more-background-color, $accent);
77 | color: white;
78 | padding: 15px 30px;
79 | }
80 | }
81 | }
82 |
83 | .toolkit_svg__accented {
84 | fill: $accent;
85 | }
86 |
87 | @mixin visibility-hidden {
88 | position: absolute;
89 | overflow: hidden;
90 | width: 1px;
91 | height: 1px;
92 | padding: 0;
93 | border: 0;
94 | clip: rect(1px, 1px, 1px, 1px);
95 | }
96 |
--------------------------------------------------------------------------------
/src/styles/_grid.scss:
--------------------------------------------------------------------------------
1 | @import '~breakpoint-sass';
2 |
3 | // Responsive Mixins
4 | @mixin for-phone-only {
5 | @media (max-width: 599px) { @content; }
6 | }
7 | @mixin for-tablet-portrait-up {
8 | @media (min-width: 600px) { @content; }
9 | }
10 | @mixin for-tablet-landscape-up {
11 | @media (min-width: 900px) { @content; }
12 | }
13 | // specifically for navigation
14 | @mixin for-desktop-small-up {
15 | @media (min-width: 1080px) { @content; }
16 | }
17 | @mixin for-desktop-up {
18 | @media (min-width: 1280px) { @content; }
19 | }
20 | // specifically for navigation
21 | @mixin for-desktop-medium-up {
22 | @media (min-width: 1400px) { @content; }
23 | }
24 | @mixin for-big-desktop-up {
25 | @media (min-width: 1800px) { @content; }
26 | }
27 | // short screen, for when hero elements are 100vh
28 | @mixin for-short-screen {
29 | @media (max-height: 725px) { @content; }
30 | }
31 |
32 | @mixin container {
33 | position: relative;
34 | margin: 0px 8.33vw;
35 |
36 | @include for-tablet-landscape-up {
37 | margin: 0 8.33vw 0;
38 | }
39 | }
40 |
41 | @mixin streamblockPadding {
42 | @include for-desktop-up {
43 | max-width: 50vw;
44 | margin-left: 16.66vw;
45 | }
46 | }
47 |
48 | /* autoprefixer grid: on */
49 | * {
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/styles/_mixins.scss:
--------------------------------------------------------------------------------
1 | // All credits go to: https://gist.github.com/tcrammond/99fd936007685dba97b7
2 | @mixin target-ie {
3 | @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
4 | @content;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/styles/_typography.scss:
--------------------------------------------------------------------------------
1 | h1 {
2 | margin: 0 0 10px;
3 | color: $primary;
4 | font-size: 2em;
5 | line-height: 1.4em;
6 | font-weight: bold;
7 | font-variant-ligatures: none;
8 |
9 | @include for-tablet-landscape-up {
10 | margin-right: 8.33vw;
11 | }
12 | }
13 |
14 | h2 {
15 | font-weight: 800;
16 | font-size: 32px;
17 | line-height: 38px;
18 | margin: 60px 0 20px;
19 | color: $blue;
20 |
21 | @include for-tablet-landscape-up {
22 | font-size: 36px;
23 | line-height: 44px;
24 | }
25 | }
26 |
27 | h3 {
28 | color: $primary;
29 | font-size: 26px;
30 | font-weight: bold;
31 | line-height: 1.2em;
32 | margin: 60px 0 10px;
33 | }
34 |
35 | h4 {
36 | @extend h3;
37 | }
38 |
39 | p {
40 | margin: 0 0 30px;
41 | color: #333;
42 | }
43 |
44 | a {
45 | color: $link-color;
46 | text-decoration: none;
47 | border-bottom: 2px solid var(--link-underscore-color, $accent);
48 |
49 | &:hover {
50 | color: $link-hover-color;
51 | border-bottom-color: var(--link-underscore-hover-color, $green);
52 | }
53 | }
54 |
55 | b {
56 | font-weight: bold;
57 | }
58 |
59 | strong {
60 | font-weight: bold;
61 | }
62 |
63 | i {
64 | font-style: italic;
65 | }
66 |
67 | .strongBlack {
68 | font-weight: 800;
69 | }
70 |
71 | pre {
72 | margin: 0 0 30px 0;
73 | padding: 10px;
74 | font-family: monospace;
75 | word-break: break-all;
76 | white-space: pre-wrap;
77 | background: $light-grey;
78 | border-radius: 5px;
79 | }
80 |
--------------------------------------------------------------------------------
/src/styles/_vars.scss:
--------------------------------------------------------------------------------
1 | // Colours
2 | :root {
3 | --color-coral: #fd5765;
4 | --color-coral-dark: #eb0316;
5 | --color-green: #3beccd;
6 | --color-light-blue: #2f128d;
7 | --color-blue: #251657;
8 | --color-light-grey: #f4f3f6;
9 | --color-light-grey-accessible: #757575;
10 | --color-grey: #444;
11 | }
12 | $coral: var(--color-coral);
13 | $coral-dark: var(--color-coral-dark);
14 | $light-blue: var(--color-light-blue);
15 | $blue: var(--color-blue);
16 | $light-grey: var(--color-light-grey);
17 | $light-grey-accessible: var(--color-light-grey-accessible);
18 | $grey: var(--color-grey);
19 | $green: var(--color-green);
20 |
21 | // Theme Vars (For good browsers that support CSS Vars)
22 | $primary: var(--color-primary);
23 | $accent: var(--color-accent);
24 | $accent-small-text: var(--color-accent-small);
25 | $link-color: var(--color-link);
26 | $link-hover-color: var(--color-link-hover);
27 |
28 | // Fonts
29 | $mainFont: 'Apercu Pro', arial, helvetica, sans-serif;
30 |
31 | // Sizes
32 | $small: '(max-width: 599px)';
33 | $tablet: '(min-width: 600px)';
34 | $tablet-landscape: '(min-width: 900px)';
35 | $desktop-small: '(min-width: 1080px)'; // specifically for navigation
36 | $desktop: '(min-width: 1200px)';
37 | $desktop-medium: '(min-width: 1400px)'; // specifically for navigation
38 | $desktop-big: '(min-width: 1800px)';
39 | $short-screen: '(max-height: 725px)'; // for when hero elements are 100vh
40 |
41 |
--------------------------------------------------------------------------------
/src/styles/app.module.scss:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | @import '~animate-scss';
3 |
4 | // Local Styles
5 | @import 'grid';
6 | @import 'vars';
7 | @import 'animations';
8 | @import 'typography';
9 | @import 'global';
10 | @import 'mixins';
11 | @import 'base-page';
12 |
--------------------------------------------------------------------------------
/src/templates/blog-post/blog-post.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import TitleBlock from '@components/title-block'
6 | import AuthorBlock from '@components/author-block'
7 | import StreamfieldBlock from '@components/streamfield-block'
8 | import Contact from '@components/contact-detailed'
9 | import Blogs from '@components/blogs-listing-block'
10 | // Utilities
11 | import { blogsUrl } from '@utils/urls'
12 | // Styles
13 | import styles from './blog-post.module.scss'
14 |
15 | const BlogPostPage = ({
16 | title,
17 | author,
18 | datePublished,
19 | readTime,
20 | tags,
21 | streamfield,
22 | extraBlogPosts,
23 | contact,
24 | contactReasons,
25 | }) => (
26 |
27 |
28 |
35 |
40 |
41 | {extraBlogPosts ? (
42 |
49 | ) : null}
50 |
51 |
52 | )
53 |
54 | BlogPostPage.propTypes = {
55 | title: PropTypes.string,
56 | author: PropTypes.object,
57 | datePublished: PropTypes.string,
58 | readTime: PropTypes.number,
59 | tags: PropTypes.array,
60 | streamfield: PropTypes.array,
61 | extraBlogPosts: PropTypes.array,
62 | contact: PropTypes.object,
63 | contactReasons: PropTypes.object,
64 | }
65 |
66 | BlogPostPage.defaultProps = {
67 | tags: [],
68 | blogs: [],
69 | streamfield: [],
70 | caseStudies: [],
71 | teasers: [],
72 | contact: {},
73 | extraBlogPosts: [],
74 | }
75 |
76 | export default BlogPostPage
77 |
--------------------------------------------------------------------------------
/src/templates/blog-post/blog-post.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 | &Streamfield {
5 | margin-bottom: 60px !important;
6 | }
7 |
8 | &Contact {
9 | margin-top: 80px;
10 | }
11 |
12 | &Author {
13 | @include for-tablet-landscape-up {
14 | margin: 0;
15 | }
16 |
17 | @include for-desktop-up {
18 | margin: 0 0 0 16.66vw;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/templates/blogs/blog-listing.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 | padding-top: 230px !important;
5 |
6 | &FilterTags {
7 | @include container;
8 | @include for-tablet-landscape-up {
9 | padding-left: 16.66vw;
10 | padding-top: 10px;
11 | }
12 | }
13 |
14 | &BlogListing {
15 | @include container;
16 | @include for-tablet-landscape-up {
17 | padding: 0 16.66vw;
18 | }
19 | margin-top: 70px !important;
20 |
21 | &Link {
22 | display: block;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/templates/blogs/index.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 |
4 | import { graphql } from 'gatsby'
5 | import PropTypes from 'prop-types'
6 | // Components
7 | import BlogListingPage from './blog-listing'
8 | import Layout from '@components/layout'
9 |
10 | const BlogsListingContainer = ({ data }) => {
11 | return (
12 |
16 |
22 |
23 | )
24 | }
25 |
26 | export const query = graphql`
27 | query {
28 | wagtail {
29 | blogIndexPage {
30 | pageTitle
31 | searchDescription
32 | title
33 | }
34 | blogPosts {
35 | slug
36 | title
37 | date
38 | contact {
39 | ...contactSnippet
40 | }
41 | tags: relatedServices {
42 | name
43 | slug
44 | }
45 | authors {
46 | name
47 | personPage {
48 | slug
49 | role
50 | image {
51 | ...iconImage
52 | }
53 | }
54 | }
55 | }
56 | contact {
57 | ...contactSnippet
58 | }
59 | contactReasons {
60 | ...contactReasonsSnippet
61 | }
62 | }
63 | }
64 | `
65 |
66 | export const previewQuery = `
67 | query($previewToken: String) {
68 | blogIndexPage(previewToken: $previewToken) {
69 | pageTitle
70 | searchDescription
71 | title
72 | }
73 | blogPosts {
74 | slug
75 | title
76 | date
77 | contact {
78 | ...contactSnippet
79 | }
80 | contactReasons {
81 | ...contactReasonsSnippet
82 | }
83 | tags: relatedServices {
84 | name
85 | slug
86 | }
87 | authors {
88 | name
89 | personPage {
90 | slug
91 | role
92 | image {
93 | ...iconImage
94 | }
95 | }
96 | }
97 | }
98 | contact {
99 | ...contactSnippet
100 | }
101 | contactReasons {
102 | ...contactReasonsSnippet
103 | }
104 | }
105 | `
106 |
107 | BlogsListingContainer.propTypes = {
108 | data: PropTypes.object,
109 | }
110 |
111 | export default BlogsListingContainer
112 |
--------------------------------------------------------------------------------
/src/templates/case-studies/case-study-listing.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 |
5 | &FilterTags {
6 | @include container;
7 | @include for-tablet-landscape-up {
8 | padding-left: 16.66vw;
9 | padding-top: 10px;
10 | }
11 | }
12 |
13 | &Contact {
14 | margin-top: 0px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/templates/case-study/case-study.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import TitleBlock from '@components/title-block'
6 | import AuthorBlock from '@components/author-block'
7 | import StreamfieldBlock from '@components/streamfield-block'
8 | import CaseStudiesBlock from '@components/case-studies-block'
9 | import Contact from '@components/contact-detailed'
10 | // Utilities
11 | import { caseStudiesFilterUrl } from '@utils/urls'
12 | // Styles
13 | import styles from './case-study.module.scss'
14 |
15 | const CaseStudyPage = ({
16 | title,
17 | client,
18 | author,
19 | tags,
20 | readTime,
21 | streamfield,
22 | caseStudies,
23 | serviceSlug,
24 | teasers,
25 | contact,
26 | contactReasons
27 | }) => (
28 |
29 |
30 | {client}
31 |
32 |
33 |
39 |
44 |
45 |
51 |
52 |
53 | )
54 |
55 | CaseStudyPage.propTypes = {
56 | title: PropTypes.string,
57 | author: PropTypes.object,
58 | readTime: PropTypes.number,
59 | client: PropTypes.string,
60 | tags: PropTypes.array,
61 | streamfield: PropTypes.array,
62 | caseStudies: PropTypes.array,
63 | teasers: PropTypes.array,
64 | contact: PropTypes.object,
65 | contactReasons: PropTypes.object,
66 | serviceSlug: PropTypes.string,
67 | }
68 |
69 | CaseStudyPage.defaultProps = {
70 | tags: [],
71 | streamfield: [],
72 | caseStudies: [],
73 | className: '',
74 | teasers: [],
75 | }
76 |
77 | export default CaseStudyPage
78 |
--------------------------------------------------------------------------------
/src/templates/case-study/case-study.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 | padding-top: 230px;
5 |
6 |
7 | &Streamfield {
8 | padding-top: 0 !important;
9 | margin-top: -40px;
10 | }
11 |
12 | &Client {
13 | &Container {
14 | @include container;
15 | }
16 |
17 | @include streamblockPadding;
18 | display: block;
19 | text-transform: uppercase;
20 | letter-spacing: 0.15em;
21 | font-size: 12px;
22 | font-weight: bold;
23 | color: $accent-small-text;
24 | margin: 0 0 15px;
25 | position: relative;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/templates/culture/culture-page.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import StreamfieldBlock from '@components/streamfield-block'
6 | import Contact from '@components/contact-detailed'
7 | import TeaserLink from '@components/teaser-block/teaser'
8 | // Utilities
9 | import { renderTorchUp, parseToHtml } from '@utils/torchup'
10 | import { pageUrl } from '@utils/urls'
11 | import { ReactComponent as GreetingImage } from '@images/man-fruit.svg'
12 | // Styles
13 | import styles from './culture-page.module.scss'
14 | import RichText from 'src/components/rich-text/index';
15 |
16 | const CulturePage = ({ strapline, straplineVisible, heroImage, intro, links, body, contact, contactReasons }) => {
17 | const Teasers = () => (
18 |
19 | {links.map((link, index) => (
20 |
27 | ))}
28 |
29 | )
30 |
31 | const heroTitleStyles = [styles.pageHeroTitle];
32 | if (!straplineVisible) {
33 | heroTitleStyles.push(styles.pageHeroTitleHidden);
34 | }
35 |
36 | return (
37 |
38 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
60 |
61 |
62 | )
63 | }
64 |
65 | CulturePage.propTypes = {
66 | strapline: PropTypes.string,
67 | straplineVisible: PropTypes.bool.isRequired,
68 | heroImage: PropTypes.string,
69 | intro: PropTypes.string,
70 | links: PropTypes.array,
71 | body: PropTypes.array,
72 | contact: PropTypes.object,
73 | contactReasons: PropTypes.object,
74 | }
75 |
76 | CulturePage.defaultProps = {
77 | strapline: '',
78 | intro: '',
79 | links: [],
80 | body: [],
81 | }
82 |
83 | export default CulturePage
84 |
--------------------------------------------------------------------------------
/src/templates/culture/culture-page.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import CulturePage from './culture-page'
6 |
7 | storiesOf('Pages', module).add('Culture Page', () => {
8 | return
9 | })
10 |
--------------------------------------------------------------------------------
/src/templates/culture/index.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 |
4 | import { graphql } from 'gatsby'
5 | import PropTypes from 'prop-types'
6 | // Components
7 | import Layout from '@components/layout'
8 | import CulturePage from './culture-page'
9 |
10 | const CulturePageContainer = ({ data, location }) => {
11 | const page = data.wagtail.culturePages[0]
12 | if (page) {
13 | return (
14 |
20 |
30 |
31 | )
32 | }
33 | return null
34 | }
35 |
36 | export const query = graphql`
37 | query($slug: String) {
38 | wagtail {
39 | culturePages(slug: $slug) {
40 | pageTitle
41 | searchDescription
42 | slug
43 | strapline
44 | straplineVisible
45 | intro
46 | body
47 | heroImage {
48 | ...maxImage
49 | }
50 | links {
51 | title
52 | description
53 | link {
54 | type
55 | slug
56 | }
57 | }
58 | contact {
59 | ...contactSnippet
60 | }
61 | contactReasons {
62 | ...contactReasonsSnippet
63 | }
64 | }
65 | }
66 | }
67 | `
68 |
69 | export const previewQuery = `
70 | query($previewToken: String) {
71 | culturePages(previewToken: $previewToken) {
72 | pageTitle
73 | searchDescription
74 | slug
75 | strapline
76 | intro
77 | body
78 | heroImage {
79 | ...maxImage
80 | }
81 | links {
82 | title
83 | description
84 | link {
85 | type
86 | slug
87 | }
88 | }
89 | contact {
90 | ...contactSnippet
91 | }
92 | contactReasons {
93 | ...contactReasonsSnippet
94 | }
95 | }
96 | }
97 | `
98 |
99 | CulturePageContainer.propTypes = {
100 | data: PropTypes.object,
101 | location: PropTypes.object,
102 | }
103 |
104 | export default CulturePageContainer
105 |
--------------------------------------------------------------------------------
/src/templates/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/src/templates/index.js
--------------------------------------------------------------------------------
/src/templates/jobs/index.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { graphql } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Components
6 | import Layout from '@components/layout'
7 | import JobsListingPage from './jobs-listing'
8 |
9 | const JobsListingContainer = ({ data }) => {
10 | const page = data.wagtail.jobsIndexPage
11 | return (
12 |
15 |
22 |
23 | )
24 | }
25 |
26 | export const query = graphql`
27 | query {
28 | wagtail {
29 | jobsIndexPage {
30 | title
31 | intro
32 | strapline
33 | pageTitle
34 | searchDescription
35 |
36 | jobs {
37 | id
38 | url
39 | title
40 | level
41 | location
42 | description
43 | }
44 | contact {
45 | ...contactSnippet
46 | }
47 | contactReasons {
48 | ...contactReasonsSnippet
49 | }
50 | }
51 | }
52 | }
53 | `
54 |
55 | export const previewQuery = `
56 | query($previewToken: String) {
57 | jobsIndexPage(previewToken: $previewToken) {
58 | title
59 | intro
60 | strapline
61 | pageTitle
62 | searchDescription
63 |
64 | jobs {
65 | id
66 | url
67 | title
68 | level
69 | location
70 | description
71 | }
72 | contact {
73 | ...contactSnippet
74 | }
75 | contactReasons {
76 | ...contactReasonsSnippet
77 | }
78 | }
79 | }
80 | `
81 |
82 | JobsListingContainer.propTypes = {
83 | data: PropTypes.object,
84 | }
85 |
86 | export default JobsListingContainer
87 |
--------------------------------------------------------------------------------
/src/templates/jobs/jobs-listing.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import TitleBlock from '@components/title-block'
6 | import RichText from '@components/rich-text'
7 | import Contact from '@components/contact-detailed'
8 | import JobsBlock from '@components/jobs-listing-block/jobs-block'
9 | // Styles
10 | import styles from './jobs-listing.module.scss'
11 |
12 | export class JobsListingPage extends React.Component {
13 | render() {
14 | const { title, intro, jobs, teasers, contact, contactReasons } = this.props
15 |
16 | const listing = jobs.map(job => {
17 | return {
18 | id: job.id,
19 | title: job.title,
20 | level: job.level,
21 | location: job.location,
22 | description: job.description,
23 | href: job.url,
24 | }
25 | })
26 |
27 | return (
28 |
29 |
34 |
35 |
39 |
40 |
45 |
50 |
51 | )
52 | }
53 | }
54 |
55 | JobsListingPage.propTypes = {
56 | title: PropTypes.string,
57 | jobs: PropTypes.array,
58 | teasers: PropTypes.array,
59 | contact: PropTypes.object,
60 | contactReasons: PropTypes.object,
61 | }
62 |
63 | JobsListingPage.defaultProps = {
64 | jobs: [],
65 | teasers: [],
66 | }
67 |
68 | export default JobsListingPage
69 |
--------------------------------------------------------------------------------
/src/templates/jobs/jobs-listing.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 |
5 | &JobListing {
6 | @include for-tablet-landscape-up {
7 | padding-top: 100px;
8 | }
9 | }
10 |
11 | &Intro {
12 | @include container;
13 | &Text {
14 | font-size: 22px;
15 | line-height: 36px;
16 | margin: 0 0 20px 0;
17 |
18 | p {
19 | color: $primary;
20 | }
21 |
22 | @include for-tablet-landscape-up {
23 | font-size: 24px;
24 | line-height: 42px;
25 | }
26 |
27 | @include for-desktop-up {
28 | margin: 0 8.33vw 20px 16.66vw;
29 | }
30 | }
31 | }
32 |
33 | &Contact {
34 | margin-top: 60px;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/templates/jobs/jobs-listing.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import JobsListingPage from './jobs-listing'
6 |
7 | storiesOf('Pages', module).add('Job Listing', () => {
8 | return (
9 | , 'services'],
44 | description: 'For web builds with the Wagtail open source CMS',
45 | link: '#',
46 | },
47 | {
48 | image: require('@images/tbx-flame.svg'),
49 | title: ['Data',
, 'marketing'],
50 | description: 'For our data driven digital marketing services',
51 | link: '#',
52 | },
53 | ]}
54 | />
55 | )
56 | })
57 |
--------------------------------------------------------------------------------
/src/templates/person/person.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 |
5 | &Container {
6 | @include container();
7 |
8 | margin-top: 45px;
9 |
10 | @include for-tablet-landscape-up {
11 | margin-top: 45px;
12 | }
13 | }
14 |
15 | &Title {
16 | width: 100%;
17 | margin: 0;
18 |
19 | h1 {
20 | font-size: 45px;
21 | line-height: 50px;
22 | margin-bottom: 0;
23 |
24 | @include for-tablet-landscape-up {
25 | font-size: 70px;
26 | line-height: 80px;
27 | }
28 | }
29 | }
30 |
31 | &Role {
32 | @include streamblockPadding;
33 | display: block;
34 | font-size: 16px;
35 | margin-top: 30px;
36 | letter-spacing: 0.15em;
37 | font-weight: bold;
38 | text-transform: uppercase;
39 | color: $coral-dark;
40 | }
41 |
42 | &Avatar {
43 | @include streamblockPadding;
44 | position: relative;
45 | margin: 40px 0 20px;
46 | min-height: 100px;
47 |
48 | &Icon {
49 | position: absolute;
50 | z-index: 2;
51 |
52 | right: 0px;
53 | top: -40px;
54 | width: 80px;
55 |
56 | @include for-tablet-portrait-up {
57 | right: 20px;
58 | top: -40px;
59 | width: 120px;
60 | }
61 |
62 | @include for-tablet-landscape-up {
63 | right: -25px;
64 | top: -40px;
65 | width: 180px;
66 | }
67 | }
68 |
69 | &Image {
70 | position: relative;
71 | width: 100%;
72 | }
73 | }
74 | &Biography {
75 | padding-top: 0;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/templates/service/service-page.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 | padding: 0;
5 |
6 | &Contact {
7 | margin-top: -30px;
8 | }
9 |
10 | .toolkit_svg__accented {
11 | display: none;
12 | fill: $accent;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/templates/standard/index.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { graphql } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Components
6 | import Layout from '@components/layout'
7 | import StandardPage from './standard'
8 |
9 | const StandardPageContainer = ({ data }) => {
10 | const page = data.wagtail.standardPages[0]
11 | return (
12 |
15 |
21 |
22 | )
23 | }
24 |
25 | export const query = graphql`
26 | query($slug: String) {
27 | wagtail {
28 | standardPages(slug: $slug) {
29 | slug
30 | title
31 | pageTitle
32 | searchDescription
33 | body
34 | contact {
35 | ...contactSnippet
36 | }
37 | contactReasons{
38 | ...contactReasonsSnippet
39 | }
40 | }
41 | }
42 | }
43 | `
44 |
45 | export const previewQuery = `
46 | query($previewToken: String) {
47 | standardPages(previewToken: $previewToken) {
48 | slug
49 | title
50 | pageTitle
51 | searchDescription
52 | body
53 | contact {
54 | ...contactSnippet
55 | }
56 | contactReasons{
57 | ...contactReasonsSnippet
58 | }
59 | }
60 | }
61 | `
62 |
63 | StandardPageContainer.propTypes = {
64 | data: PropTypes.object,
65 | }
66 |
67 | export default StandardPageContainer
68 |
--------------------------------------------------------------------------------
/src/templates/standard/standard.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import TitleBlock from '@components/title-block'
6 | import Contact from '@components/contact-detailed'
7 | import StreamfieldBlock from '@components/streamfield-block/streamfield-block'
8 | // Styles
9 | import styles from './standard.module.scss'
10 |
11 | export class StandardPage extends React.Component {
12 | render() {
13 | const { title, body, contact, contactReasons } = this.props
14 | return (
15 |
16 |
17 |
22 |
23 |
24 | )
25 | }
26 | }
27 |
28 | StandardPage.propTypes = {
29 | title: PropTypes.string,
30 | body: PropTypes.array,
31 | contact: PropTypes.object,
32 | contactReasons: PropTypes.object,
33 | }
34 |
35 | StandardPage.defaultProps = {
36 | title: '',
37 | body: [],
38 | }
39 |
40 | export default StandardPage
41 |
--------------------------------------------------------------------------------
/src/templates/standard/standard.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 |
5 | &TeamListing {
6 | padding-top: 90px !important;
7 | @include for-tablet-landscape-up {
8 | padding-top: 130px !important;
9 | }
10 | }
11 |
12 | &Contact {
13 | margin-top: 60px;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/templates/standard/standard.stories.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { storiesOf } from '@storybook/react'
4 | // Components
5 | import TeamListingPage from './standard'
6 |
7 | storiesOf('Pages', module).add('Team Listing', () => {
8 | return
9 | })
10 |
--------------------------------------------------------------------------------
/src/templates/team/index.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { graphql } from 'gatsby'
4 | import PropTypes from 'prop-types'
5 | // Components
6 | import Layout from '@components/layout'
7 | import TeamListingPage from './team-listing'
8 |
9 | const TeamListingContainer = ({ data }) => {
10 | return (
11 |
15 |
21 |
22 | )
23 | }
24 |
25 | export const query = graphql`
26 | query {
27 | wagtail {
28 | personIndexPage {
29 | pageTitle
30 | searchDescription
31 | strapline
32 | }
33 | personPages {
34 | firstName
35 | lastName
36 | slug
37 | role
38 | isSenior
39 | image {
40 | ...largeIconImage
41 | }
42 | }
43 | contact {
44 | ...contactSnippet
45 | }
46 | contactReasons {
47 | ...contactReasonsSnippet
48 | }
49 | }
50 | }
51 | `
52 |
53 | export const previewQuery = `
54 | query($previewToken: String) {
55 | personIndexPage(previewToken: $previewToken) {
56 | pageTitle
57 | searchDescription
58 | strapline
59 | }
60 | personPages {
61 | firstName
62 | lastName
63 | slug
64 | role
65 | isSenior
66 | image {
67 | ...largeIconImage
68 | }
69 | }
70 | contact {
71 | ...contactSnippet
72 | }
73 | contactReasons {
74 | ...contactReasonsSnippet
75 | }
76 | }
77 | `
78 |
79 | TeamListingContainer.propTypes = {
80 | data: PropTypes.object,
81 | }
82 |
83 | export default TeamListingContainer
84 |
--------------------------------------------------------------------------------
/src/templates/team/team-listing.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import PropTypes from 'prop-types'
4 | // Components
5 | import TitleBlock from '@components/title-block'
6 | import Contact from '@components/contact-detailed'
7 | import TeamListingBlock from '@components/team-listing-block'
8 | // Utilities
9 | import { teamUrl } from '@utils/urls'
10 | // Styles
11 | import styles from './team-listing.module.scss'
12 |
13 | export class TeamListingPage extends React.Component {
14 | render() {
15 | const { title, team, contact, contactReasons } = this.props
16 |
17 | /*
18 | * Quick hack to stop the list being rendered during static build as the React diffing algorithm is having
19 | * issues replacing the static html (from Gatsby build) with dynamic html (from vanilla React) when this component
20 | * is rebuilt in runtime. The diffing messes up the ordering so the images and text don't match. Need someone to look
21 | * at this, my brain goes immeditetly to conflicting fiber node keys but that doesn't seem to be the case... _weird_ :/
22 | */
23 | let listing = []
24 | if (typeof window != `undefined`) {
25 | listing = listing.concat(team)
26 | .map(person => ({
27 | key: `person-${person.firstName}-${person.lastName}`,
28 | name:`${person.firstName} ${person.lastName}`,
29 | role: person.role,
30 | avatar: person.image.src.url,
31 | alt: person.image.alt,
32 | href: teamUrl(person.slug),
33 | isSenior: person.isSenior,
34 | }))
35 | .sort(person => (person.isSenior ? -1 : 1))
36 | }
37 |
38 | return (
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 | }
47 |
48 | TeamListingPage.propTypes = {
49 | title: PropTypes.string,
50 | team: PropTypes.array,
51 | contact: PropTypes.object,
52 | }
53 |
54 | TeamListingPage.defaultProps = {
55 | team: [],
56 | teasers: [],
57 | }
58 |
59 | export default TeamListingPage
60 |
--------------------------------------------------------------------------------
/src/templates/team/team-listing.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/app.module';
2 |
3 | .page {
4 |
5 | &TeamListing {
6 | padding-top: 90px !important;
7 | @include for-tablet-landscape-up {
8 | padding-top: 130px !important;
9 | }
10 | }
11 |
12 | &Contact {
13 | margin-top: 60px;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/safeget.js:
--------------------------------------------------------------------------------
1 | export const safeGet = (obj, key, defaultValue) => {
2 | try {
3 | return key.split('.').reduce((o, x) => {
4 | return typeof o == 'undefined' || o === null ? defaultValue : o[x]
5 | }, obj)
6 | } catch (e) {
7 | return defaultValue
8 | }
9 | }
10 |
11 | export default safeGet
12 |
--------------------------------------------------------------------------------
/src/utils/selectors.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import qs from 'query-string'
3 | // Utilities
4 | import safeGet from '../utils/safeget'
5 | import { blogsUrl, caseStudiesUrl } from './urls'
6 |
7 | export const authorDetails = authors => ({
8 | name: safeGet(authors, '0.name', ''),
9 | role: safeGet(authors, '0.personPage.role', ''),
10 | avatar:
11 | safeGet(
12 | authors,
13 | '0.personPage.image.src.url',
14 | require('../images/default-avatar.png')
15 | ) || require('../images/default-avatar.png'),
16 | slug: safeGet(authors, '0.personPage.slug', ''),
17 | })
18 |
19 | export const postTags = (tags, hrefPrefix = '') =>
20 | tags.map(tag => ({
21 | label: tag.name,
22 | href: hrefPrefix + tag.slug,
23 | }))
24 |
25 | export const blogListing = blog => ({
26 | title: blog.title,
27 | tags: blog.tags,
28 | description: blog.listingSummary,
29 | href: blogsUrl(blog.slug),
30 | datePublished: blog.date,
31 | authorRole: authorDetails(blog.authors).role,
32 | authorName: authorDetails(blog.authors).name,
33 | authorAvatar: authorDetails(blog.authors).avatar,
34 | })
35 |
36 | export const caseStudyListing = caseStudy => ({
37 | client: caseStudy.client,
38 | tags: caseStudy.tags,
39 | title: caseStudy.title,
40 | href: caseStudiesUrl(caseStudy.slug),
41 | description: caseStudy.listingSummary,
42 | feedImage: safeGet(caseStudy, 'feedImage.src.url', null),
43 | homepageImage: safeGet(caseStudy, 'homepageImage.src.url', null),
44 | })
45 |
46 | export const readTime = bodyWordCount => Math.ceil(bodyWordCount * (1 / 275))
47 |
--------------------------------------------------------------------------------
/src/utils/tags.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import qs from 'query-string'
3 | import { removeSlashes } from './urls'
4 |
5 | export const postContainsTag = (tags, tagName) => {
6 | let tagExists = false
7 | tags.map(existingTag => {
8 | tagExists = tagExists ? true : existingTag.name === tagName
9 | })
10 | return tagExists
11 | }
12 |
13 | export const getUniqueTagsFromPosts = posts => {
14 | let tags = []
15 | if (posts) {
16 | posts.map(blog =>
17 | (blog.tags || []).map(tag => {
18 | let tagExists = postContainsTag(tags, tag.name)
19 | if (!tagExists) {
20 | tags.push(tag)
21 | }
22 | return tagExists
23 | })
24 | )
25 | }
26 | return tags
27 | }
28 |
29 | export const getCurrentFilterIndex = tags => {
30 | if (typeof window !== `undefined`) {
31 | const { filter } = qs.parse(window.location.hash)
32 | let selectedIndex = 0
33 | if (tags && filter) {
34 | tags.map((tag, index) => {
35 | if (tag.slug === removeSlashes(filter)) {
36 | selectedIndex = index
37 | }
38 | })
39 | }
40 | return selectedIndex
41 | }
42 | return 0
43 | }
44 |
--------------------------------------------------------------------------------
/src/utils/torchup.js:
--------------------------------------------------------------------------------
1 | // Styles
2 | import styles from '../styles/app.module.scss'
3 |
4 | export const parseToHtml = text => {
5 | let formattedText = [],
6 | isCurrentlyBold = false
7 |
8 | for (let i = 0; i < text.length; i++) {
9 | const char = text[i]
10 |
11 | // TorchUp Syntax:
12 | // *foo* == foo (in bold)
13 | // {foo} == foo (in heavy-bold/black)
14 | // [foo] == foo (with text in accent color)
15 |
16 | switch (char) {
17 | case '*':
18 | formattedText.push(isCurrentlyBold ? '' : '')
19 | isCurrentlyBold = !isCurrentlyBold
20 | break
21 |
22 | case '{':
23 | formattedText.push(``)
24 | break
25 |
26 | case '}':
27 | formattedText.push('')
28 | break
29 |
30 | case '[':
31 | formattedText.push(``)
32 | break
33 |
34 | case ']':
35 | formattedText.push('')
36 | break
37 |
38 | // Escape and insert 'special' char
39 | case '\\':
40 | i++
41 | formattedText.push(text[i])
42 | break
43 |
44 | default:
45 | formattedText.push(char)
46 | break
47 | }
48 | }
49 |
50 | return formattedText.join('')
51 | }
52 |
53 | export const renderTorchUp = text => {
54 | return {
55 | dangerouslySetInnerHTML: {
56 | __html: parseToHtml(text),
57 | },
58 | }
59 | }
60 |
61 | export default parseToHtml
62 |
--------------------------------------------------------------------------------
/src/utils/urls.js:
--------------------------------------------------------------------------------
1 | export const caseStudiesUrl = (slug = '') => `/work/${slug}/`
2 | export const caseStudiesFilterUrl = filter => {
3 | if (!filter) {
4 | return caseStudiesUrl()
5 | }
6 | return `/work/#filter=${filter}`
7 | };
8 | export const blogsUrl = (slug = '') => {
9 | if (slug) {
10 | return `/blog/${slug}/`;
11 | }
12 |
13 | return '/blog/';
14 | };
15 | export const blogsFilterUrl = filter => {
16 | if (!filter) {
17 | return blogsUrl()
18 | }
19 | return `/blog/#filter=${filter}`
20 | };
21 | export const teamUrl = (slug = '') => {
22 | if (slug) {
23 | return `/team/${slug}/`;
24 | }
25 |
26 | return '/team/';
27 | };
28 | export const jobsUrl = (slug = '') => {
29 | if (slug) {
30 | return `/jobs/${slug}/`;
31 | }
32 |
33 | return '/jobs/';
34 | };
35 |
36 | export const serviceUrl = (slug = '', parentServiceSlug = null) => {
37 | if (parentServiceSlug) {
38 | return `/${parentServiceSlug}/${slug}/`
39 | } else {
40 | return `/${slug}/`
41 | }
42 | }
43 |
44 | export const pageUrl = page => {
45 | if (page) {
46 | const { type, slug, serviceSlug } = page
47 | switch (type) {
48 | case 'HomePage':
49 | return '/'
50 |
51 | case 'WorkPage':
52 | return caseStudiesUrl(slug)
53 |
54 | case 'WorkIndexPage':
55 | return caseStudiesUrl()
56 |
57 | case 'BlogPage':
58 | return blogsUrl(slug)
59 |
60 | case 'BlogIndexPage':
61 | return blogsUrl()
62 |
63 | case 'PersonIndexPage':
64 | return teamUrl()
65 |
66 | case 'PersonPage':
67 | return teamUrl(slug)
68 |
69 | case 'JobIndexPage':
70 | return jobsUrl()
71 |
72 | case 'JobPage':
73 | return jobsUrl()
74 |
75 | case 'ServicePage':
76 | return serviceUrl(slug)
77 |
78 | case 'SubServicePage':
79 | return serviceUrl(slug, serviceSlug)
80 |
81 | default:
82 | return '/'
83 | }
84 | }
85 | return '/'
86 | }
87 |
88 | export const removeSlashes = url => url.replace(/\//g, '')
89 |
--------------------------------------------------------------------------------
/src/utils/wagtail-preview.js:
--------------------------------------------------------------------------------
1 | // Vendor Modules
2 | import React from 'react'
3 | import { request } from 'graphql-request'
4 | import qs from 'query-string'
5 |
6 | // Fragments
7 | import { previewFragments } from '../fragments'
8 |
9 |
10 | const WagtailPreviewProvider = previewMappings => {
11 | return class WagtailPreviewDecorator extends React.Component {
12 | state = { Template: null, propOverides: null }
13 |
14 | componentDidMount() {
15 | this.fetchToken()
16 | }
17 |
18 | fetchToken = async () => {
19 | if (typeof window !== 'undefined') {
20 | const { pageContext } = this.props
21 | const { token, content_type, review_token, allow_responses } = qs.parse(window.location.search)
22 |
23 | const previewMapping = previewMappings[content_type]
24 | if (token && previewMapping) {
25 | this.setState({ Template: previewMapping.template })
26 | const query = previewFragments + previewMapping.query
27 | try {
28 | const wagtail = await request(
29 | process.env.GATSBY_WAGTAIL_ENDPOINT ||
30 | 'http://localhost:8000/graphql/',
31 | query,
32 | { ...pageContext, previewToken: token }
33 | )
34 | this.setState({
35 | propOverides: { ...this.state.propOverides, data: { wagtail } },
36 | }, () => {
37 | // Initialise review UI
38 | if (review_token) {
39 | import('wagtail-review-ui').then(({APIClient, initCommentsApp}) => {
40 | let commentsElement = document.createElement('div');
41 | document.body.appendChild(commentsElement);
42 |
43 | const reviewEndpoint = process.env.GATSBY_WAGTAIL_REVIEW_ENDPOINT || 'http://localhost:8000/review/api/';
44 |
45 | let commentsApi = new APIClient(reviewEndpoint, review_token);
46 | initCommentsApp(commentsElement, commentsApi, function (addAnnotatableSection) {
47 | for (let element of document.querySelectorAll('[data-contentpath-field]')) {
48 | addAnnotatableSection(element.dataset.contentpathField, element);
49 | }
50 | }, !!allow_responses);
51 | });
52 | }
53 | });
54 | } catch (e) {
55 | console.error(e)
56 | }
57 | }
58 | }
59 | }
60 |
61 | render() {
62 | const { Template, propOverides } = this.state
63 | if (propOverides !== null && Template !== null) {
64 | return
65 | }
66 |
67 | return Rendering preview...
68 | }
69 | }
70 | }
71 |
72 | export default WagtailPreviewProvider
73 |
--------------------------------------------------------------------------------
/static/BingSiteAuth.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 38AFBDD7C9611AD7DAD9B790B4FD4634
4 |
--------------------------------------------------------------------------------
/static/_redirects:
--------------------------------------------------------------------------------
1 | # / /wagtail-cms 302! Country=us
2 | / /digital-products 302!
3 | https://tbx-production.netlify.app/* https://torchbox.com/:splat 301!
4 |
--------------------------------------------------------------------------------
/static/images/default-search-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/torchbox-frontend/e9bb1e531dfba15d8d30c92af45a4ecb065bb14b/static/images/default-search-image.jpg
--------------------------------------------------------------------------------
/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------