docs: convert website from postcss to emotion (#2068)

This commit is contained in:
Zeb Pykosz 2020-01-23 21:55:18 -05:00 committed by Shawn Erquhart
parent 7b0838dfef
commit 3d474b1944
69 changed files with 7769 additions and 6834 deletions

View File

@ -1,46 +1,14 @@
{
presets: [
"presets": ["babel-preset-gatsby"],
"plugins": [
[
"@babel/preset-env",
"prismjs",
{
loose: true,
modules: false,
useBuiltIns: "usage",
shippedProposals: true,
targets: {
browsers: [">0.25%", "not dead"],
},
},
],
[
"@babel/preset-react",
{
useBuiltIns: true,
pragma: "React.createElement",
},
],
],
plugins: [
["prismjs", {
"languages": ["javascript", "css", "markup", "yaml", "json"],
"plugins": ["line-numbers"],
"theme": "tomorrow",
"css": true,
}],
[
"@babel/plugin-proposal-class-properties",
{
loose: true,
},
],
"@babel/plugin-syntax-dynamic-import",
"babel-plugin-macros",
[
"@babel/plugin-transform-runtime",
{
helpers: true,
regenerator: true,
},
],
],
"css": true
}
]
]
}

View File

@ -1,11 +0,0 @@
{
"extends": [
"stylelint-config-recommended"
],
"rules": {
"at-rule-no-unknown": [true, {
"ignoreAtRules": ["/^neat/"]
}],
"no-descending-specificity": null
}
}

View File

@ -68,7 +68,6 @@ Then import it (assuming your project has tooling for imports):
```js
import CMS from 'netlify-cms'
// Now the registry is available via the CMS object.
CMS.registerPreviewTemplate('my-template', MyTemplate)
```

View File

@ -19,11 +19,10 @@ To use it in your own project stored on GitHub or GitLab, follow these steps:
1. Head over to the [Netlify Identity docs](https://www.netlify.com/docs/identity) and follow the steps to get started.
2. Add the following lines to your Netlify CMS `config.yml` file:
```yaml
backend:
name: git-gateway
```
```yaml
backend:
name: git-gateway
```
### Reconnect after Changing Repository Permissions
@ -45,12 +44,11 @@ To enable basic GitHub authentication:
1. Follow the authentication provider setup steps in the [Netlify docs](https://www.netlify.com/docs/authentication-providers/#using-an-authentication-provider).
2. Add the following lines to your Netlify CMS `config.yml` file:
```yaml
backend:
name: github
repo: owner-name/repo-name # Path to your GitHub repository
```
```yaml
backend:
name: github
repo: owner-name/repo-name # Path to your GitHub repository
```
### Specifying a status for deploy previews
The GitHub backend supports [deploy preview links](../deploy-preview-links). Netlify CMS checks the
@ -58,7 +56,6 @@ The GitHub backend supports [deploy preview links](../deploy-preview-links). Net
one that seems to represent a deploy preview. If you need to customize this behavior, you can
specify which context to look for using `preview_context`:
```yaml
backend:
name: github
@ -87,11 +84,11 @@ To enable it:
2. Follow the [Netlify docs](https://www.netlify.com/docs/authentication-providers/#using-an-authentication-provider) to add your new GitLab Application ID and Secret to your Netlify site dashboard.
3. In your repository, add the following lines to your Netlify CMS `config.yml` file:
```yaml
backend:
name: gitlab
repo: owner-name/repo-name # Path to your GitLab repository
```
```yaml
backend:
name: gitlab
repo: owner-name/repo-name # Path to your GitLab repository
```
### Client-Side Implicit Grant (GitLab)

View File

@ -215,20 +215,15 @@ Assuming you have the netlify-cms package installed to your project, manual init
```js
// This global flag enables manual initialization.
window.CMS_MANUAL_INIT = true
// Usage with import from npm package
import CMS, { init } from 'netlify-cms'
// Usage with script tag
const { CMS, initCMS: init } = window
/**
* Initialize without passing in config - equivalent to just importing
* Netlify CMS the old way.
*/
init()
/**
* Optionally pass in a config object. This object will be merged into
* `config.yml` if it exists, and any portion that conflicts with
@ -239,7 +234,6 @@ init()
* your `config.yml` can be missing its backend property, allowing you
* to set this property at runtime.
*/
init({
config: {
backend: {
@ -247,7 +241,6 @@ init({
},
},
})
/**
* Optionally pass in a complete config object and set a flag
* (`load_config_file: false`) to ignore the `config.yml`.
@ -257,7 +250,6 @@ init({
* It is not required if the `config.yml` file is missing to set
* `load_config_file`, but will improve performance and avoid a load error.
*/
init({
config: {
backend: {
@ -276,7 +268,6 @@ init({
],
},
})
// The registry works as expected, and can be used before or after init.
CMS.registerPreviewTemplate(...);
```
@ -292,7 +283,6 @@ CMS.registerPreviewTemplate(...);
*/
import CMS from 'netlify-cms';
import styles from '!css-loader!sass-loader!../main.scss'
CMS.registerPreviewStyle(styles.toString(), { raw: true })
```

View File

@ -17,7 +17,7 @@ You can [sign up for Cloudinary](https://cloudinary.com/users/register/free) for
To use the Cloudinary media library within Netlify CMS, you'll need to update your Netlify CMS configuration file with the information from your Cloudinary account:
```yml
```yaml
media_library:
name: cloudinary
config:
@ -70,17 +70,17 @@ Global configuration, which is meant to affect the Cloudinary widget at all time
as seen below, under the primary `media_library` property. Settings applied here will affect every
instance of the Cloudinary widget.
```yml
```yaml
# global
media_library:
name: cloudinary
output_filename_only: false
config:
default_transformations:
- - fetch_format: auto
width: 160
quality: auto
crop: scale
- - fetch_format: auto
width: 160
quality: auto
crop: scale
```
#### Field configuration
@ -89,22 +89,22 @@ Configuration can also be provided for individual fields that use the media libr
is very similar to the global configuration, except the settings are added to an individual `field`.
For example:
```yml
```yaml
# field
fields: # The fields each document in this collection have
- label: 'Cover Image'
name: 'image'
widget: 'image'
required: false
tagname: ''
media_library:
config:
default_transformations:
- - fetch_format: auto
width: 300
quality: auto
crop: fill
effect: grayscale
- label: 'Cover Image'
name: 'image'
widget: 'image'
required: false
tagname: ''
media_library:
config:
default_transformations:
- fetch_format: auto
width: 300
quality: auto
crop: fill
effect: grayscale
```
## Inserting Cloudinary URL in page templates
@ -113,24 +113,22 @@ If you prefer to provide direction so that images are transformed in a specific
* Either globally or for specific fields, configure the Cloudinary extension to only output the asset filename
```yml
# global
media_library:
name: cloudinary
output_filename_only: true
# field
media_library:
name: cloudinary
output_filename_only: true
```
```yaml
# global
media_library:
name: cloudinary
output_filename_only: true
# field
media_library:
name: cloudinary
output_filename_only: true
```
* Provide a dynamic URL in the site template
```hbs
{{! handlebars example }}
<img src="https://res.cloudinary.com/<cloud_name>/<resource_type>/<type>/<transformations>/{{image}}"/>
```
```handlebars
{{! handlebars example }}
<img src="https://res.cloudinary.com/<cloud_name>/<resource_type>/<type>/<transformations>/{{image}}"/>
```
Your dynamic URL can be formed conditionally to provide any desired transformations - please see Cloudinary's [image transformation reference](https://cloudinary.com/documentation/image_transformation_reference) for available transformations.

View File

@ -9,7 +9,6 @@ Alternatively, you can specify a custom config file using a link tag:
```html
<!-- Note the "type" and "rel" attribute values, which are required. -->
<link href="path/to/config.yml" type="text/yaml" rel="cms-config-url">
```

View File

@ -49,7 +49,7 @@ to a post in your repo may look like `content/blog/2018-01-new-post.md`, but the
on your site would look more like: `/blog/2018-01-new-post/`. Here's how you would use
`preview_path` in your configuration for this scenario:
```yml
```yaml
collections:
- name: blog
folder: content/blog
@ -65,9 +65,8 @@ template, `{{slug}}` is only the url-safe [identifier
field](../configuration-options/#identifier_field), while in the `preview_path` template, `{{slug}}`
is the entire slug for the entry. For example:
```yml
```yaml
# for an entry created Jan 1, 2000 with identifier "My New Post!"
collections:
- name: posts
slug: {{year}}-{{month}}-{{slug}} # {{slug}} will compile to "my-new-post"
@ -91,7 +90,6 @@ through static site generators.
```yaml
# This collection's date field will be inferred because it has a field named `"date"`
collections:
- name: posts
preview_path: blog/{{year}}/{{month}}/{{title}}
@ -99,9 +97,7 @@ collections:
- { name: title, label: Title }
{ name: date, label: Date, widget: date }
{ name: body, label: Body, widget: markdown }
# This collection requires `path_preview_date_field` because the no obvious date field is available
collections:
- name: posts
preview_path: blog/{{year}}/{{month}}/{{title}}

View File

@ -70,7 +70,7 @@ module.exports = {
},
'gatsby-transformer-yaml',
'gatsby-transformer-json',
'gatsby-plugin-postcss',
'gatsby-plugin-emotion',
'gatsby-plugin-react-helmet',
'gatsby-plugin-catch-links',
{
@ -80,7 +80,7 @@ module.exports = {
},
},
{
resolve: `gatsby-plugin-manifest`,
resolve: 'gatsby-plugin-manifest',
options: {
name: 'NetlifyCMS',
short_name: 'NetlifyCMS',

View File

@ -1,8 +1,8 @@
const path = require('path');
const { createFilePath } = require('gatsby-source-filesystem');
exports.createPages = async ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators;
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const docPage = path.resolve('./src/templates/doc-page.js');
const blogPost = path.resolve('./src/templates/blog-post.js');
@ -51,8 +51,8 @@ exports.createPages = async ({ graphql, boundActionCreators }) => {
const pad = n => (n >= 10 ? n : `0${n}`);
exports.onCreateNode = ({ node, boundActionCreators, getNode }) => {
const { createNodeField } = boundActionCreators;
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
if (node.internal.type === 'MarkdownRemark') {
const value = createFilePath({ node, getNode });

View File

@ -13,38 +13,38 @@
"homepage": "https://www.netlifycms.org/",
"license": "MIT",
"dependencies": {
"classnames": "^2.2.5",
"dayjs": "^1.7.5",
"gatsby": "next",
"gatsby-plugin-catch-links": "next",
"gatsby-plugin-manifest": "next",
"gatsby-plugin-netlify-cms": "^3.0.0-rc.1",
"gatsby-plugin-postcss": "^1.0.0",
"gatsby-plugin-react-helmet": "next",
"gatsby-remark-autolink-headers": "next",
"gatsby-remark-prismjs": "next",
"gatsby-source-filesystem": "next",
"gatsby-transformer-json": "next",
"gatsby-transformer-remark": "next",
"gatsby-transformer-yaml": "next",
"github-buttons": "git+https://github.com/ntkme/github-buttons.git",
"lodash": "^4.17.13",
"netlify-cms": "^2.0.11",
"postcss-at2x": "^2.0.0",
"postcss-cssnext": "^2.7.0",
"postcss-neat": "^2.5.2",
"postcss-nested": "^1.0.0",
"postcss-simple-extend": "^1.0.0",
"postcss-simple-vars-async": "^1.2.1",
"prismjs": "^1.15.0",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-helmet": "^5.2.0",
"react-markdown": "^3.3.2",
"smooth-scroll": "^14.2.0"
"@emotion/core": "^10.0.27",
"@emotion/styled": "^10.0.27",
"dayjs": "^1.8.19",
"emotion-theming": "^10.0.27",
"gatsby": "2.19.3",
"gatsby-plugin-catch-links": "2.1.24",
"gatsby-plugin-emotion": "^4.1.21",
"gatsby-plugin-manifest": "2.2.38",
"gatsby-plugin-netlify-cms": "^4.1.37",
"gatsby-plugin-react-helmet": "3.1.21",
"gatsby-remark-autolink-headers": "2.1.23",
"gatsby-remark-prismjs": "3.3.30",
"gatsby-source-filesystem": "2.1.46",
"gatsby-transformer-json": "2.2.25",
"gatsby-transformer-remark": "2.6.48",
"gatsby-transformer-yaml": "2.2.23",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"netlify-cms": "^2.10.11",
"prismjs": "^1.19.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-github-btn": "^1.1.1",
"react-helmet": "^5.2.1",
"react-markdown": "^4.3.1",
"smooth-scroll": "^16.1.2"
},
"devDependencies": {
"babel-plugin-prismjs": "^1.0.2"
"babel-plugin-prismjs": "^2.0.1",
"babel-preset-gatsby": "^0.2.27",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.20.0"
},
"private": true
}

View File

@ -1,19 +0,0 @@
const neatgrid = require('postcss-neat');
const nestedcss = require('postcss-nested');
// const colorfunctions = require('postcss-colour-functions');
const hdBackgrounds = require('postcss-at2x');
const cssextend = require('postcss-simple-extend');
const cssvars = require('postcss-simple-vars-async');
const styleVariables = require('./src/theme');
module.exports = () => ({
plugins: [
neatgrid(),
nestedcss(),
// colorfunctions(),
hdBackgrounds(),
cssextend(),
cssvars({ variables: styleVariables }),
],
});

View File

@ -5,16 +5,9 @@ import Prism from 'prismjs';
import { BlogPostTemplate } from '../templates/blog-post';
import { DocsTemplate } from '../templates/doc-page';
import WidgetDoc from '../components/widget-doc';
import Release from '../components/release';
import WhatsNew from '../components/whats-new';
import Notification from '../components/notification';
import Community from '../components/community';
import '../css/imports/hero.css';
import '../css/imports/docs.css';
import '../css/imports/whatsnew.css';
import '../css/imports/header.css';
import '../css/imports/collab.css';
import '../css/imports/community.css';
const withHighlight = WrappedComponent =>
class Highlight extends React.Component {
@ -70,16 +63,15 @@ const WidgetDocPreview = ({ entry, widgetFor }) => (
);
const ReleasePreview = ({ entry }) => (
<WhatsNew>
{entry.getIn(['data', 'updates']).map((release, idx) => (
<Release
key={idx}
version={release.get('version')}
date={dayjs(release.get('date')).format('MMMM D, YYYY')}
description={release.get('description')}
/>
))}
</WhatsNew>
<WhatsNew
updates={[
entry.getIn(['data', 'updates']).map(release => ({
version: release.get('version'),
date: dayjs(release.get('date')).format('MMMM D, YYYY'),
description: release.get('description'),
})),
]}
/>
);
const NotificationPreview = ({ entry }) =>

View File

@ -0,0 +1,34 @@
import { css } from '@emotion/core';
import styled from '@emotion/styled';
import theme from '../theme';
// prettier-ignore
const Button = styled.button`
display: inline-block;
background-image: linear-gradient(0deg, #97bf2f 14%, #c9fa4b 94%);
color: ${theme.colors.darkGray};
border-radius: ${theme.radii[1]};
font-size: ${theme.fontsize[3]};
font-weight: 700;
padding: ${theme.space[2]} ${theme.space[3]};
border: 2px solid ${theme.colors.darkGreen};
cursor: pointer;
${p => p.block && css`
display: block;
width: 100%;
`};
${p => p.outline && css`
background: none;
font-weight: 500;
`};
${p => p.active && css`
background: ${theme.colors.darkGreen};
color: white;
`};
`;
export default Button;

View File

@ -0,0 +1,18 @@
import React from 'react';
import styled from '@emotion/styled';
const ChatLink = styled.a`
z-index: 100;
position: fixed;
bottom: 10px;
right: 10px;
cursor: pointer;
`;
const ChatButton = () => (
<ChatLink href="/chat">
<img src="/img/slack.svg" />
</ChatLink>
);
export default ChatButton;

View File

@ -0,0 +1,56 @@
import React from 'react';
import styled from '@emotion/styled';
import theme from '../theme';
const StyledCommunityChannelsList = styled.ul`
margin-left: 0;
li {
list-style-type: none;
margin-bottom: 24px;
}
a {
display: block;
font-weight: inherit;
position: relative;
&:focus,
&:active,
&:hover {
&:before {
display: block;
}
}
&:before {
display: none;
content: '';
position: absolute;
width: 3px;
height: 100%;
background-color: ${theme.colors.darkGreen};
left: -16px;
}
}
p {
color: ${theme.colors.gray};
margin-bottom: 0;
}
`;
const CommunityChannelsList = ({ channels }) => (
<StyledCommunityChannelsList>
{channels.map(({ title, description, url }, idx) => (
<li key={idx}>
<a href={url}>
<strong>{title}</strong>
<p>{description}</p>
</a>
</li>
))}
</StyledCommunityChannelsList>
);
export default CommunityChannelsList;

View File

@ -0,0 +1,32 @@
import styled from '@emotion/styled';
import { css } from '@emotion/core';
import { mq } from '../utils';
import theme from '../theme';
const Container = styled.div`
margin-left: auto;
margin-right: auto;
max-width: 1280px;
padding-left: ${theme.space[4]};
padding-right: ${theme.space[4]};
${p =>
p.size === 'sm' &&
css`
max-width: 800px;
`};
${p =>
p.size === 'md' &&
css`
max-width: 1024px;
`};
${mq[3]} {
padding-left: ${theme.space[5]};
padding-right: ${theme.space[5]};
}
`;
export default Container;

View File

@ -1,67 +1,101 @@
import React, { Component } from 'react';
import Link from 'gatsby-link';
import React, { useState } from 'react';
import { Link } from 'gatsby';
import styled from '@emotion/styled';
/**
* Manually get table of contents since tableOfContents from markdown
* nodes have code added.
*
* https://github.com/gatsbyjs/gatsby/issues/5436
*/
class TableOfContents extends Component {
state = {
headings: [],
import Button from './button';
import TableOfContents from './table-of-contents';
import { mq } from '../utils';
import theme from '../theme';
const Menu = styled.nav`
margin-bottom: ${theme.space[5]};
`;
const MenuBtn = styled(Button)`
${mq[1]} {
display: none;
}
`;
const MenuContent = styled.div`
display: ${p => (p.isOpen ? 'block' : 'none')};
background: white;
padding: ${theme.space[3]};
${mq[1]} {
display: block;
background: transparent;
padding: 0;
}
`;
const MenuSection = styled.div`
margin-bottom: ${theme.space[3]};
`;
const SectionTitle = styled.h3`
font-size: ${theme.fontsize[4]};
margin-bottom: ${theme.space[2]};
`;
const SectionList = styled.ul``;
const MenuItem = styled.li``;
const NavLink = styled(Link)`
display: block;
/* font-weight: $regular; */
font-size: ${theme.fontsize[3]};
color: ${theme.colors.gray};
line-height: ${theme.lineHeight[1]};
text-transform: capitalize;
transition: color 0.2s ease;
padding: ${theme.space[2]} 0;
&.active {
color: ${theme.colors.darkGreen};
font-weight: bold;
}
&:hover {
color: ${theme.colors.darkGreen};
}
`;
const DocsNav = ({ items, location }) => {
const [isMenuOpen, setMenuOpen] = useState(false);
const toggleMenu = () => {
setMenuOpen(isOpen => !isOpen);
};
componentDidMount() {
const contentHeadings = document.querySelectorAll('.docs-content h2');
const headings = [];
contentHeadings.forEach(h => {
headings.push({
id: h.id,
text: h.innerText,
});
});
this.setState({
headings,
});
}
render() {
const { headings } = this.state;
return (
<ul className="nav-subsections">
{headings.map(h => (
<li key={h.id}>
<a href={`#${h.id}`} className="subnav-link">
{h.text}
</a>
</li>
return (
<Menu>
<MenuBtn onClick={toggleMenu} block>
{isMenuOpen ? <span>&times;</span> : <span>&#9776;</span>} {isMenuOpen ? 'Hide' : 'Show'}{' '}
Navigation
</MenuBtn>
<MenuContent isOpen={isMenuOpen}>
{items.map(item => (
<MenuSection key={item.title}>
<SectionTitle>{item.title}</SectionTitle>
<SectionList>
{item.group.edges.map(({ node }) => (
<MenuItem key={node.fields.slug}>
<NavLink to={node.fields.slug} activeClassName="active">
{node.frontmatter.title}
</NavLink>
{location.pathname === node.fields.slug && <TableOfContents />}
</MenuItem>
))}
</SectionList>
</MenuSection>
))}
</ul>
);
}
}
const DocsNav = ({ items, location }) => (
<nav className="docs-nav" id="docs-nav">
{items.map(item => (
<div className="docs-nav-section" key={item.title}>
<div className="docs-nav-section-title">{item.title}</div>
<ul className="docs-nav-section-list">
{item.group.edges.map(({ node }) => (
<li className="docs-nav-item" key={node.fields.slug}>
<Link to={node.fields.slug} className="nav-link" activeClassName="active">
{node.frontmatter.title}
</Link>
{location.pathname === node.fields.slug && <TableOfContents />}
</li>
))}
</ul>
</div>
))}
</nav>
);
</MenuContent>
</Menu>
);
};
export default DocsNav;
export { NavLink };

View File

@ -1,35 +1,56 @@
import React, { Component } from 'react';
import React, { useState, useEffect, memo } from 'react';
import styled from '@emotion/styled';
import theme from '../theme';
import searchIcon from '../img/search.svg';
class DocSearch extends Component {
state = {
enabled: true,
};
componentDidMount() {
const SearchForm = styled.form`
> span {
width: 100%;
}
`;
const SearchField = styled.input`
color: white;
font-size: ${theme.fontsize[3]};
border-radius: ${theme.radii[1]};
background-color: rgba(255, 255, 255, 0.1);
background-image: url(${searchIcon});
background-repeat: no-repeat;
background-position: ${theme.space[2]} 50%;
border: 0;
appearance: none;
width: 100%;
padding: ${theme.space[2]};
padding-left: 30px;
outline: 0;
`;
const DocSearch = () => {
const [enabled, setEnabled] = useState(true);
useEffect(() => {
if (window.docsearch) {
window.docsearch({
apiKey: '08d03dc80862e84c70c5a1e769b13019',
indexName: 'netlifycms',
inputSelector: '.algolia-search',
inputSelector: '#algolia-search',
debug: false, // Set debug to true if you want to inspect the dropdown
});
} else {
this.setState({ enabled: false });
}
}
render() {
if (!this.state.enabled) {
return null;
setEnabled(false);
}
}, []);
return (
<div className="utility-input">
<img src={searchIcon} alt="" />
<input type="search" placeholder="Search the docs" className="algolia-search" />
</div>
);
if (!enabled) {
return null;
}
}
export default DocSearch;
return (
<SearchForm>
<SearchField type="search" placeholder="Search the docs" id="algolia-search" />
</SearchForm>
);
};
export default memo(DocSearch);

View File

@ -1,32 +1,44 @@
import React from 'react';
import { css } from '@emotion/core';
const EditLink = ({ path }) => (
<a
className="edit-this-page"
href={`https://github.com/netlify/netlify-cms/blob/master/website/content/${path}`}
<div
css={css`
float: right;
a {
font-weight: 700;
}
#pencil {
fill: #7ca511;
}
`}
>
<svg
version="1.1"
id="pencil"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
width="14px"
height="14px"
viewBox="0 0 512 512"
enableBackground="new 0 0 512 512"
xmlSpace="preserve"
>
<path
d="M398.875,248.875L172.578,475.187l-22.625-22.625L376.25,226.265L398.875,248.875z M308.375,158.39L82.063,384.687
<a href={`https://github.com/netlify/netlify-cms/blob/master/website/content/${path}`}>
<svg
version="1.1"
id="pencil"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
width="14px"
height="14px"
viewBox="0 0 512 512"
enableBackground="new 0 0 512 512"
xmlSpace="preserve"
>
<path
d="M398.875,248.875L172.578,475.187l-22.625-22.625L376.25,226.265L398.875,248.875z M308.375,158.39L82.063,384.687
l45.266,45.25L353.625,203.64L308.375,158.39z M263.094,113.125L36.828,339.437l22.625,22.625L285.75,135.765L263.094,113.125z
M308.375,67.875L285.719,90.5L421.5,226.265l22.625-22.625L308.375,67.875z M376.25,0L331,45.25l135.75,135.766L512,135.781
L376.25,0z M32,453.5V480h26.5L32,453.5 M0,376.25L135.766,512H0V376.25L0,376.25z"
/>
</svg>{' '}
Edit this page
</a>
/>
</svg>{' '}
Edit this page
</a>
</div>
);
export default EditLink;

View File

@ -0,0 +1,126 @@
import React, { useState, useEffect } from 'react';
import moment from 'moment';
import styled from '@emotion/styled';
import Markdownify from './markdownify';
import theme from '../theme';
const Root = styled.div`
text-align: center;
background: ${theme.colors.darkerGray};
background-image: linear-gradient(
-17deg,
${theme.colors.darkerGray} 17%,
${theme.colors.darkGray} 94%
);
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 24px;
padding-top: 40px;
max-width: 446px;
`;
const Title = styled.h2`
font-size: 36px;
color: white;
`;
const Cal = styled.div`
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
margin: 24px auto;
max-width: 250px;
`;
const Month = styled.div`
background: ${theme.colors.green};
color: ${theme.colors.gray};
font-weight: bold;
text-transform: uppercase;
letter-spacing: 4px;
font-size: 14px;
padding: 8px;
`;
const Day = styled.div`
font-size: 104px;
line-height: 1.3;
font-weight: bold;
color: white;
border: 1px solid ${theme.colors.gray};
border-top: none;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
`;
const CalDates = styled.p`
color: white;
font-weight: bold;
font-size: ${theme.fontsize[4]};
margin-bottom: ${theme.space[3]};
`;
const CalCta = styled.div``;
const EventBox = ({ title, cta }) => {
const [loading, setLoading] = useState(true);
const [eventDate, setEventDate] = useState('');
useEffect(() => {
const eventbriteToken = 'C5PX65CJBVIXWWLNFKLO';
const eventbriteOrganiser = '14281996019';
const url = `https://www.eventbriteapi.com/v3/events/search/?token=${eventbriteToken}&organizer.id=${eventbriteOrganiser}&expand=venue%27`;
fetch(url)
.then(res => res.json())
.then(data => {
const eventDate = data.events[0].start.utc;
setEventDate(eventDate);
setLoading(false);
})
.catch(err => {
console.log(err); // eslint-disable-line no-console
// TODO: set state to show error message
setLoading(false);
});
}, []);
const eventDateMoment = moment(eventDate);
const offset = eventDateMoment.isDST() ? -7 : -8;
const month = eventDateMoment.format('MMMM');
const day = eventDateMoment.format('DD');
const datePrefix = eventDateMoment.format('dddd, MMMM Do');
const dateSuffix = eventDateMoment.utcOffset(offset).format('h a');
const ellip = <span>&hellip;</span>;
return (
<Root>
<Title>{title}</Title>
<Cal>
<Month>{loading ? 'loading' : month}</Month>
<Day>{loading ? ellip : day}</Day>
</Cal>
<CalDates>
{loading ? (
ellip
) : (
<span>
{datePrefix} at {dateSuffix} PT
</span>
)}
</CalDates>
<CalCta>
<Markdownify source={cta} />
</CalCta>
</Root>
);
};
export default EventBox;

View File

@ -1,71 +0,0 @@
import React, { Component } from 'react';
import moment from 'moment';
class EventWidget extends Component {
state = {
loading: false,
eventDate: '',
};
componentDidMount() {
const eventbriteToken = 'C5PX65CJBVIXWWLNFKLO';
const eventbriteOrganiser = '14281996019';
const url = `https://www.eventbriteapi.com/v3/events/search/?token=${eventbriteToken}&organizer.id=${eventbriteOrganiser}&expand=venue%27`;
this.setState({
loading: true,
});
fetch(url)
.then(res => res.json())
.then(data => {
const eventDate = data.events[0].start.utc;
this.setState({
loading: false,
eventDate,
});
})
.catch(err => {
console.log(err); // eslint-disable-line no-console
// TODO: set state to show error message
this.setState({
loading: false,
});
});
}
render() {
const { loading, eventDate } = this.state;
if (loading) {
return <span>Loading...</span>;
}
const eventDateMoment = moment(eventDate);
const offset = eventDateMoment.isDST() ? -7 : -8;
const month = eventDateMoment.format('MMMM');
const day = eventDateMoment.format('DD');
const datePrefix = eventDateMoment.format('dddd, MMMM Do');
const dateSuffix = eventDateMoment.utcOffset(offset).format('h a');
return (
<div>
<div className="calendar">
<div className="month">{month}</div>
<div className="day">{day}</div>
</div>
<h3>
<strong>
{datePrefix} at {dateSuffix} PT
</strong>
</h3>
</div>
);
}
}
export default EventWidget;

View File

@ -0,0 +1,43 @@
import React from 'react';
import styled from '@emotion/styled';
import Markdownify from './markdownify';
import theme from '../theme';
const Box = styled.div`
margin-bottom: ${theme.space[5]};
img {
margin-bottom: ${theme.space[3]};
margin-left: -${theme.space[2]};
}
`;
const Title = styled.h3`
color: ${p => (p.kind === 'light' ? theme.colors.white : theme.colors.gray)};
font-size: ${theme.fontsize[4]};
`;
const Text = styled.p`
font-size: 18px;
a {
font-weight: 700;
}
`;
const FeatureItem = ({ feature, description, imgpath, kind }) => (
<Box>
{imgpath && <img src={require(`../img/${imgpath}`)} alt="" />}
<Title kind={kind}>
<Markdownify source={feature} />
</Title>
<Text>
<Markdownify source={description} />
</Text>
</Box>
);
const Features = ({ items, kind }) =>
items.map(item => <FeatureItem kind={kind} {...item} key={item.feature} />);
export default Features;

View File

@ -1,36 +1,96 @@
import React from 'react';
import styled from '@emotion/styled';
import '../css/imports/footer.css';
import Container from './container';
import theme from '../theme';
import { mq } from '../utils';
const Root = styled.footer`
background: white;
padding-top: ${theme.space[4]};
padding-bottom: ${theme.space[5]};
`;
const FooterGrid = styled.div`
text-align: center;
${mq[2]} {
display: flex;
align-items: center;
text-align: left;
}
`;
const FooterButtons = styled.div`
margin-bottom: ${theme.space[3]};
${mq[2]} {
margin-bottom: 0;
}
`;
const SocialButton = styled.a`
display: inline-block;
padding: ${theme.space[1]} ${theme.space[3]};
background-color: ${theme.colors.lightishGray};
color: white;
font-weight: 700;
font-size: ${theme.fontsize[2]};
border-radius: ${theme.radii[1]};
margin-right: ${theme.space[2]};
&:active,
&:hover {
background-color: ${theme.colors.darkGreen};
}
`;
const Info = styled.div`
font-size: ${theme.fontsize[1]};
color: ${theme.colors.gray};
opacity: 0.5;
${mq[2]} {
padding-left: ${theme.space[4]};
}
a {
font-weight: 700;
color: ${theme.colors.gray};
}
`;
const Footer = ({ buttons }) => (
<footer>
<div className="contained">
<div className="social-buttons">
{buttons.map(btn => (
<a href={btn.url} key={btn.url}>
{btn.name}
</a>
))}
</div>
<div className="footer-info">
<p>
<a
href="https://github.com/netlify/netlify-cms/blob/master/LICENSE"
className="text-link"
>
Distributed under MIT License
</a>{' '}
·{' '}
<a
href="https://github.com/netlify/netlify-cms/blob/master/CODE_OF_CONDUCT.md"
className="text-link"
>
Code of Conduct
</a>
</p>
</div>
</div>
</footer>
<Root>
<Container>
<FooterGrid>
<FooterButtons>
{buttons.map(btn => (
<SocialButton href={btn.url} key={btn.url}>
{btn.name}
</SocialButton>
))}
</FooterButtons>
<Info>
<p>
<a
href="https://github.com/netlify/netlify-cms/blob/master/LICENSE"
className="text-link"
>
Distributed under MIT License
</a>{' '}
·{' '}
<a
href="https://github.com/netlify/netlify-cms/blob/master/CODE_OF_CONDUCT.md"
className="text-link"
>
Code of Conduct
</a>
</p>
</Info>
</FooterGrid>
</Container>
</Root>
);
export default Footer;

View File

@ -0,0 +1,14 @@
import styled from '@emotion/styled';
import { mq } from '../utils';
import theme from '../theme';
const Grid = styled.div`
${mq[2]} {
display: grid;
grid-template-columns: repeat(${p => p.cols}, 1fr);
grid-gap: ${theme.space[7]};
}
`;
export default Grid;

View File

@ -1,88 +1,215 @@
import React, { Component } from 'react';
import Link from 'gatsby-link';
import classnames from 'classnames';
import { Location } from '@reach/router';
import React, { useState, useEffect } from 'react';
import { Link } from 'gatsby';
import styled from '@emotion/styled';
import { css } from '@emotion/core';
import GitHubButton from 'react-github-btn';
import Container from './container';
import Notifications from './notifications';
import DocSearch from './docsearch';
import GitHubButton from './github-button';
import logo from '../img/netlify-cms-logo.svg';
import searchIcon from '../img/search.svg';
import '../css/imports/header.css';
import theme from '../theme';
import { mq } from '../utils';
class Header extends Component {
state = {
scrolled: false,
};
const StyledHeader = styled.header`
background: ${theme.colors.darkerGray};
padding-top: ${theme.space[3]};
padding-bottom: ${theme.space[3]};
transition: background 0.2s ease, padding 0.2s ease, box-shadow 0.2s ease;
async componentDidMount() {
${mq[2]} {
position: sticky;
top: 0;
width: 100%;
z-index: ${theme.zIndexes.header};
${p =>
p.hasHeroBelow &&
!p.scrolled &&
css`
background: #2a2c24;
padding-top: ${theme.space[5]};
padding-bottom: ${theme.space[5]};
`};
}
`;
const HeaderContainer = styled(Container)`
display: flex;
align-items: center;
flex-wrap: wrap;
`;
const Logo = styled.div`
flex: 1 0 50%;
${mq[1]} {
flex: 0 0 auto;
margin-right: ${theme.space[5]};
}
`;
const MenuActions = styled.div`
flex: 1 0 50%;
display: flex;
justify-content: flex-end;
${mq[1]} {
display: none;
}
`;
const MenuBtn = styled.button`
background: none;
border: 0;
color: white;
padding: ${theme.space[3]};
font-size: ${theme.fontsize[4]};
line-height: 1;
`;
const SearchBtn = styled(MenuBtn)``;
const ToggleArea = styled.div`
display: ${p => (p.open ? 'block' : 'none')};
flex: 1;
width: 100%;
margin-top: ${theme.space[3]};
${mq[1]} {
display: block;
width: auto;
margin-top: 0;
}
`;
const SearchBox = styled(ToggleArea)`
${mq[1]} {
flex: 1;
max-width: 200px;
margin-right: ${theme.space[3]};
}
`;
const Menu = styled(ToggleArea)`
${mq[1]} {
flex: 0 0 auto;
margin-left: auto;
}
`;
const MenuList = styled.ul`
${mq[1]} {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
`;
const MenuItem = styled.li`
margin-bottom: ${theme.space[3]};
${mq[1]} {
margin-bottom: 0;
&:not(:last-child) {
margin-right: ${theme.space[3]};
}
}
`;
const NavLink = styled(Link)`
color: white;
text-decoration: none;
font-weight: 600;
`;
const Header = ({ hasHeroBelow }) => {
const [scrolled, setScrolled] = useState(false);
const [isNavOpen, setNavOpen] = useState(false);
const [isSearchOpen, setSearchOpen] = useState(false);
useEffect(() => {
// TODO: use raf to throttle events
window.addEventListener('scroll', this.handleScroll);
}
window.addEventListener('scroll', handleScroll);
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
handleScroll = () => {
const handleScroll = () => {
const currentWindowPos = document.documentElement.scrollTop || document.body.scrollTop;
const scrolled = currentWindowPos > 0;
this.setState({
scrolled,
});
setScrolled(scrolled);
};
render() {
const { scrolled } = this.state;
const handleMenuBtnClick = () => {
setNavOpen(s => !s);
setSearchOpen(false);
};
return (
<Location>
{({ location }) => {
const isDocs = location.pathname.indexOf('docs') !== -1;
const isBlog = location.pathname.indexOf('blog') !== -1;
const handleSearchBtnClick = () => {
setSearchOpen(s => !s);
setNavOpen(false);
};
return (
<header
id="header"
className={classnames({
docs: isDocs,
blog: isBlog,
scrolled,
})}
return (
<StyledHeader scrolled={scrolled} id="header" hasHeroBelow={hasHeroBelow}>
<Notifications />
<HeaderContainer>
<Logo>
<Link to="/">
<img src={logo} alt="Netlify CMS logo" />
</Link>
</Logo>
<MenuActions>
<SearchBtn onClick={handleSearchBtnClick}>
{isSearchOpen ? <span>&times;</span> : <img src={searchIcon} alt="search" />}
</SearchBtn>
<MenuBtn onClick={handleMenuBtnClick}>
{isNavOpen ? <span>&times;</span> : <span>&#9776;</span>}
</MenuBtn>
</MenuActions>
<SearchBox open={isSearchOpen}>
<DocSearch />
</SearchBox>
<Menu open={isNavOpen}>
<MenuList>
<MenuItem
css={css`
margin-top: 8px;
`}
>
<div className="contained">
<div className="logo-container">
<Link to="/" className="logo">
<img src={logo} alt="Netlify CMS" />
</Link>
<DocSearch />
</div>
<div className="nav-container">
<span className="gh-button">
<GitHubButton />
</span>
<Link className="nav-link docs-link" to="/docs/intro/">
Docs
</Link>
<Link className="nav-link contributing-link" to="/docs/contributor-guide/">
Contributing
</Link>
<Link className="nav-link" to="/community/">
Community
</Link>
<Link className="nav-link" to="/blog/">
Blog
</Link>
</div>
</div>
</header>
);
}}
</Location>
);
}
}
<GitHubButton
href="https://github.com/netlify/netlify-cms"
data-icon="octicon-star"
data-show-count="true"
aria-label="Star netlify/netlify-cms on GitHub"
>
Star
</GitHubButton>
</MenuItem>
<MenuItem>
<NavLink to="/docs/intro/">Docs</NavLink>
</MenuItem>
<MenuItem>
<NavLink to="/docs/contributor-guide/">Contributing</NavLink>
</MenuItem>
<MenuItem>
<NavLink to="/community/">Community</NavLink>
</MenuItem>
<MenuItem>
<NavLink to="/blog/">Blog</NavLink>
</MenuItem>
</MenuList>
</Menu>
</HeaderContainer>
</StyledHeader>
);
};
export default Header;

View File

@ -0,0 +1,16 @@
import styled from '@emotion/styled';
import theme from '../theme';
import { mq } from '../utils';
const HeroTitle = styled.h1`
color: ${theme.colors.green};
font-size: ${theme.fontsize[6]};
margin-bottom: ${theme.space[1]};
${mq[2]} {
font-size: ${theme.fontsize[7]};
margin-bottom: ${theme.space[2]};
}
`;
export default HeroTitle;

View File

@ -0,0 +1,36 @@
import React from 'react';
import styled from '@emotion/styled';
import Container from './container';
import Page from './page';
import theme from '../theme';
const Header = styled.header`
text-align: center;
padding-top: ${theme.space[7]};
padding-bottom: ${theme.space[7]};
`;
const Title = styled.h2`
font-size: ${theme.fontsize[6]};
`;
const Text = styled.div`
max-width: 710px;
margin: 0 auto;
`;
const HomeSection = ({ title, text, children, ...props }) => (
<Page as="section" {...props}>
<Container>
<Header>
<Title>{title}</Title>
{text && <Text>{text}</Text>}
</Header>
{children}
</Container>
</Page>
);
export default HomeSection;

View File

@ -1,67 +1,53 @@
import React, { Fragment } from 'react';
import React from 'react';
import Helmet from 'react-helmet';
import { graphql, StaticQuery } from 'gatsby';
import { ThemeProvider } from 'emotion-theming';
import Header from './header';
import Footer from './footer';
import Notification from './notification';
import GlobalStyles from '../global-styles';
import theme from '../theme';
import '../css/imports/base.css';
import '../css/imports/utilities.css';
import '../css/imports/chat.css';
const Layout = ({ children }) => {
return (
<StaticQuery
query={graphql`
query layoutQuery {
site {
siteMetadata {
title
description
}
}
footer: file(relativePath: { regex: "/global/" }) {
childDataYaml {
footer {
buttons {
url
name
}
}
}
}
notifs: file(relativePath: { regex: "/notifications/" }) {
childDataYaml {
notifications {
published
loud
message
url
}
}
const LAYOUT_QUERY = graphql`
query layoutQuery {
site {
siteMetadata {
title
description
}
}
footer: file(relativePath: { regex: "/global/" }) {
childDataYaml {
footer {
buttons {
url
name
}
}
`}
>
}
}
}
`;
const Layout = ({ hasPageHero, children }) => {
return (
<StaticQuery query={LAYOUT_QUERY}>
{data => {
const { title, description } = data.site.siteMetadata;
const notifs = data.notifs.childDataYaml.notifications.filter(notif => notif.published);
return (
<Fragment>
<ThemeProvider theme={theme}>
<GlobalStyles />
<Helmet defaultTitle={title} titleTemplate={`%s | ${title}`}>
<meta name="description" content={description} />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,700,900|Roboto+Mono:400,700"
/>
</Helmet>
{notifs.map((node, i) => (
<Notification key={i} url={node.url} loud={node.loud}>
{node.message}
</Notification>
))}
<Header notifications={notifs} />
<Header hasHeroBelow={hasPageHero} />
{children}
<Footer buttons={data.footer.childDataYaml.footer.buttons} />
</Fragment>
</ThemeProvider>
);
}}
</StaticQuery>

View File

@ -0,0 +1,10 @@
import styled from '@emotion/styled';
const Lead = styled.p`
font-size: 20px;
margin-bottom: 24px;
${p => p.light && 'color: white;'};
`;
export default Lead;

View File

@ -0,0 +1,132 @@
import React from 'react';
import styled from '@emotion/styled';
import theme from '../theme';
const StyledMarkdown = styled.div`
> :first-child {
margin-top: 0;
}
> :last-child {
margin-bottom: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: ${theme.lineHeight[1]};
margin-top: 2em;
margin-bottom: 0.25em;
}
h1 {
font-size: ${theme.fontsize[6]};
}
h2 {
font-size: ${theme.fontsize[5]};
}
h3 {
font-size: ${theme.fontsize[4]};
}
h4 {
font-size: ${theme.fontsize[3]};
}
ol,
ul {
margin-left: ${theme.space[3]};
}
ul {
list-style: disc;
}
ol {
list-style: decimal;
}
li {
margin-bottom: 0;
}
ol,
ul,
p {
font-size: 18px;
margin-bottom: 1rem;
}
a {
font-weight: bold;
&:hover {
text-decoration: underline;
}
}
table {
border: 0;
background: #f7f7f7;
border-radius: 4px;
margin-top: 40px;
margin-bottom: 40px;
width: 100%;
text-align: left;
}
tbody tr {
&:nth-child(odd) {
background: #fdfdfd;
}
}
th,
td {
padding: 8px;
}
th {
font-weight: 700;
font-size: 18px;
}
td {
font-size: 14px;
}
iframe {
width: 100%;
}
pre {
border-radius: ${theme.radii[2]};
margin-bottom: ${theme.space[4]};
margin-top: ${theme.space[4]};
}
pre > code {
font-size: ${theme.fontsize[2]};
line-height: ${theme.lineHeight[0]};
}
*:not(pre) > code {
color: inherit;
background: #e6e6e6;
border-radius: 2px;
padding: 2px 6px;
white-space: nowrap;
font-size: ${theme.fontsize[2]};
}
`;
const Markdown = ({ html }) => {
return <StyledMarkdown dangerouslySetInnerHTML={{ __html: html }} />;
};
export default Markdown;

View File

@ -0,0 +1,10 @@
import styled from '@emotion/styled';
import theme from '../theme';
const MetaInfo = styled.p`
font-size: ${theme.fontsize[2]};
margin-bottom: ${theme.space[4]};
`;
export default MetaInfo;

View File

@ -1,52 +0,0 @@
import React, { Component } from 'react';
import { Link } from 'gatsby';
class MobileNav extends Component {
state = {
isOpen: false,
};
toggleNav = () => {
this.setState({
isOpen: !this.state.isOpen,
});
};
render() {
const { items } = this.props;
const { isOpen } = this.state;
return (
<div className="mobile-docs-nav">
<button className="btn-primary mobile-docs-nav-btn" onClick={this.toggleNav}>
{isOpen ? <span>&times;</span> : <span>&#9776;</span>} {isOpen ? 'Hide' : 'Show'}{' '}
Navigation
</button>
{isOpen && (
<nav className="mobile-docs-nav-content">
<ul className="mobile-docs-nav-list">
{items.map(item => (
<li key={item.title} className="mobile-docs-nav-item">
{item.title}
<ul className="mobile-docs-nav-list">
{item.group.edges.map(({ node }) => (
<li key={node.fields.slug} className="mobile-docs-nav-item">
<Link
to={node.fields.slug}
className="mobile-docs-nav-link"
onClick={this.toggleNav}
>
{node.frontmatter.title}
</Link>
</li>
))}
</ul>
</li>
))}
</ul>
</nav>
)}
</div>
);
}
}
export default MobileNav;

View File

@ -1,10 +1,46 @@
import React from 'react';
import cn from 'classnames';
import styled from '@emotion/styled';
import { css } from '@emotion/core';
import theme from '../theme';
const Notif = styled.a`
background-color: ${theme.colors.darkerGray};
color: white;
display: block;
padding: ${theme.space[2]} ${theme.space[3]};
text-align: center;
/* prettier-ignore */
${p =>
p.loud &&
css`
background-color: ${theme.colors.green};
color: ${theme.colors.darkerGray};
`}
em {
font-style: normal;
color: #8b8b8b;
padding: 0 8px;
}
sup,
sub {
font-size: initial;
vertical-align: initial;
}
.text-link {
text-decoration: underline;
color: ${theme.colors.green};
}
`;
const Notification = ({ url, loud, children }) => (
<a href={url} className={cn('notification', { 'notification-loud': loud })}>
<Notif href={url} loud={loud}>
{children}
</a>
</Notif>
);
export default Notification;

View File

@ -0,0 +1,34 @@
import React from 'react';
import { graphql, StaticQuery } from 'gatsby';
import Notification from './notification';
const NOTIFS_QUERY = graphql`
query notifs {
file(relativePath: { regex: "/notifications/" }) {
childDataYaml {
notifications {
published
loud
message
url
}
}
}
}
`;
const Notifications = () => (
<StaticQuery query={NOTIFS_QUERY}>
{data => {
const notifs = data.file.childDataYaml.notifications.filter(notif => notif.published);
return notifs.map((node, i) => (
<Notification key={i} url={node.url} loud={node.loud}>
{node.message}
</Notification>
));
}}
</StaticQuery>
);
export default Notifications;

View File

@ -0,0 +1,29 @@
import React from 'react';
import { css } from '@emotion/core';
import Container from './container';
import theme from '../theme';
import { mq } from '../utils';
const PageHero = ({ children }) => (
<section
css={css`
background: ${theme.colors.darkerGray};
background-image: linear-gradient(to bottom, #2a2c24 0%, ${theme.colors.darkerGray} 20%);
color: ${theme.colors.blueGray};
position: relative;
padding-top: ${theme.space[6]};
padding-bottom: ${theme.space[6]};
${mq[3]} {
padding-top: ${theme.space[6]};
padding-bottom: ${theme.space[8]};
}
`}
>
<Container>{children}</Container>
</section>
);
export default PageHero;

View File

@ -0,0 +1,16 @@
import styled from '@emotion/styled';
import theme from '../theme';
import { mq } from '../utils';
const Page = styled.div`
padding-top: ${theme.space[5]};
padding-bottom: ${theme.space[5]};
${mq[1]} {
padding-top: ${theme.space[6]};
padding-bottom: ${theme.space[6]};
}
`;
export default Page;

View File

@ -1,23 +1,67 @@
import React from 'react';
import moment from 'moment';
import styled from '@emotion/styled';
import { css } from '@emotion/core';
import Markdownify from '../components/markdownify';
import theme from '../theme';
const ReleaseLink = styled.a`
color: white;
display: block;
padding: ${theme.space[2]} ${theme.space[3]};
border-radius: ${theme.radii[1]};
height: 100%;
&:hover {
background: ${theme.colors.darkGray};
}
`;
const Version = styled.span`
background: ${theme.colors.shadeBlue};
font-size: ${theme.fontsize[1]};
padding: 0 ${theme.space[1]};
border-radius: ${theme.radii[1]};
font-weight: 700;
margin-right: ${theme.space[2]};
color: ${theme.colors.gray};
`;
const Release = ({ version, versionPrevious, date, description, url }) => {
const displayDate = moment(date).format('MMMM D, YYYY');
const defaultUrl = `https://github.com/netlify/netlify-cms/compare/netlify-cms@${versionPrevious}...netlify-cms@${version}`;
return (
<a href={url || defaultUrl} key={version}>
<li>
<div className="update-metadata">
<span className="update-version">{version}</span>
<span className="update-date">{displayDate}</span>
<li
css={css`
flex: 1;
`}
>
<ReleaseLink href={url || defaultUrl}>
<div
css={css`
margin-bottom: ${theme.space[1]};
`}
>
<Version>{version}</Version>
<span
css={css`
font-size: ${theme.fontsize[1]};
color: rgba(255, 255, 255, 0.6);
`}
>
{displayDate}
</span>
</div>
<span className="update-description">
<span
css={css`
font-size: ${theme.fontsize[2]};
`}
>
<Markdownify source={description} />
</span>
</li>
</a>
</ReleaseLink>
</li>
);
};

View File

@ -0,0 +1,23 @@
import styled from '@emotion/styled';
import theme from '../theme';
const SectionLabel = styled.h3`
color: ${theme.colors.gray};
font-size: ${theme.fontsize[1]};
font-weight: 600;
letter-spacing: 1.5px;
text-transform: uppercase;
margin-bottom: ${theme.space[4]};
&:after {
background: ${theme.colors.darkGreen};
content: ' ';
display: block;
height: 2px;
margin-top: 5px;
width: ${theme.space[5]};
}
`;
export default SectionLabel;

View File

@ -0,0 +1,22 @@
import React from 'react';
import { css } from '@emotion/core';
import Page from './page';
import { mq } from '../utils';
const SidebarLayout = ({ sidebar, children }) => (
<Page
css={css`
${mq[1]} {
display: grid;
grid-template-columns: 300px 1fr;
grid-gap: 2rem;
}
`}
>
<div>{sidebar}</div>
<div>{children}</div>
</Page>
);
export default SidebarLayout;

View File

@ -0,0 +1,59 @@
import React, { useState, useEffect } from 'react';
import styled from '@emotion/styled';
import theme from '../theme';
const TocList = styled.ol`
margin: ${theme.space[2]} 0;
padding-left: ${theme.space[3]};
border-left: 2px solid ${theme.colors.lightestGray};
list-style-type: none;
`;
const TocLink = styled.a`
display: block;
font-size: ${theme.fontsize[2]};
color: ${theme.colors.gray};
transition: color 0.2s;
line-height: ${theme.lineHeight[1]};
margin: ${theme.space[2]} 0;
&:hover {
color: ${theme.colors.darkGreen};
}
`;
/**
* Maually get table of contents since tableOfContents from markdown
* nodes have code added.
*
* https://github.com/gatsbyjs/gatsby/issues/5436
*/
const TableOfContents = () => {
const [headings, setHeadings] = useState([]);
useEffect(() => {
const contentHeadings = document.querySelectorAll('[data-docs-content] h2');
const headings = [];
contentHeadings.forEach(h => {
headings.push({
id: h.id,
text: h.innerText,
});
});
setHeadings(headings);
}, []);
return (
<TocList>
{headings.map(h => (
<li key={h.id}>
<TocLink href={`#${h.id}`}>{h.text}</TocLink>
</li>
))}
</TocList>
);
};
export default TableOfContents;

View File

@ -1,7 +1,65 @@
import React, { Component } from 'react';
import React, { useState } from 'react';
import styled from '@emotion/styled';
import theme from '../theme';
import screenshotEditor from '../img/screenshot-editor.jpg';
const VideoLink = styled.a`
position: relative;
cursor: pointer;
display: block;
&:hover {
div {
background-color: ${theme.colors.blue};
box-shadow: 0 6px 18px 0 rgba(0, 0, 0, 0.15), 0 2px 6px 0 rgba(0, 0, 0, 0.3);
transform: scale(1.1);
}
svg {
fill: #fff;
}
}
&:active {
div {
transform: scale(0.9);
}
}
img,
iframe {
width: 100%;
border-radius: ${theme.radii[2]};
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.15), 0 3px 9px 0 rgba(0, 0, 0, 0.3);
}
`;
const VideoButton = styled.div`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 90px;
height: 90px;
margin: auto;
color: ${theme.colors.blue};
background-color: rgba(255, 255, 255, 0.85);
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.15);
border-radius: 100px;
transition: 0.1s;
svg {
position: absolute;
left: 30px;
top: 24px;
width: 44px;
height: 44px;
fill: #3a69c7;
}
`;
/**
* We should be able to import complete inline svg's rather than base64, this
* component is a stopgap for now. Source in '../img/play.svg'.
@ -16,45 +74,35 @@ const PlayIcon = ({ className }) => (
</svg>
);
class VideoEmbed extends Component {
state = {
toggled: false,
};
toggleVideo = () => {
this.setState({
toggled: true,
});
};
render() {
const { toggled } = this.state;
const VideoEmbed = () => {
const [toggled, setToggled] = useState(false);
const embedcode = (
<iframe
width={560}
height={315}
src="https://www.youtube-nocookie.com/embed/p6h-rYSVX90?rel=0&showinfo=0&autoplay=1"
frameBorder={0}
allow="autoplay; encrypted-media"
allowFullScreen
title="video_embed"
/>
);
const toggleVideo = () => setToggled(true);
const imgPlaceholder = (
<img src={screenshotEditor} className="responsive" alt="editor video screenshot" />
);
const embedcode = (
<iframe
title="Netlify CMS video"
width={560}
height={315}
src="https://www.youtube-nocookie.com/embed/p6h-rYSVX90?rel=0&showinfo=0&autoplay=1"
frameBorder={0}
allow="autoplay; encrypted-media"
allowFullScreen
/>
);
return (
<div className="hero-graphic" onClick={this.toggleVideo}>
{toggled ? embedcode : imgPlaceholder}
{!toggled && (
<div className="hero-videolink">
<PlayIcon className="hero-videolink-arrow" />
</div>
)}
</div>
);
}
}
const imgPlaceholder = <img src={screenshotEditor} alt="Netlify CMS editor" />;
return (
<VideoLink onClick={toggleVideo}>
{toggled ? embedcode : imgPlaceholder}
{!toggled && (
<VideoButton>
<PlayIcon />
</VideoButton>
)}
</VideoLink>
);
};
export default VideoEmbed;

View File

@ -1,10 +1,26 @@
import React from 'react';
import { css } from '@emotion/core';
const WhatsNew = ({ children }) => (
<section className="whatsnew">
<div className="contained">
<ol>{children}</ol>
</div>
import Container from './container';
import Release from './release';
import Grid from './grid';
import theme from '../theme';
const WhatsNew = ({ updates }) => (
<section
css={css`
background: ${theme.colors.lightishGray};
padding-top: ${theme.space[6]};
padding-bottom: ${theme.space[5]};
`}
>
<Container>
<Grid as="ol" cols={3}>
{updates.slice(0, 3).map((item, idx) => (
<Release {...item} versionPrevious={updates[idx + 1].version} key={item.version} />
))}
</Grid>
</Container>
</section>
);

View File

@ -1,11 +1,18 @@
import React from 'react';
import classnames from 'classnames';
const WidgetDoc = ({ visible, label, body, html }) => (
<div className={classnames('widget', { widget_open: visible })}>
<h3>{label}</h3>
{body ? body : <div dangerouslySetInnerHTML={{ __html: html }} />}
</div>
);
import Markdown from './markdown';
const WidgetDoc = ({ visible, label, body, html }) => {
if (!visible) {
return null;
}
return (
<div>
<h3>{label}</h3>
<Markdown html={body || html} />
</div>
);
};
export default WidgetDoc;

View File

@ -1,79 +1,76 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import React, { useState, useEffect } from 'react';
import styled from '@emotion/styled';
import WidgetDoc from './widget-doc';
import Button from './button';
import '../css/imports/widgets.css';
import theme from '../theme';
class Widgets extends Component {
state = {
currentWidget: null,
};
const WidgetsNav = styled.nav`
margin-bottom: 1rem;
componentDidMount() {
const { widgets } = this.props;
> button {
margin-right: 8px;
margin-bottom: 8px;
}
`;
const WidgetsContent = styled.div`
background: ${theme.colors.lightGray};
padding: ${theme.space[3]};
border-radius: 4px;
`;
const Widgets = ({ widgets }) => {
const [currentWidget, setWidget] = useState(null);
useEffect(() => {
const hash = window.location.hash ? window.location.hash.replace('#', '') : '';
const widgetsContainHash = widgets.edges.some(w => w.node.frontmatter.title === hash);
if (widgetsContainHash) {
return this.setState({
currentWidget: hash,
});
return setWidget(hash);
}
this.setState({
currentWidget: widgets.edges[0].node.frontmatter.title,
});
}
setWidget(widgets.edges[0].node.frontmatter.title);
}, []);
handleWidgetChange = (event, title) => {
const handleWidgetChange = (event, title) => {
event.preventDefault();
this.setState(
{
currentWidget: title,
},
() => {
window.history.pushState(null, null, `#${title}`);
},
);
setWidget(title);
window.history.pushState(null, null, `#${title}`);
};
render() {
const { widgets } = this.props;
const { currentWidget } = this.state;
return (
<div>
<section className="widgets">
<div className="widgets__cloud">
{widgets.edges.map(({ node }) => {
const { label, title } = node.frontmatter;
return (
<button
key={title}
className={classnames('widgets__item', {
widgets__item_active: currentWidget === title,
})}
onClick={event => this.handleWidgetChange(event, title)}
>
{label}
</button>
);
})}
</div>
<div className="widgets__container">
{widgets.edges.map(({ node }) => {
const { frontmatter, html } = node;
const { title, label } = frontmatter;
const isVisible = currentWidget === title;
return <WidgetDoc key={label} visible={isVisible} label={label} html={html} />;
})}
</div>
</section>
</div>
);
}
}
return (
<section>
<WidgetsNav>
{widgets.edges.map(({ node }) => {
const { label, title } = node.frontmatter;
return (
<Button
key={title}
active={currentWidget === title}
onClick={event => handleWidgetChange(event, title)}
outline
>
{label}
</Button>
);
})}
</WidgetsNav>
<WidgetsContent>
{widgets.edges.map(({ node }) => {
const { frontmatter, html } = node;
const { title, label } = frontmatter;
const isVisible = currentWidget === title;
return <WidgetDoc key={label} visible={isVisible} label={label} html={html} />;
})}
</WidgetsContent>
</section>
);
};
export default Widgets;

View File

@ -1,169 +0,0 @@
@import url(
https://fonts.googleapis.com/css?family=Roboto:400,
100,
100italic,
300,
300italic,
400italic,
500,
700,
900|Roboto+Mono:400,
700
);
body {
background-color: $shadeBlue;
color: $grey;
font-family: $roboto;
margin: 0;
-webkit-font-smoothing: antialiased;
display: flex;
flex-direction: column;
min-height: 100vh;
@media screen and (min-width: $tablet) {
text-align: left;
}
}
.page {
flex-grow: 1;
}
h1 {
font-weight: $bold;
font-size: 36px;
line-height: 48px;
margin: 0 0 $small 0;
@media screen and (min-width: $tablet) {
font-size: 42px;
line-height: 56px;
}
}
h2 {
font-family: $roboto;
font-size: 36px;
font-weight: $bold;
color: $darkGrey;
margin-bottom: $tiny;
&.subhead {
font-weight: $regular;
}
}
h3 {
font-family: $roboto;
font-size: 18px;
font-weight: $bold;
line-height: 26px;
padding-bottom: $micro;
margin: 0;
}
p,
ul {
font-size: 18px;
line-height: 26px;
margin-top: 0;
}
a {
color: $darkGreen;
text-decoration: none;
font-weight: $bold;
}
ul {
margin: $tiny 0 $tiny $small;
padding: 0;
}
.contained {
margin: 0 auto;
max-width: $display;
padding: 0 $small;
@media screen and (min-width: $tablet) {
padding: 0 $medium;
}
}
*[class^='btn-'] {
border-radius: $borderRadius;
box-sizing: border-box;
display: inline-block;
font-family: $roboto;
font-size: $tiny;
font-weight: $bold;
margin: 0;
padding: $tiny $small;
position: relative;
overflow: hidden;
&:after {
content: '';
position: absolute;
top: -40%;
left: -210%;
width: 200%;
height: 200%;
opacity: 0;
transform: rotate(30deg);
background: rgba(255, 255, 255, 0.2);
background: linear-gradient(
to right,
rgba(255, 255, 255, 0.2) 0%,
rgba(255, 255, 255, 0.2) 77%,
rgba(255, 255, 255, 0.6) 92%,
rgba(255, 255, 255, 0) 100%
);
}
&:hover:after {
opacity: 1;
left: 110%;
transition-property: left, opacity;
transition-duration: 0.6s, 0.1s;
transition-timing-function: ease;
}
&:active:after {
opacity: 0;
}
&.small {
padding: $micro $tiny;
}
}
.btn-primary {
background-image: linear-gradient(0deg, $lightGreen 14%, $green 94%);
color: $darkerGrey;
}
.btn-secondary {
border: 1px solid white;
color: white;
}
pre {
border-radius: $borderRadius;
line-height: 1 !important;
}
/* fixes overflowing code example in widgets */
.widget pre {
box-sizing: border-box;
width: 100%;
}
.code,
code {
font-family: 'Roboto Mono', monospace !important;
font-size: 80%;
text-transform: none;
line-height: 1 !important;
}

View File

@ -1,7 +0,0 @@
.chat-button {
z-index: 100;
position: fixed;
bottom: 10px;
right: 10px;
cursor: pointer;
}

View File

@ -1,92 +0,0 @@
.collab,
.community-channels {
margin: $medium $tiny;
@media screen and (min-width: $mobile) {
margin: $large auto;
}
}
.collab {
h1 {
margin-bottom: $small;
text-align: center;
@media screen and (min-width: $mobile) {
margin-bottom: $large;
}
}
.collab-graphic {
@media screen and (min-width: $mobile) {
@neat-span-columns 4;
@neat-shift 1;
}
}
p {
margin-top: $medium;
@media screen and (min-width: $mobile) {
margin-top: 0;
text-align: left;
@neat-span-columns 5;
@neat-shift 1;
}
@media screen and (min-width: $tablet) {
margin-top: $medium;
}
}
}
.community-channels {
padding-bottom: $xl;
text-align: left;
.section-label:not(:first-child) {
margin-top: $large;
}
a {
color: $darkGreen;
}
}
.community-channels-list {
margin-left: 0;
li {
list-style-type: none;
margin-bottom: 24px;
}
a {
display: block;
font-weight: inherit;
position: relative;
&:focus,
&:active,
&:hover {
&:before {
display: block;
}
}
&:before {
display: none;
content: '';
position: absolute;
width: 3px;
height: 100%;
background-color: $darkGreen;
left: -16px;
}
}
p {
color: $grey;
margin-bottom: 0;
}
}

View File

@ -1,57 +0,0 @@
.communitysupport {
@neat-row;
background-color: white;
padding: $large 0 $large 0;
@media screen and (min-width: $desktop) {
padding: $xl 0 $large 0;
h2 {
text-align: center;
margin-bottom: $large;
}
}
.community {
@neat-span-columns 12;
@media screen and (min-width: $tablet) {
@neat-span-columns 8;
}
@media screen and (min-width: $desktop) {
@neat-span-columns 6;
}
}
.community-features {
.feature {
margin-top: $medium;
}
}
.contributors {
@neat-span-columns 12;
margin-top: $medium;
@media screen and (min-width: $tablet) {
@neat-span-columns 10;
}
@media screen and (min-width: $desktop) {
@neat-span-columns 5;
float: right;
}
.contributor-list {
margin-top: 4px;
img {
width: 32px;
margin: 0 4px 4px 0;
border-radius: 16px;
transition: 0.1s;
}
img:hover {
transform: scale(1.3);
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.25), 0 4px 12px 0 rgba(0, 0, 0, 0.25);
}
}
}
}

View File

@ -1,67 +0,0 @@
.cta {
@media screen and (min-width: $desktop) {
position: relative;
top: -65px;
width: 880px;
margin: auto;
}
.cta-primary {
background-color: white;
padding: $medium $small $medium $small;
@media screen and (min-width: $tablet) {
padding: $medium;
}
@media screen and (min-width: $desktop) {
padding: $small $medium $small $medium;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 10px 30px 0 rgba(28, 30, 30, 0.1), 0 3px 9px 0 rgba(28, 30, 30, 0.15);
border-radius: $largeBorderRadius;
}
p {
font-size: 18px;
line-height: 25px;
color: $lightishGrey;
margin: 0;
.hook {
font-weight: $bold;
color: $darkGrey;
}
}
a {
color: white;
text-transform: uppercase;
font-size: $tiny;
letter-spacing: 0.5px;
background-color: $blue;
background-image: linear-gradient(-180deg, #4a7fdd 0%, #3a69c7 100%);
box-shadow: 0 4px 12px 0 rgba(68, 74, 87, 0.1), 0 1px 3px 0 rgba(68, 74, 87, 0.2);
border-radius: $borderRadius;
padding: 12px 18px 12px 18px;
transition: 0.2s;
display: inline-block;
margin-top: $tiny;
@media screen and (min-width: $desktop) {
flex-shrink: 0;
margin: 0 0 0 $small;
}
&:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px 0 rgba(68, 74, 87, 0.2), 0 1px 3px 0 rgba(68, 74, 87, 0.4);
}
&:active {
transform: scale(0.95);
box-shadow: none;
}
}
}
}

View File

@ -1,254 +0,0 @@
.docs.page,
.blog.page {
padding: 69px $tiny $xl;
text-align: left;
@media screen and (min-width: $mobile) {
padding: 157px $medium $xl;
}
.sidebar {
@media screen and (min-width: $tablet) {
@neat-span-columns 6 24;
}
}
.docs-nav {
display: none;
@media screen and (min-width: $tablet) {
display: block;
height: 100%;
overflow-y: auto;
}
.nav-link,
.subnav-link {
display: block;
font-weight: $regular;
font-size: $tiny;
color: $grey;
line-height: 1.3;
margin: 10px 0;
text-decoration: none;
text-transform: capitalize;
transition: color 0.2s ease;
&.active {
color: $darkGreen;
font-weight: $bold;
}
&:hover {
color: $darkGreen;
}
}
}
.docs-nav-section {
margin-bottom: 30px;
}
.docs-nav-section-list {
margin: 0;
}
.docs-nav-section-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
}
.nav-subsections {
margin: $tiny 0;
padding: 0 0 0 $tiny;
border-left: 2px solid $lightestGrey;
list-style-type: none;
li {
margin: 0;
padding: 0;
}
.subnav-link {
font-size: 14px;
}
}
h1,
h2 {
font-size: 36px;
line-height: 1.4em;
&.intro-headline {
padding: 0 $small;
margin-bottom: 86px;
}
}
h2 {
font-size: $small;
}
h3 {
color: $grey;
font-size: 20px;
margin-top: $medium;
margin-bottom: $small;
&.inverse {
color: white;
}
}
.meta-info {
font-size: $tiny;
}
table {
width: 100%;
text-align: left;
margin: 34px 0 $medium 0;
th,
td {
padding: $micro;
}
th {
font-size: 18px;
font-weight: $bold;
}
tbody tr {
&:nth-child(odd) {
background: #fdfdfd;
}
}
td {
font-size: 14px;
}
}
}
.docs-content,
.blog-content {
font-size: 18px;
line-height: 28px;
.edit-this-page {
float: right;
}
#pencil {
fill: #7ca511;
}
@media screen and (min-width: $tablet) {
@neat-span-columns 17 24;
@neat-shift 1 24;
}
h2:not(:first-child) {
margin-top: $medium;
}
p {
line-height: 1.7;
}
a {
text-decoration: none;
color: $darkGreen;
}
iframe {
width: 100%;
}
img {
max-width: 100%;
height: auto;
}
table {
background: #f7f7f7;
border-radius: $borderRadius;
}
pre {
margin: 30px -16px !important;
@media (min-width: $xlarge) {
margin-right: -120px !important;
}
}
figure {
margin-left: 0;
.gatsby-highlight {
margin-top: -20px;
}
}
figcaption {
font-style: italic;
}
:not(pre) > code {
color: inherit;
background: $lightestGrey;
border-radius: 2px;
padding: 2px 6px;
white-space: nowrap;
}
}
.blog-content h1 {
margin-bottom: 0;
}
.blog-list-item h2 {
margin: 48px 0 0;
line-height: 36px;
}
.mobile-docs-nav {
display: block;
position: relative;
margin-bottom: $small;
@media screen and (min-width: $tablet) {
display: none;
}
}
.mobile-docs-nav-btn {
display: block;
width: 100%;
}
.mobile-docs-nav-content {
/* border: 1px solid $lightishGrey; */
background-color: $lighterGrey;
padding: $tiny;
}
.mobile-docs-nav-list {
list-style: none;
margin: 0;
.mobile-docs-nav-list {
margin-left: $small;
}
}
.mobile-docs-nav-item {
font-weight: bold;
font-size: 14px;
}
.mobile-docs-nav-link {
font-size: 16px;
display: block;
padding: $micro;
}

View File

@ -1,52 +0,0 @@
.editors {
@neat-row;
margin: $large 0 $large 0;
@media screen and (min-width: $desktop) {
text-align: center;
margin: $xxl 0 $xxl 0;
}
h2,
p {
max-width: $desktop;
@media screen and (min-width: $desktop) {
margin-left: auto;
margin-right: auto;
}
}
p#editor-intro {
max-width: 710px;
}
.editors-features {
display: flex;
justify-content: space-between;
margin-top: calc($large * 2);
@media screen and (max-width: $desktop) {
justify-content: flex-start;
flex-wrap: wrap;
margin-top: $small;
}
.feature {
min-width: 260px;
width: 320px;
text-align: left;
margin-right: $medium;
img {
max-width: 300px;
margin-bottom: $tiny;
margin-left: -9px;
}
@media screen and (max-width: $desktop) {
margin-top: $large;
}
}
}
}

View File

@ -1,42 +0,0 @@
footer {
background: white;
padding: $medium 0 $large 0;
text-align: center;
.footer-info {
@media screen and (min-width: $tablet) {
float: left;
margin-right: $small;
}
p {
color: $grey;
font-family: $roboto;
font-size: 12px;
opacity: 0.5;
}
a {
color: $grey;
}
}
.social-buttons {
margin-bottom: $small;
@media screen and (min-width: $tablet) {
float: left;
margin-right: $small;
}
a {
padding: $micro $tiny $micro $tiny;
margin-right: $micro;
background-color: $lightishGrey;
color: white;
border-radius: $borderRadius;
}
a:hover {
background-color: $darkGreen;
}
a:active {
background-color: $darkerGreen;
}
}
}

View File

@ -1,237 +0,0 @@
.notification {
background-color: $darkerGrey;
box-sizing: border-box;
color: white;
display: block;
padding: $tiny $small;
position: absolute;
text-align: center;
width: 100%;
z-index: 100;
@media screen and (min-width: $mobile) {
position: fixed;
}
em {
font-style: normal;
color: #8b8b8b;
padding: 0 8px;
}
sup,
sub {
font-size: initial;
vertical-align: initial;
}
.text-link {
text-decoration: underline;
color: $green;
}
&.notification-loud {
background-color: $green;
color: $darkerGrey;
}
+ header {
margin-top: 100px;
@media screen and (min-width: 360px) {
margin-top: 74px;
}
@media screen and (min-width: 712px) {
margin-top: 50px;
}
+ div:before,
+ .hero:before {
content: '';
display: block;
height: 100px;
width: 100%;
@media screen and (min-width: 360px) {
height: 74px;
}
@media screen and (min-width: 712px) {
height: 50px;
}
}
}
}
header {
background: transparent;
box-shadow: none;
font-family: $roboto;
position: absolute;
padding: $medium 0;
text-align: center;
transition: background 0.2s ease, padding 0.2s ease, box-shadow 0.2s ease;
width: 100%;
z-index: 100;
@media screen and (min-width: $tablet) {
text-align: right;
position: fixed;
}
&.scrolled {
@media screen and (min-width: $tablet) {
background: $darkerGrey;
padding: $small 0;
}
}
&.docs,
&.blog {
background: $darkerGrey;
padding: $small 0;
@media screen and (max-width: $tablet) {
position: static;
}
}
.nav-container {
padding-top: $micro;
.nav-link {
color: white;
display: inline-block;
vertical-align: middle;
-webkit-vertical-align: middle !important;
margin-left: $tiny;
&:first-child {
margin-left: 0;
}
&:hover {
color: $green;
}
}
.gh-button {
display: inline-block;
margin-left: $small;
vertical-align: middle;
position: relative;
top: 2px;
@media screen and (max-width: $mobile) {
margin-top: $tiny;
top: 0;
margin-left: 0;
}
}
}
img {
margin: 0;
padding: 0;
}
.algolia-search {
margin-top: 1px;
-webkit-margin-top: 0;
-webkit-display: inline-block;
vertical-align: baseline !important;
}
input.closed {
display: none;
}
input {
padding: -5px 0 0 0;
border: none;
border-radius: 0;
appearance: none;
background: none;
color: $lightGrey;
display: inline-block;
font-size: $tiny;
font-weight: $regular;
max-width: 160px;
-webkit-appearance: none;
visibility: visible;
&:focus {
outline: none;
display: inline-block;
~ .bar {
&::before,
&::after {
width: 50%;
}
}
~ .button-submit {
margin-top: -35px;
}
}
}
.utility-input {
display: none;
padding: $micro;
position: relative;
top: -4px;
width: auto;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.1);
color: white;
margin: 0 0 0 $tiny;
padding-top: -5px;
padding-bottom: 9px;
text-align: left;
text-decoration: none;
font-weight: 600;
font-size: 16px;
line-height: 24px;
transition: all 0.2s ease-in-out;
@media screen and (min-width: 1024px) {
display: inline;
}
&:focus,
&:active {
outline-style: none;
}
&::-webkit-input-placeholder,
&:-moz-placeholder,
&::-moz-placeholder,
&:-ms-input-placeholder {
font-size: $tiny;
font-weight: $semibold;
text-align: left;
text-decoration: none;
line-height: $small;
}
img {
margin-right: 4px;
}
}
.logo-container {
@media screen and (min-width: $tablet) {
margin-top: 10px;
float: left;
}
.logo {
display: block;
margin: 0 auto $tiny auto;
width: 100%;
@media screen and (min-width: $tablet) {
margin: -14px 20px -6px 0;
width: initial;
float: left;
}
}
}
}

View File

@ -1,257 +0,0 @@
.hero {
@neat-row;
background: $darkerGrey;
background-image: linear-gradient(-180deg, #2a2c24 0%, $darkerGrey 20%);
color: $blueGrey;
overflow: hidden;
padding: calc($xl * 2.25) 0 0 0;
position: relative;
@media screen and (min-width: $mobile) {
padding-top: calc($xl * 1.5);
}
@media screen and (min-width: $tablet) {
padding-top: calc($xl * 1.5);
}
&.landing {
padding-bottom: 100px;
@media screen and (min-width: $desktop) {
padding-bottom: 200px;
}
}
.hero-copy {
margin: $medium auto $xl auto;
}
.subhead {
display: inline-block;
margin: $micro auto;
font-size: 18px;
line-height: 26px;
@media screen and (min-width: $desktop) {
font-size: 20px;
}
}
strong {
display: block;
}
h1 {
color: $green;
margin-bottom: 0;
}
h2 {
color: white;
}
h3 {
color: white;
}
.cta-header {
display: block;
margin-top: $small;
}
.cta-header a {
display: inline-block;
color: white;
text-transform: uppercase;
font-size: $tiny;
letter-spacing: 0.5px;
background-color: $blue;
background-image: linear-gradient(-180deg, #4a7fdd 0%, #3a69c7 100%);
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.3), 0 1px 3px 0 rgba(0, 0, 0, 0.6);
border-radius: $borderRadius;
padding: 10px 14px 8px 14px;
transition: 0.2s;
&:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.5), 0 1px 3px 0 rgba(0, 0, 0, 1);
}
&:active {
transform: scale(0.95);
box-shadow: none;
}
}
.hero-features {
@media screen and (min-width: $tablet) {
@neat-span-columns 5;
}
.feature {
margin: 0 0 $medium 0;
}
}
.hero-graphic {
@neat-span-columns 12;
position: relative;
cursor: pointer;
@media screen and (min-width: $tablet) {
@neat-span-columns 6;
float: right;
margin: 0;
}
img,
iframe {
width: 100%;
border-radius: $largeBorderRadius;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.15), 0 3px 9px 0 rgba(0, 0, 0, 0.3);
}
.hero-videolink {
position: absolute;
margin: auto;
padding: 10px 0 0 10px;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 90px;
height: 90px;
color: $blue;
background-color: rgba(255, 255, 255, 0.85);
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.15);
border-radius: 100px;
transition: 0.1s;
}
.hero-videolink-arrow {
position: absolute;
left: 33px;
top: 28px;
width: 44px;
height: 44px;
fill: #3a69c7;
}
&:hover {
.hero-videolink {
background-color: $blue;
box-shadow: 0 6px 18px 0 rgba(0, 0, 0, 0.15), 0 2px 6px 0 rgba(0, 0, 0, 0.3);
transform: scale(1.1);
}
.hero-videolink-arrow {
fill: #fff;
}
}
&:active {
.hero-videolink {
transform: scale(0.9);
}
}
}
/*COMMUNITY PAGE*/
&.community {
.hero-copy {
text-align: left;
@media screen and (min-width: $tablet) {
@neat-span-columns 5;
}
}
}
.ctas {
margin-bottom: $small;
@media screen and (min-width: $mobile) {
text-align: left;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
a {
color: $green;
font-weight: $semibold;
}
}
.calendar-cta {
text-align: center;
background: $darkerGrey;
background-image: linear-gradient(-17deg, $darkerGrey 17%, $darkGrey 94%);
border-radius: $largeBorderRadius;
box-shadow: 0 $micro $small rgba(0, 0, 0, 0.1);
padding: $medium;
box-sizing: border-box;
@media screen and (min-width: $tablet) {
max-width: 446px;
}
@media screen and (min-width: $tablet) {
@neat-span-columns 5;
@neat-shift 1;
display: inline-block;
position: fixed;
right: $medium;
max-height: calc(100vh - ($xl * 1.5) - $medium);
overflow-y: auto;
}
@media screen and (min-width: 1280px) {
right: initial;
left: calc(50% - $large);
}
.calendar {
border-radius: $largeBorderRadius;
overflow: hidden;
box-shadow: 0 $micro $small rgba(0, 0, 0, 0.5);
margin: $small auto;
max-width: 250px;
.month {
background: $green;
color: $grey;
font-weight: $black;
text-transform: uppercase;
letter-spacing: 4px;
font-size: $tiny;
padding: $tiny;
}
.day {
font-size: $xl;
font-weight: $black;
color: white;
border: 1px solid $grey;
border-top: none;
border-bottom-left-radius: $largeBorderRadius;
border-bottom-right-radius: $largeBorderRadius;
}
}
strong {
display: inline-block;
}
h2:not(:first-child) {
font-weight: $light;
}
.cal-cta {
margin-top: $micro;
a {
color: $green;
}
}
}
}

View File

@ -1,258 +0,0 @@
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.centered-text {
text-align: center;
}
.container {
@neat-outer-container;
}
.half,
.third,
.quarter {
padding-bottom: $small;
@neat-span-columns 12;
@media screen and (min-width: $tablet) {
@neat-span-columns 6;
padding-bottom: 0;
}
&:nth-child(even) {
@media screen and (min-width: $mobile) and (max-width: 767px) {
margin-right: 0;
}
}
}
.third {
@media screen and (min-width: $tablet) {
@neat-span-columns 4;
}
}
.quarter {
@media screen and (min-width: $tablet) {
@neat-span-columns 3;
}
}
.clearfix {
&:after {
content: ' ';
width: 100%;
display: table;
}
}
.section-label {
color: $grey;
font-size: 12px;
font-weight: $semibold;
letter-spacing: 1.5px;
text-transform: uppercase;
&:after {
background: $darkGreen;
content: ' ';
display: block;
height: 2px;
margin-top: 5px;
width: $small;
}
&.inverse {
color: white;
&.mono:after {
background: $grey;
}
}
&.extended {
display: inline-block;
line-height: 1.15;
&:after {
position: relative;
height: 1px;
left: -10000%;
width: calc(10100%);
z-index: -1;
}
}
}
img.responsive {
width: 100%;
height: auto;
}
.img-bg-hero {
color: white;
background-size: 100% auto;
background-size: cover !important;
h1,
h2 {
color: white;
font-weight: $light;
@media screen and (min-width: $tablet) {
font-weight: $thin;
}
}
}
.pagination {
text-align: center;
margin: 0;
padding: 0;
list-style-type: none;
@media screen and (min-width: $tablet) {
@neat-span-columns 8;
@neat-shift 2;
}
li {
margin: 0;
padding: 0;
display: inline;
@media screen and (min-width: $tablet) {
display: inline-block;
}
&.active a,
a[aria-label] {
display: inline-block;
}
}
a {
text-decoration: none;
font-weight: $light;
color: $grey;
width: 18px;
font-size: $tiny;
padding: $micro;
border-radius: 99px;
display: none;
@media screen and (min-width: $tablet) {
display: inline-block;
}
}
.active a {
color: white;
background: $green;
}
.disabled a {
color: $lighterGrey;
}
}
.unordered-list ul {
margin: 0 0 $small 0;
padding: 0;
font-size: 16px;
line-height: 28px;
list-style-type: none;
list-style-position: inside;
&:last-child {
margin-bottom: 0;
}
li span {
position: relative;
padding-left: $small;
display: list-item;
&:before {
content: '\2192';
color: $green;
position: absolute;
left: 0;
}
}
}
.numbered-list ol {
list-style-type: decimal-leading-zero;
margin: 32px 0 0 0;
padding: 0 0 0 78px;
li {
margin-bottom: 33px;
font-size: 34px;
font-weight: $bold;
color: $grey;
position: relative;
&:before,
&:after {
position: absolute;
font-size: 12px;
}
&:before {
content: 'N';
top: 6px;
left: -78px;
padding-bottom: 2px;
border-bottom: 1px solid $green;
padding-right: 25px;
}
&:after {
content: 'ọ';
left: -69px;
top: 4px;
}
&:last-child {
margin-bottom: 0;
}
}
h2 {
font-size: 34px;
margin-bottom: $tiny;
@media screen and (min-width: $tablet) {
margin-bottom: 12px;
}
&:before {
content: ' ';
position: absolute;
background: white;
left: -19px;
height: 10px;
width: 10px;
top: $small;
}
}
p {
margin: 0;
font-size: 18px;
font-weight: $regular;
line-height: 28px;
color: $lightGrey;
}
}

View File

@ -1,63 +0,0 @@
.whatsnew {
padding: $medium 0 $medium 0;
background-color: $lightishGrey;
color: white;
@media screen and (min-width: $desktop) {
margin-top: -98px;
padding-top: 75px;
}
ol {
display: flex;
justify-content: space-between;
list-style: none;
padding-left: 0;
@media screen and (max-width: $desktop) {
flex-wrap: wrap;
}
a {
color: white;
font-weight: $regular;
min-width: 250px;
max-width: 320px;
margin-right: $medium;
@media screen and (max-width: $desktop) {
max-width: 100%;
min-width: 100%;
padding: 16px 0 16px 0;
}
}
a:hover {
padding: 16px 24px 16px 24px;
margin: -16px calc($medium - 24px) -16px -24px;
background-color: $darkGrey;
border-radius: $borderRadius;
@media screen and (max-width: $desktop) {
margin: 0 -24px 0 -24px;
}
}
a:active {
background-color: $darkerGrey;
}
li {
.update-metadata {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
display: block;
margin-bottom: $micro;
.update-version {
font-weight: $bold;
background-color: $shadeBlue;
color: $darkGrey;
padding: 0 4px 0 4px;
margin-right: 8px;
border-radius: 2px;
}
}
.update-description {
font-size: 15px;
line-height: 20px;
}
}
}
}

View File

@ -1,77 +0,0 @@
.widgets {
margin: 2rem 0;
}
.widgets__cloud {
margin: $micro calc(-$micro / 2);
}
.widgets__item {
font-family: inherit;
font-size: inherit;
line-height: inherit;
background: transparent;
color: $darkGrey;
border: 2px solid $darkGreen;
border-radius: $borderRadius;
padding: calc($micro / 2) $micro;
margin: calc($micro / 2);
cursor: pointer;
transition: color 0.2s ease, background 0.2s ease;
display: inline-block;
font-weight: normal;
}
.widgets__item:hover,
.widgets__item_active {
background: $darkGreen;
color: white !important;
}
.widgets__container {
margin: 1em 0;
background: $lightGrey;
border-radius: $borderRadius;
transition: height 0.15s ease;
}
.widget {
padding: 0.5em 1em;
display: none;
}
.widget_open {
display: block;
}
.widget_closing {
display: block;
animation: widgetOpacity 0.15s ease forwards reverse;
}
.widget_opening {
display: block;
position: absolute;
top: 0;
left: 0;
opacity: 0;
animation: widgetOpacity 0.15s 0.05s ease forwards;
}
@keyframes widgetOpacity {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.widget pre {
margin-right: 0 !important;
margin-left: 0 !important;
}
.widget h3 {
margin-top: 0 !important;
}

View File

@ -0,0 +1,96 @@
import React from 'react';
import { Global, css } from '@emotion/core';
import theme from './theme';
const globalStyles = css`
* {
box-sizing: border-box;
}
body {
color: ${theme.colors.gray};
font-family: ${theme.fontFamily};
line-height: ${theme.lineHeight[2]};
font-size: ${theme.fontsize[3]};
background: ${theme.colors.shadeBlue};
margin: 0;
-webkit-font-smoothing: antialiased;
}
img {
max-width: 100%;
}
ol,
ul {
list-style: none;
margin: 0;
padding: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: ${theme.lineHeight[1]};
margin-top: 0;
margin-bottom: 0.5em;
}
h1 {
font-size: 36px;
}
h2 {
font-size: 28px;
}
h3 {
font-size: 24px;
}
p {
margin-top: 0;
margin-bottom: 0;
}
a {
color: ${theme.colors.darkGreen};
text-decoration: none;
}
.gitter-open-chat-button {
&,
&:visited {
padding: ${theme.space[3]} ${theme.space[4]};
font-family: ${theme.fontFamily};
font-size: ${theme.fontsize[3]};
letter-spacing: 0.5px;
line-height: 1;
color: ${theme.colors.gray};
background-color: ${theme.colors.green};
box-shadow: 0 2px 16px 0 rgba(68, 74, 87, 0.15), 0 1px 4px 0 rgba(68, 74, 87, 0.3);
}
&:hover {
background-color: ${theme.colors.lightGreen};
box-shadow: 0 2px 16px 0 rgba(68, 74, 87, 0.25), 0 1px 4px 0 rgba(68, 74, 87, 0.5);
}
&:focus {
box-shadow: 0 0 6px 3px rgba(62, 160, 127, 0.6);
transition: none;
}
&:active {
color: ${theme.colors.lightGray};
}
}
`;
const GlobalStyles = () => <Global styles={globalStyles} />;
export default GlobalStyles;

View File

@ -1,4 +1,5 @@
import React from 'react';
import ChatButton from './components/chat-button';
class HTML extends React.Component {
render() {
@ -24,9 +25,7 @@ class HTML extends React.Component {
{this.props.preBodyComponents}
<div key={'body'} id="___gatsby" dangerouslySetInnerHTML={{ __html: this.props.body }} />
{this.props.postBodyComponents}
<a className="chat-button" href="/chat">
<img src="/img/slack.svg" />
</a>
<ChatButton />
<script src="//unpkg.com/docsearch.js@2.4.1/dist/cdn/docsearch.min.js" />
</body>
</html>

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0" y="0" viewBox="0 0 56.966 56.966" xml:space="preserve" width="16" height="12" enable-background="new 0 0 56.966 56.966"><path d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z" fill="#FFF"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0" y="0" viewBox="0 0 56.966 56.966" xml:space="preserve" width="16" height="12" enable-background="new 0 0 56.966 56.966"><path d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

After

Width:  |  Height:  |  Size: 630 B

View File

@ -1,34 +1,48 @@
import React from 'react';
import Helmet from 'react-helmet';
import { Link, graphql } from 'gatsby';
import { css } from '@emotion/core';
import Layout from '../components/layout';
import Container from '../components/container';
import MetaInfo from '../components/meta-info';
import Page from '../components/page';
import Lead from '../components/lead';
import theme from '../theme';
const Blog = ({ data }) => (
<Layout>
<div className="blog page">
<Helmet>
<title>Blog</title>
<meta name="description" content="Recent news and updates about Netlify CMS." />
</Helmet>
<div className="container">
<Helmet>
<title>Blog</title>
<meta name="description" content="Recent news and updates about Netlify CMS." />
</Helmet>
<Page>
<Container size="sm">
<h1>Netlify CMS Blog</h1>
{data.allMarkdownRemark.edges.map(({ node }) => (
<article className="blog-list-item" key={node.id}>
<h2>
<Link to={node.fields.slug} className="article">
{node.frontmatter.title}
</Link>
<article
key={node.id}
css={css`
margin-bottom: ${theme.space[5]};
`}
>
<h2
css={css`
margin-bottom: 0;
`}
>
<Link to={node.fields.slug}>{node.frontmatter.title}</Link>
</h2>
<p className="meta-info">
<MetaInfo>
by {node.frontmatter.author} on {node.frontmatter.date}
</p>
<p>{node.frontmatter.description}</p>
</MetaInfo>
<Lead>{node.frontmatter.description}</Lead>
</article>
))}
{/* TODO: pagination */}
</div>
</div>
</Container>
</Page>
</Layout>
);

View File

@ -1,19 +1,60 @@
import React from 'react';
import Helmet from 'react-helmet';
import { graphql } from 'gatsby';
import { css } from '@emotion/core';
import Layout from '../components/layout';
import Community from '../components/community';
import Markdownify from '../components/markdownify';
import PageHero from '../components/page-hero';
import HeroTitle from '../components/hero-title';
import Lead from '../components/lead';
import Container from '../components/container';
import SectionLabel from '../components/section-label';
import Page from '../components/page';
import Grid from '../components/grid';
import CommunityChannelsList from '../components/community-channels-list';
import '../css/imports/collab.css';
import theme from '../theme';
const CommunityPage = ({ data }) => {
const { title, headline, subhead, sections } = data.markdownRemark.frontmatter;
return (
<Layout>
<Layout hasPageHero>
<Helmet title={title} />
<Community headline={headline} subhead={subhead} sections={sections} />
<PageHero>
<div
css={css`
margin-bottom: 20px;
`}
>
<HeroTitle>
<Markdownify source={headline} />
</HeroTitle>
<Lead light>
<Markdownify source={subhead} />
</Lead>
</div>
</PageHero>
<Container>
<Page>
<Grid cols={2}>
<div
css={css`
margin-bottom: ${theme.space[5]};
`}
>
{sections.map(({ title: sectionTitle, channels }, channelIdx) => (
<React.Fragment key={channelIdx}>
<SectionLabel>{sectionTitle}</SectionLabel>
<CommunityChannelsList channels={channels} />
</React.Fragment>
))}
</div>
</Grid>
</Page>
</Container>
</Layout>
);
};

View File

@ -1,30 +1,69 @@
import React from 'react';
import { graphql } from 'gatsby';
import styled from '@emotion/styled';
import { css } from '@emotion/core';
import Layout from '../components/layout';
import Markdownify from '../components/markdownify';
import PageHero from '../components/page-hero';
import HeroTitle from '../components/hero-title';
import VideoEmbed from '../components/video-embed';
import WhatsNew from '../components/whats-new';
import Release from '../components/release';
import Lead from '../components/lead';
import Features from '../components/features';
import HomeSection from '../components/home-section';
import Grid from '../components/grid';
import '../css/imports/hero.css';
import '../css/imports/cta.css';
import '../css/imports/whatsnew.css';
import '../css/imports/editors.css';
import '../css/imports/community.css';
import theme from '../theme';
import { mq } from '../utils';
const Features = ({ items }) =>
items.map(item => (
<div className="feature" key={item.feature}>
{item.imgpath && <img src={require(`../img/${item.imgpath}`)} alt="" />}
<h3>
<Markdownify source={item.feature} />
</h3>
<p>
<Markdownify source={item.description} />
</p>
</div>
));
const MarkdownButton = styled.span`
a {
white-space: nowrap;
display: inline-block;
color: white;
text-transform: uppercase;
font-weight: 700;
font-size: ${theme.fontsize[3]};
letter-spacing: 0.5px;
line-height: ${theme.lineHeight[1]};
background-color: ${theme.colors.blue};
background-image: linear-gradient(-180deg, #4a7fdd 0%, #3a69c7 100%);
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.3), 0 1px 3px 0 rgba(0, 0, 0, 0.6);
border-radius: ${theme.radii[1]};
padding: ${theme.space[2]} ${theme.space[3]};
transition: 0.2s;
text-decoration: none;
&:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.5), 0 1px 3px 0 rgba(0, 0, 0, 1);
}
&:active {
transform: scale(0.95);
box-shadow: none;
}
}
`;
const ContribList = styled.div`
display: flex;
flex-wrap: wrap;
img {
height: 32px;
width: 32px;
border-radius: 10rem;
margin-right: ${theme.space[1]};
margin-bottom: ${theme.space[1]};
transition: 0.1s;
&:hover {
transform: scale(1.3);
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.25), 0 4px 12px 0 rgba(0, 0, 0, 0.25);
}
}
`;
const HomePage = ({ data }) => {
const landing = data.landing.childDataYaml;
@ -32,90 +71,115 @@ const HomePage = ({ data }) => {
const contribs = data.contribs.childDataJson;
return (
<Layout>
<div className="landing page">
<section className="landing hero">
<div className="contained">
<div className="hero-copy">
<h1 className="headline">
<Markdownify source={landing.hero.headline} />
</h1>
<span className="subhead">
<Markdownify source={landing.hero.subhead} />
</span>
<span className="cta-header">
<Markdownify source={landing.cta.button} />
</span>
</div>
<div className="hero-features">
<Features items={landing.hero.devfeatures} />
</div>
<Layout hasPageHero>
<PageHero>
<div
css={css`
margin-bottom: ${theme.space[7]};
`}
>
<HeroTitle>
<Markdownify source={landing.hero.headline} />
</HeroTitle>
<Lead>
<Markdownify source={landing.hero.subhead} />
</Lead>
<Lead>
<MarkdownButton>
<Markdownify source={landing.cta.button} />
</MarkdownButton>
</Lead>
</div>
<Grid cols={2}>
<div>
<Features items={landing.hero.devfeatures} kind="light" />
</div>
<div>
<VideoEmbed />
</div>
</section>
</Grid>
</PageHero>
<section className="cta">
<div className="cta-primary">
<p>
<span className="hook">
<Markdownify source={landing.cta.primaryhook} />
</span>{' '}
<Markdownify source={landing.cta.primary} />
</p>
<section
css={css`
background: white;
${mq[2]} {
position: absolute;
left: 50%;
transform: translate(-50%, -75%);
width: 880px;
border-radius: 8px;
}
`}
>
<div
css={css`
padding: ${theme.space[4]} ${theme.space[5]};
color: ${theme.colors.lightishGray};
${mq[2]} {
display: flex;
}
`}
>
<Lead
css={css`
margin-right: 2rem;
font-size: 18px;
${mq[2]} {
margin-bottom: 0;
}
`}
>
<strong>
<Markdownify source={landing.cta.primaryhook} />
</strong>{' '}
<Markdownify source={landing.cta.primary} />
</Lead>
<MarkdownButton>
<Markdownify source={landing.cta.button} />
</div>
</section>
</MarkdownButton>
</div>
</section>
<WhatsNew>
{updates.updates.slice(0, 3).map((node, idx) => (
<Release
key={idx}
version={node.version}
versionPrevious={updates.updates[idx + 1].version}
date={node.date}
description={node.description}
url={node.url}
/>
))}
</WhatsNew>
<WhatsNew updates={updates.updates} />
<section className="editors">
<div className="contained">
<h2>
<Markdownify source={landing.editors.hook} />
</h2>
<p id="editor-intro">
<Markdownify source={landing.editors.intro} />
</p>
<div className="editors-features">
<Features items={landing.editors.features} />
</div>
</div>
</section>
<HomeSection
title={<Markdownify source={landing.editors.hook} />}
text={<Markdownify source={landing.editors.intro} />}
>
<Grid cols={3}>
<Features items={landing.editors.features} />
</Grid>
</HomeSection>
<section className="communitysupport">
<div className="contained">
<h2>
<Markdownify source={landing.community.hook} />
</h2>
<div className="community">
<div className="community-features">
<Features items={landing.community.features} />
</div>
</div>
<div className="contributors feature">
<h3>{landing.community.contributors}</h3>
<div className="contributor-list">
{contribs.contributors.map(user => (
<a href={user.profile} title={user.name} key={user.login}>
<img src={user.avatar_url.replace('v=4', 's=32')} alt={user.login} />
</a>
))}
</div>
</div>
<HomeSection
css={css`
background: white;
`}
title={<Markdownify source={landing.community.hook} />}
>
<Grid cols={2}>
<div>
<Features items={landing.community.features} />
</div>
</section>
</div>
<div>
<h3
css={css`
font-size: 18px;
`}
>
{landing.community.contributors}
</h3>
<ContribList>
{contribs.contributors.map(user => (
<a href={user.profile} title={user.name} key={user.login}>
<img src={user.avatar_url.replace('v=4', 's=32')} alt={user.login} />
</a>
))}
</ContribList>
</div>
</Grid>
</HomeSection>
</Layout>
);
};

View File

@ -2,24 +2,31 @@ import React from 'react';
import Helmet from 'react-helmet';
import { graphql } from 'gatsby';
import { trimStart, trimEnd } from 'lodash';
import { css } from '@emotion/core';
import TwitterMeta from '../components/twitter-meta';
import Layout from '../components/layout';
import Container from '../components/container';
import Markdown from '../components/markdown';
import MetaInfo from '../components/meta-info';
import Page from '../components/page';
export const BlogPostTemplate = ({ title, author, date, body, html }) => (
<div className="docs page">
<div className="container">
<article className="blog-content" id="blog-content">
<div className="blog-post-header">
<h1>{title}</h1>
<p className="meta-info">
by {author} on {date}
</p>
</div>
{body ? body : <div dangerouslySetInnerHTML={{ __html: html }} />}
</article>
</div>
</div>
<Container size="sm">
<Page as="article">
<h1
css={css`
margin-bottom: 0;
`}
>
{title}
</h1>
<MetaInfo>
by {author} on {date}
</MetaInfo>
<Markdown html={body || html} />
</Page>
</Container>
);
const BlogPost = ({ data }) => {

View File

@ -7,9 +7,9 @@ import Layout from '../components/layout';
import EditLink from '../components/edit-link';
import Widgets from '../components/widgets';
import DocsNav from '../components/docs-nav';
import MobileNav from '../components/mobile-nav';
import '../css/imports/docs.css';
import Container from '../components/container';
import SidebarLayout from '../components/sidebar-layout';
import Markdown from '../components/markdown';
const toMenu = (menu, nav) =>
menu.map(group => ({
@ -17,10 +17,9 @@ const toMenu = (menu, nav) =>
group: nav.group.find(g => g.fieldValue === group.name),
}));
const DocsSidebar = ({ docsNav, location, history }) => (
<aside id="sidebar" className="sidebar">
const DocsSidebar = ({ docsNav, location }) => (
<aside>
<DocsNav items={docsNav} location={location} />
<MobileNav items={docsNav} history={history} />
</aside>
);
@ -34,22 +33,22 @@ export const DocsTemplate = ({
showSidebar,
docsNav,
location,
history,
}) => (
<div className="docs detail page">
<div className="container">
{showSidebar && <DocsSidebar docsNav={docsNav} location={location} history={history} />}
<article className="docs-content" id="docs-content">
<Container size="md">
<SidebarLayout
sidebar={<div>{showSidebar && <DocsSidebar docsNav={docsNav} location={location} />}</div>}
>
<article data-docs-content>
{editLinkPath && <EditLink path={editLinkPath} />}
<h1>{title}</h1>
{body ? body : <div dangerouslySetInnerHTML={{ __html: html }} />}
<Markdown html={body || html} />
{showWidgets && <Widgets widgets={widgets} />}
</article>
</div>
</div>
</SidebarLayout>
</Container>
);
const DocPage = ({ data, location, history }) => {
const DocPage = ({ data, location }) => {
const { nav, page, widgets, menu } = data;
const docsNav = toMenu(menu.siteMetadata.menu.docs, nav);
@ -66,7 +65,6 @@ const DocPage = ({ data, location, history }) => {
widgets={widgets}
docsNav={docsNav}
location={location}
history={history}
showSidebar
/>
</Layout>

View File

@ -1,50 +1,37 @@
// if you change these you must restart the server
module.exports = {
// colors
lightestGrey: '#E6E6E6',
lighterGrey: '#F7F8F8',
lightGrey: '#F6F6F6',
lightishGrey: '#51555D',
grey: '#313D3E',
darkGrey: '#2F3132',
darkerGrey: '#1E1F21',
blueGrey: '#BCC2CE',
lightGreen: '#97bf2f',
green: '#C9FA4B',
darkGreen: '#7CA511',
darkerGreen: '#628013',
shadeBlue: '#EFF0F4',
blue: '#3A69C7',
// typography
thin: 100,
light: 300,
regular: 400,
semibold: 500,
bold: 700,
black: 900,
// fonts
roboto: "'Roboto', -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif",
// padding
micro: '8px',
tiny: '16px',
small: '24px',
medium: '40px',
large: '64px',
xl: '104px',
xxl: '152px',
// border radius
borderRadius: '4px',
largeBorderRadius: '8px',
// responsive breakpoints
mobile: '480px',
tablet: '768px',
desktop: '960px',
display: '1200px',
xlarge: '1280px',
colors: {
white: '#fff',
black: '#000',
lightestGray: '#E6E6E6',
lighterGray: '#F7F8F8',
lightGray: '#F6F6F6',
lightishGray: '#51555D',
gray: '#313D3E',
darkGray: '#2F3132',
darkerGray: '#1E1F21',
blueGray: '#BCC2CE',
lightGreen: '#97bf2f',
green: '#C9FA4B',
darkGreen: '#7CA511',
darkerGreen: '#628013',
shadeBlue: '#EFF0F4',
blue: '#3A69C7',
},
fontWeight: {
thin: 100,
light: 300,
regular: 400,
semibold: 500,
bold: 700,
black: 900,
},
fontFamily: "'Roboto', -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif",
fontsize: ['10px', '12px', '14px', '16px', '18px', '24px', '32px', '42px', '64px'],
lineHeight: [1, 1.3, 1.7],
space: [0, '4px', '8px', '16px', '24px', '40px', '64px', '104px', '152px'],
radii: [0, '4px', '8px'],
breakpoints: [480, 768, 960, 1200, 1280],
zIndexes: {
header: 100,
},
};

5
website/src/utils.js Normal file
View File

@ -0,0 +1,5 @@
import theme from './theme';
export const mq = theme.breakpoints.map(bp => `@media (min-width: ${bp}px)`);
export const themeGet = (key, initial) => props => props.theme[key] || initial;

File diff suppressed because it is too large Load Diff